@rindle/query-compiler 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +42 -0
- package/dist/ast.d.ts +2 -0
- package/dist/ast.d.ts.map +1 -0
- package/dist/ast.js +2 -0
- package/dist/ast.js.map +1 -0
- package/dist/catalog.d.ts +51 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/catalog.js +7 -0
- package/dist/catalog.js.map +1 -0
- package/dist/complete-ordering.d.ts +3 -0
- package/dist/complete-ordering.d.ts.map +1 -0
- package/dist/complete-ordering.js +44 -0
- package/dist/complete-ordering.js.map +1 -0
- package/dist/dialect.d.ts +49 -0
- package/dist/dialect.d.ts.map +1 -0
- package/dist/dialect.js +35 -0
- package/dist/dialect.js.map +1 -0
- package/dist/errors.d.ts +13 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +26 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/dist/pg-types.d.ts +20 -0
- package/dist/pg-types.d.ts.map +1 -0
- package/dist/pg-types.js +107 -0
- package/dist/pg-types.js.map +1 -0
- package/dist/postgres.d.ts +7 -0
- package/dist/postgres.d.ts.map +1 -0
- package/dist/postgres.js +134 -0
- package/dist/postgres.js.map +1 -0
- package/dist/reject.d.ts +3 -0
- package/dist/reject.d.ts.map +1 -0
- package/dist/reject.js +74 -0
- package/dist/reject.js.map +1 -0
- package/dist/walker.d.ts +11 -0
- package/dist/walker.d.ts.map +1 -0
- package/dist/walker.js +350 -0
- package/dist/walker.js.map +1 -0
- package/package.json +39 -0
- package/src/ast.ts +18 -0
- package/src/catalog.ts +59 -0
- package/src/complete-ordering.ts +48 -0
- package/src/dialect.ts +85 -0
- package/src/errors.ts +30 -0
- package/src/index.ts +74 -0
- package/src/pg-types.ts +119 -0
- package/src/postgres.ts +152 -0
- package/src/reject.ts +71 -0
- package/src/walker.ts +418 -0
package/dist/walker.js
ADDED
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
// The compiler core — a syntax-directed `Ast → SELECT` lowering, ported one-to-one from
|
|
2
|
+
// `rindle-d2s` (rust/rindle-d2s/src/lib.rs) and bound to our exact wire `Ast`. The one
|
|
3
|
+
// deliberate change from `rindle-d2s` is that filter/paging **values are parameterized**
|
|
4
|
+
// (pushed onto `params`, emitted as placeholders by the dialect) instead of inlined — so the
|
|
5
|
+
// output is injection-safe and plan-cacheable (Invariant 4). Everything else — the walker
|
|
6
|
+
// structure, ordering completion, keyset-paging predicate, empty-fold — matches the oracle.
|
|
7
|
+
import { completeOrdering } from "./complete-ordering.js";
|
|
8
|
+
import { missingAlias, unknownRelationship, unknownTable } from "./errors.js";
|
|
9
|
+
import { rejectUnsupported } from "./reject.js";
|
|
10
|
+
/** Compile `ast` against `catalog` for `dialect`. `singular` ⇒ a `.one()` root (single
|
|
11
|
+
* object or `null`); else a plural root (a JSON array). Pure: mutates only a clone. */
|
|
12
|
+
export function compileWith(ast, catalog, dialect, singular) {
|
|
13
|
+
rejectUnsupported(ast);
|
|
14
|
+
const cloned = structuredClone(ast);
|
|
15
|
+
completeOrdering(cloned, (t) => pkOf(catalog, t));
|
|
16
|
+
const cx = new Cx(catalog, dialect);
|
|
17
|
+
const expr = cx.collection(cloned, singular, null);
|
|
18
|
+
return { sql: `SELECT ${expr} AS "rindle_result"`, params: cx.params };
|
|
19
|
+
}
|
|
20
|
+
/** Compile context: the catalog, dialect, the accumulating params, and an alias counter. */
|
|
21
|
+
class Cx {
|
|
22
|
+
params = [];
|
|
23
|
+
#n = 0;
|
|
24
|
+
// Explicit fields (not constructor parameter properties): Node's strip-only type stripping
|
|
25
|
+
// does not support parameter properties, and this source runs directly from `.ts`.
|
|
26
|
+
#catalog;
|
|
27
|
+
#d;
|
|
28
|
+
constructor(catalog, d) {
|
|
29
|
+
this.#catalog = catalog;
|
|
30
|
+
this.#d = d;
|
|
31
|
+
}
|
|
32
|
+
#alias(table) {
|
|
33
|
+
return `${table}_${this.#n++}`;
|
|
34
|
+
}
|
|
35
|
+
/** A scalar-subquery expression producing the JSON for `ast` as a single object
|
|
36
|
+
* (`singular`) or a `json_group_array`/`jsonb_agg` of objects, correlated to a parent
|
|
37
|
+
* when `correlate` is set (`[parentAlias, Correlation]`). */
|
|
38
|
+
collection(ast, singular, correlate) {
|
|
39
|
+
const table = ast.table;
|
|
40
|
+
const cols = columnsOf(this.#catalog, table);
|
|
41
|
+
const alias = this.#alias(table);
|
|
42
|
+
const scope = { table, alias };
|
|
43
|
+
const row = this.#rowObject(ast, scope, cols);
|
|
44
|
+
const conds = [];
|
|
45
|
+
if (correlate)
|
|
46
|
+
conds.push(correlateSql(this.#d, correlate[0], alias, correlate[1]));
|
|
47
|
+
if (ast.where)
|
|
48
|
+
conds.push(this.#whereSql(ast.where, scope));
|
|
49
|
+
// Keyset paging: keep rows at/after `start` under the already-PK-completed sort — the
|
|
50
|
+
// exact comparator the engine's `Skip` uses.
|
|
51
|
+
if (ast.start)
|
|
52
|
+
conds.push(this.#startSql(ast.start, ast.orderBy ?? [], scope));
|
|
53
|
+
const from = `${this.#d.quoteIdent(table)} AS ${this.#d.quoteIdent(alias)}`;
|
|
54
|
+
return this.#pluralOrSingular(singular, row, from, whereClause(conds), ast.orderBy ?? [], alias, ast.limit);
|
|
55
|
+
}
|
|
56
|
+
/** `json_object('c', a."c", …, 'rel', <child expr>, …)`. A column shadowed by a
|
|
57
|
+
* same-named relationship is dropped (the relationship wins, as in the View). */
|
|
58
|
+
#rowObject(ast, scope, cols) {
|
|
59
|
+
const relNames = new Set((ast.related ?? []).map((r) => r.subquery.alias).filter((a) => a != null));
|
|
60
|
+
const selected = ast.select ? new Set(ast.select) : null;
|
|
61
|
+
const parts = [];
|
|
62
|
+
for (const col of cols) {
|
|
63
|
+
if (relNames.has(col))
|
|
64
|
+
continue;
|
|
65
|
+
if (selected && !selected.has(col))
|
|
66
|
+
continue;
|
|
67
|
+
const colRef = `${this.#d.quoteIdent(scope.alias)}.${this.#d.quoteIdent(col)}`;
|
|
68
|
+
const projected = this.#d.projectColumn(colRef, columnTypeOf(this.#catalog, scope.table, col));
|
|
69
|
+
parts.push(`${jsonKey(col)}, ${projected}`);
|
|
70
|
+
}
|
|
71
|
+
for (const rel of ast.related ?? []) {
|
|
72
|
+
const name = rel.subquery.alias;
|
|
73
|
+
if (name == null)
|
|
74
|
+
throw missingAlias(rel.subquery.table);
|
|
75
|
+
// A relationship aggregate (`count(child)`) is a scalar column, not a nested collection.
|
|
76
|
+
const expr = rel.subquery.aggregate === "count"
|
|
77
|
+
? this.#countExpr(rel, scope.alias)
|
|
78
|
+
: this.#relationExpr(scope.table, rel, scope.alias);
|
|
79
|
+
parts.push(`${jsonKey(name)}, ${expr}`);
|
|
80
|
+
}
|
|
81
|
+
return this.#d.objectBuild(parts);
|
|
82
|
+
}
|
|
83
|
+
/** One materialized relationship as a nested-JSON column. */
|
|
84
|
+
#relationExpr(parentTable, rel, parentAlias) {
|
|
85
|
+
const name = rel.subquery.alias;
|
|
86
|
+
if (name == null)
|
|
87
|
+
throw missingAlias(rel.subquery.table);
|
|
88
|
+
const singular = cardinalityOf(this.#catalog, parentTable, name) === "one";
|
|
89
|
+
return this.collection(rel.subquery, singular, [parentAlias, rel.correlation]);
|
|
90
|
+
}
|
|
91
|
+
/** A scalar `count(*)` for a relationship aggregate: `(SELECT count(*) FROM child AS a
|
|
92
|
+
* WHERE <correlation> [AND <child.where>])`. `0` when no child matches. */
|
|
93
|
+
#countExpr(rel, parentAlias) {
|
|
94
|
+
const sub = rel.subquery;
|
|
95
|
+
const alias = this.#alias(sub.table);
|
|
96
|
+
const conds = [correlateSql(this.#d, parentAlias, alias, rel.correlation)];
|
|
97
|
+
if (sub.where)
|
|
98
|
+
conds.push(this.#whereSql(sub.where, { table: sub.table, alias }));
|
|
99
|
+
return `(SELECT count(*) FROM ${this.#d.quoteIdent(sub.table)} AS ${this.#d.quoteIdent(alias)} WHERE ${conds.join(" AND ")})`;
|
|
100
|
+
}
|
|
101
|
+
/** A boolean SQL expression for a `where` condition tree. */
|
|
102
|
+
#whereSql(cond, scope) {
|
|
103
|
+
switch (cond.type) {
|
|
104
|
+
case "and":
|
|
105
|
+
return cond.conditions.length === 0
|
|
106
|
+
? this.#d.trueLit
|
|
107
|
+
: `(${cond.conditions.map((c) => this.#whereSql(c, scope)).join(" AND ")})`;
|
|
108
|
+
case "or":
|
|
109
|
+
return cond.conditions.length === 0
|
|
110
|
+
? this.#d.falseLit
|
|
111
|
+
: `(${cond.conditions.map((c) => this.#whereSql(c, scope)).join(" OR ")})`;
|
|
112
|
+
case "correlatedSubquery":
|
|
113
|
+
return this.#existsSql(cond, scope.alias);
|
|
114
|
+
case "simple":
|
|
115
|
+
return this.#simpleSql(cond, scope);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/** `[NOT] EXISTS (SELECT 1 FROM child AS a WHERE <correlation> [AND <child.where>])`.
|
|
119
|
+
* `flip`/`scalar` are IVM routing flags with no effect on the SQL. */
|
|
120
|
+
#existsSql(c, parentAlias) {
|
|
121
|
+
const sub = c.related.subquery;
|
|
122
|
+
const alias = this.#alias(sub.table);
|
|
123
|
+
const conds = [correlateSql(this.#d, parentAlias, alias, c.related.correlation)];
|
|
124
|
+
if (sub.where)
|
|
125
|
+
conds.push(this.#whereSql(sub.where, { table: sub.table, alias }));
|
|
126
|
+
const inner = `SELECT 1 FROM ${this.#d.quoteIdent(sub.table)} AS ${this.#d.quoteIdent(alias)} WHERE ${conds.join(" AND ")}`;
|
|
127
|
+
return c.op === "EXISTS" ? `EXISTS (${inner})` : `NOT EXISTS (${inner})`;
|
|
128
|
+
}
|
|
129
|
+
/** A single comparison. Operators render verbatim except: `IS`/`IS NOT` render the RHS as
|
|
130
|
+
* a SQL keyword (`NULL`/`TRUE`/`FALSE`) not a param; `ILIKE` folds via `lower()`; `IN`/
|
|
131
|
+
* `NOT IN` bind the list (empty ⇒ the constant truth value). */
|
|
132
|
+
#simpleSql(sc, scope) {
|
|
133
|
+
// A literal is bound with the *other* side's column type when that side is a column
|
|
134
|
+
// (drives Postgres casts); `null` ⇒ infer from the JS type.
|
|
135
|
+
const leftColType = this.#colTypeOf(scope, sc.left);
|
|
136
|
+
const rightColType = this.#colTypeOf(scope, sc.right);
|
|
137
|
+
const l = this.#valuePos(scope, sc.left, rightColType);
|
|
138
|
+
const r = () => this.#valuePos(scope, sc.right, leftColType);
|
|
139
|
+
switch (sc.op) {
|
|
140
|
+
case "=":
|
|
141
|
+
return `${l} = ${r()}`;
|
|
142
|
+
case "!=":
|
|
143
|
+
return `${l} != ${r()}`;
|
|
144
|
+
case "<":
|
|
145
|
+
return `${l} < ${r()}`;
|
|
146
|
+
case "<=":
|
|
147
|
+
return `${l} <= ${r()}`;
|
|
148
|
+
case ">":
|
|
149
|
+
return `${l} > ${r()}`;
|
|
150
|
+
case ">=":
|
|
151
|
+
return `${l} >= ${r()}`;
|
|
152
|
+
case "IS":
|
|
153
|
+
return `${l} IS ${this.#isRhs(scope, sc.right)}`;
|
|
154
|
+
case "IS NOT":
|
|
155
|
+
return `${l} IS NOT ${this.#isRhs(scope, sc.right)}`;
|
|
156
|
+
case "LIKE":
|
|
157
|
+
return `${l} LIKE ${r()} ESCAPE '\\'`;
|
|
158
|
+
case "NOT LIKE":
|
|
159
|
+
return `${l} NOT LIKE ${r()} ESCAPE '\\'`;
|
|
160
|
+
case "ILIKE":
|
|
161
|
+
return `lower(${l}) LIKE lower(${r()}) ESCAPE '\\'`;
|
|
162
|
+
case "NOT ILIKE":
|
|
163
|
+
return `lower(${l}) NOT LIKE lower(${r()}) ESCAPE '\\'`;
|
|
164
|
+
case "IN":
|
|
165
|
+
return this.#inSql(scope, l, sc.right, false, leftColType);
|
|
166
|
+
case "NOT IN":
|
|
167
|
+
return this.#inSql(scope, l, sc.right, true, leftColType);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/** The RHS of `IS`/`IS NOT` — a SQL keyword position (Postgres forbids a param here), so
|
|
171
|
+
* `NULL`/`TRUE`/`FALSE` render literally. Any other RHS (unusual) falls back to a value. */
|
|
172
|
+
#isRhs(scope, vp) {
|
|
173
|
+
if (vp.type === "literal") {
|
|
174
|
+
if (vp.value === null)
|
|
175
|
+
return "NULL";
|
|
176
|
+
if (vp.value === true)
|
|
177
|
+
return this.#d.trueLit;
|
|
178
|
+
if (vp.value === false)
|
|
179
|
+
return this.#d.falseLit;
|
|
180
|
+
}
|
|
181
|
+
return this.#valuePos(scope, vp, null);
|
|
182
|
+
}
|
|
183
|
+
/** The column type of `vp` when it is a column reference; `null` for a literal. */
|
|
184
|
+
#colTypeOf(scope, vp) {
|
|
185
|
+
return vp.type === "column" ? columnTypeOf(this.#catalog, scope.table, vp.name) : null;
|
|
186
|
+
}
|
|
187
|
+
/** A column reference (`a."c"`) or a bound literal (`?` / `$N::text::<type>`). `siblingCol`
|
|
188
|
+
* is the compared column's type, used when `vp` is a literal. */
|
|
189
|
+
#valuePos(scope, vp, siblingCol) {
|
|
190
|
+
if (vp.type === "column") {
|
|
191
|
+
return `${this.#d.quoteIdent(scope.alias)}.${this.#d.quoteIdent(vp.name)}`;
|
|
192
|
+
}
|
|
193
|
+
return this.#d.bindValue(this.params, vp.value, siblingCol, true);
|
|
194
|
+
}
|
|
195
|
+
/** `lhs [NOT] IN (v0, v1, …)`. An empty list folds to the constant truth value; a non-array
|
|
196
|
+
* RHS falls back to `=`/`!=` (as `rindle-d2s`'s `in_sql`). */
|
|
197
|
+
#inSql(scope, lhs, right, negate, lhsCol) {
|
|
198
|
+
if (right.type === "literal" && Array.isArray(right.value)) {
|
|
199
|
+
const items = right.value;
|
|
200
|
+
if (items.length === 0)
|
|
201
|
+
return negate ? this.#d.trueLit : this.#d.falseLit;
|
|
202
|
+
const list = items
|
|
203
|
+
.map((it) => this.#d.bindValue(this.params, it, lhsCol, true))
|
|
204
|
+
.join(", ");
|
|
205
|
+
return `${lhs} ${negate ? "NOT IN" : "IN"} (${list})`;
|
|
206
|
+
}
|
|
207
|
+
const rr = this.#valuePos(scope, right, lhsCol);
|
|
208
|
+
return negate ? `${lhs} != ${rr}` : `${lhs} = ${rr}`;
|
|
209
|
+
}
|
|
210
|
+
/** Emit the plural (`json_group_array`/`jsonb_agg`) or singular (`… LIMIT 1`) form. When a
|
|
211
|
+
* `LIMIT` is present the rows are selected (ordered + limited) in an inner subquery and
|
|
212
|
+
* then array-aggregated in declared order, so both the top-N selection and the array order
|
|
213
|
+
* are correct. */
|
|
214
|
+
#pluralOrSingular(singular, row, from, where, order, alias, limit) {
|
|
215
|
+
if (singular) {
|
|
216
|
+
return `(SELECT ${row} FROM ${from}${where}${this.#orderClause(order, alias)} LIMIT 1)`;
|
|
217
|
+
}
|
|
218
|
+
if (limit !== undefined) {
|
|
219
|
+
const innerOrder = this.#orderClause(order, alias);
|
|
220
|
+
const proj = this.#orderProj(order, alias);
|
|
221
|
+
const agg = this.#aggOrderProjected(order);
|
|
222
|
+
// Re-assert the JSON subtype: the object round-trips through the inner subquery's
|
|
223
|
+
// `AS __r` column, which SQLite would otherwise re-encode as a string.
|
|
224
|
+
const elem = this.#d.reassertJson("__r");
|
|
225
|
+
const lim = Math.max(0, Math.trunc(Number(limit)));
|
|
226
|
+
return `COALESCE((SELECT ${this.#d.groupArray(elem, agg)} FROM (SELECT ${row} AS __r${proj} FROM ${from}${where}${innerOrder} LIMIT ${lim})), ${this.#d.emptyArray})`;
|
|
227
|
+
}
|
|
228
|
+
const agg = this.#aggOrderInline(order, alias);
|
|
229
|
+
return `COALESCE((SELECT ${this.#d.groupArray(row, agg)} FROM ${from}${where}), ${this.#d.emptyArray})`;
|
|
230
|
+
}
|
|
231
|
+
/** ` ORDER BY a."c0" ASC, a."c1" DESC` (empty when `order` is empty). */
|
|
232
|
+
#orderClause(order, alias) {
|
|
233
|
+
return order.length === 0 ? "" : ` ORDER BY ${this.#orderTerms(order, alias)}`;
|
|
234
|
+
}
|
|
235
|
+
/** In-aggregate ordering referencing the FROM columns. */
|
|
236
|
+
#aggOrderInline(order, alias) {
|
|
237
|
+
return order.length === 0 ? "" : ` ORDER BY ${this.#orderTerms(order, alias)}`;
|
|
238
|
+
}
|
|
239
|
+
/** In-aggregate ordering over the inner subquery's projected order columns (`__o0`, …). */
|
|
240
|
+
#aggOrderProjected(order) {
|
|
241
|
+
if (order.length === 0)
|
|
242
|
+
return "";
|
|
243
|
+
return ` ORDER BY ${order.map(([, dir], i) => `__o${i} ${this.#d.orderDir(dir)}`).join(", ")}`;
|
|
244
|
+
}
|
|
245
|
+
/** Project the order columns into the inner subquery: `, a."c0" AS __o0, …`. */
|
|
246
|
+
#orderProj(order, alias) {
|
|
247
|
+
return order
|
|
248
|
+
.map(([field], i) => `, ${this.#d.quoteIdent(alias)}.${this.#d.quoteIdent(field)} AS __o${i}`)
|
|
249
|
+
.join("");
|
|
250
|
+
}
|
|
251
|
+
#orderTerms(order, alias) {
|
|
252
|
+
return order
|
|
253
|
+
.map(([field, dir]) => `${this.#d.quoteIdent(alias)}.${this.#d.quoteIdent(field)} ${this.#d.orderDir(dir)}`)
|
|
254
|
+
.join(", ");
|
|
255
|
+
}
|
|
256
|
+
/** The keyset paging predicate for a `start` bound: keep a row iff the bound sorts before
|
|
257
|
+
* it (`B < R`), or sorts equal with an inclusive bound (`B <= R`), under the already-PK-
|
|
258
|
+
* completed sort. Lexicographic with **null-low** ordering (NULL before any value on ASC,
|
|
259
|
+
* after every value on DESC) — faithful to the engine's `Skip`. */
|
|
260
|
+
#startSql(start, order, scope) {
|
|
261
|
+
// Per sort column: the column ref, direction, bound value (absent/NULL ⇒ null-low), type.
|
|
262
|
+
const cols = order.map(([field, dir]) => ({
|
|
263
|
+
x: `${this.#d.quoteIdent(scope.alias)}.${this.#d.quoteIdent(field)}`,
|
|
264
|
+
asc: dir === "asc",
|
|
265
|
+
b: nullLowBound(start.row[field]),
|
|
266
|
+
col: columnTypeOf(this.#catalog, scope.table, field),
|
|
267
|
+
}));
|
|
268
|
+
if (cols.length === 0)
|
|
269
|
+
return this.#d.trueLit; // no sort columns (unreachable post-completion)
|
|
270
|
+
// `B < R` = OR_i ( AND_{j<i} eq_j AND before_i ). Each `eq`/`before` fragment is
|
|
271
|
+
// (re)emitted exactly where it appears — regenerated, not string-reused — so every
|
|
272
|
+
// placeholder gets its own bound param, in left-to-right order. (`rindle-d2s` can reuse a
|
|
273
|
+
// fragment string because its literals are inlined and side-effect-free; our params are
|
|
274
|
+
// positional, so a reused or discarded fragment would desync placeholders from params.)
|
|
275
|
+
const orTerms = [];
|
|
276
|
+
for (let i = 0; i < cols.length; i++) {
|
|
277
|
+
const conj = [];
|
|
278
|
+
for (let j = 0; j < i; j++)
|
|
279
|
+
conj.push(this.#eqSql(cols[j].x, cols[j].b, cols[j].col));
|
|
280
|
+
conj.push(this.#beforeSql(cols[i].x, cols[i].asc, cols[i].b, cols[i].col));
|
|
281
|
+
orTerms.push(`(${conj.join(" AND ")})`);
|
|
282
|
+
}
|
|
283
|
+
const strictLt = `(${orTerms.join(" OR ")})`;
|
|
284
|
+
if (start.exclusive)
|
|
285
|
+
return strictLt;
|
|
286
|
+
// `B <= R` adds the all-columns-equal term.
|
|
287
|
+
const allEq = cols.map((c) => this.#eqSql(c.x, c.b, c.col)).join(" AND ");
|
|
288
|
+
return `(${strictLt} OR (${allEq}))`;
|
|
289
|
+
}
|
|
290
|
+
/** `x` strictly after the bound value `b` in this column's direction, null-low. `b` is
|
|
291
|
+
* `undefined` for an omitted/NULL bound. */
|
|
292
|
+
#beforeSql(x, asc, b, col) {
|
|
293
|
+
if (b === undefined) {
|
|
294
|
+
// NULL bound: before every non-null `x` on ASC; never before on DESC.
|
|
295
|
+
return asc ? `${x} IS NOT NULL` : this.#d.falseLit;
|
|
296
|
+
}
|
|
297
|
+
const v = this.#d.bindValue(this.params, b, col, true);
|
|
298
|
+
// ASC: `v < x` (NULL `x` ⇒ NULL ⇒ false, i.e. a null row is not after a non-null bound).
|
|
299
|
+
// DESC: a null `x` sorts last, so it is after the bound; else `v > x`.
|
|
300
|
+
return asc ? `${v} < ${x}` : `(${x} IS NULL OR ${v} > ${x})`;
|
|
301
|
+
}
|
|
302
|
+
/** `x` equal to the bound value `b` under the sort's null-identical equality. */
|
|
303
|
+
#eqSql(x, b, col) {
|
|
304
|
+
if (b === undefined)
|
|
305
|
+
return `${x} IS NULL`;
|
|
306
|
+
return `${x} = ${this.#d.bindValue(this.params, b, col, true)}`;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
// ── free helpers (dialect-independent) ────────────────────────────────────────────────────
|
|
310
|
+
/** `parent."p0" = child."c0" AND parent."p1" = child."c1" …`. */
|
|
311
|
+
function correlateSql(d, parentAlias, childAlias, corr) {
|
|
312
|
+
return corr.parentField
|
|
313
|
+
.map((p, i) => {
|
|
314
|
+
const c = corr.childField[i];
|
|
315
|
+
return `${d.quoteIdent(parentAlias)}.${d.quoteIdent(p)} = ${d.quoteIdent(childAlias)}.${d.quoteIdent(c)}`;
|
|
316
|
+
})
|
|
317
|
+
.join(" AND ");
|
|
318
|
+
}
|
|
319
|
+
function whereClause(conds) {
|
|
320
|
+
return conds.length === 0 ? "" : ` WHERE ${conds.join(" AND ")}`;
|
|
321
|
+
}
|
|
322
|
+
/** A `json_object`/`jsonb_build_object` key: a single-quoted string literal. */
|
|
323
|
+
function jsonKey(name) {
|
|
324
|
+
return `'${name.replace(/'/g, "''")}'`;
|
|
325
|
+
}
|
|
326
|
+
/** A paging bound value, with an absent or explicit-NULL value normalized to `undefined`
|
|
327
|
+
* (the null-low sentinel `beforeSql`/`eqSql` treat as "sorts below everything"). */
|
|
328
|
+
function nullLowBound(raw) {
|
|
329
|
+
return raw === undefined || raw === null ? undefined : raw;
|
|
330
|
+
}
|
|
331
|
+
// ── catalog accessors ─────────────────────────────────────────────────────────────────────
|
|
332
|
+
function columnsOf(catalog, table) {
|
|
333
|
+
const t = catalog.tables[table];
|
|
334
|
+
if (!t)
|
|
335
|
+
throw unknownTable(table);
|
|
336
|
+
return t.columns;
|
|
337
|
+
}
|
|
338
|
+
function pkOf(catalog, table) {
|
|
339
|
+
return catalog.tables[table]?.primaryKey ?? [];
|
|
340
|
+
}
|
|
341
|
+
function cardinalityOf(catalog, table, rel) {
|
|
342
|
+
const card = catalog.tables[table]?.relationships[rel];
|
|
343
|
+
if (!card)
|
|
344
|
+
throw unknownRelationship(table, rel);
|
|
345
|
+
return card;
|
|
346
|
+
}
|
|
347
|
+
function columnTypeOf(catalog, table, col) {
|
|
348
|
+
return catalog.tables[table]?.columnTypes[col] ?? null;
|
|
349
|
+
}
|
|
350
|
+
//# sourceMappingURL=walker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"walker.js","sourceRoot":"","sources":["../src/walker.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rindle/query-compiler",
|
|
3
|
+
"version": "0.4.3",
|
|
4
|
+
"license": "Apache-2.0",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/rindle-sh/rindle.git",
|
|
8
|
+
"directory": "packages/query-compiler"
|
|
9
|
+
},
|
|
10
|
+
"type": "module",
|
|
11
|
+
"description": "Compiles a Rindle query AST to one SQL SELECT (whole nested result as a single JSON column) for a live SQL backend — the server realization of 203-MUTATOR-READS Phase 2 (POSTGRES-READ-COMPILER-DESIGN.md). Postgres dialect drives the BYO-Postgres mutator read; the SQLite dialect drives the daemon-backend mutator read through an interactive mutation session (and doubles as the offline differential oracle).",
|
|
12
|
+
"main": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"sideEffects": false,
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"@rindle/source": "./src/index.ts",
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"default": "./dist/index.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"src",
|
|
25
|
+
"README.md"
|
|
26
|
+
],
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@rindle/client": "0.4.3"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/node": "^22.10.0",
|
|
32
|
+
"typescript": "^5.7.0"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsc -p tsconfig.build.json",
|
|
36
|
+
"typecheck": "tsc --noEmit",
|
|
37
|
+
"test": "tsc --noEmit && node --conditions=@rindle/source --test test/*.test.ts"
|
|
38
|
+
}
|
|
39
|
+
}
|
package/src/ast.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Re-export the canonical Zero-wire AST types from @rindle/client — the single source of
|
|
2
|
+
// truth for the query shape the builder emits (`client/src/ast.ts`) and the Rust engine
|
|
3
|
+
// deserializes (`rust/src/ast.rs`). The compiler consumes these verbatim, so it can never
|
|
4
|
+
// drift from what the client produces.
|
|
5
|
+
export type {
|
|
6
|
+
Aggregate,
|
|
7
|
+
Ast,
|
|
8
|
+
Bound,
|
|
9
|
+
Condition,
|
|
10
|
+
CorrelatedSubquery,
|
|
11
|
+
Correlation,
|
|
12
|
+
Dir,
|
|
13
|
+
ExistsOp,
|
|
14
|
+
LitValue,
|
|
15
|
+
OrderPart,
|
|
16
|
+
SimpleOp,
|
|
17
|
+
ValuePosition,
|
|
18
|
+
} from "@rindle/client";
|
package/src/catalog.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// The static, declared type catalog (POSTGRES-READ-COMPILER-DESIGN.md §7) — the second
|
|
2
|
+
// input to the compiler, beside the `Ast`. Compilation is a pure function of
|
|
3
|
+
// `(Ast, Catalog)`; the catalog carries the facts the AST does not: column order, primary
|
|
4
|
+
// key, relationship cardinality, and — for the Postgres dialect — the per-column native
|
|
5
|
+
// type detail the `::text::<type>` cast strategy (§6.2) branches on.
|
|
6
|
+
|
|
7
|
+
/** Whether a relationship yields a single child object (`one`) or an array (`many`). */
|
|
8
|
+
export type Cardinality = "one" | "many";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Per-column type detail the Postgres compiler needs for value-model↔native-type
|
|
12
|
+
* reconciliation (§6.2). Mirrors z2s's `ServerColumnSchema`: the raw Postgres type name
|
|
13
|
+
* plus the two flags the cast switch branches on. The extension over what
|
|
14
|
+
* `rindle-pg-source` derives today (§7, review decision 2): enums and arrays are carried
|
|
15
|
+
* distinctly instead of collapsing into a text fallback.
|
|
16
|
+
*
|
|
17
|
+
* - `type` — the native Postgres type name: `"int4"`, `"text"`, `"bool"`, `"float8"`,
|
|
18
|
+
* `"numeric"`, `"timestamptz"`, `"timestamp"`, `"date"`, `"timetz"`, `"time"`, `"uuid"`,
|
|
19
|
+
* `"json"`/`"jsonb"`, or an **enum type name** (paired with `isEnum: true`).
|
|
20
|
+
* - `isEnum` — `type` names a Postgres enum ⇒ cast via `$N::text::"<type>"`.
|
|
21
|
+
* - `isArray` — the column is an array ⇒ unnest via `jsonb_array_elements_text`.
|
|
22
|
+
*
|
|
23
|
+
* The SQLite oracle dialect ignores this entirely: it binds native values (no casts).
|
|
24
|
+
*/
|
|
25
|
+
export interface ColumnType {
|
|
26
|
+
type: string;
|
|
27
|
+
isEnum: boolean;
|
|
28
|
+
isArray: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Per-table metadata the compiler needs beyond the `Ast`:
|
|
33
|
+
* - `columns` — the projected column list, **in projection order** (the order
|
|
34
|
+
* `json_object`/`jsonb_build_object` enumerates), matching the View's `Schema`.
|
|
35
|
+
* - `primaryKey` — the PK columns, for ordering-completion (§8): the compiler appends the
|
|
36
|
+
* full PK to every `orderBy` at every level for a total order, exactly as the engine's
|
|
37
|
+
* builder does (`rindle::complete_ordering`).
|
|
38
|
+
* - `columnTypes` — per-column native type detail, keyed by column name (§6.2; Postgres
|
|
39
|
+
* dialect only).
|
|
40
|
+
* - `relationships` — declared relationships → cardinality. This is the one structural
|
|
41
|
+
* fact not carried by the `Ast` (the relationship *shape* — correlation keys, nesting,
|
|
42
|
+
* where/order/limit — lives in the `Ast`'s `related` subqueries).
|
|
43
|
+
*/
|
|
44
|
+
export interface TableSchema {
|
|
45
|
+
columns: string[];
|
|
46
|
+
primaryKey: string[];
|
|
47
|
+
columnTypes: Record<string, ColumnType>;
|
|
48
|
+
relationships: Record<string, Cardinality>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The catalog: every table the compiler may reference, keyed by table name. Produced
|
|
53
|
+
* statically by `rindle pg prepare` from an ephemeral migration-built Postgres (§7,
|
|
54
|
+
* Phase B); hand-authored for tests. A pure input — the compiler never touches a database
|
|
55
|
+
* to obtain it.
|
|
56
|
+
*/
|
|
57
|
+
export interface Catalog {
|
|
58
|
+
tables: Record<string, TableSchema>;
|
|
59
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Ordering completion — the parity-critical pass (§8). The engine's builder appends the
|
|
2
|
+
// full primary key (as `asc`) to every `orderBy`, at every level, for a total order; this is
|
|
3
|
+
// a faithful port of `rindle::complete_ordering` (builder.rs:209), which is itself the port
|
|
4
|
+
// of the client builder's `completeOrdering`. Running the *same* completion is what makes the
|
|
5
|
+
// SELECT's row/child order match the engine's `Skip`/`Sort` tie-breaks exactly.
|
|
6
|
+
//
|
|
7
|
+
// Mutates `ast` in place (the caller passes a clone). `getPk(table)` yields a table's PK
|
|
8
|
+
// column names in PK order.
|
|
9
|
+
|
|
10
|
+
import type { Ast, Condition } from "./ast.ts";
|
|
11
|
+
|
|
12
|
+
export function completeOrdering(ast: Ast, getPk: (table: string) => string[]): void {
|
|
13
|
+
const pk = getPk(ast.table);
|
|
14
|
+
for (const csq of ast.related ?? []) {
|
|
15
|
+
completeOrdering(csq.subquery, getPk);
|
|
16
|
+
}
|
|
17
|
+
if (ast.where) {
|
|
18
|
+
completeOrderingInCondition(ast.where, getPk);
|
|
19
|
+
}
|
|
20
|
+
addPrimaryKeys(pk, ast);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function completeOrderingInCondition(cond: Condition, getPk: (table: string) => string[]): void {
|
|
24
|
+
switch (cond.type) {
|
|
25
|
+
case "simple":
|
|
26
|
+
return;
|
|
27
|
+
case "correlatedSubquery":
|
|
28
|
+
completeOrdering(cond.related.subquery, getPk);
|
|
29
|
+
return;
|
|
30
|
+
case "and":
|
|
31
|
+
case "or":
|
|
32
|
+
for (const c of cond.conditions) {
|
|
33
|
+
completeOrderingInCondition(c, getPk);
|
|
34
|
+
}
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Append each PK column not already present (matched by name), as `asc`, in PK order.
|
|
40
|
+
* Already-present PK columns keep their existing position and direction. */
|
|
41
|
+
function addPrimaryKeys(pk: string[], ast: Ast): void {
|
|
42
|
+
const order = (ast.orderBy ??= []);
|
|
43
|
+
for (const pkCol of pk) {
|
|
44
|
+
if (!order.some(([field]) => field === pkCol)) {
|
|
45
|
+
order.push([pkCol, "asc"]);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
package/src/dialect.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// The dialect seam (§6.1). The walker is dialect-independent; only these leaves differ
|
|
2
|
+
// between SQLite and Postgres: the Postgres leaf carries the §6.2 `::text::<type>` casts and
|
|
3
|
+
// explicit NULLS ordering; the SQLite leaf binds natives with NO casts (the canonical store
|
|
4
|
+
// needs no reconciliation — DAEMON-INTERACTIVE-TXN-DESIGN.md §5.4).
|
|
5
|
+
|
|
6
|
+
import type { Dir } from "./ast.ts";
|
|
7
|
+
import type { ColumnType } from "./catalog.ts";
|
|
8
|
+
|
|
9
|
+
/** A scalar value bindable as a parameter (an array filter value is split into scalars). */
|
|
10
|
+
export type LitScalar = null | boolean | number | string;
|
|
11
|
+
|
|
12
|
+
export interface Dialect {
|
|
13
|
+
readonly name: "postgres" | "sqlite";
|
|
14
|
+
|
|
15
|
+
/** `json_object('k', v, …)` | `jsonb_build_object('k', v, …)`. `parts` are the flat
|
|
16
|
+
* `'key', valueExpr` fragments, in order. */
|
|
17
|
+
objectBuild(parts: string[]): string;
|
|
18
|
+
|
|
19
|
+
/** The array aggregate over `elem` with `orderSql` (e.g. ` ORDER BY __o0 ASC`) inside:
|
|
20
|
+
* `json_group_array(elem ORDER BY …)` | `jsonb_agg(elem ORDER BY …)`. */
|
|
21
|
+
groupArray(elem: string, orderSql: string): string;
|
|
22
|
+
|
|
23
|
+
/** The empty-array default for the `COALESCE` wrapper: `'[]'` | `'[]'::jsonb`. */
|
|
24
|
+
readonly emptyArray: string;
|
|
25
|
+
|
|
26
|
+
/** Re-assert the JSON subtype on a value round-tripped through a subquery's `AS` column
|
|
27
|
+
* (`json(x)` in SQLite; identity in Postgres, where `jsonb` is a real type). */
|
|
28
|
+
reassertJson(x: string): string;
|
|
29
|
+
|
|
30
|
+
/** The truthy/falsy constants for empty `AND`/`OR` folding and boolean keyword positions:
|
|
31
|
+
* `1`/`0` | `TRUE`/`FALSE`. */
|
|
32
|
+
readonly trueLit: string;
|
|
33
|
+
readonly falseLit: string;
|
|
34
|
+
|
|
35
|
+
/** An `ORDER BY` direction with the engine's null-low ordering made explicit where the
|
|
36
|
+
* dialect default differs (§8): SQLite is null-low by default (`ASC`/`DESC`); Postgres's
|
|
37
|
+
* default is the opposite, so it must emit `ASC NULLS FIRST` / `DESC NULLS LAST`. */
|
|
38
|
+
orderDir(dir: Dir): string;
|
|
39
|
+
|
|
40
|
+
/** Project a stored column into the value model's representation for the result JSON.
|
|
41
|
+
* Identity in SQLite; the outbound half of §6.2 in Postgres (e.g. timestamptz → epoch-ms). */
|
|
42
|
+
projectColumn(colRef: string, col: ColumnType | null): string;
|
|
43
|
+
|
|
44
|
+
/** Bind a scalar filter/paging value as a parameter: push onto `params`, return the
|
|
45
|
+
* placeholder SQL. `col` is the compared column's type when known (drives the Postgres
|
|
46
|
+
* `::text::<type>` casts, §6.2); `null` ⇒ infer from the JS type. SQLite ignores `col`
|
|
47
|
+
* and `isComparison` — it binds natives. */
|
|
48
|
+
bindValue(params: unknown[], value: LitScalar, col: ColumnType | null, isComparison: boolean): string;
|
|
49
|
+
|
|
50
|
+
/** Double-quote an identifier, doubling embedded `"`. Identical across dialects (kept on
|
|
51
|
+
* the seam for uniformity). */
|
|
52
|
+
quoteIdent(name: string): string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function quoteIdent(name: string): string {
|
|
56
|
+
return `"${name.replace(/"/g, '""')}"`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The SQLite dialect — the daemon backend's session-read target (DAEMON-INTERACTIVE-TXN §5.4)
|
|
61
|
+
* and the offline differential oracle (§9). It binds native values as parameters (no
|
|
62
|
+
* `::text::<type>` casts — SQLite is the canonical store, so there is no driver seam to pin
|
|
63
|
+
* and no second representation to reconcile), relies on SQLite's null-low default ordering
|
|
64
|
+
* (which matches the engine's `null < everything`), and re-asserts the `json()` subtype where
|
|
65
|
+
* objects round-trip through a subquery column. Matches `rindle-d2s`'s raw SQL shapes so the
|
|
66
|
+
* two agree modulo parameterization; the executing connection must run
|
|
67
|
+
* `PRAGMA case_sensitive_like = ON` (every daemon cluster connection does — `open_wal2`).
|
|
68
|
+
*/
|
|
69
|
+
export const sqliteDialect: Dialect = {
|
|
70
|
+
name: "sqlite",
|
|
71
|
+
objectBuild: (parts) => `json_object(${parts.join(", ")})`,
|
|
72
|
+
groupArray: (elem, orderSql) => `json_group_array(${elem}${orderSql})`,
|
|
73
|
+
emptyArray: "'[]'",
|
|
74
|
+
reassertJson: (x) => `json(${x})`,
|
|
75
|
+
trueLit: "1",
|
|
76
|
+
falseLit: "0",
|
|
77
|
+
orderDir: (dir) => (dir === "asc" ? "ASC" : "DESC"),
|
|
78
|
+
projectColumn: (colRef) => colRef,
|
|
79
|
+
bindValue: (params, value) => {
|
|
80
|
+
// SQLite has no boolean type; its driver binds 1/0. Everything else binds natively.
|
|
81
|
+
params.push(typeof value === "boolean" ? (value ? 1 : 0) : value);
|
|
82
|
+
return "?";
|
|
83
|
+
},
|
|
84
|
+
quoteIdent,
|
|
85
|
+
};
|