fxsql
Version:
Functional query builder based on fxjs
666 lines (576 loc) • 22.6 kB
JavaScript
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: _ => _
});