UNPKG

fxsql

Version:

Functional query builder based on fxjs

666 lines (576 loc) 22.6 kB
import _Symbol from "@babel/runtime-corejs3/core-js-stable/symbol"; import _Array$isArray from "@babel/runtime-corejs3/core-js-stable/array/is-array"; import _reduceInstanceProperty from "@babel/runtime-corejs3/core-js-stable/instance/reduce"; import _Object$assign from "@babel/runtime-corejs3/core-js-stable/object/assign"; import _concatInstanceProperty from "@babel/runtime-corejs3/core-js-stable/instance/concat"; import _mapInstanceProperty from "@babel/runtime-corejs3/core-js-stable/instance/map"; import _sliceInstanceProperty from "@babel/runtime-corejs3/core-js-stable/instance/slice"; import _filterInstanceProperty from "@babel/runtime-corejs3/core-js-stable/instance/filter"; import _trimInstanceProperty from "@babel/runtime-corejs3/core-js-stable/instance/trim"; import _someInstanceProperty from "@babel/runtime-corejs3/core-js-stable/instance/some"; import _spliceInstanceProperty from "@babel/runtime-corejs3/core-js-stable/instance/splice"; import _includesInstanceProperty from "@babel/runtime-corejs3/core-js-stable/instance/includes"; import _Object$entries from "@babel/runtime-corejs3/core-js-stable/object/entries"; import _findInstanceProperty from "@babel/runtime-corejs3/core-js-stable/instance/find"; import _valuesInstanceProperty from "@babel/runtime-corejs3/core-js-stable/instance/values"; import _Object$keys from "@babel/runtime-corejs3/core-js-stable/object/keys"; import _Object$values from "@babel/runtime-corejs3/core-js-stable/object/values"; import _JSON$stringify from "@babel/runtime-corejs3/core-js-stable/json/stringify"; import _Promise from "@babel/runtime-corejs3/core-js-stable/promise"; import _bindInstanceProperty from "@babel/runtime-corejs3/core-js-stable/instance/bind"; import dumper from 'dumper.js'; import { curry, deepFlat, each, filter, flat, go, groupBy, indexBy, isFunction, isString, map, mapC, minBy, object, pipe, pluck, reduce, reject, tap, uniq, uniqueBy } from "fxjs"; import mysql from 'mysql'; import Pool from "pg-pool"; import pluralize from 'pluralize'; import load_ljoin from './ljoin.js'; const { dump } = dumper; const { plural, singular } = pluralize; export const FxSQL_DEBUG = { DUMP: false, LOG: false, ERROR_WITH_SQL: false }; const SymbolColumn = _Symbol('COLUMN'); const SymbolTag = _Symbol('TAG'); const SymbolInjection = _Symbol('INJECTION'); const SymbolDefault = _Symbol('DEFAULT'); const wrap_arr = a => _Array$isArray(a) ? a : [a]; const mix = (arr1, arr2) => _reduceInstanceProperty(arr1).call(arr1, (res, item, i) => { res.push(item); i < arr2.length && res.push(arr2[i]); return res; }, []); const uniq_index_by = curry((f, coll) => indexBy(f, 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' ? _Object$assign(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(...go(_concatInstanceProperty(_context = me.column.originals).call(_context, pluck('left_key', me.rels)), map(c => isString(c) ? me.as + '.' + c : c), uniq)) : is_tag(me.column) ? CL(me.column) : tag(SymbolInjection); }; const columnize = v => { var _context2; return v == '*' ? '*' : v.match(/\s*\sas\s\s*/i) ? _mapInstanceProperty(_context2 = v.split(/\s*\sas\s\s*/i)).call(_context2, dq).join(' AS ') : dq(v); }; const dq = str => { var _context3; return _mapInstanceProperty(_context3 = ('' + str).split('.')).call(_context3, s => s == '*' ? s : escape_dq(s)).join('.'); }; function ASSOCIATE_MODULE(strs, ...tails) { var _context4, _context5; strs = _sliceInstanceProperty(strs).call(strs); strs.push(strs.pop() + '\n'); var [strs2, tails2] = import_module(strs, tails); const splited = _filterInstanceProperty(_context4 = _filterInstanceProperty(_context5 = deepFlat(_mapInstanceProperty(strs).call(strs, str => str.split('\n')))).call(_context5, str => str.match(/^\s*/)[0])).call(_context4, str => _trimInstanceProperty(str).call(str)); const min = minBy(str => str.match(/^\s*/)[0].length, splited) || ''; const a = '\n' + min.match(/^\s*/)[0]; return [_mapInstanceProperty(strs2).call(strs2, str => str.split(a).join('\n')), tails2]; } function import_module(strs, tails) { var _context6, _context7; if (!_someInstanceProperty(tails).call(tails, tail => typeof tail == 'function' && !is_tag(tail))) return [strs, tails]; var strs2 = [...strs]; var j = 0; var tails2 = _mapInstanceProperty(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(); _spliceInstanceProperty(strs2).call(strs2, k + 1, 0, _mapInstanceProperty(strs3).call(strs3, str => str.replace(/\n/g, '\n' + spaces))); return tails3; }); return [_reduceInstanceProperty(_context6 = _filterInstanceProperty(_context7 = deepFlat(strs2)).call(_context7, str => _trimInstanceProperty(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 (!_trimInstanceProperty(_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; }, []), deepFlat(tails2)]; } function ready_sqls(strs, tails) { const [strs2, tails2] = import_module(strs, tails); const options = _mapInstanceProperty(strs2).call(strs2, s => { var _context9; return _mapInstanceProperty(_context9 = s.replace(/\s*\n/, '').split('\n')).call(_context9, s => { var _context10; var depth = s.match(/^\s*/)[0].length, as = _trimInstanceProperty(s).call(s), rel_type; var prefix = as.substr(0, 2); if (_includesInstanceProperty(_context10 = ['- ', '< ', 'x ']).call(_context10, prefix)) { var _context11; rel_type = _trimInstanceProperty(prefix).call(prefix); as = _trimInstanceProperty(_context11 = as.substr(1)).call(_context11); return { depth, as, rel_type }; } else if (prefix == 'p ') { var _context12; rel_type = as[2]; as = _trimInstanceProperty(_context12 = as.substr(3)).call(_context12); return { depth, as, rel_type, is_poly: true }; } else { return { depth, as }; } }); }); go(tails2, map(tail => is_tag(tail) ? { query: tail } : _Object$assign({}, tail, { query: tail.query || tag() })), _Object$entries, each(([i, t]) => go(options[i], last, _ => _Object$assign(_, t)))); return options; } function merge_query(queries, sep = ' ') { var _context13; if (_findInstanceProperty(queries).call(queries, is_injection)) return SymbolInjection; var query = reduce((res, query) => { if (!query) return res; if (query.text) res.text += sep + query.text; if (_valuesInstanceProperty(query)) _valuesInstanceProperty(res).push(..._valuesInstanceProperty(query)); return res; }, { text: '', values: [] }, queries); query.text = _trimInstanceProperty(_context13 = query.text.replace(/\n/g, ' ').replace(/\s\s*/g, ' ')).call(_context13); return query; } function VALUES(values) { return tag(function () { var _context14; values = _Array$isArray(values) ? values : [values]; const columns = go(values, map(_Object$keys), flat, uniq); const DEFAULTS = go(columns, map(k => [k, SymbolDefault]), object); values = _mapInstanceProperty(_context14 = _mapInstanceProperty(values).call(values, v => _Object$assign({}, DEFAULTS, v))).call(_context14, v => _Object$values(v)); return { text: `(${COLUMN(...columns)().text}) VALUES (${_mapInstanceProperty(values).call(values, v => _mapInstanceProperty(v).call(v, v => v == SymbolDefault ? 'DEFAULT' : to_q()).join(', ')).join('), (')})`, values: flat(_mapInstanceProperty(values).call(values, v => _filterInstanceProperty(v).call(v, v => v != SymbolDefault))) }; }); } function COLUMN(...originals) { return _Object$assign(tag(function () { let sqls = flat(_mapInstanceProperty(originals).call(originals, v => { var _context15; return isString(v) ? [{ text: columnize(v) }, { text: ', ' }] : is_tag(v) ? [v(), { text: ', ' }] : [{ text: _mapInstanceProperty(_context15 = _Object$entries(v)).call(_context15, v => _mapInstanceProperty(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 = _mapInstanceProperty(_context16 = _Object$keys(obj)).call(_context16, k => `${columnize(k)} = ${to_q()}`).join(sep); const values = _Object$values(obj); return { text: text.replace(reg_q, function () { const value = values[i++]; return is_column(value) ? value().text : to_q(); }), values: 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 = uniq(values); var keys_text = COLUMN(...wrap_arr(key))().text; return { text: `${_Array$isArray(key) ? `(${keys_text})` : keys_text} ${operator} (${_mapInstanceProperty(values).call(values, _Array$isArray(key) ? v => `(${_mapInstanceProperty(v).call(v, to_q).join(', ')})` : to_q).join(', ')})`, values: 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 go(mix(_mapInstanceProperty(texts).call(texts, text => ({ text })), _mapInstanceProperty(values).call(values, value => is_tag(value) ? value() : 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 _findInstanceProperty(sqls).call(sqls, sql => !is_tag(sql)) ? SymbolInjection : merge_query(_mapInstanceProperty(sqls).call(sqls, sql => sql())); }); } function baseAssociate(QUERY) { return async function (strs, ...tails) { return go(ready_sqls(strs, tails), deepFlat, filter(t => t.as), 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]; 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 go([lefts, me], function recur([lefts, option]) { return lefts.length && option.rels.length && go(option.rels, 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 = uniq(_concatInstanceProperty(_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 = filter(a => a != null, 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, () => ({})] : [groupBy, () => []]; return go(rights, is_row_num ? map(r => delete r['--row_number--'] && r) : r => r, folder(a => a[fold_key]), folded => each(function (left) { left._ = left._ || {}; left._[me.as] = folded[left[me.left_key]] || default_value(); if (me.rel_type == 'x') each(a => delete a[fold_key], left._[me.as]); }, lefts), _ => recur([rights, me]), _ => me.hook && each(left => 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 (_Array$isArray(transaction_querys)) transaction_querys.push({ text: query.text, values: _JSON$stringify(_valuesInstanceProperty(query)), stack: FxSQL_DEBUG.LOG && new Error().stack }); return await go(is_injection(query) ? _Promise.reject('INJECTION ERROR') : query, 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', _valuesInstanceProperty(query)); }), excute_query); } catch (e) { FxSQL_DEBUG.ERROR_WITH_SQL && (e.stack = `\nFxSQL_DEBUG.ERROR_WITH_SQL:\n text: ${query.text}\n values: ${_JSON$stringify(_valuesInstanceProperty(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 = pipe(QUERY, first), ASSOCIATE = baseAssociate(QUERY), ASSOCIATE1 = pipe(ASSOCIATE, first); var ljoin = null; async function LOAD_LJOIN(QUERY) { if (!ljoin) ljoin = await load_ljoin({ 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 = pipe(QUERY, first), ASSOCIATE = baseAssociate(QUERY), ASSOCIATE1 = 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 = curry((name, obj) => new _Promise((resolve, reject) => obj[name]((err, res) => err ? reject(err) : resolve(res)))); export const PostgreSQL = BASE({ create_pool: connection_info => new Pool(connection_info), end_pool: pool => pool.end(), query_fn: pool => { var _context18; return pipe(_bindInstanceProperty(_context18 = pool.query).call(_context18, pool), res => res.rows); }, use_ljoin: true }), MySQL = BASE({ create_pool: connection_info => mysql.createPool(connection_info), end_pool: pool => new _Promise((resolve, reject) => pool.end(err => err ? reject(err) : resolve())), query_fn: pool => ({ text, values }) => new _Promise((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.escapeId, replace_q: _ => _ });