turbine-orm 0.50.0 → 0.51.0
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/README.md +66 -66
- package/dist/adapters/cockroachdb.d.ts +5 -5
- package/dist/adapters/cockroachdb.js +10 -10
- package/dist/adapters/index.d.ts +5 -5
- package/dist/adapters/index.js +7 -7
- package/dist/adapters/yugabytedb.d.ts +7 -7
- package/dist/adapters/yugabytedb.js +10 -10
- package/dist/cjs/adapters/cockroachdb.d.ts +5 -5
- package/dist/cjs/adapters/cockroachdb.js +10 -10
- package/dist/cjs/adapters/index.d.ts +5 -5
- package/dist/cjs/adapters/index.js +7 -7
- package/dist/cjs/adapters/yugabytedb.d.ts +7 -7
- package/dist/cjs/adapters/yugabytedb.js +10 -10
- package/dist/cjs/cli/config.d.ts +13 -2
- package/dist/cjs/cli/config.js +3 -2
- package/dist/cjs/cli/destructive.d.ts +1 -1
- package/dist/cjs/cli/destructive.js +1 -1
- package/dist/cjs/cli/index.d.ts +10 -10
- package/dist/cjs/cli/index.js +49 -45
- package/dist/cjs/cli/loader.d.ts +7 -7
- package/dist/cjs/cli/loader.js +9 -9
- package/dist/cjs/cli/mcp.js +4 -4
- package/dist/cjs/cli/migrate.d.ts +5 -5
- package/dist/cjs/cli/migrate.js +11 -11
- package/dist/cjs/cli/studio-ui.generated.js +1 -1
- package/dist/cjs/cli/ui.d.ts +2 -2
- package/dist/cjs/cli/ui.js +2 -2
- package/dist/cjs/client.d.ts +49 -38
- package/dist/cjs/client.js +57 -56
- package/dist/cjs/dialect.d.ts +62 -18
- package/dist/cjs/dialect.js +40 -2
- package/dist/cjs/errors.d.ts +5 -5
- package/dist/cjs/errors.js +11 -11
- package/dist/cjs/generate.d.ts +6 -6
- package/dist/cjs/generate.js +31 -29
- package/dist/cjs/index-advisor.d.ts +5 -5
- package/dist/cjs/index-advisor.js +0 -0
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.js +7 -7
- package/dist/cjs/introspect.d.ts +35 -9
- package/dist/cjs/introspect.js +83 -32
- package/dist/cjs/mssql.d.ts +11 -11
- package/dist/cjs/mssql.js +64 -29
- package/dist/cjs/mysql.d.ts +8 -8
- package/dist/cjs/mysql.js +61 -23
- package/dist/cjs/nested-write.d.ts +21 -2
- package/dist/cjs/nested-write.js +51 -14
- package/dist/cjs/optional-peer-import.cjs +7 -7
- package/dist/cjs/optional-peer-import.d.cts +7 -7
- package/dist/cjs/pipeline-submittable.d.ts +2 -2
- package/dist/cjs/pipeline-submittable.js +6 -6
- package/dist/cjs/pipeline.d.ts +1 -1
- package/dist/cjs/pipeline.js +4 -4
- package/dist/cjs/powdb-introspect.d.ts +1 -1
- package/dist/cjs/powdb-introspect.js +1 -1
- package/dist/cjs/powdb.d.ts +28 -28
- package/dist/cjs/powdb.js +66 -66
- package/dist/cjs/powql.d.ts +27 -27
- package/dist/cjs/powql.js +73 -52
- package/dist/cjs/query/aggregates.d.ts +1 -1
- package/dist/cjs/query/aggregates.js +5 -5
- package/dist/cjs/query/batched-loader.d.ts +11 -11
- package/dist/cjs/query/batched-loader.js +24 -24
- package/dist/cjs/query/builder.d.ts +39 -21
- package/dist/cjs/query/builder.js +99 -57
- package/dist/cjs/query/compound-unique.d.ts +1 -1
- package/dist/cjs/query/compound-unique.js +0 -0
- package/dist/cjs/query/deferred.d.ts +12 -6
- package/dist/cjs/query/deferred.js +1 -1
- package/dist/cjs/query/filters.d.ts +31 -11
- package/dist/cjs/query/filters.js +67 -14
- package/dist/cjs/query/index.d.ts +1 -1
- package/dist/cjs/query/index.js +1 -1
- package/dist/cjs/query/relations.d.ts +9 -9
- package/dist/cjs/query/relations.js +164 -57
- package/dist/cjs/query/types.d.ts +86 -35
- package/dist/cjs/query/types.js +1 -1
- package/dist/cjs/query/utils.d.ts +27 -10
- package/dist/cjs/query/utils.js +86 -14
- package/dist/cjs/query/where.d.ts +47 -28
- package/dist/cjs/query/where.js +130 -31
- package/dist/cjs/query/writes.d.ts +24 -5
- package/dist/cjs/query/writes.js +102 -13
- package/dist/cjs/realtime.d.ts +7 -7
- package/dist/cjs/realtime.js +9 -9
- package/dist/cjs/schema-builder.d.ts +18 -7
- package/dist/cjs/schema-builder.js +17 -10
- package/dist/cjs/schema-metadata.d.ts +3 -3
- package/dist/cjs/schema-metadata.js +9 -9
- package/dist/cjs/schema-sql.d.ts +9 -9
- package/dist/cjs/schema-sql.js +20 -20
- package/dist/cjs/schema.d.ts +19 -9
- package/dist/cjs/schema.js +6 -6
- package/dist/cjs/serverless.d.ts +15 -15
- package/dist/cjs/serverless.js +16 -16
- package/dist/cjs/sqlite.d.ts +8 -8
- package/dist/cjs/sqlite.js +53 -22
- package/dist/cjs/typed-sql.d.ts +4 -4
- package/dist/cjs/typed-sql.js +5 -5
- package/dist/cli/config.d.ts +13 -2
- package/dist/cli/config.js +3 -2
- package/dist/cli/destructive.d.ts +1 -1
- package/dist/cli/destructive.js +1 -1
- package/dist/cli/index.d.ts +10 -10
- package/dist/cli/index.js +49 -45
- package/dist/cli/loader.d.ts +7 -7
- package/dist/cli/loader.js +9 -9
- package/dist/cli/mcp.js +4 -4
- package/dist/cli/migrate.d.ts +5 -5
- package/dist/cli/migrate.js +11 -11
- package/dist/cli/studio-ui.generated.js +1 -1
- package/dist/cli/ui.d.ts +2 -2
- package/dist/cli/ui.js +2 -2
- package/dist/client.d.ts +49 -38
- package/dist/client.js +57 -56
- package/dist/dialect.d.ts +62 -18
- package/dist/dialect.js +40 -2
- package/dist/errors.d.ts +5 -5
- package/dist/errors.js +11 -11
- package/dist/generate.d.ts +6 -6
- package/dist/generate.js +31 -29
- package/dist/index-advisor.d.ts +5 -5
- package/dist/index-advisor.js +0 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +7 -7
- package/dist/introspect.d.ts +35 -9
- package/dist/introspect.js +82 -32
- package/dist/mssql.d.ts +11 -11
- package/dist/mssql.js +64 -29
- package/dist/mysql.d.ts +8 -8
- package/dist/mysql.js +61 -23
- package/dist/nested-write.d.ts +21 -2
- package/dist/nested-write.js +51 -14
- package/dist/optional-peer-import.cjs +7 -7
- package/dist/optional-peer-import.d.cts +7 -7
- package/dist/pipeline-submittable.d.ts +2 -2
- package/dist/pipeline-submittable.js +6 -6
- package/dist/pipeline.d.ts +1 -1
- package/dist/pipeline.js +4 -4
- package/dist/powdb-introspect.d.ts +1 -1
- package/dist/powdb-introspect.js +1 -1
- package/dist/powdb.d.ts +28 -28
- package/dist/powdb.js +66 -66
- package/dist/powql.d.ts +27 -27
- package/dist/powql.js +73 -52
- package/dist/query/aggregates.d.ts +1 -1
- package/dist/query/aggregates.js +5 -5
- package/dist/query/batched-loader.d.ts +11 -11
- package/dist/query/batched-loader.js +24 -24
- package/dist/query/builder.d.ts +39 -21
- package/dist/query/builder.js +100 -58
- package/dist/query/compound-unique.d.ts +1 -1
- package/dist/query/compound-unique.js +0 -0
- package/dist/query/deferred.d.ts +12 -6
- package/dist/query/deferred.js +1 -1
- package/dist/query/filters.d.ts +31 -11
- package/dist/query/filters.js +66 -13
- package/dist/query/index.d.ts +1 -1
- package/dist/query/index.js +1 -1
- package/dist/query/relations.d.ts +9 -9
- package/dist/query/relations.js +165 -58
- package/dist/query/types.d.ts +86 -35
- package/dist/query/types.js +1 -1
- package/dist/query/utils.d.ts +27 -10
- package/dist/query/utils.js +84 -14
- package/dist/query/where.d.ts +47 -28
- package/dist/query/where.js +129 -32
- package/dist/query/writes.d.ts +24 -5
- package/dist/query/writes.js +101 -13
- package/dist/realtime.d.ts +7 -7
- package/dist/realtime.js +9 -9
- package/dist/schema-builder.d.ts +18 -7
- package/dist/schema-builder.js +17 -10
- package/dist/schema-metadata.d.ts +3 -3
- package/dist/schema-metadata.js +9 -9
- package/dist/schema-sql.d.ts +9 -9
- package/dist/schema-sql.js +20 -20
- package/dist/schema.d.ts +19 -9
- package/dist/schema.js +6 -6
- package/dist/serverless.d.ts +15 -15
- package/dist/serverless.js +16 -16
- package/dist/sqlite.d.ts +8 -8
- package/dist/sqlite.js +53 -22
- package/dist/typed-sql.d.ts +4 -4
- package/dist/typed-sql.js +5 -5
- package/package.json +2 -2
package/dist/powql.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* PowqlInterface
|
|
2
|
+
* PowqlInterface, Turbine's PowQL query generator (the PowDB analogue of
|
|
3
3
|
* {@link QueryInterface}). It exposes the same public method surface as the SQL
|
|
4
|
-
* `QueryInterface` (`findMany`, `create`, `update`, …) but emits **PowQL
|
|
5
|
-
* pipeline language, not SQL
|
|
4
|
+
* `QueryInterface` (`findMany`, `create`, `update`, …) but emits **PowQL**, a
|
|
5
|
+
* pipeline language, not SQL, executed through {@link PowdbPool}.
|
|
6
6
|
*
|
|
7
7
|
* It is a *parallel* implementation rather than a `Dialect` of the SQL builder:
|
|
8
8
|
* PowQL's grammar (`T filter <e> order <k> { .col }`) shares no surface with
|
|
@@ -14,14 +14,14 @@
|
|
|
14
14
|
* server):
|
|
15
15
|
* - `create`/`createMany`/`update`/`delete` use PowDB 0.7.0's trailing
|
|
16
16
|
* `returning` keyword (`RETURNING *`, all columns) to surface affected rows
|
|
17
|
-
* in one round-trip. `upsert` is the lone exception
|
|
17
|
+
* in one round-trip. `upsert` is the lone exception, its statement does not
|
|
18
18
|
* accept `returning`, so it reselects the row by PK (a composite-PK upsert
|
|
19
19
|
* reselects-or-writes inside one flat transaction).
|
|
20
20
|
* - The PK is server-assigned when the column is `isGenerated` (PowDB's `auto`
|
|
21
|
-
* int
|
|
21
|
+
* int, read back via `returning`); otherwise a defaulted **string** PK is
|
|
22
22
|
* generated client-side (UUID).
|
|
23
|
-
* - `with` (nested relations) uses **batched N+1 loaders
|
|
24
|
-
* depth D, not one query
|
|
23
|
+
* - `with` (nested relations) uses **batched N+1 loaders**, D round-trips for
|
|
24
|
+
* depth D, not one query, including manyToMany (junction → targets).
|
|
25
25
|
* - **Relation filters** (`some`/`none`/`every`, all cardinalities incl. m2m)
|
|
26
26
|
* are resolved client-side to a literal `in (…)` list, never an IN-subquery:
|
|
27
27
|
* PowDB's executor caches a subquery's result by plan shape and would return
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
* shared nested-write engine as one flat top-level transaction (PowDB is
|
|
31
31
|
* single-writer, no savepoints).
|
|
32
32
|
* - pgvector / JSON / array filters and cursor streaming throw
|
|
33
|
-
* {@link UnsupportedFeatureError} (E017)
|
|
33
|
+
* {@link UnsupportedFeatureError} (E017), they have no PowDB equivalent.
|
|
34
34
|
*
|
|
35
35
|
* @module
|
|
36
36
|
*/
|
|
@@ -42,6 +42,7 @@ import { assertAggregatePiiOptIn } from './query/aggregates.js';
|
|
|
42
42
|
import { expandCompoundUniqueWhere } from './query/compound-unique.js';
|
|
43
43
|
import { isJsonFilter, isRelationPickOrderBy, orderByEntries } from './query/filters.js';
|
|
44
44
|
import { escapeLike } from './query/utils.js';
|
|
45
|
+
import { assertJsonFilterKeys, jsonStringEntries } from './query/where.js';
|
|
45
46
|
import { normalizeKeyColumns, snakeToCamel, } from './schema.js';
|
|
46
47
|
/**
|
|
47
48
|
* Max parent keys per relation-loader `in (…)` query. A `with` over a large
|
|
@@ -88,7 +89,7 @@ const POWQL_WRITE_ACTIONS = new Set([
|
|
|
88
89
|
* relation before compiling to it. Keyed on the pool object identity so every
|
|
89
90
|
* table interface over the same connection shares one snapshot; a WeakMap lets a
|
|
90
91
|
* discarded pool's snapshot be collected. A fetch failure caches `[]` (a missing
|
|
91
|
-
* listing means "no verifiable links"
|
|
92
|
+
* listing means "no verifiable links", a silent fallback to loaders, never an
|
|
92
93
|
* error).
|
|
93
94
|
*/
|
|
94
95
|
const LINK_SNAPSHOT_CACHE = new WeakMap();
|
|
@@ -227,8 +228,8 @@ export class PowqlInterface {
|
|
|
227
228
|
return `$${params.length}`;
|
|
228
229
|
}
|
|
229
230
|
/**
|
|
230
|
-
* Render a value for a write *assignment* (`col := …`). Every value
|
|
231
|
-
* columns included
|
|
231
|
+
* Render a value for a write *assignment* (`col := …`). Every value, float
|
|
232
|
+
* columns included, is sent as a positional `$N` param. PowDB ≥ 0.7.0 fixed
|
|
232
233
|
* the int→float UPDATE coercion bug (`score := $n` with an integer param now
|
|
233
234
|
* reads back the integer value, not the raw i64 bits), so the float-literal
|
|
234
235
|
* inlining workaround Turbine carried for ≤ 0.6.2 is gone. Marks the column as
|
|
@@ -259,7 +260,7 @@ export class PowqlInterface {
|
|
|
259
260
|
get capabilities() {
|
|
260
261
|
return this.pool.capabilities ?? ALL_POWDB_CAPABILITIES;
|
|
261
262
|
}
|
|
262
|
-
/** A predicate that is always false
|
|
263
|
+
/** A predicate that is always false, the empty-`in` / contradiction sentinel. */
|
|
263
264
|
alwaysFalse() {
|
|
264
265
|
const pk = this.meta.primaryKey[0] ?? this.meta.columns[0]?.name;
|
|
265
266
|
return `(.${pk} is null and .${pk} is not null)`;
|
|
@@ -302,7 +303,7 @@ export class PowqlInterface {
|
|
|
302
303
|
}
|
|
303
304
|
else if (this.meta.relations[key]) {
|
|
304
305
|
// Relation filters are pre-resolved to scalar in/notIn by
|
|
305
|
-
// resolveRelationFilters() before buildWhere runs
|
|
306
|
+
// resolveRelationFilters() before buildWhere runs, reaching here means
|
|
306
307
|
// a caller skipped that step (an internal bug, not user error).
|
|
307
308
|
throw new ValidationError(`[turbine] internal: relation filter "${key}" reached buildWhere unresolved (missing resolveRelationFilters()).`);
|
|
308
309
|
}
|
|
@@ -336,7 +337,7 @@ export class PowqlInterface {
|
|
|
336
337
|
}
|
|
337
338
|
rejectUnsupportedFilter(op, field);
|
|
338
339
|
if (!Object.keys(op).some((k) => OPERATOR_KEYS.has(k))) {
|
|
339
|
-
// A bare object that is not an operator set
|
|
340
|
+
// A bare object that is not an operator set, equality by value.
|
|
340
341
|
return `${ref} = ${this.param(value, params)}`;
|
|
341
342
|
}
|
|
342
343
|
const insensitive = op.mode === 'insensitive';
|
|
@@ -434,6 +435,10 @@ export class PowqlInterface {
|
|
|
434
435
|
* by the empty-where guard.
|
|
435
436
|
*/
|
|
436
437
|
buildJsonPathCondition(col, filter, params, alias) {
|
|
438
|
+
// Same strict-key check as the SQL path. Without it an unrecognized
|
|
439
|
+
// operator compiled to zero conditions and the predicate silently
|
|
440
|
+
// disappeared, returning every row (see assertJsonFilterKeys).
|
|
441
|
+
assertJsonFilterKeys(filter, col.name);
|
|
437
442
|
const conds = [];
|
|
438
443
|
// Bind the path segments at most once and reuse the expression string across
|
|
439
444
|
// equals + range comparisons (they share the same `path`).
|
|
@@ -479,8 +484,20 @@ export class PowqlInterface {
|
|
|
479
484
|
}
|
|
480
485
|
conds.push(`${pathP()} ${powOp} ${this.param(v, params)}`);
|
|
481
486
|
}
|
|
482
|
-
|
|
483
|
-
|
|
487
|
+
// Substring comparisons against the text at `path`, in the same fixed
|
|
488
|
+
// order as the SQL path. PowQL has `like`, so these compile natively.
|
|
489
|
+
for (const { pattern, value } of jsonStringEntries(filter, col.name)) {
|
|
490
|
+
const insensitive = filter.mode === 'insensitive';
|
|
491
|
+
const lhs = insensitive ? `lower(${pathP()})` : pathP();
|
|
492
|
+
conds.push(`${lhs} like ${this.bindLike(pattern(escapeLike(value)), params, insensitive)}`);
|
|
493
|
+
}
|
|
494
|
+
// Every reachable shape now emits a condition or throws: assertJsonFilterKeys
|
|
495
|
+
// rejects a filter that compares nothing, so an empty list here would be a
|
|
496
|
+
// generator bug rather than a user error, and must not silently widen the
|
|
497
|
+
// query the way it used to.
|
|
498
|
+
if (!conds.length) {
|
|
499
|
+
throw new ValidationError(`[turbine] internal: JSON filter on "${col.name}" compiled to no condition. This is a bug in turbine-orm.`);
|
|
500
|
+
}
|
|
484
501
|
return conds.length > 1 ? `(${conds.join(' and ')})` : conds[0];
|
|
485
502
|
}
|
|
486
503
|
/** Bind a value, lowercasing for case-insensitive comparisons. */
|
|
@@ -493,7 +510,7 @@ export class PowqlInterface {
|
|
|
493
510
|
const ph = this.param(pattern, params);
|
|
494
511
|
return insensitive ? `lower(${ph})` : ph;
|
|
495
512
|
}
|
|
496
|
-
/** `lhs [not] in ($1, $2, …)
|
|
513
|
+
/** `lhs [not] in ($1, $2, …)`, empty list collapses to a constant. */
|
|
497
514
|
buildInList(lhs, values, params, insensitive, negate) {
|
|
498
515
|
if (!Array.isArray(values) || values.length === 0) {
|
|
499
516
|
// `in []` matches nothing; `not in []` matches everything (SQL parity requires a
|
|
@@ -520,7 +537,7 @@ export class PowqlInterface {
|
|
|
520
537
|
* so a second subquery of the same shape with a different value returns the
|
|
521
538
|
* first one's stale rows (reproduced live on the embedded engine; the
|
|
522
539
|
* single-statement literal `in (list)` form is always correct). Resolving
|
|
523
|
-
* client-side trades extra round-trips for correctness, and recurses
|
|
540
|
+
* client-side trades extra round-trips for correctness, and recurses, nested
|
|
524
541
|
* relation filters in the inner predicate resolve when the target query runs.
|
|
525
542
|
*/
|
|
526
543
|
async resolveRelationFilters(where, timeout) {
|
|
@@ -562,7 +579,7 @@ export class PowqlInterface {
|
|
|
562
579
|
const fk = normalizeKeyColumns(rel.foreignKey);
|
|
563
580
|
const rk = normalizeKeyColumns(rel.referenceKey);
|
|
564
581
|
if (fk.length > 1 || rk.length > 1) {
|
|
565
|
-
throw new UnsupportedFeatureError('composite-key relation filters', 'PowDB', `relation "${rel.name}" uses a composite key
|
|
582
|
+
throw new UnsupportedFeatureError('composite-key relation filters', 'PowDB', `relation "${rel.name}" uses a composite key, PowQL has no tuple-\`in\` to express it`);
|
|
566
583
|
}
|
|
567
584
|
const targetMeta = this.schema.tables[rel.to];
|
|
568
585
|
if (!targetMeta)
|
|
@@ -603,7 +620,7 @@ export class PowqlInterface {
|
|
|
603
620
|
if (!targetMeta)
|
|
604
621
|
throw new ValidationError(`[turbine] Relation "${rel.name}" targets unknown table "${rel.to}".`);
|
|
605
622
|
if (sourceJ.length > 1 || targetJ.length > 1 || sourceRef.length > 1 || targetMeta.primaryKey.length > 1) {
|
|
606
|
-
throw new UnsupportedFeatureError('composite-key manyToMany filters', 'PowDB', `relation "${rel.name}"
|
|
623
|
+
throw new UnsupportedFeatureError('composite-key manyToMany filters', 'PowDB', `relation "${rel.name}", PowQL has no tuple-\`in\` for composite junction/target keys`);
|
|
607
624
|
}
|
|
608
625
|
const sourceJCol = sourceJ[0];
|
|
609
626
|
const targetJCol = targetJ[0];
|
|
@@ -621,7 +638,7 @@ export class PowqlInterface {
|
|
|
621
638
|
});
|
|
622
639
|
return [...new Set(rows.map((r) => r[targetPkField]).filter((v) => v != null))];
|
|
623
640
|
};
|
|
624
|
-
// Junction source keys linking any of `targetPks` (literal IN-list
|
|
641
|
+
// Junction source keys linking any of `targetPks` (literal IN-list, never a subquery).
|
|
625
642
|
const sourcesForTargets = async (targetPks) => {
|
|
626
643
|
if (!targetPks.length)
|
|
627
644
|
return [];
|
|
@@ -769,7 +786,7 @@ export class PowqlInterface {
|
|
|
769
786
|
}
|
|
770
787
|
return `${this.ref(field, alias)} ${spec.sort === 'desc' ? 'desc' : 'asc'}`;
|
|
771
788
|
}
|
|
772
|
-
// Name the actual feature in the refusal
|
|
789
|
+
// Name the actual feature in the refusal, a pick-row ordering
|
|
773
790
|
// reported as "vector / distance ordering" sends users hunting for
|
|
774
791
|
// pgvector docs. Everything else stays E017 on PowDB.
|
|
775
792
|
const feature = isRelationPickOrderBy(dir)
|
|
@@ -951,8 +968,8 @@ export class PowqlInterface {
|
|
|
951
968
|
*
|
|
952
969
|
* When the engine supports nested projections (>= 0.18) and the strategy
|
|
953
970
|
* does not opt out, eligible `with` relations compile INTO this statement as
|
|
954
|
-
* nested-projection blocks (`nestedPlans`)
|
|
955
|
-
* shape
|
|
971
|
+
* nested-projection blocks (`nestedPlans`), one round-trip for the whole
|
|
972
|
+
* shape, and only the ineligible remainder (`residualWith`) goes to the
|
|
956
973
|
* post-execution loaders. Without nesting the emitted PowQL is byte-identical
|
|
957
974
|
* to the pre-0.18 output (no alias, `.col` refs).
|
|
958
975
|
*/
|
|
@@ -1110,7 +1127,7 @@ export class PowqlInterface {
|
|
|
1110
1127
|
return row;
|
|
1111
1128
|
}
|
|
1112
1129
|
// -------------------------------------------------------------------------
|
|
1113
|
-
// Nested relations
|
|
1130
|
+
// Nested relations, batched N+1 loaders (hasMany / hasOne / belongsTo)
|
|
1114
1131
|
// -------------------------------------------------------------------------
|
|
1115
1132
|
/**
|
|
1116
1133
|
* Load each requested relation for `parents` and attach it onto each row.
|
|
@@ -1242,7 +1259,7 @@ export class PowqlInterface {
|
|
|
1242
1259
|
}
|
|
1243
1260
|
}
|
|
1244
1261
|
/**
|
|
1245
|
-
* manyToMany nested read
|
|
1262
|
+
* manyToMany nested read, a three-hop batched loader (no `json_agg`/join
|
|
1246
1263
|
* pushdown): (1) read the junction rows for all parents in `sourceKey in (…)`
|
|
1247
1264
|
* chunks, (2) read the target rows for the collected `targetKey`s, (3) stitch
|
|
1248
1265
|
* each parent → its junction rows → its targets in memory. Mirrors the
|
|
@@ -1260,7 +1277,7 @@ export class PowqlInterface {
|
|
|
1260
1277
|
if (!targetMeta)
|
|
1261
1278
|
throw new ValidationError(`[turbine] Relation "${relName}" targets unknown table "${rel.to}".`);
|
|
1262
1279
|
if (sourceJ.length > 1 || targetJ.length > 1 || sourceRef.length > 1 || targetMeta.primaryKey.length > 1) {
|
|
1263
|
-
throw new UnsupportedFeatureError('composite-key manyToMany', 'PowDB', `relation "${relName}"
|
|
1280
|
+
throw new UnsupportedFeatureError('composite-key manyToMany', 'PowDB', `relation "${relName}", PowQL has no tuple-\`in\`, so composite junction/target keys can't be loaded`);
|
|
1264
1281
|
}
|
|
1265
1282
|
const sourceJCol = sourceJ[0];
|
|
1266
1283
|
const targetJCol = targetJ[0];
|
|
@@ -1706,7 +1723,7 @@ export class PowqlInterface {
|
|
|
1706
1723
|
/**
|
|
1707
1724
|
* Shape the nested JSON children back into typed entities on every parent
|
|
1708
1725
|
* row. The nested field arrives as a decoded JSON array on the native wire
|
|
1709
|
-
* (or JSON text on the legacy wire
|
|
1726
|
+
* (or JSON text on the legacy wire, parsed here); its values are real JSON
|
|
1710
1727
|
* types, so each child object goes through the NATIVE coercion policy
|
|
1711
1728
|
* (`rowToEntity(…, true)`: a date column's micros number becomes a `Date`, a
|
|
1712
1729
|
* json column's document passes through, a str `"null"` stays a string).
|
|
@@ -1803,7 +1820,7 @@ export class PowqlInterface {
|
|
|
1803
1820
|
* must stay on the loaders (ALWAYS a silent fallback with identical output).
|
|
1804
1821
|
*
|
|
1805
1822
|
* SCOPED TIGHT: this fires ONLY for a to-one relation whose child projection
|
|
1806
|
-
* includes a bigint/bytes column
|
|
1823
|
+
* includes a bigint/bytes column, exactly the case a JSON nested block cannot
|
|
1807
1824
|
* carry, so nested projections have already fallen back to a per-relation loader
|
|
1808
1825
|
* (`planNestedRelation` returned `null` for the same shape). Cases nested
|
|
1809
1826
|
* projections DO serve keep nested projections: link-bearing statements are
|
|
@@ -1811,9 +1828,9 @@ export class PowqlInterface {
|
|
|
1811
1828
|
* link path would regress a hot path for no gain. Requires: single-column
|
|
1812
1829
|
* belongsTo; no relation `with` / `where` / `distinct` / `orderBy` /
|
|
1813
1830
|
* `limit` / `offset` (a scalar path has no per-hop filter/order and cannot
|
|
1814
|
-
* reproduce those
|
|
1831
|
+
* reproduce those, such inputs stay on the loader for exact parity); a link
|
|
1815
1832
|
* name and all projected columns that are bare identifiers (a quoted segment in
|
|
1816
|
-
* a dotted link path is outside the verified spelling
|
|
1833
|
+
* a dotted link path is outside the verified spelling, fall back); and a
|
|
1817
1834
|
* DECLARED link that verifiably matches (`findMatchingLink`).
|
|
1818
1835
|
*/
|
|
1819
1836
|
async planLinkPathRelation(relName, rel, opt, includePii, parentCols, index) {
|
|
@@ -1844,7 +1861,7 @@ export class PowqlInterface {
|
|
|
1844
1861
|
});
|
|
1845
1862
|
if (!hasCarrierBlockedCol)
|
|
1846
1863
|
return null;
|
|
1847
|
-
// All cheap checks passed: NOW fetch (and cache) the link snapshot
|
|
1864
|
+
// All cheap checks passed: NOW fetch (and cache) the link snapshot, never
|
|
1848
1865
|
// before, so a query with no link-path candidate issues no `schema links`.
|
|
1849
1866
|
const link = this.findMatchingLink(await this.linksSnapshot(), rel);
|
|
1850
1867
|
if (!link)
|
|
@@ -1875,7 +1892,7 @@ export class PowqlInterface {
|
|
|
1875
1892
|
}
|
|
1876
1893
|
/**
|
|
1877
1894
|
* Reconstruct each link-path relation's child entity from its flat hop fields
|
|
1878
|
-
* and attach it under the relation name
|
|
1895
|
+
* and attach it under the relation name, output indistinguishable from the
|
|
1879
1896
|
* loader (same keys, same coercions). Presence: the target PK cell arriving
|
|
1880
1897
|
* Empty (a null/dangling FK at the hop) means no linked row → `null`, matching
|
|
1881
1898
|
* the loader's `matches[0] ?? null`. Otherwise the gathered snake cells go
|
|
@@ -1905,7 +1922,7 @@ export class PowqlInterface {
|
|
|
1905
1922
|
}
|
|
1906
1923
|
}
|
|
1907
1924
|
// -------------------------------------------------------------------------
|
|
1908
|
-
// Writes (reselect
|
|
1925
|
+
// Writes (reselect, PowDB has no RETURNING)
|
|
1909
1926
|
// -------------------------------------------------------------------------
|
|
1910
1927
|
/** Split `data` into scalar assignments; reject relation (nested-write) keys. */
|
|
1911
1928
|
scalarData(data) {
|
|
@@ -1914,7 +1931,7 @@ export class PowqlInterface {
|
|
|
1914
1931
|
if (value === undefined)
|
|
1915
1932
|
continue;
|
|
1916
1933
|
if (this.meta.relations[field]) {
|
|
1917
|
-
throw new UnsupportedFeatureError('nested writes', 'PowDB', `relation "${field}"
|
|
1934
|
+
throw new UnsupportedFeatureError('nested writes', 'PowDB', `relation "${field}", nested writes need create()/update(), not createMany()/upsert()`);
|
|
1918
1935
|
}
|
|
1919
1936
|
out.push({ col: this.column(field), value });
|
|
1920
1937
|
}
|
|
@@ -1923,8 +1940,8 @@ export class PowqlInterface {
|
|
|
1923
1940
|
/**
|
|
1924
1941
|
* Fill in a client-generated UUID for a defaulted **string** PK that wasn't
|
|
1925
1942
|
* supplied. A server-generated PK ({@link ColumnMetadata.isGenerated}, e.g. an
|
|
1926
|
-
* `int` column with PowDB's `auto` modifier) is left untouched
|
|
1927
|
-
* it and the trailing `returning` reads it back
|
|
1943
|
+
* `int` column with PowDB's `auto` modifier) is left untouched, PowDB assigns
|
|
1944
|
+
* it and the trailing `returning` reads it back, as is any non-string PK.
|
|
1928
1945
|
*/
|
|
1929
1946
|
applyPkDefault(data) {
|
|
1930
1947
|
const out = { ...data };
|
|
@@ -1941,7 +1958,7 @@ export class PowqlInterface {
|
|
|
1941
1958
|
return out;
|
|
1942
1959
|
}
|
|
1943
1960
|
/**
|
|
1944
|
-
* The table name as a PowQL type reference
|
|
1961
|
+
* The table name as a PowQL type reference, backtick-quoted when it is a
|
|
1945
1962
|
* reserved word (e.g. a table named `order`). Used in every emitted
|
|
1946
1963
|
* statement; plain `this.table` stays in error messages.
|
|
1947
1964
|
*/
|
|
@@ -2024,7 +2041,7 @@ export class PowqlInterface {
|
|
|
2024
2041
|
if (value === undefined)
|
|
2025
2042
|
continue;
|
|
2026
2043
|
if (this.meta.relations[field]) {
|
|
2027
|
-
throw new UnsupportedFeatureError('nested writes', 'PowDB', `relation "${field}"
|
|
2044
|
+
throw new UnsupportedFeatureError('nested writes', 'PowDB', `relation "${field}", nested writes need create()/update(), not updateMany()/upsert()`);
|
|
2028
2045
|
}
|
|
2029
2046
|
const colMeta = this.column(field);
|
|
2030
2047
|
const ref = this.ref(field);
|
|
@@ -2053,11 +2070,11 @@ export class PowqlInterface {
|
|
|
2053
2070
|
return parts.join(', ');
|
|
2054
2071
|
}
|
|
2055
2072
|
// -------------------------------------------------------------------------
|
|
2056
|
-
// Nested writes
|
|
2073
|
+
// Nested writes, create/update whose `data` carries relation ops (create,
|
|
2057
2074
|
// connect, connectOrCreate, disconnect, set, delete, update, upsert). Reuses
|
|
2058
2075
|
// the engine-agnostic nested-write engine (it only needs ctx.schema + the
|
|
2059
2076
|
// table accessors PowqlInterface already provides). Runs as ONE flat top-level
|
|
2060
|
-
// PowDB transaction
|
|
2077
|
+
// PowDB transaction, single global write lock, no savepoints, so the whole
|
|
2061
2078
|
// tree commits or rolls back together (mirrors the SQL path's coverage:
|
|
2062
2079
|
// hasMany / hasOne / belongsTo; manyToMany nested writes are not handled by
|
|
2063
2080
|
// the shared engine on any backend).
|
|
@@ -2100,9 +2117,13 @@ export class PowqlInterface {
|
|
|
2100
2117
|
// Pass the PowDB pool so its read-only guard + capabilities carry into
|
|
2101
2118
|
// the transaction-scoped proxy pool (see createTxPool).
|
|
2102
2119
|
this.pool);
|
|
2103
|
-
const ctx = {
|
|
2120
|
+
const ctx = {
|
|
2121
|
+
schema: this.schema,
|
|
2122
|
+
tx: tx,
|
|
2123
|
+
scopedConnect: this.options?.scopedConnect === true,
|
|
2124
|
+
};
|
|
2104
2125
|
// Plant the single-writer re-entrancy marker for the implicit tx's
|
|
2105
|
-
// subtree (same seam TurbineClient.$transaction uses)
|
|
2126
|
+
// subtree (same seam TurbineClient.$transaction uses), user code that
|
|
2106
2127
|
// fires db.$transaction from inside (e.g. $use middleware around a
|
|
2107
2128
|
// nested-write child op) must fast-fail E017, not queue into deadlock.
|
|
2108
2129
|
const wrap = client
|
|
@@ -2112,7 +2133,7 @@ export class PowqlInterface {
|
|
|
2112
2133
|
return result;
|
|
2113
2134
|
}
|
|
2114
2135
|
catch (err) {
|
|
2115
|
-
// Only roll back a transaction we actually opened
|
|
2136
|
+
// Only roll back a transaction we actually opened, a failed BEGIN
|
|
2116
2137
|
// (queue timeout, re-entrancy E017) must not emit a stray ROLLBACK that
|
|
2117
2138
|
// could land inside another transaction on a shared engine handle.
|
|
2118
2139
|
if (began) {
|
|
@@ -2120,7 +2141,7 @@ export class PowqlInterface {
|
|
|
2120
2141
|
await client.query(d?.rollbackStatement?.() ?? 'rollback');
|
|
2121
2142
|
}
|
|
2122
2143
|
catch {
|
|
2123
|
-
/* best-effort
|
|
2144
|
+
/* best-effort, the connection may be gone */
|
|
2124
2145
|
}
|
|
2125
2146
|
}
|
|
2126
2147
|
throw err;
|
|
@@ -2143,7 +2164,7 @@ export class PowqlInterface {
|
|
|
2143
2164
|
const resolvedWhere = await this.resolveRelationFilters(args.where, args.timeout);
|
|
2144
2165
|
const where = this.buildWhere(resolvedWhere, params);
|
|
2145
2166
|
this.assertCompiledWhere(where, false, 'delete');
|
|
2146
|
-
// `returning` hands back the deleted row(s)
|
|
2167
|
+
// `returning` hands back the deleted row(s), no separate pre-image reselect needed.
|
|
2147
2168
|
const { rows, native } = await this.exec(`${this.qt} filter ${where} delete returning`, params, args.timeout, 'delete');
|
|
2148
2169
|
const row = rows.length ? this.stripWritePii(this.shape(rows, native)[0]) : null;
|
|
2149
2170
|
if (!row)
|
|
@@ -2231,7 +2252,7 @@ export class PowqlInterface {
|
|
|
2231
2252
|
}
|
|
2232
2253
|
async aggregate(args) {
|
|
2233
2254
|
return this.withMiddleware('aggregate', args, async () => {
|
|
2234
|
-
// One scalar query per aggregate
|
|
2255
|
+
// One scalar query per aggregate, PowDB's bare-projection aggregate is broken.
|
|
2235
2256
|
const result = {};
|
|
2236
2257
|
const filterParams = [];
|
|
2237
2258
|
const resolvedWhere = await this.resolveRelationFilters(args.where, args.timeout);
|
|
@@ -2600,12 +2621,12 @@ export class PowqlInterface {
|
|
|
2600
2621
|
// -------------------------------------------------------------------------
|
|
2601
2622
|
// Streaming / unsupported
|
|
2602
2623
|
// -------------------------------------------------------------------------
|
|
2603
|
-
// biome-ignore lint/correctness/useYield: intentionally throws before yielding
|
|
2624
|
+
// biome-ignore lint/correctness/useYield: intentionally throws before yielding, PowDB has no server cursor.
|
|
2604
2625
|
async *findManyStream() {
|
|
2605
2626
|
throw new UnsupportedFeatureError('cursor streaming (findManyStream)', 'PowDB', 'PowDB has no server-side cursor; page with findMany({ limit, offset }) instead');
|
|
2606
2627
|
}
|
|
2607
2628
|
// -------------------------------------------------------------------------
|
|
2608
|
-
// Reselect helper (upsert only
|
|
2629
|
+
// Reselect helper (upsert only, PowDB's upsert has no `returning`)
|
|
2609
2630
|
// -------------------------------------------------------------------------
|
|
2610
2631
|
/** Reselect a single row by its single-column primary key value. */
|
|
2611
2632
|
async reselectByPk(pkValue, timeout) {
|
|
@@ -2618,12 +2639,12 @@ export class PowqlInterface {
|
|
|
2618
2639
|
return rows.length ? this.shape(rows, native)[0] : null;
|
|
2619
2640
|
}
|
|
2620
2641
|
/**
|
|
2621
|
-
* Empty-where guard
|
|
2642
|
+
* Empty-where guard, blocks accidental whole-table writes. Mirrors the SQL
|
|
2622
2643
|
* path's `assertMutationHasPredicate` (query/builder.ts): it gates on the
|
|
2623
2644
|
* *compiled* PowQL filter fragment, NOT the shape of the `where` object. A
|
|
2624
|
-
* `where` whose conditions all evaporate during compilation
|
|
2645
|
+
* `where` whose conditions all evaporate during compilation, `{}`,
|
|
2625
2646
|
* `{ id: undefined }`, `{ OR: [] }`, `{ AND: [] }`, `{ NOT: {} }`,
|
|
2626
|
-
* `{ OR: [{ f: undefined }] }
|
|
2647
|
+
* `{ OR: [{ f: undefined }] }`, compiles to the empty string and is refused,
|
|
2627
2648
|
* because emitting a filter-less write would hit every row.
|
|
2628
2649
|
*/
|
|
2629
2650
|
assertCompiledWhere(compiledWhere, allow, action) {
|
|
@@ -73,7 +73,7 @@ export declare function buildDistinctOnSource<T extends object>(qi: BuilderCtx,
|
|
|
73
73
|
* throws {@link ValidationError} for unknown fields and `qi.q()` quotes via
|
|
74
74
|
* the dialect, so no unvalidated identifier ever reaches the SQL string. Every
|
|
75
75
|
* comparison value is pushed onto the shared `params` array and referenced by
|
|
76
|
-
* a `$N` placeholder via {@link buildHavingNumericClauses}
|
|
76
|
+
* a `$N` placeholder via {@link buildHavingNumericClauses}, there is no string
|
|
77
77
|
* interpolation of user values.
|
|
78
78
|
*
|
|
79
79
|
* `jsonAggExprs` (from {@link buildGroupBy}) maps `alias:aggKey` to the
|
package/dist/query/aggregates.js
CHANGED
|
@@ -64,7 +64,7 @@ export function buildGroupBy(qi, args) {
|
|
|
64
64
|
? buildDistinctOnSource(qi, args.distinctOn, whereSql, params)
|
|
65
65
|
: `${qi.q(qi.table)}${whereSql}`;
|
|
66
66
|
// Group keys: plain columns and/or JSON-path keys. Output-name collisions
|
|
67
|
-
// are rejected up front
|
|
67
|
+
// are rejected up front, and the check runs over the EMITTED SQL output
|
|
68
68
|
// column names (snake_case column / JSON alias / `_agg_key` aggregate
|
|
69
69
|
// alias), not just the given arg keys: the driver keeps only the LAST
|
|
70
70
|
// duplicate field per row object, so a JSON alias equal to another key's
|
|
@@ -211,7 +211,7 @@ export function buildGroupBy(qi, args) {
|
|
|
211
211
|
buildAggregates('_min', 'MIN', args._min);
|
|
212
212
|
buildAggregates('_max', 'MAX', args._max);
|
|
213
213
|
let sql = `SELECT ${selectExprs.join(', ')} FROM ${fromSql} GROUP BY ${groupExprs.join(', ')}`;
|
|
214
|
-
// HAVING
|
|
214
|
+
// HAVING, filter whole groups by their aggregate values.
|
|
215
215
|
// Appends to the same `params` array, so placeholders continue from the
|
|
216
216
|
// WHERE clause's parameter positions (qi.p(params.length) below).
|
|
217
217
|
if (args.having) {
|
|
@@ -467,7 +467,7 @@ export function buildDistinctOnSource(qi, distinctOn, whereSql, params) {
|
|
|
467
467
|
* throws {@link ValidationError} for unknown fields and `qi.q()` quotes via
|
|
468
468
|
* the dialect, so no unvalidated identifier ever reaches the SQL string. Every
|
|
469
469
|
* comparison value is pushed onto the shared `params` array and referenced by
|
|
470
|
-
* a `$N` placeholder via {@link buildHavingNumericClauses}
|
|
470
|
+
* a `$N` placeholder via {@link buildHavingNumericClauses}, there is no string
|
|
471
471
|
* interpolation of user values.
|
|
472
472
|
*
|
|
473
473
|
* `jsonAggExprs` (from {@link buildGroupBy}) maps `alias:aggKey` to the
|
|
@@ -479,7 +479,7 @@ export function buildDistinctOnSource(qi, distinctOn, whereSql, params) {
|
|
|
479
479
|
export function buildHavingClauses(qi, having, params, jsonAggExprs) {
|
|
480
480
|
const clauses = [];
|
|
481
481
|
// Maps the per-field aggregate key to its SQL function name. The set of
|
|
482
|
-
// allowed keys is fixed here
|
|
482
|
+
// allowed keys is fixed here, any other key on a field's filter object is
|
|
483
483
|
// rejected by ValidationError below (never interpolated).
|
|
484
484
|
const aggFnByKey = {
|
|
485
485
|
_sum: 'SUM',
|
|
@@ -502,7 +502,7 @@ export function buildHavingClauses(qi, having, params, jsonAggExprs) {
|
|
|
502
502
|
`expected an aggregate object like { _sum: { gt: 100 } }.`);
|
|
503
503
|
}
|
|
504
504
|
// toColumn validates the field against schema metadata (throws
|
|
505
|
-
// ValidationError on unknown columns) and q() quotes the identifier
|
|
505
|
+
// ValidationError on unknown columns) and q() quotes the identifier, no
|
|
506
506
|
// unvalidated identifier ever reaches the SQL string. Resolution is lazy:
|
|
507
507
|
// a JSON-path aggregate alias is not a column, so it must not hit
|
|
508
508
|
// toColumn when every aggregate under it resolves via `jsonAggExprs`.
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* turbine-orm
|
|
2
|
+
* turbine-orm, Batched relation loader (the `relationLoadStrategy: 'batched'` path)
|
|
3
3
|
*
|
|
4
4
|
* ## Why this exists
|
|
5
5
|
*
|
|
6
6
|
* Turbine's default `with`-clause strategy resolves nested relations in ONE SQL
|
|
7
|
-
* statement using correlated `json_agg(json_build_object(...))` subqueries
|
|
7
|
+
* statement using correlated `json_agg(json_build_object(...))` subqueries, one
|
|
8
8
|
* probe per parent row (see `buildRelationSubquery` in builder.ts). That is the
|
|
9
9
|
* right default: a single round-trip, and when the child FK columns are indexed
|
|
10
10
|
* each probe is an index seek. But it degrades in two situations:
|
|
11
11
|
*
|
|
12
|
-
* 1. **Missing FK index
|
|
12
|
+
* 1. **Missing FK index**, a correlated probe per parent row becomes
|
|
13
13
|
* N-parents × full-table-scan. A batched-loader ORM pays that missing index
|
|
14
14
|
* only ONCE (a single `WHERE fk = ANY($1)` seq-scan), which is why schemas
|
|
15
15
|
* migrated from those ORMs often lack the index the json_agg path needs.
|
|
16
|
-
* 2. **Huge unpaginated result sets
|
|
16
|
+
* 2. **Huge unpaginated result sets**, the JSON wire format
|
|
17
17
|
* (`json_build_object` per row, re-serialized inside `json_agg`) is heavy to
|
|
18
18
|
* encode/decode compared with flat rows.
|
|
19
19
|
*
|
|
@@ -29,19 +29,19 @@
|
|
|
29
29
|
* - **Same executor / connection path.** Every follow-up query runs through the
|
|
30
30
|
* caller's own executor ({@link RelationLoadContext.exec}) and child query
|
|
31
31
|
* interfaces built on the caller's pool. Inside a `$transaction` that pool is
|
|
32
|
-
* the pinned-connection `txPool`, so batched loads join the transaction
|
|
32
|
+
* the pinned-connection `txPool`, so batched loads join the transaction, no
|
|
33
33
|
* separate pool checkout per query.
|
|
34
34
|
* - **Identical output shape.** The stitched result is byte-for-byte the same
|
|
35
35
|
* shape the join strategy produces: relation arrays for hasMany/manyToMany
|
|
36
36
|
* (`[]` when empty), single-or-null for hasOne/belongsTo, with the same
|
|
37
|
-
* camelCase keys and Date coercion
|
|
37
|
+
* camelCase keys and Date coercion, because the child rows are parsed by the
|
|
38
38
|
* very same `parseRow`/`buildFindMany` machinery via a child QueryInterface.
|
|
39
39
|
* - **Stitch keys never leak.** To stitch, the follow-up query must select the
|
|
40
40
|
* FK/PK it joins on even when the caller's `select`/`omit` excluded it; the
|
|
41
41
|
* loader adds those columns for the query and strips them from the returned
|
|
42
42
|
* entities afterwards ({@link includeKeysForBatching}).
|
|
43
43
|
*
|
|
44
|
-
* PowDB (powql.ts) has its own batched loaders for the same reasons
|
|
44
|
+
* PowDB (powql.ts) has its own batched loaders for the same reasons, this is the
|
|
45
45
|
* clean Postgres/SQL implementation, deliberately NOT shared with PowQL.
|
|
46
46
|
*
|
|
47
47
|
* @module
|
|
@@ -132,7 +132,7 @@ export declare function defaultProjectionFields(meta: TableMetadata, includePii:
|
|
|
132
132
|
*
|
|
133
133
|
* Used both for the base query (parent keys) and each follow-up query (child
|
|
134
134
|
* keys) so a caller's `select: { title: true }` on a relation still stitches even
|
|
135
|
-
* though the FK was not requested
|
|
135
|
+
* though the FK was not requested, and the FK never appears in the output.
|
|
136
136
|
*/
|
|
137
137
|
export declare function includeKeysForBatching(select: Record<string, boolean> | undefined, omit: Record<string, boolean> | undefined, fields: string[],
|
|
138
138
|
/**
|
|
@@ -173,13 +173,13 @@ export declare function neededParentKeyFields(parentMeta: TableMetadata, withCla
|
|
|
173
173
|
*/
|
|
174
174
|
export declare function resolveCountRelations(parentMeta: TableMetadata, countSpec: WithCount): RelationDef[];
|
|
175
175
|
/**
|
|
176
|
-
* Reject pick-row relation ordering anywhere inside a `with` tree's orderBy
|
|
176
|
+
* Reject pick-row relation ordering anywhere inside a `with` tree's orderBy -
|
|
177
177
|
* strategy parity with the join path, which throws this exact E003 at SQL
|
|
178
178
|
* build time (`pickOrderNestedError` in builder.ts). Without this guard the
|
|
179
179
|
* loaders would forward `options.orderBy` as the child reader's TOP-LEVEL
|
|
180
|
-
* findMany orderBy, where the pick shape compiles fine
|
|
180
|
+
* findMany orderBy, where the pick shape compiles fine, so the same query
|
|
181
181
|
* would execute on 'batched' but throw on 'join'. Walks the whole tree up
|
|
182
|
-
* front so acceptance never depends on which levels have rows
|
|
182
|
+
* front so acceptance never depends on which levels have rows, the batched
|
|
183
183
|
* runners in builder.ts call this BEFORE the base query (a zero-row base
|
|
184
184
|
* result must still reject, exactly like the join strategy's build-time throw).
|
|
185
185
|
*/
|