UNPKG

fxsql

Version:

Functional query builder based on fxjs

699 lines (590 loc) 24.6 kB
"use strict"; var _mapInstanceProperty2 = require("@babel/runtime-corejs3/core-js-stable/instance/map"); var _reduceInstanceProperty2 = require("@babel/runtime-corejs3/core-js-stable/instance/reduce"); var _flatInstanceProperty = require("@babel/runtime-corejs3/core-js-stable/instance/flat"); var _filterInstanceProperty2 = require("@babel/runtime-corejs3/core-js-stable/instance/filter"); var _Object$defineProperty = require("@babel/runtime-corejs3/core-js-stable/object/define-property"); var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault"); _Object$defineProperty(exports, "__esModule", { value: true }); exports.MySQL = exports.PostgreSQL = exports.FxSQL_DEBUG = void 0; var _symbol = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/symbol")); var _isArray = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/array/is-array")); var _reduce = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/reduce")); var _assign = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/object/assign")); var _concat = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/concat")); var _map = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/map")); var _slice = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/slice")); var _filter = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/filter")); var _trim = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/trim")); var _some = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/some")); var _splice = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/splice")); var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/includes")); var _entries = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/object/entries")); var _find = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/find")); var _values = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/values")); var _keys = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/object/keys")); var _values2 = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/object/values")); var _stringify = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/json/stringify")); var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise")); var _bind = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/bind")); var _dumper = _interopRequireDefault(require("dumper.js")); var _fxjs = require("fxjs"); var _mysql = _interopRequireDefault(require("mysql")); var _pgPool = _interopRequireDefault(require("pg-pool")); var _pluralize = _interopRequireDefault(require("pluralize")); var _ljoin = _interopRequireDefault(require("./ljoin.js")); const { dump } = _dumper.default; const { plural, singular } = _pluralize.default; const FxSQL_DEBUG = { DUMP: false, LOG: false, ERROR_WITH_SQL: false }; exports.FxSQL_DEBUG = FxSQL_DEBUG; const SymbolColumn = (0, _symbol.default)('COLUMN'); const SymbolTag = (0, _symbol.default)('TAG'); const SymbolInjection = (0, _symbol.default)('INJECTION'); const SymbolDefault = (0, _symbol.default)('DEFAULT'); const wrap_arr = a => (0, _isArray.default)(a) ? a : [a]; const mix = (arr1, arr2) => (0, _reduce.default)(arr1).call(arr1, (res, item, i) => { res.push(item); i < arr2.length && res.push(arr2[i]); return res; }, []); const uniq_index_by = (0, _fxjs.curry)((f, coll) => (0, _fxjs.indexBy)(f, (0, _fxjs.uniqueBy)(f, coll))); const first = a => a && a[0]; const last = a => a && a[a.length - 1]; const is_plain_object = obj => !!obj && typeof obj == 'object' && obj.constructor == Object; const is_column = f => !!(f && f[SymbolColumn]); const is_tag = f => !!(f && f[SymbolTag]); const is_injection = query => query == SymbolInjection; const tag = f => typeof f == 'function' ? (0, _assign.default)(f, { [SymbolTag]: true }) : tag(_ => f); function BASE({ create_pool, end_pool, query_fn, get_connection = pool => pool.connect(), BEGIN = client => client.query('BEGIN'), COMMIT = async client => { await client.query('COMMIT'); return await client.release(); }, ROLLBACK = async client => { await client.query('ROLLBACK'); return await client.release(); }, reg_q = /\?\?/g, to_q = () => '??', escape_dq = idtf => `"${('' + idtf).replace(/\\/g, '\\\\').replace(/"/g, '""')}"`, replace_q = query => { if (is_injection(query)) return SymbolInjection; let i = 0; query.text = query.text.replace(reg_q, _ => `$${++i}`); return query; }, use_ljoin }) { const add_column = me => { var _context; return me.column == '*' ? COLUMN(me.as + '.*') : is_column(me.column) ? COLUMN(...(0, _fxjs.go)((0, _concat.default)(_context = me.column.originals).call(_context, (0, _fxjs.pluck)('left_key', me.rels)), (0, _mapInstanceProperty2(_fxjs))(c => (0, _fxjs.isString)(c) ? me.as + '.' + c : c), _fxjs.uniq)) : is_tag(me.column) ? CL(me.column) : tag(SymbolInjection); }; const columnize = v => { var _context2; return v == '*' ? '*' : v.match(/\s*\sas\s\s*/i) ? (0, _map.default)(_context2 = v.split(/\s*\sas\s\s*/i)).call(_context2, dq).join(' AS ') : dq(v); }; const dq = str => { var _context3; return (0, _map.default)(_context3 = ('' + str).split('.')).call(_context3, s => s == '*' ? s : escape_dq(s)).join('.'); }; function ASSOCIATE_MODULE(strs, ...tails) { var _context4, _context5; strs = (0, _slice.default)(strs).call(strs); strs.push(strs.pop() + '\n'); var [strs2, tails2] = import_module(strs, tails); const splited = (0, _filter.default)(_context4 = (0, _filter.default)(_context5 = (0, _fxjs.deepFlat)((0, _map.default)(strs).call(strs, str => str.split('\n')))).call(_context5, str => str.match(/^\s*/)[0])).call(_context4, str => (0, _trim.default)(str).call(str)); const min = (0, _fxjs.minBy)(str => str.match(/^\s*/)[0].length, splited) || ''; const a = '\n' + min.match(/^\s*/)[0]; return [(0, _map.default)(strs2).call(strs2, str => str.split(a).join('\n')), tails2]; } function import_module(strs, tails) { var _context6, _context7; if (!(0, _some.default)(tails).call(tails, tail => typeof tail == 'function' && !is_tag(tail))) return [strs, tails]; var strs2 = [...strs]; var j = 0; var tails2 = (0, _map.default)(tails).call(tails, function (tail, i) { if (typeof tail != 'function' || is_tag(tail)) return tail; var k = i + j++; var spaces = last(strs2[k].split('\n')).match(/^\s*/)[0]; var [strs3, tails3] = tail(); (0, _splice.default)(strs2).call(strs2, k + 1, 0, (0, _map.default)(strs3).call(strs3, str => str.replace(/\n/g, '\n' + spaces))); return tails3; }); return [(0, _reduce.default)(_context6 = (0, _filter.default)(_context7 = (0, _fxjs.deepFlat)(strs2)).call(_context7, str => (0, _trim.default)(str).call(str))).call(_context6, (strs, str, i) => { var _context8; if (i == 0) return strs.push(str), strs; const splited = last(strs).split('\n'); if (!(0, _trim.default)(_context8 = last(splited)).call(_context8)) { splited[splited.length - 1] = str.substr(1); strs[strs.length - 1] = splited.join('\n'); } else { strs.push(str); } return strs; }, []), (0, _fxjs.deepFlat)(tails2)]; } function ready_sqls(strs, tails) { const [strs2, tails2] = import_module(strs, tails); const options = (0, _map.default)(strs2).call(strs2, s => { var _context9; return (0, _map.default)(_context9 = s.replace(/\s*\n/, '').split('\n')).call(_context9, s => { var _context10; var depth = s.match(/^\s*/)[0].length, as = (0, _trim.default)(s).call(s), rel_type; var prefix = as.substr(0, 2); if ((0, _includes.default)(_context10 = ['- ', '< ', 'x ']).call(_context10, prefix)) { var _context11; rel_type = (0, _trim.default)(prefix).call(prefix); as = (0, _trim.default)(_context11 = as.substr(1)).call(_context11); return { depth, as, rel_type }; } else if (prefix == 'p ') { var _context12; rel_type = as[2]; as = (0, _trim.default)(_context12 = as.substr(3)).call(_context12); return { depth, as, rel_type, is_poly: true }; } else { return { depth, as }; } }); }); (0, _fxjs.go)(tails2, (0, _mapInstanceProperty2(_fxjs))(tail => is_tag(tail) ? { query: tail } : (0, _assign.default)({}, tail, { query: tail.query || tag() })), _entries.default, (0, _fxjs.each)(([i, t]) => (0, _fxjs.go)(options[i], last, _ => (0, _assign.default)(_, t)))); return options; } function merge_query(queries, sep = ' ') { var _context13; if ((0, _find.default)(queries).call(queries, is_injection)) return SymbolInjection; var query = (0, _reduceInstanceProperty2(_fxjs))((res, query) => { if (!query) return res; if (query.text) res.text += sep + query.text; if ((0, _values.default)(query)) (0, _values.default)(res).push(...(0, _values.default)(query)); return res; }, { text: '', values: [] }, queries); query.text = (0, _trim.default)(_context13 = query.text.replace(/\n/g, ' ').replace(/\s\s*/g, ' ')).call(_context13); return query; } function VALUES(values) { return tag(function () { var _context14; values = (0, _isArray.default)(values) ? values : [values]; const columns = (0, _fxjs.go)(values, (0, _mapInstanceProperty2(_fxjs))(_keys.default), _flatInstanceProperty(_fxjs), _fxjs.uniq); const DEFAULTS = (0, _fxjs.go)(columns, (0, _mapInstanceProperty2(_fxjs))(k => [k, SymbolDefault]), _fxjs.object); values = (0, _map.default)(_context14 = (0, _map.default)(values).call(values, v => (0, _assign.default)({}, DEFAULTS, v))).call(_context14, v => (0, _values2.default)(v)); return { text: `(${COLUMN(...columns)().text}) VALUES (${(0, _map.default)(values).call(values, v => (0, _map.default)(v).call(v, v => v == SymbolDefault ? 'DEFAULT' : to_q()).join(', ')).join('), (')})`, values: (0, _flatInstanceProperty(_fxjs))((0, _map.default)(values).call(values, v => (0, _filter.default)(v).call(v, v => v != SymbolDefault))) }; }); } function COLUMN(...originals) { return (0, _assign.default)(tag(function () { let sqls = (0, _flatInstanceProperty(_fxjs))((0, _map.default)(originals).call(originals, v => { var _context15; return (0, _fxjs.isString)(v) ? [{ text: columnize(v) }, { text: ', ' }] : is_tag(v) ? [v(), { text: ', ' }] : [{ text: (0, _map.default)(_context15 = (0, _entries.default)(v)).call(_context15, v => (0, _map.default)(v).call(v, dq).join(' AS ')).join(', ') }, { text: ', ' }]; })); sqls.pop(); return merge_query(sqls, ''); }), { [SymbolColumn]: true, originals: originals }); } const CL = COLUMN, TABLE = COLUMN, TB = TABLE; function PARAMS(obj, sep) { return tag(function () { var _context16; let i = 0; const text = (0, _map.default)(_context16 = (0, _keys.default)(obj)).call(_context16, k => `${columnize(k)} = ${to_q()}`).join(sep); const values = (0, _values2.default)(obj); return { text: text.replace(reg_q, function () { const value = values[i++]; return is_column(value) ? value().text : to_q(); }), values: (0, _fxjs.reject)(is_column, values) }; }); } function EQ(obj, sep = 'AND') { return PARAMS(obj, ' ' + sep + ' '); } function SET(obj) { return tag(function () { const query = PARAMS(obj, ', ')(); query.text = 'SET ' + query.text; return query; }); } function BASE_IN(key, operator, values) { values = (0, _fxjs.uniq)(values); var keys_text = COLUMN(...wrap_arr(key))().text; return { text: `${(0, _isArray.default)(key) ? `(${keys_text})` : keys_text} ${operator} (${(0, _map.default)(values).call(values, (0, _isArray.default)(key) ? v => `(${(0, _map.default)(v).call(v, to_q).join(', ')})` : to_q).join(', ')})`, values: (0, _fxjs.deepFlat)(values) }; } function IN(key, values) { return tag(function () { if (!values || !values.length) return { text: `1=??`, values: [0] }; return BASE_IN(key, 'IN', values); }); } function NOT_IN(key, values) { return tag(function () { if (!values || !values.length) return { text: `1=??`, values: [0] }; return BASE_IN(key, 'NOT IN', values); }); } function _SQL(texts, values) { return (0, _fxjs.go)(mix((0, _map.default)(texts).call(texts, text => ({ text })), (0, _map.default)(values).call(values, value => is_tag(value) ? value() : (0, _fxjs.isFunction)(value) ? SymbolInjection : { text: to_q(), values: [value] })), merge_query); } function SQL(texts, ...values) { return tag(function () { return _SQL(texts, values); }); } function SQLS(sqls) { return tag(function () { return (0, _find.default)(sqls).call(sqls, sql => !is_tag(sql)) ? SymbolInjection : merge_query((0, _map.default)(sqls).call(sqls, sql => sql())); }); } function baseAssociate(QUERY) { return async function (strs, ...tails) { return (0, _fxjs.go)(ready_sqls(strs, tails), _fxjs.deepFlat, (0, _filterInstanceProperty2(_fxjs))(t => t.as), (0, _fxjs.each)(option => { option.column = option.column || '*'; option.join = option.join || SQL``; option.query = option.query || tag(); option.table = option.table || (option.rel_type == '-' ? plural(option.as) : option.as); option.rels = []; option.row_number = option.row_number || []; }), function setting([left, ...rest]) { const cur = [left]; (0, _fxjs.each)(me => { while (!(last(cur).depth < me.depth)) cur.pop(); const left = last(cur); left.rels.push(me); if (me.rel_type == '-') { me.left_key = me.left_key || (me.is_poly ? 'id' : singular(me.table) + '_id'); me.where_key = me.key || (me.is_poly ? 'attached_id' : 'id'); me.xjoin = tag(); } else if (me.rel_type == '<') { me.left_key = me.left_key || 'id'; me.where_key = me.key || (me.is_poly ? 'attached_id' : singular(left.table) + '_id'); me.xjoin = tag(); } else if (me.rel_type == 'x') { me.left_key = me.left_key || 'id'; const xtable = me.xtable || left.table + '_' + me.table; const xtable_as = me.xtable_as || xtable; me.where_key = `${xtable_as}.${me.left_xkey || singular(left.table) + '_id'}`; me.xjoin = SQL`INNER JOIN ${TB(xtable)} AS ${TB(xtable_as)} ON ${EQ({ [`${xtable_as}.${me.xkey || singular(me.table) + '_id'}`]: COLUMN(me.as + '.' + (me.key || 'id')) })}`; } me.poly_type = me.is_poly ? SQL`AND ${EQ(is_plain_object(me.poly_type) ? me.poly_type : { attached_type: me.poly_type || left.table })}` : tag(); cur.push(me); }, rest); return left; }, async function (me) { const lefts = await QUERY` SELECT ${add_column(me)} FROM ${TB(me.table)} AS ${TB(me.as)} ${me.query}`; return (0, _fxjs.go)([lefts, me], function recur([lefts, option]) { return lefts.length && option.rels.length && (0, _fxjs.go)(option.rels, (0, _fxjs.mapC)(async function (me) { var _context17; const query = me.query(); if (query && query.text) query.text = query.text.replace(/^\s*WHERE/i, 'AND'); var fold_key = me.rel_type == 'x' ? `_#_${me.where_key.split('.')[1]}_#_` : me.where_key; const colums = (0, _fxjs.uniq)((0, _concat.default)(_context17 = add_column(me).originals).call(_context17, me.rel_type != 'x' ? me.as + '.' + me.where_key : me.where_key + ' AS ' + fold_key)); const in_vals = (0, _filterInstanceProperty2(_fxjs))(a => a != null, (0, _fxjs.pluck)(me.left_key, lefts)); const is_row_num = me.row_number.length == 2; const from_sql = SQL` FROM ${TB(me.table)} AS ${TB(me.as)} ${me.join} ${me.xjoin} WHERE ${IN((me.rel_type == 'x' ? '' : me.as + '.') + me.where_key, in_vals)} ${me.poly_type} ${tag(query)}`; const rights = !in_vals.length ? [] : await (is_row_num ? QUERY` SELECT * FROM ( SELECT ${COLUMN(...colums)}, ROW_NUMBER() OVER (PARTITION BY ${CL(me.where_key)} ORDER BY ${me.row_number[1]}) as "--row_number--" ${from_sql} ) AS "--row_number_table--" WHERE "--row_number_table--"."--row_number--"<=${me.row_number[0]}` : QUERY`SELECT ${COLUMN(...colums)} ${from_sql}`); const [folder, default_value] = me.rel_type == '-' ? [uniq_index_by, () => ({})] : [_fxjs.groupBy, () => []]; return (0, _fxjs.go)(rights, is_row_num ? (0, _mapInstanceProperty2(_fxjs))(r => delete r['--row_number--'] && r) : r => r, folder(a => a[fold_key]), folded => (0, _fxjs.each)(function (left) { left._ = left._ || {}; left._[me.as] = folded[left[me.left_key]] || default_value(); if (me.rel_type == 'x') (0, _fxjs.each)(a => delete a[fold_key], left._[me.as]); }, lefts), _ => recur([rights, me]), _ => me.hook && (0, _fxjs.each)(left => (0, _fxjs.go)(me.hook(left._[me.as]), right => left._[me.as] = right), lefts)); })); }, _ => me.hook ? me.hook(lefts) : lefts); }); }; } function CONNECT(connection_info) { const pool = create_pool(connection_info); const pool_query = query_fn(pool); const _on2_obj = { error: function () {} }; pool.queryError = cb => { _on2_obj['error'] = cb; }; async function base_query(excute_query, texts, values, transaction_querys) { const error_for_stack = new Error(); try { var query = replace_q(_SQL(texts, values)); if ((0, _isArray.default)(transaction_querys)) transaction_querys.push({ text: query.text, values: (0, _stringify.default)((0, _values.default)(query)), stack: FxSQL_DEBUG.LOG && new Error().stack }); return await (0, _fxjs.go)(is_injection(query) ? _promise.default.reject('INJECTION ERROR') : query, (0, _fxjs.tap)(function (query) { if (FxSQL_DEBUG.DUMP) dump(query); typeof FxSQL_DEBUG.LOG == 'function' ? FxSQL_DEBUG.LOG(query) : FxSQL_DEBUG.LOG && console.log(query.text, '\n', (0, _values.default)(query)); }), excute_query); } catch (e) { FxSQL_DEBUG.ERROR_WITH_SQL && (e.stack = `\nFxSQL_DEBUG.ERROR_WITH_SQL:\n text: ${query.text}\n values: ${(0, _stringify.default)((0, _values.default)(query))}\n${e.stack}`); error_for_stack.message = e.message; _on2_obj.error(query, error_for_stack); throw e; } } function QUERY(texts, ...values) { return base_query(pool_query, texts, values); } function END() { return end_pool(pool); } const QUERY1 = (0, _fxjs.pipe)(QUERY, first), ASSOCIATE = baseAssociate(QUERY), ASSOCIATE1 = (0, _fxjs.pipe)(ASSOCIATE, first); var ljoin = null; async function LOAD_LJOIN(QUERY) { if (!ljoin) ljoin = await (0, _ljoin.default)({ ready_sqls, add_column, tag, FxSQL_DEBUG, connection_info, QUERY, VALUES, IN, NOT_IN, EQ, SET, COLUMN, CL, TABLE, TB, SQL, SQLS }); return ljoin(QUERY); } let baseTransactionQuery = function () {}; let transactionErrorHandler = function (err) { throw err; }; return { POOL: pool, VALUES, IN, NOT_IN, EQ, SET, COLUMN, CL, TABLE, TB, SQL, SQLS, FxSQL_DEBUG, QUERY, QUERY1, ASSOCIATE, ASSOCIATE1, ASSOCIATE_MODULE, END, LOAD_LJOIN: use_ljoin ? LOAD_LJOIN : null, config: { setBaseTransactionQuery(func) { baseTransactionQuery = func; }, setTransactionErrorHandler(func) { transactionErrorHandler = func; } }, async TRANSACTION() { const stack = Error('Transaction start stacktrace'); try { const client = await get_connection(pool); const client_query = query_fn(client); const transaction_querys = []; await BEGIN(client); const QUERY = function QUERY(texts, ...values) { return base_query(client_query, texts, values, transaction_querys); }; const QUERY1 = (0, _fxjs.pipe)(QUERY, first), ASSOCIATE = baseAssociate(QUERY), ASSOCIATE1 = (0, _fxjs.pipe)(ASSOCIATE, first); await baseTransactionQuery(QUERY, QUERY1); client.on('error', err => { transactionErrorHandler(err, client, transaction_querys, stack); }); return { client, VALUES, IN, NOT_IN, EQ, SET, COLUMN, CL, TABLE, TB, SQL, QUERY, QUERY1, ASSOCIATE, ASSOCIATE1, LJOIN: use_ljoin && ljoin ? await ljoin(QUERY) : null, COMMIT: _ => { const { stack } = new Error(); transaction_querys.push({ query: 'COMMIT', VALUES: [], stack }); return COMMIT(client); }, ROLLBACK: _ => { const { stack } = new Error(); transaction_querys.push({ query: 'ROLLBACK', VALUES: [], stack }); return ROLLBACK(client); } }; } catch (e) { throw e; } } }; } return { CONNECT, VALUES, IN, NOT_IN, EQ, SET, COLUMN, CL, TABLE, TB, SQL, SQLS, FxSQL_DEBUG }; } const method_promise = (0, _fxjs.curry)((name, obj) => new _promise.default((resolve, reject) => obj[name]((err, res) => err ? reject(err) : resolve(res)))); const PostgreSQL = BASE({ create_pool: connection_info => new _pgPool.default(connection_info), end_pool: pool => pool.end(), query_fn: pool => { var _context18; return (0, _fxjs.pipe)((0, _bind.default)(_context18 = pool.query).call(_context18, pool), res => res.rows); }, use_ljoin: true }), MySQL = BASE({ create_pool: connection_info => _mysql.default.createPool(connection_info), end_pool: pool => new _promise.default((resolve, reject) => pool.end(err => err ? reject(err) : resolve())), query_fn: pool => ({ text, values }) => new _promise.default((resolve, reject) => pool.query(text, values, (err, results) => err ? reject(err) : resolve(results))), get_connection: method_promise('getConnection'), BEGIN: method_promise('beginTransaction'), COMMIT: method_promise('commit'), ROLLBACK: method_promise('rollback'), reg_q: /\?/g, to_q: () => '?', escape_dq: _mysql.default.escapeId, replace_q: _ => _ }); exports.MySQL = MySQL; exports.PostgreSQL = PostgreSQL;