You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

201 lines
6.1 KiB
JavaScript

"use strict";
const util = require("util");
let { select, onlyColumns, where, withRelations, withDerived, column, through, inValues, sql, postProcess, belongsTo, has, value, parameter, not, anyOf, allOf, lessThan, moreThan, alias, foreignColumn, table, expression, equals } = require("../src/operations");
const astToQuery = require("../src/ast-to-query");
Error.stackTraceLimit = Infinity;
function withOwner() {
return withRelations({ owner: "owner_id" });
}
// FIXME: Mark AST nodes with a special marker -- and disallow these from being interpreted as a WHERE object!
// FIXME: Figure out composability for something like connect-session-knex
console.time("queryGen");
////// VALIDATION TESTING START ///////
let query;
// Assume that the below numbers were generated by some external code somehow, and we don't know upfront which one is going to be lowest
let a = 523;
let b = 62;
let c = 452;
// let flooz = { a: anyOf };
try {
// query = expression({
// left: "foo",
// condition: equals("bar")
// });
query = select("projects", [
where({
// foo: anyOf([ "bar", "baz", anyOf([ "bar2", "baz2" ]), sql("TRUE") ]),
// qux: anyOf([ 13, moreThan(42) ]),
complex: anyOf([
30,
40,
allOf([
moreThan(100),
lessThan(200),
lessThan(parameter("max"))
])
])
}),
where({ second: 2 })
]);
// query = select("projects", [
// onlyColumns([
// "foo",
// alias("bar", 42),
// alias("baz", sql("foo"))
// ]),
// where(anyOf([
// { foo: "bar", qux: anyOf([ "quz", "quy" ]) },
// { baz: lessThan(42) }
// ]))
// ]);
/* {
query: 'SELECT foo, ? AS bar, foo AS baz FROM projects WHERE foo = ? OR baz < ?;',
params: [ 42, 'bar', 42 ],
placeholders: []
} */
// query = select("projects", [
// onlyColumns([ "id", "name" ]),
// where({
// active: true,
// visible: true,
// // primary_category_id: anyOf([ 2, 3, 5, 7, 8 ])
// // primary_category_id: anyOf(parameter("categoryIDs"))
// primary_category_id: not(anyOf(parameter("categoryIDs"))) // FIXME/MARKER: This gets stringified wrong!
// }),
// // FIXME: where pivot table entry exists for category in that list
// withRelations({
// primaryCategory: belongsTo("primary_category_id", { query: [ withOwner() ] }),
// categories: through([
// has("projects_categories.project_id", { query: [
// // Optional extra clauses for the query on the pivot table, eg. for filtering entries
// where({ adminApproved: true })
// ]}),
// "category_id"
// ]),
// // all user groups for a given project ID -> all memberships for the given user group IDs -> for each membership, the record referenced by the given user_id
// users: through([ "user_groups.project_id", "membership.user_group_id", "user_id" ]),
// // ... expands to ...
// // users: through([
// // has({ column: foreignColumn({ table: "user_groups", column: "project_id" }) }),
// // has({ column: foreignColumn({ table: "memberships", column: "user_group_id" }) }),
// // belongsTo({ column: column("user_id") }),
// // ]),
// owner: "owner_id",
// // ... expands to
// // owner: belongsTo({ column: "owner_id" }),
// releases: "releases.project_id",
// // ... expands to ...
// // releases: has({ column: "releases.project_id" })
// }),
// withDerived({
// capitalized_name: sql("UPPER(name)"),
// team_count: sql("moderator_count + admin_count"),
// // fourty_two: value(42), // This makes no sense in withDerived!
// name_distance: (project) => wordDistanceAlgorithm(project.name, "someReferenceName") // NOTE: This could have returned a Promise!
// }),
// mapCase({ from: "snake", to: "camel" })
// ]);
console.timeEnd("queryGen");
console.log(util.inspect(query, { depth: null, colors: true }));
console.log("");
console.log(util.inspect(astToQuery(query), { depth: null, colors: true }));
} catch (error) {
// console.error(error.message);
// console.error("");
if (error.name === "AggregrateValidationError") {
console.error(error.stack);
console.error("# Inputs:", util.inspect(error.errors.map((error) => {
let stringifiedPath = error.path.join(" -> ");
return [ stringifiedPath + ":", error.value ];
}), { colors: true, depth: null }));
process.exit(1);
} else {
throw error;
}
}
////// VALIDATION TESTING END ///////
// TODO: Allow specifying placeholders that can be filled in later, for truly reusable query builders (so that eg. query planning only needs to be done once, this could even use prepared statements under the hood)
// let query = select("projects", [
// where(allOf([
// { score: not(lessThan(anyOf([ a, b, parameter("score") ]))) },
// anyOf([
// { active: true },
// { always_show: true }
// ])
// ]))
// ]);
// let query = select("projects", [
// where({
// active: true,
// // score: not(lessThan(anyOf([ a, b, c ])))
// // MARKER: Implement parameter tracking, so that the below can be made to work (needs a `placeholder` query gen handler that just returns parameter metadata or something, and this should be used elsewhere as well, and the anyOf/allOf code should be updated to also check for placeholders, not just SQL expressions)
// score: not(lessThan(anyOf([
// a,
// b,
// sql("44 + 33"),
// parameter("foo")
// ])))
// })
// ]);
// FIXME: Pre-processing (eg. inverse case-mapping for inserted objects or WHERE clauses) - maybe pre-processing that subscribes to particular operations? Something along the lines of axios interceptors perhaps
// FIXME: `either`/`all` for OR/AND representation respectively?
// FIXME: I guess `withDerived` could be implemented externally, as something that (depending on value type) either a) adds a column selector or b) adds a post-processing hook
// FIXME: Aggregrates (GROUP BY)
// return db.execute(query);
/* Hypothetical `mapCase` implementation */
function mapObjectCase(object, from, to) {
return mapObject(object, (key, value) => {
return [
caseMapper(key, from, to),
value
];
});
}
function mapCase({ from, to }) {
return postProcess((results) => {
// NOTE: This could have returned a Promise!
return results.map((result) => mapObjectCase(result, from, to));
});
}