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