joist-core 2.3.0-next.76 → 2.3.0-next.78
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/build/index.cjs +1 -0
- package/build/index.cjs.map +1 -1
- package/build/index.d.cts +2 -2
- package/build/index.d.cts.map +1 -1
- package/build/index.d.mts +2 -2
- package/build/index.d.mts.map +1 -1
- package/build/index.js +2 -2
- package/build/index.js.map +1 -1
- package/build/query.cjs +237 -15
- package/build/query.cjs.map +1 -1
- package/build/query.d.cts +115 -6
- package/build/query.d.cts.map +1 -1
- package/build/query.d.mts +115 -6
- package/build/query.d.mts.map +1 -1
- package/build/query.js +237 -16
- package/build/query.js.map +1 -1
- package/package.json +2 -2
package/build/query.cjs
CHANGED
|
@@ -59,6 +59,46 @@ function query(q) {
|
|
|
59
59
|
else return newSubqueryProxy(handle);
|
|
60
60
|
}
|
|
61
61
|
/**
|
|
62
|
+
* Declares a `WITH RECURSIVE` CTE from its two terms, and returns the CTE as a readable value.
|
|
63
|
+
*
|
|
64
|
+
* `base` is the non-recursive term, which seeds the rows and, as in PostgreSQL, supplies the CTE's
|
|
65
|
+
* columns. `step` is the recursive term: it receives the CTE itself, so it can join back to the rows
|
|
66
|
+
* found so far. The two terms are combined with UNION ALL, or UNION when `union: "distinct"` drops
|
|
67
|
+
* duplicates, which is how a cyclic graph is kept from looping forever.
|
|
68
|
+
*
|
|
69
|
+
* ```ts
|
|
70
|
+
* const [a] = tables(Author);
|
|
71
|
+
* const tree = recursiveQuery(
|
|
72
|
+
* "tree",
|
|
73
|
+
* { from: a, where: a.mentor_id.isNull(), select: { id: a.id, mentorId: a.mentor_id } },
|
|
74
|
+
* (self) => ({
|
|
75
|
+
* from: a,
|
|
76
|
+
* join: [{ inner: self, on: a.mentor_id.eq(self.id) }],
|
|
77
|
+
* select: { id: a.id, mentorId: a.mentor_id },
|
|
78
|
+
* }),
|
|
79
|
+
* );
|
|
80
|
+
* const rows = await em.query({ with: tree, from: tree, select: tree });
|
|
81
|
+
* ```
|
|
82
|
+
*
|
|
83
|
+
* Unlike `query()`, the name is required, because the step term must name it.
|
|
84
|
+
*/
|
|
85
|
+
function recursiveQuery(name, base, step, opts = {}) {
|
|
86
|
+
if (typeof name !== "string" || name === "") require_utils.fail("A recursive CTE needs a name");
|
|
87
|
+
if (opts.union !== void 0 && opts.union !== "all" && opts.union !== "distinct") require_utils.fail("A recursive CTE's union must be 'all' or 'distinct'");
|
|
88
|
+
const handle = new SubqueryHandle(toQuery(base), true);
|
|
89
|
+
if (handle.output().kind !== "pojo") require_utils.fail("A recursive CTE's base term needs a named projection");
|
|
90
|
+
const self = newSubqueryProxy(handle);
|
|
91
|
+
const operands = [base, step(self)];
|
|
92
|
+
handle.setBody(opts.union === "distinct" ? {
|
|
93
|
+
union: operands,
|
|
94
|
+
as: name
|
|
95
|
+
} : {
|
|
96
|
+
unionAll: operands,
|
|
97
|
+
as: name
|
|
98
|
+
});
|
|
99
|
+
return self;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
62
102
|
* Builds a SQL expression from a tagged template.
|
|
63
103
|
*
|
|
64
104
|
* For an Author alias `a` assigned the SQL alias `a1`:
|
|
@@ -147,9 +187,23 @@ function projectionToSql(select, ctx) {
|
|
|
147
187
|
}
|
|
148
188
|
/** The runtime identity of a `query(...)` value; `Ctx.aliasFor` keys on it, like a table's `TableMgmt`. */
|
|
149
189
|
var SubqueryHandle = class {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
190
|
+
recursive;
|
|
191
|
+
#q;
|
|
192
|
+
constructor(q, recursive = false) {
|
|
193
|
+
this.recursive = recursive;
|
|
194
|
+
this.#q = q;
|
|
195
|
+
}
|
|
196
|
+
get q() {
|
|
197
|
+
return this.#q;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Gives a recursive CTE its finished body. `recursiveQuery` starts the handle on its base term, so
|
|
201
|
+
* the step term can read the CTE's columns while the body that will hold that step is still being
|
|
202
|
+
* built. The base term supplies the CTE's columns either way, PostgreSQL's rule for a recursive WITH.
|
|
203
|
+
*/
|
|
204
|
+
setBody(q) {
|
|
205
|
+
if (!this.recursive) require_utils.fail("Only a recursive CTE replaces its body");
|
|
206
|
+
this.#q = q;
|
|
153
207
|
}
|
|
154
208
|
get name() {
|
|
155
209
|
return this.q.as;
|
|
@@ -313,6 +367,7 @@ const MUTATION_KEYS = [
|
|
|
313
367
|
"allowAll"
|
|
314
368
|
];
|
|
315
369
|
const READ_KEYS = [
|
|
370
|
+
"with",
|
|
316
371
|
"from",
|
|
317
372
|
"join",
|
|
318
373
|
"where",
|
|
@@ -369,6 +424,7 @@ function setOperands(q) {
|
|
|
369
424
|
if (keys.length !== 1) require_utils.fail("A set query requires exactly one operation key");
|
|
370
425
|
validateQueryKeys(q, [
|
|
371
426
|
keys[0],
|
|
427
|
+
"with",
|
|
372
428
|
"orderBy",
|
|
373
429
|
"limit",
|
|
374
430
|
"offset",
|
|
@@ -492,10 +548,13 @@ var OutputExpr = class extends require_Expr.BaseExpr {
|
|
|
492
548
|
* output positions without moving DISTINCT/order/pagination or repeating volatile selected expressions.
|
|
493
549
|
* Parenthesizing each accumulated left side preserves array association and explicit nested grouping.
|
|
494
550
|
*/
|
|
495
|
-
function parseSetQuery(q, parent, assigner) {
|
|
551
|
+
function parseSetQuery(q, parent, assigner, recursiveSelf) {
|
|
496
552
|
const [operation, operands] = setOperands(q);
|
|
497
553
|
const output = queryOutput(q);
|
|
498
|
-
const
|
|
554
|
+
const ctx = new Ctx(assigner, parent);
|
|
555
|
+
if (recursiveSelf) ctx.setRecursiveSelf(recursiveSelf);
|
|
556
|
+
const ctes = registerCtes(q, ctx, assigner);
|
|
557
|
+
const plans = operands.map((operand) => parseQuery(toQuery(operand), ctx, assigner));
|
|
499
558
|
let sql = "";
|
|
500
559
|
for (const plan of plans) {
|
|
501
560
|
let branch = plan.sql;
|
|
@@ -516,10 +575,25 @@ function parseSetQuery(q, parent, assigner) {
|
|
|
516
575
|
sql += " OFFSET ?";
|
|
517
576
|
bindings.push(q.offset);
|
|
518
577
|
}
|
|
578
|
+
const referenced = new Set(plans.flatMap((plan) => plan.outerRefs));
|
|
579
|
+
const keptCtes = [];
|
|
580
|
+
for (let i = ctes.length - 1; i >= 0; i--) {
|
|
581
|
+
const cte = ctes[i];
|
|
582
|
+
if (!referenced.has(cte.alias)) continue;
|
|
583
|
+
for (const ref of cte.plan.outerRefs) referenced.add(ref);
|
|
584
|
+
keptCtes.unshift(cte);
|
|
585
|
+
}
|
|
586
|
+
if (keptCtes.length > 0) {
|
|
587
|
+
const clause = withFragment(keptCtes);
|
|
588
|
+
sql = clause.sql + sql;
|
|
589
|
+
bindings.unshift(...clause.bindings);
|
|
590
|
+
}
|
|
591
|
+
const cteAliases = new Set(ctes.map((cte) => cte.alias));
|
|
592
|
+
const outerRefs = [...ctx.outerRefs, ...plans.flatMap((plan) => plan.outerRefs)];
|
|
519
593
|
return {
|
|
520
594
|
sql,
|
|
521
595
|
bindings,
|
|
522
|
-
outerRefs: [...new Set(
|
|
596
|
+
outerRefs: [...new Set(outerRefs.filter((ref) => !cteAliases.has(ref)))],
|
|
523
597
|
output,
|
|
524
598
|
decodeRows: plans[0].decodeRows
|
|
525
599
|
};
|
|
@@ -550,14 +624,84 @@ var Ctx = class {
|
|
|
550
624
|
assigner;
|
|
551
625
|
parent;
|
|
552
626
|
aliases = /* @__PURE__ */ new Map();
|
|
627
|
+
/**
|
|
628
|
+
* The subset of `aliases` that are CTE names. A `from`/`join` on one of these emits just the name,
|
|
629
|
+
* i.e. `FROM book_stats`, where an ordinary `query(...)` value emits its whole body inline, i.e.
|
|
630
|
+
* `FROM (SELECT ...) AS sq`.
|
|
631
|
+
*/
|
|
632
|
+
ctes = /* @__PURE__ */ new Map();
|
|
633
|
+
/**
|
|
634
|
+
* `with` entries named but not yet turned into SQL. `registerCtes` names every entry first, then
|
|
635
|
+
* generates the bodies one at a time, so while one body is being generated the entries after it sit
|
|
636
|
+
* here. Reading one of those is a forward reference.
|
|
637
|
+
*/
|
|
638
|
+
pendingCtes = /* @__PURE__ */ new Map();
|
|
639
|
+
/** CTEs already read by this query's `from`/`join`, so a second read fails instead of colliding. */
|
|
640
|
+
usedCtes = /* @__PURE__ */ new Set();
|
|
641
|
+
/** The recursive CTE whose body this query is part of, if any; see `recursiveSelf`. */
|
|
642
|
+
ownRecursiveSelf;
|
|
553
643
|
outerRefs = /* @__PURE__ */ new Set();
|
|
554
644
|
constructor(assigner, parent) {
|
|
555
645
|
this.assigner = assigner;
|
|
556
646
|
this.parent = parent;
|
|
557
647
|
}
|
|
648
|
+
/**
|
|
649
|
+
* One handle can hold only one alias per query, so a value used twice must be told apart, not
|
|
650
|
+
* silently collapsed into the second registration's alias.
|
|
651
|
+
*/
|
|
558
652
|
register(handle, alias) {
|
|
653
|
+
if (this.aliases.has(handle)) require_utils.fail(`${describeHandle(handle)} is already in this query's \`with\`/\`from\`/\`join\`; use a separate table(...)/query(...) value for each use`);
|
|
559
654
|
this.aliases.set(handle, alias);
|
|
560
655
|
}
|
|
656
|
+
/** Names every `with` entry up front, so a CTE reading a later sibling is reported, not inlined. */
|
|
657
|
+
declareCte(handle, alias) {
|
|
658
|
+
this.pendingCtes.set(handle, alias);
|
|
659
|
+
}
|
|
660
|
+
/** Brings a pending CTE into scope, for the sources and columns that may now read it. */
|
|
661
|
+
promoteCte(handle) {
|
|
662
|
+
const alias = this.pendingCtes.get(handle) ?? require_utils.fail("CTE was not declared");
|
|
663
|
+
this.pendingCtes.delete(handle);
|
|
664
|
+
this.ctes.set(handle, alias);
|
|
665
|
+
this.register(handle, alias);
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* The CTE name for `handle`, looking in the enclosing queries too, because a CTE is in scope for the
|
|
669
|
+
* whole statement.
|
|
670
|
+
*
|
|
671
|
+
* Unlike `aliasFor`, a hit in an enclosing query is not added to `outerRefs`: reading a CTE by name
|
|
672
|
+
* is not a correlated reference, so it must not keep an enclosing join alive.
|
|
673
|
+
*/
|
|
674
|
+
cteAliasFor(handle) {
|
|
675
|
+
return this.ctes.get(handle) ?? this.parent?.cteAliasFor(handle);
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* The recursive CTE this query's terms belong to, inherited from the enclosing query.
|
|
679
|
+
*
|
|
680
|
+
* PostgreSQL requires a recursive term to reference its own CTE, so that reference is not optional
|
|
681
|
+
* and must survive pruning, unlike an ordinary explicit join that nothing else reads.
|
|
682
|
+
*/
|
|
683
|
+
get recursiveSelf() {
|
|
684
|
+
return this.ownRecursiveSelf ?? this.parent?.recursiveSelf;
|
|
685
|
+
}
|
|
686
|
+
setRecursiveSelf(handle) {
|
|
687
|
+
this.ownRecursiveSelf = handle;
|
|
688
|
+
}
|
|
689
|
+
/** Whether `handle` is a CTE with no SQL yet, i.e. itself or a later `with` entry. */
|
|
690
|
+
isPendingCte(handle) {
|
|
691
|
+
return this.pendingCtes.has(handle) || (this.parent?.isPendingCte(handle) ?? false);
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Fails if this query already reads `handle`. A `Ctx` maps each handle to a single alias, so two
|
|
695
|
+
* reads of one CTE value would render as the same name, i.e. `FROM tree JOIN tree`, and neither the
|
|
696
|
+
* SQL nor a column expression could say which one it meant. Each read needs its own `query(...)`.
|
|
697
|
+
*
|
|
698
|
+
* Only this query is checked, not the enclosing ones: a nested subquery has its own FROM, so reading
|
|
699
|
+
* the same CTE in there is fine.
|
|
700
|
+
*/
|
|
701
|
+
useCte(handle) {
|
|
702
|
+
if (this.usedCtes.has(handle)) require_utils.fail(`${describeHandle(handle)} is already in this query's \`from\`/\`join\`; use a separate query(...) value for each use`);
|
|
703
|
+
this.usedCtes.add(handle);
|
|
704
|
+
}
|
|
561
705
|
aliasFor(handle) {
|
|
562
706
|
const local = this.aliases.get(handle);
|
|
563
707
|
if (local) return local;
|
|
@@ -587,11 +731,13 @@ function describeHandle(handle) {
|
|
|
587
731
|
* 3. Prune: drop joins nothing references (see below), then reject a kept join whose ON collapsed.
|
|
588
732
|
* 4. Assemble the SQL from the kept fragments, so pruned bindings disappear with their SQL.
|
|
589
733
|
*/
|
|
590
|
-
function parseQuery(q, parent, assigner) {
|
|
734
|
+
function parseQuery(q, parent, assigner, recursiveSelf) {
|
|
591
735
|
validateReadQuery(q);
|
|
592
|
-
if (isSetQuery(q)) return parseSetQuery(q, parent, assigner);
|
|
736
|
+
if (isSetQuery(q)) return parseSetQuery(q, parent, assigner, recursiveSelf);
|
|
593
737
|
const ctx = new Ctx(assigner, parent);
|
|
738
|
+
if (recursiveSelf) ctx.setRecursiveSelf(recursiveSelf);
|
|
594
739
|
const joinEntries = [...q.join ?? []].filter(isDefined);
|
|
740
|
+
const ctes = registerCtes(q, ctx, assigner);
|
|
595
741
|
const parseFrom = registerSource(q.from, ctx, assigner);
|
|
596
742
|
const pendingJoins = joinEntries.flatMap((j) => {
|
|
597
743
|
const kind = "inner" in j && j.inner ? "inner" : "left";
|
|
@@ -636,7 +782,7 @@ function parseQuery(q, parent, assigner) {
|
|
|
636
782
|
const having = conditionToSql(q.having, ctx, true);
|
|
637
783
|
const groupBys = (q.groupBy ?? []).map((g) => asExpr(g, "groupBy").toSql(ctx));
|
|
638
784
|
const orderBys = orderBysToSql(q, ctx);
|
|
639
|
-
const kept = pruneJoins(q, from, joins, [
|
|
785
|
+
const { joins: kept, ctes: keptCtes } = pruneJoins(q, ctx, from, joins, ctes, [
|
|
640
786
|
...selects,
|
|
641
787
|
...groupBys,
|
|
642
788
|
...orderBys,
|
|
@@ -651,6 +797,7 @@ function parseQuery(q, parent, assigner) {
|
|
|
651
797
|
if (forward) require_utils.fail(`Join ${describeHandle(j.source.handle)} references '${forward}', which is joined later; move that join earlier in the join array`);
|
|
652
798
|
}
|
|
653
799
|
const out = [];
|
|
800
|
+
if (keptCtes.length > 0) out.push(withFragment(keptCtes));
|
|
654
801
|
out.push({
|
|
655
802
|
sql: `SELECT ${q.distinct ? "DISTINCT " : ""}`,
|
|
656
803
|
bindings: [],
|
|
@@ -714,6 +861,20 @@ function parseQuery(q, parent, assigner) {
|
|
|
714
861
|
*/
|
|
715
862
|
function registerSource(source, ctx, assigner) {
|
|
716
863
|
const handle = handleOf(source);
|
|
864
|
+
const cteAlias = ctx.cteAliasFor(handle);
|
|
865
|
+
if (cteAlias) {
|
|
866
|
+
ctx.useCte(handle);
|
|
867
|
+
return () => ({
|
|
868
|
+
handle,
|
|
869
|
+
alias: cteAlias,
|
|
870
|
+
sql: require_keywords.safeKq(cteAlias),
|
|
871
|
+
bindings: [],
|
|
872
|
+
refs: [],
|
|
873
|
+
entitySelects: [],
|
|
874
|
+
meta: void 0
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
if (ctx.isPendingCte(handle)) require_utils.fail(`${describeHandle(handle)} is declared later in this query's \`with\`; a CTE can only read earlier ones, and cannot read itself`);
|
|
717
878
|
if (handle instanceof SubqueryHandle) {
|
|
718
879
|
const alias = handle.name ? assigner.getLiteralAlias(handle.name) : assigner.getLiteralAlias("sq");
|
|
719
880
|
ctx.register(handle, alias);
|
|
@@ -1058,14 +1219,28 @@ function refsOf(parsed) {
|
|
|
1058
1219
|
* explicit `{ inner: b, on }` here does filter rows, so pruning it when unreferenced drops that filter;
|
|
1059
1220
|
* that matches `{ books: { title: undefined } }` in em.find and is deliberate. `keep: true` pins it, and
|
|
1060
1221
|
* a pure existence filter is better written as `a.id.in(query({ ... }))`, which is never `undefined`.
|
|
1222
|
+
*
|
|
1223
|
+
* CTEs prune on the same rule and through the same dependency map: a `with` entry nothing reads
|
|
1224
|
+
* anymore drops with the join that read it, and a CTE read only by another CTE survives with it.
|
|
1225
|
+
* A CTE joined into the query shares its alias with that join, so their dependencies are merged.
|
|
1226
|
+
*
|
|
1227
|
+
* The one join that never prunes is a recursive term's reference to its own CTE: PostgreSQL requires
|
|
1228
|
+
* it, so it is not the caller's optional filter (see `Ctx.recursiveSelf`).
|
|
1061
1229
|
*/
|
|
1062
|
-
function pruneJoins(q, from, joins, used) {
|
|
1063
|
-
if (q.pruneJoins === false) return
|
|
1230
|
+
function pruneJoins(q, ctx, from, joins, ctes, used) {
|
|
1231
|
+
if (q.pruneJoins === false) return {
|
|
1232
|
+
joins,
|
|
1233
|
+
ctes
|
|
1234
|
+
};
|
|
1064
1235
|
const deps = /* @__PURE__ */ new Map();
|
|
1065
|
-
|
|
1066
|
-
const
|
|
1067
|
-
deps.
|
|
1236
|
+
function addDeps(alias, refs) {
|
|
1237
|
+
const own = refs.filter((r) => r !== alias);
|
|
1238
|
+
const existing = deps.get(alias);
|
|
1239
|
+
if (existing) existing.push(...own);
|
|
1240
|
+
else deps.set(alias, own);
|
|
1068
1241
|
}
|
|
1242
|
+
for (const j of joins) addDeps(j.source.alias, [...j.userOn?.refs ?? [], ...j.source.refs]);
|
|
1243
|
+
for (const c of ctes) addDeps(c.alias, c.plan.outerRefs);
|
|
1069
1244
|
const required = /* @__PURE__ */ new Set();
|
|
1070
1245
|
function markRequired(alias) {
|
|
1071
1246
|
if (required.has(alias)) return;
|
|
@@ -1075,7 +1250,53 @@ function pruneJoins(q, from, joins, used) {
|
|
|
1075
1250
|
markRequired(from.alias);
|
|
1076
1251
|
for (const r of used.flatMap((u) => u.refs)) markRequired(r);
|
|
1077
1252
|
for (const j of joins) if (j.keep) markRequired(j.source.alias);
|
|
1078
|
-
|
|
1253
|
+
for (const j of joins) if (j.source.handle === ctx.recursiveSelf) markRequired(j.source.alias);
|
|
1254
|
+
return {
|
|
1255
|
+
joins: joins.filter((j) => required.has(j.source.alias)),
|
|
1256
|
+
ctes: ctes.filter((c) => required.has(c.alias))
|
|
1257
|
+
};
|
|
1258
|
+
}
|
|
1259
|
+
/**
|
|
1260
|
+
* Names and parses each `with` entry, in declaration order.
|
|
1261
|
+
*
|
|
1262
|
+
* Each entry is in scope before the next is parsed, so a CTE can read an *earlier* sibling by name,
|
|
1263
|
+
* PostgreSQL's rule for a non-recursive WITH. A forward reference, or a CTE reading the query's own
|
|
1264
|
+
* `from`, fails as "not in this query's from/join", the same error any out-of-scope source gets.
|
|
1265
|
+
*
|
|
1266
|
+
* I.e. `with: [totals, ranked]` parses `totals` first, so `ranked` can join it, but not the reverse.
|
|
1267
|
+
*/
|
|
1268
|
+
function registerCtes(q, ctx, assigner) {
|
|
1269
|
+
const handles = (q.with === void 0 ? [] : Array.isArray(q.with) ? q.with : [q.with]).filter(isDefined).map(withEntryHandle);
|
|
1270
|
+
const aliases = handles.map((handle) => assigner.getLiteralAlias(handle.name ?? "cte"));
|
|
1271
|
+
handles.forEach((handle, i) => ctx.declareCte(handle, aliases[i]));
|
|
1272
|
+
return handles.map((handle, i) => {
|
|
1273
|
+
if (handle.recursive) ctx.promoteCte(handle);
|
|
1274
|
+
const plan = parseQuery(handle.q, ctx, assigner, handle.recursive ? handle : void 0);
|
|
1275
|
+
if (!handle.recursive) ctx.promoteCte(handle);
|
|
1276
|
+
return {
|
|
1277
|
+
alias: aliases[i],
|
|
1278
|
+
plan,
|
|
1279
|
+
recursive: handle.recursive
|
|
1280
|
+
};
|
|
1281
|
+
});
|
|
1282
|
+
}
|
|
1283
|
+
/** A CTE must be a table shape, so entity-mode and scalar values, which have no columns, are out. */
|
|
1284
|
+
function withEntryHandle(entry) {
|
|
1285
|
+
if (!isSubqueryValue(entry)) require_utils.fail(entry instanceof SubqueryExpr || isEntityQueryValue(entry) ? "A `with` entry needs named columns; entity and scalar query(...) values have none" : "A `with` entry must be a query(...) value");
|
|
1286
|
+
return readValueHandle(entry);
|
|
1287
|
+
}
|
|
1288
|
+
/**
|
|
1289
|
+
* Renders `WITH a AS (...), b AS (...) `, whose bindings lead the statement, as their SQL does.
|
|
1290
|
+
*
|
|
1291
|
+
* One recursive CTE makes the whole clause `WITH RECURSIVE`, PostgreSQL's rule: the keyword is on the
|
|
1292
|
+
* clause, not on the entry that needs it, and it does not force the other entries to be recursive.
|
|
1293
|
+
*/
|
|
1294
|
+
function withFragment(ctes) {
|
|
1295
|
+
return {
|
|
1296
|
+
sql: `WITH${ctes.some((c) => c.recursive) ? " RECURSIVE" : ""} ${ctes.map((c) => `${require_keywords.safeKq(c.alias)} AS (${c.plan.sql})`).join(", ")} `,
|
|
1297
|
+
bindings: ctes.flatMap((c) => c.plan.bindings),
|
|
1298
|
+
refs: []
|
|
1299
|
+
};
|
|
1079
1300
|
}
|
|
1080
1301
|
function asExpr(value, where) {
|
|
1081
1302
|
if (value instanceof require_Expr.BaseExpr) return value;
|
|
@@ -1101,6 +1322,7 @@ exports.isReadQueryValue = isReadQueryValue;
|
|
|
1101
1322
|
exports.parseUserQuery = parseUserQuery;
|
|
1102
1323
|
exports.projectionToSql = projectionToSql;
|
|
1103
1324
|
exports.query = query;
|
|
1325
|
+
exports.recursiveQuery = recursiveQuery;
|
|
1104
1326
|
exports.scalarQueryBrand = scalarQueryBrand;
|
|
1105
1327
|
exports.sql = sql;
|
|
1106
1328
|
exports.subqueryBrand = subqueryBrand;
|