refql
Version:
A Node.js and Deno library for composing and running SQL queries.
219 lines (218 loc) • 8.57 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.isRQLTag = void 0;
exports.createRQLTag = createRQLTag;
const consts_1 = require("../common/consts");
const isEmptyTag_1 = __importDefault(require("../common/isEmptyTag"));
const joinMembers_1 = __importDefault(require("../common/joinMembers"));
const Prop_1 = __importDefault(require("../Prop"));
const RefProp_1 = __importDefault(require("../Prop/RefProp"));
const SQLProp_1 = __importDefault(require("../Prop/SQLProp"));
const SQLTag_1 = require("../SQLTag");
const Raw_1 = __importDefault(require("../SQLTag/Raw"));
const sql_1 = require("../SQLTag/sql");
const Limit_1 = __importDefault(require("./Limit"));
const Offset_1 = __importDefault(require("./Offset"));
const OrderBy_1 = __importDefault(require("./OrderBy"));
const RefNode_1 = __importDefault(require("./RefNode"));
const RQLNode_1 = require("./RQLNode");
const type = "refql/RQLTag";
let prototype = Object.assign({}, RQLNode_1.rqlNodePrototype, {
constructor: createRQLTag,
[consts_1.refqlType]: type,
concat,
[consts_1.flConcat]: concat,
interpret,
compile,
run
});
function createRQLTag(table, nodes, options) {
const tag = ((params) => {
return options.runner(tag, params);
});
Object.setPrototypeOf(tag, Object.assign(Object.create(Function.prototype), prototype, {
table,
nodes,
options
}));
return tag;
}
const concatDeep = (nodes) => {
return nodes.reduce((acc, node) => {
if (RefNode_1.default.isRefNode(node)) {
const { table } = node.tag;
const id = table.toString();
if (acc[id]) {
acc[id] = acc[id].concat(node.tag);
}
else {
acc[id] = node.tag;
}
}
else {
acc.nodes.push(node);
}
return acc;
}, { nodes: [] });
};
function concat(other) {
if (!this.table.equals(other.table)) {
throw new Error("U can't concat RQLTags that come from different tables");
}
const _a = concatDeep(this.nodes.concat(other.nodes)), { nodes } = _a, refs = __rest(_a, ["nodes"]);
const refNodes = Object.values(this.table.props)
.filter(prop => RefProp_1.default.isRefProp(prop) && refs[prop.child.toString()])
.map((prop) => {
const ref = refs[prop.child.toString()];
return (0, RefNode_1.default)(createRQLTag(ref.table, ref.nodes, this.options), prop, this.table, this.options);
});
return createRQLTag(this.table, [...nodes, ...refNodes], this.options);
}
function interpret(where = (0, sql_1.sqlX) `where 1 = 1`) {
const { nodes, table } = this, next = [], members = [];
let filters = (0, sql_1.sqlX) ``;
let orderBies = (0, sql_1.sqlX) ``;
let limit = (0, sql_1.sqlX) ``;
let offset = (0, sql_1.sqlX) ``;
let memberCount = 0;
const caseOfRef = (tag, info, single) => {
for (const lr of info.lRef) {
members.push({ as: lr.as, node: (0, Raw_1.default)(lr), isOmitted: false });
}
next.push({ tag, link: [info.as, info.lRef.map(lr => lr.as)], single });
};
for (const node of nodes) {
if (Prop_1.default.isProp(node) || SQLProp_1.default.isSQLProp(node)) {
const col = node.interpret();
members.push({ as: node.as, node: (0, sql_1.sqlX) `${col} ${(0, Raw_1.default)(`"${node.as}"`)}`, isOmitted: node.isOmitted });
if (node.operations.length === 0 && !node.isOmitted && !SQLProp_1.default.isSQLProp(node)) {
memberCount += 1;
}
let propFilters = (0, sql_1.sqlX) ``;
let filterIdx = 0;
for (const op of node.operations) {
if (OrderBy_1.default.isOrderBy(op)) {
const delimiter = (0, isEmptyTag_1.default)(orderBies) ? "order by " : ", ";
orderBies = orderBies.join(delimiter, op.interpret(col, true));
}
else {
propFilters = propFilters.join(filterIdx > 0 ? " " : "", op.interpret(col, filterIdx > 0));
filterIdx += 1;
}
}
if (!(0, isEmptyTag_1.default)(propFilters)) {
filters = filters.join(" ", (0, sql_1.sqlX) `and (${propFilters})`);
}
}
else if (Limit_1.default.isLimit(node)) {
limit = node.interpret();
}
else if (Offset_1.default.isOffset(node)) {
offset = node.interpret();
}
else if (RefNode_1.default.isRefNode(node)) {
caseOfRef(node.joinLateral(), node.info, node.single);
}
else if ((0, SQLTag_1.isSQLTag)(node)) {
filters = filters.join(" ", node);
}
else {
throw new Error(`Unknown RQLNode Type: "${String(node)}"`);
}
}
// Select all columns
if (memberCount === 0) {
const fieldProps = Object.entries(table.props)
.map(([, prop]) => prop)
.filter(prop => Prop_1.default.isProp(prop) && !(0, SQLTag_1.isSQLTag)(prop.col))
.map(prop => ({
as: prop.as,
node: (0, sql_1.sqlX) `${prop.interpret()} ${(0, Raw_1.default)(`"${prop.as}"`)}`,
isOmitted: false
}));
members.push(...fieldProps);
}
let tag = (0, sql_1.sqlX) `
select ${(0, joinMembers_1.default)(members)}
from ${(0, Raw_1.default)(table)}
`
.concat(where)
.join("", filters)
.concat(orderBies)
.concat(limit)
.concat(offset);
return {
next,
tag
};
}
function compile(params) {
if (!this.interpreted) {
let { next, tag } = this.interpret();
this.interpreted = {
tag,
next
};
}
return [
...this.interpreted.tag.compile(params),
this.interpreted.next
];
}
function run(params, querier) {
return __awaiter(this, void 0, void 0, function* () {
const [query, values, next] = this.compile(params);
const refQLRows = yield (querier || this.options.querier)(query, values);
if (!refQLRows.length)
return [];
const nextData = yield Promise.all(next.map(
// { ...null } = {}
n => n.tag.run(Object.assign(Object.assign({}, params), { refQLRows }), querier)));
return refQLRows.map(row => nextData.reduce((agg, nextRows, idx) => {
const { single, link: [lAs, rAs] } = next[idx];
agg[lAs] = nextRows
.filter((r) => rAs.reduce((acc, as) => acc && r[as] === row[as], true))
.map((r) => {
let matched = Object.assign({}, r);
for (const as of rAs) {
delete matched[as];
}
return matched;
});
if (single) {
agg[lAs] = agg[lAs][0] || null;
}
for (const as of rAs) {
delete agg[as];
}
return agg;
}, row));
});
}
const isRQLTag = function (x) {
return x != null && x[consts_1.refqlType] === type;
};
exports.isRQLTag = isRQLTag;
;