fxsql
Version:
Functional query builder based on fxjs
295 lines (283 loc) • 8.7 kB
JavaScript
import {
each,
filter,
flatten as cat,
go,
groupBy,
isString,
last,
map,
mapC,
pluck,
reject,
uniq,
} from 'fxjs/es';
import pluralize from 'pluralize';
const { singular } = pluralize;
export default async function load_ljoin({
ready_sqls,
add_column,
tag,
connection_info,
QUERY,
IN,
EQ,
COLUMN,
CL,
TB,
SQL,
SQLS,
}) {
const table_columns = {};
const add_as_join = (me, as) =>
COLUMN(
...go(
me.column.originals.concat(pluck('left_key', me.left_joins)),
map((c) => me.as + '.' + c + ' AS ' + `${as}>_<${c}`),
uniq,
),
);
Object.assign(
table_columns,
await go(
QUERY`
SELECT table_name, column_name
FROM information_schema.columns
WHERE
table_name in (
SELECT tablename
FROM pg_tables
WHERE
tableowner=${connection_info.user || process.env.PGUSER}
) ORDER BY table_name;`,
groupBy((v) => v.table_name),
map((v) => pluck('column_name', v)),
),
await go(
QUERY`
SELECT *
FROM INFORMATION_SCHEMA.view_column_usage
WHERE view_catalog=${connection_info.database || process.env.PGDATABASE}
;`,
groupBy((v) => v.view_name),
map((v) => pluck('column_name', v)),
),
);
function where_in_query(left, where_in, QUERY) {
const colums = uniq(add_column(left).originals.concat(left.key));
const query = left.query();
if (query && query.text)
query.text = query.text.replace(/^\s*WHERE/i, 'AND');
return left.row_number.length == 2
? QUERY`
SELECT *
FROM (
SELECT
${COLUMN(...colums)},
ROW_NUMBER() OVER (PARTITION BY ${CL(left.key)} ORDER BY ${
left.row_number[1]
}) as "--row_number--"
FROM ${TB(left.table)} AS ${TB(left.as)}
${where_in || tag()}
) AS "--row_number_table--"
WHERE "--row_number_table--"."--row_number--"<=${left.row_number[0]}`
: QUERY`
SELECT ${CL(...colums)}
FROM ${TB(left.table)} AS ${TB(left.as)}
${where_in || tag()}
${tag(query)}
`;
}
function left_join_query(left, where_in, QUERY) {
let i = 0;
left.lj_as = 'lj' + i++ + '//' + left.depth;
const first_col = add_as_join(left, left.lj_as).originals.concat(
left.as + '.id' + ' AS ' + `${left.lj_as}>_<id`,
);
if (left.key)
first_col.push(
left.as + '.' + left.key + ' AS ' + `${left.lj_as}>_<${left.key}`,
);
const join_columns = [first_col];
const join_sqls = [];
return go(
left,
function recur(me) {
me.left_join_over = true;
each((right) => {
const query = right.query();
right.lj_as = 'lj' + i++ + '//' + right.depth;
if (query && query.text)
query.text = query.text.replace(/^\s*WHERE/i, 'AND');
join_columns.push(
uniq(
add_as_join(right, right.lj_as)
.originals.concat(
right.as +
'.' +
right.key +
' AS ' +
`${right.lj_as}>_<${right.key}`,
)
.concat(right.as + '.id' + ' AS ' + `${right.lj_as}>_<id`),
),
);
join_sqls.push(SQL`
LEFT JOIN
${TB(right.table)} AS ${TB(right.as)}
ON
${EQ({
[me.as + '.' + right.left_key]: COLUMN(
right.as + '.' + right.key,
),
})}
${tag(query)}
`);
recur(right);
}, me.left_joins);
},
() => {
const query = left.query();
if (!query) return query;
query.text = query.text.replace(
/^\s*WHERE/i,
where_in ? 'AND' : 'WHERE',
);
return query;
},
(query) =>
left.row_number.length == 2
? QUERY`
SELECT "--row_number_table--".*
FROM (
SELECT
${COLUMN(...cat(join_columns))},
ROW_NUMBER() OVER (PARTITION BY ${CL(
left.as + '.' + left.key,
)} ORDER BY ${left.row_number[1]}) as "--row_number--"
FROM ${TB(left.table)} AS ${TB(left.as)}
${SQLS(join_sqls)}
${where_in || tag()}
${tag(query)}
) AS "--row_number_table--"
WHERE "--row_number_table--"."--row_number--"<=${left.row_number[0]}`
: QUERY`
SELECT ${COLUMN(...cat(join_columns))}
FROM ${TB(left.table)} AS ${TB(left.as)}
${SQLS(join_sqls)}
${where_in || tag()}
${tag(query)}`,
left.row_number.length == 2
? map((r) => delete r['--row_number--'] && r)
: (r) => r,
map((row) => {
const before_result_obj = {};
const result_obj = {};
for (const as in row) {
if (as.indexOf('>_<') == -1) return;
const split_as = as.split('>_<');
var tas = split_as[0];
before_result_obj[tas] = before_result_obj[tas] || { _: {} };
before_result_obj[tas][split_as[1]] = row[as];
}
!(function recur(me, memo) {
memo[me.as] = before_result_obj[me.lj_as];
each((right) => recur(right, memo[me.as]._), me.left_joins);
})(left, result_obj);
return result_obj[left.as];
}),
);
}
return function (QUERY) {
return async function ljoin(strs, ...tails) {
return go(
ready_sqls(strs, tails),
cat,
filter((t) => t.as),
each((option) => {
option.query = option.query || tag();
option.table =
option.table ||
(option.rel_type == '-' ? option.as + 's' : option.as);
option.column = option.column || CL(...table_columns[option.table]);
option.left_joins = [];
option.rels = [];
option.row_number = option.row_number || [];
}),
([left, ...rest]) => {
const cur = [left];
each((me) => {
while (!(last(cur).depth < me.depth)) cur.pop();
const left = last(cur);
if (me.rel_type == '-') {
me.left_key =
me.left_key || (me.is_poly ? 'id' : singular(me.table) + '_id');
me.key = me.key || (me.is_poly ? 'attached_id' : 'id');
left.left_joins.push(me);
} else {
me.left_key = me.left_key || 'id';
me.key =
me.key ||
(me.is_poly ? 'attached_id' : singular(left.table) + '_id');
}
left.rels.push(me);
me.poly_type = me.is_poly
? SQL`AND ${EQ(
isString(me.poly_type)
? { attached_type: me.poly_type || left.table }
: me.poly_type,
)}`
: tag();
cur.push(me);
}, rest);
return left;
},
async (left) => [
left,
left.left_joins.length
? await left_join_query(left, null, QUERY)
: await QUERY`
SELECT ${add_column(left)}
FROM ${TB(left.table)} AS ${TB(left.as)} ${left.query}`,
],
function recur([left, results]) {
if (reject((r) => r, results).length) return;
return go(
left.rels,
mapC(async function (me) {
const f_key_ids = uniq(
filter((r) => !!r, pluck(me.left_key, results)),
);
if (me.rel_type == '-' || !f_key_ids.length)
return recur([
me,
cat(map((r) => (r._ ? r._[me.as] : null), results)),
]);
return go(
(!me.left_join_over && me.left_joins.length
? left_join_query
: where_in_query)(
me,
SQL`WHERE ${IN(me.as + '.' + me.key, f_key_ids)}`,
QUERY,
),
groupBy((v) => v[me.key]),
function (groups) {
each(function (result) {
result._ = result._ || {};
result._[me.as] = groups[result[me.left_key]] || [];
}, results);
return recur([
me,
cat(map((r) => (r._ ? r._[me.as] : null), results)),
]);
},
);
}),
() => results,
);
},
);
};
};
}