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/cjs/powql.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* PowqlInterface
|
|
3
|
+
* PowqlInterface, Turbine's PowQL query generator (the PowDB analogue of
|
|
4
4
|
* {@link QueryInterface}). It exposes the same public method surface as the SQL
|
|
5
|
-
* `QueryInterface` (`findMany`, `create`, `update`, …) but emits **PowQL
|
|
6
|
-
* pipeline language, not SQL
|
|
5
|
+
* `QueryInterface` (`findMany`, `create`, `update`, …) but emits **PowQL**, a
|
|
6
|
+
* pipeline language, not SQL, executed through {@link PowdbPool}.
|
|
7
7
|
*
|
|
8
8
|
* It is a *parallel* implementation rather than a `Dialect` of the SQL builder:
|
|
9
9
|
* PowQL's grammar (`T filter <e> order <k> { .col }`) shares no surface with
|
|
@@ -15,14 +15,14 @@
|
|
|
15
15
|
* server):
|
|
16
16
|
* - `create`/`createMany`/`update`/`delete` use PowDB 0.7.0's trailing
|
|
17
17
|
* `returning` keyword (`RETURNING *`, all columns) to surface affected rows
|
|
18
|
-
* in one round-trip. `upsert` is the lone exception
|
|
18
|
+
* in one round-trip. `upsert` is the lone exception, its statement does not
|
|
19
19
|
* accept `returning`, so it reselects the row by PK (a composite-PK upsert
|
|
20
20
|
* reselects-or-writes inside one flat transaction).
|
|
21
21
|
* - The PK is server-assigned when the column is `isGenerated` (PowDB's `auto`
|
|
22
|
-
* int
|
|
22
|
+
* int, read back via `returning`); otherwise a defaulted **string** PK is
|
|
23
23
|
* generated client-side (UUID).
|
|
24
|
-
* - `with` (nested relations) uses **batched N+1 loaders
|
|
25
|
-
* depth D, not one query
|
|
24
|
+
* - `with` (nested relations) uses **batched N+1 loaders**, D round-trips for
|
|
25
|
+
* depth D, not one query, including manyToMany (junction → targets).
|
|
26
26
|
* - **Relation filters** (`some`/`none`/`every`, all cardinalities incl. m2m)
|
|
27
27
|
* are resolved client-side to a literal `in (…)` list, never an IN-subquery:
|
|
28
28
|
* PowDB's executor caches a subquery's result by plan shape and would return
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
* shared nested-write engine as one flat top-level transaction (PowDB is
|
|
32
32
|
* single-writer, no savepoints).
|
|
33
33
|
* - pgvector / JSON / array filters and cursor streaming throw
|
|
34
|
-
* {@link UnsupportedFeatureError} (E017)
|
|
34
|
+
* {@link UnsupportedFeatureError} (E017), they have no PowDB equivalent.
|
|
35
35
|
*
|
|
36
36
|
* @module
|
|
37
37
|
*/
|
|
@@ -78,6 +78,7 @@ const aggregates_js_1 = require("./query/aggregates.js");
|
|
|
78
78
|
const compound_unique_js_1 = require("./query/compound-unique.js");
|
|
79
79
|
const filters_js_1 = require("./query/filters.js");
|
|
80
80
|
const utils_js_1 = require("./query/utils.js");
|
|
81
|
+
const where_js_1 = require("./query/where.js");
|
|
81
82
|
const schema_js_1 = require("./schema.js");
|
|
82
83
|
/**
|
|
83
84
|
* Max parent keys per relation-loader `in (…)` query. A `with` over a large
|
|
@@ -124,7 +125,7 @@ const POWQL_WRITE_ACTIONS = new Set([
|
|
|
124
125
|
* relation before compiling to it. Keyed on the pool object identity so every
|
|
125
126
|
* table interface over the same connection shares one snapshot; a WeakMap lets a
|
|
126
127
|
* discarded pool's snapshot be collected. A fetch failure caches `[]` (a missing
|
|
127
|
-
* listing means "no verifiable links"
|
|
128
|
+
* listing means "no verifiable links", a silent fallback to loaders, never an
|
|
128
129
|
* error).
|
|
129
130
|
*/
|
|
130
131
|
const LINK_SNAPSHOT_CACHE = new WeakMap();
|
|
@@ -263,8 +264,8 @@ class PowqlInterface {
|
|
|
263
264
|
return `$${params.length}`;
|
|
264
265
|
}
|
|
265
266
|
/**
|
|
266
|
-
* Render a value for a write *assignment* (`col := …`). Every value
|
|
267
|
-
* columns included
|
|
267
|
+
* Render a value for a write *assignment* (`col := …`). Every value, float
|
|
268
|
+
* columns included, is sent as a positional `$N` param. PowDB ≥ 0.7.0 fixed
|
|
268
269
|
* the int→float UPDATE coercion bug (`score := $n` with an integer param now
|
|
269
270
|
* reads back the integer value, not the raw i64 bits), so the float-literal
|
|
270
271
|
* inlining workaround Turbine carried for ≤ 0.6.2 is gone. Marks the column as
|
|
@@ -295,7 +296,7 @@ class PowqlInterface {
|
|
|
295
296
|
get capabilities() {
|
|
296
297
|
return this.pool.capabilities ?? powdb_js_1.ALL_POWDB_CAPABILITIES;
|
|
297
298
|
}
|
|
298
|
-
/** A predicate that is always false
|
|
299
|
+
/** A predicate that is always false, the empty-`in` / contradiction sentinel. */
|
|
299
300
|
alwaysFalse() {
|
|
300
301
|
const pk = this.meta.primaryKey[0] ?? this.meta.columns[0]?.name;
|
|
301
302
|
return `(.${pk} is null and .${pk} is not null)`;
|
|
@@ -338,7 +339,7 @@ class PowqlInterface {
|
|
|
338
339
|
}
|
|
339
340
|
else if (this.meta.relations[key]) {
|
|
340
341
|
// Relation filters are pre-resolved to scalar in/notIn by
|
|
341
|
-
// resolveRelationFilters() before buildWhere runs
|
|
342
|
+
// resolveRelationFilters() before buildWhere runs, reaching here means
|
|
342
343
|
// a caller skipped that step (an internal bug, not user error).
|
|
343
344
|
throw new errors_js_1.ValidationError(`[turbine] internal: relation filter "${key}" reached buildWhere unresolved (missing resolveRelationFilters()).`);
|
|
344
345
|
}
|
|
@@ -372,7 +373,7 @@ class PowqlInterface {
|
|
|
372
373
|
}
|
|
373
374
|
rejectUnsupportedFilter(op, field);
|
|
374
375
|
if (!Object.keys(op).some((k) => OPERATOR_KEYS.has(k))) {
|
|
375
|
-
// A bare object that is not an operator set
|
|
376
|
+
// A bare object that is not an operator set, equality by value.
|
|
376
377
|
return `${ref} = ${this.param(value, params)}`;
|
|
377
378
|
}
|
|
378
379
|
const insensitive = op.mode === 'insensitive';
|
|
@@ -470,6 +471,10 @@ class PowqlInterface {
|
|
|
470
471
|
* by the empty-where guard.
|
|
471
472
|
*/
|
|
472
473
|
buildJsonPathCondition(col, filter, params, alias) {
|
|
474
|
+
// Same strict-key check as the SQL path. Without it an unrecognized
|
|
475
|
+
// operator compiled to zero conditions and the predicate silently
|
|
476
|
+
// disappeared, returning every row (see assertJsonFilterKeys).
|
|
477
|
+
(0, where_js_1.assertJsonFilterKeys)(filter, col.name);
|
|
473
478
|
const conds = [];
|
|
474
479
|
// Bind the path segments at most once and reuse the expression string across
|
|
475
480
|
// equals + range comparisons (they share the same `path`).
|
|
@@ -515,8 +520,20 @@ class PowqlInterface {
|
|
|
515
520
|
}
|
|
516
521
|
conds.push(`${pathP()} ${powOp} ${this.param(v, params)}`);
|
|
517
522
|
}
|
|
518
|
-
|
|
519
|
-
|
|
523
|
+
// Substring comparisons against the text at `path`, in the same fixed
|
|
524
|
+
// order as the SQL path. PowQL has `like`, so these compile natively.
|
|
525
|
+
for (const { pattern, value } of (0, where_js_1.jsonStringEntries)(filter, col.name)) {
|
|
526
|
+
const insensitive = filter.mode === 'insensitive';
|
|
527
|
+
const lhs = insensitive ? `lower(${pathP()})` : pathP();
|
|
528
|
+
conds.push(`${lhs} like ${this.bindLike(pattern((0, utils_js_1.escapeLike)(value)), params, insensitive)}`);
|
|
529
|
+
}
|
|
530
|
+
// Every reachable shape now emits a condition or throws: assertJsonFilterKeys
|
|
531
|
+
// rejects a filter that compares nothing, so an empty list here would be a
|
|
532
|
+
// generator bug rather than a user error, and must not silently widen the
|
|
533
|
+
// query the way it used to.
|
|
534
|
+
if (!conds.length) {
|
|
535
|
+
throw new errors_js_1.ValidationError(`[turbine] internal: JSON filter on "${col.name}" compiled to no condition. This is a bug in turbine-orm.`);
|
|
536
|
+
}
|
|
520
537
|
return conds.length > 1 ? `(${conds.join(' and ')})` : conds[0];
|
|
521
538
|
}
|
|
522
539
|
/** Bind a value, lowercasing for case-insensitive comparisons. */
|
|
@@ -529,7 +546,7 @@ class PowqlInterface {
|
|
|
529
546
|
const ph = this.param(pattern, params);
|
|
530
547
|
return insensitive ? `lower(${ph})` : ph;
|
|
531
548
|
}
|
|
532
|
-
/** `lhs [not] in ($1, $2, …)
|
|
549
|
+
/** `lhs [not] in ($1, $2, …)`, empty list collapses to a constant. */
|
|
533
550
|
buildInList(lhs, values, params, insensitive, negate) {
|
|
534
551
|
if (!Array.isArray(values) || values.length === 0) {
|
|
535
552
|
// `in []` matches nothing; `not in []` matches everything (SQL parity requires a
|
|
@@ -556,7 +573,7 @@ class PowqlInterface {
|
|
|
556
573
|
* so a second subquery of the same shape with a different value returns the
|
|
557
574
|
* first one's stale rows (reproduced live on the embedded engine; the
|
|
558
575
|
* single-statement literal `in (list)` form is always correct). Resolving
|
|
559
|
-
* client-side trades extra round-trips for correctness, and recurses
|
|
576
|
+
* client-side trades extra round-trips for correctness, and recurses, nested
|
|
560
577
|
* relation filters in the inner predicate resolve when the target query runs.
|
|
561
578
|
*/
|
|
562
579
|
async resolveRelationFilters(where, timeout) {
|
|
@@ -598,7 +615,7 @@ class PowqlInterface {
|
|
|
598
615
|
const fk = (0, schema_js_1.normalizeKeyColumns)(rel.foreignKey);
|
|
599
616
|
const rk = (0, schema_js_1.normalizeKeyColumns)(rel.referenceKey);
|
|
600
617
|
if (fk.length > 1 || rk.length > 1) {
|
|
601
|
-
throw new errors_js_1.UnsupportedFeatureError('composite-key relation filters', 'PowDB', `relation "${rel.name}" uses a composite key
|
|
618
|
+
throw new errors_js_1.UnsupportedFeatureError('composite-key relation filters', 'PowDB', `relation "${rel.name}" uses a composite key, PowQL has no tuple-\`in\` to express it`);
|
|
602
619
|
}
|
|
603
620
|
const targetMeta = this.schema.tables[rel.to];
|
|
604
621
|
if (!targetMeta)
|
|
@@ -639,7 +656,7 @@ class PowqlInterface {
|
|
|
639
656
|
if (!targetMeta)
|
|
640
657
|
throw new errors_js_1.ValidationError(`[turbine] Relation "${rel.name}" targets unknown table "${rel.to}".`);
|
|
641
658
|
if (sourceJ.length > 1 || targetJ.length > 1 || sourceRef.length > 1 || targetMeta.primaryKey.length > 1) {
|
|
642
|
-
throw new errors_js_1.UnsupportedFeatureError('composite-key manyToMany filters', 'PowDB', `relation "${rel.name}"
|
|
659
|
+
throw new errors_js_1.UnsupportedFeatureError('composite-key manyToMany filters', 'PowDB', `relation "${rel.name}", PowQL has no tuple-\`in\` for composite junction/target keys`);
|
|
643
660
|
}
|
|
644
661
|
const sourceJCol = sourceJ[0];
|
|
645
662
|
const targetJCol = targetJ[0];
|
|
@@ -657,7 +674,7 @@ class PowqlInterface {
|
|
|
657
674
|
});
|
|
658
675
|
return [...new Set(rows.map((r) => r[targetPkField]).filter((v) => v != null))];
|
|
659
676
|
};
|
|
660
|
-
// Junction source keys linking any of `targetPks` (literal IN-list
|
|
677
|
+
// Junction source keys linking any of `targetPks` (literal IN-list, never a subquery).
|
|
661
678
|
const sourcesForTargets = async (targetPks) => {
|
|
662
679
|
if (!targetPks.length)
|
|
663
680
|
return [];
|
|
@@ -805,7 +822,7 @@ class PowqlInterface {
|
|
|
805
822
|
}
|
|
806
823
|
return `${this.ref(field, alias)} ${spec.sort === 'desc' ? 'desc' : 'asc'}`;
|
|
807
824
|
}
|
|
808
|
-
// Name the actual feature in the refusal
|
|
825
|
+
// Name the actual feature in the refusal, a pick-row ordering
|
|
809
826
|
// reported as "vector / distance ordering" sends users hunting for
|
|
810
827
|
// pgvector docs. Everything else stays E017 on PowDB.
|
|
811
828
|
const feature = (0, filters_js_1.isRelationPickOrderBy)(dir)
|
|
@@ -987,8 +1004,8 @@ class PowqlInterface {
|
|
|
987
1004
|
*
|
|
988
1005
|
* When the engine supports nested projections (>= 0.18) and the strategy
|
|
989
1006
|
* does not opt out, eligible `with` relations compile INTO this statement as
|
|
990
|
-
* nested-projection blocks (`nestedPlans`)
|
|
991
|
-
* shape
|
|
1007
|
+
* nested-projection blocks (`nestedPlans`), one round-trip for the whole
|
|
1008
|
+
* shape, and only the ineligible remainder (`residualWith`) goes to the
|
|
992
1009
|
* post-execution loaders. Without nesting the emitted PowQL is byte-identical
|
|
993
1010
|
* to the pre-0.18 output (no alias, `.col` refs).
|
|
994
1011
|
*/
|
|
@@ -1146,7 +1163,7 @@ class PowqlInterface {
|
|
|
1146
1163
|
return row;
|
|
1147
1164
|
}
|
|
1148
1165
|
// -------------------------------------------------------------------------
|
|
1149
|
-
// Nested relations
|
|
1166
|
+
// Nested relations, batched N+1 loaders (hasMany / hasOne / belongsTo)
|
|
1150
1167
|
// -------------------------------------------------------------------------
|
|
1151
1168
|
/**
|
|
1152
1169
|
* Load each requested relation for `parents` and attach it onto each row.
|
|
@@ -1278,7 +1295,7 @@ class PowqlInterface {
|
|
|
1278
1295
|
}
|
|
1279
1296
|
}
|
|
1280
1297
|
/**
|
|
1281
|
-
* manyToMany nested read
|
|
1298
|
+
* manyToMany nested read, a three-hop batched loader (no `json_agg`/join
|
|
1282
1299
|
* pushdown): (1) read the junction rows for all parents in `sourceKey in (…)`
|
|
1283
1300
|
* chunks, (2) read the target rows for the collected `targetKey`s, (3) stitch
|
|
1284
1301
|
* each parent → its junction rows → its targets in memory. Mirrors the
|
|
@@ -1296,7 +1313,7 @@ class PowqlInterface {
|
|
|
1296
1313
|
if (!targetMeta)
|
|
1297
1314
|
throw new errors_js_1.ValidationError(`[turbine] Relation "${relName}" targets unknown table "${rel.to}".`);
|
|
1298
1315
|
if (sourceJ.length > 1 || targetJ.length > 1 || sourceRef.length > 1 || targetMeta.primaryKey.length > 1) {
|
|
1299
|
-
throw new errors_js_1.UnsupportedFeatureError('composite-key manyToMany', 'PowDB', `relation "${relName}"
|
|
1316
|
+
throw new errors_js_1.UnsupportedFeatureError('composite-key manyToMany', 'PowDB', `relation "${relName}", PowQL has no tuple-\`in\`, so composite junction/target keys can't be loaded`);
|
|
1300
1317
|
}
|
|
1301
1318
|
const sourceJCol = sourceJ[0];
|
|
1302
1319
|
const targetJCol = targetJ[0];
|
|
@@ -1742,7 +1759,7 @@ class PowqlInterface {
|
|
|
1742
1759
|
/**
|
|
1743
1760
|
* Shape the nested JSON children back into typed entities on every parent
|
|
1744
1761
|
* row. The nested field arrives as a decoded JSON array on the native wire
|
|
1745
|
-
* (or JSON text on the legacy wire
|
|
1762
|
+
* (or JSON text on the legacy wire, parsed here); its values are real JSON
|
|
1746
1763
|
* types, so each child object goes through the NATIVE coercion policy
|
|
1747
1764
|
* (`rowToEntity(…, true)`: a date column's micros number becomes a `Date`, a
|
|
1748
1765
|
* json column's document passes through, a str `"null"` stays a string).
|
|
@@ -1839,7 +1856,7 @@ class PowqlInterface {
|
|
|
1839
1856
|
* must stay on the loaders (ALWAYS a silent fallback with identical output).
|
|
1840
1857
|
*
|
|
1841
1858
|
* SCOPED TIGHT: this fires ONLY for a to-one relation whose child projection
|
|
1842
|
-
* includes a bigint/bytes column
|
|
1859
|
+
* includes a bigint/bytes column, exactly the case a JSON nested block cannot
|
|
1843
1860
|
* carry, so nested projections have already fallen back to a per-relation loader
|
|
1844
1861
|
* (`planNestedRelation` returned `null` for the same shape). Cases nested
|
|
1845
1862
|
* projections DO serve keep nested projections: link-bearing statements are
|
|
@@ -1847,9 +1864,9 @@ class PowqlInterface {
|
|
|
1847
1864
|
* link path would regress a hot path for no gain. Requires: single-column
|
|
1848
1865
|
* belongsTo; no relation `with` / `where` / `distinct` / `orderBy` /
|
|
1849
1866
|
* `limit` / `offset` (a scalar path has no per-hop filter/order and cannot
|
|
1850
|
-
* reproduce those
|
|
1867
|
+
* reproduce those, such inputs stay on the loader for exact parity); a link
|
|
1851
1868
|
* name and all projected columns that are bare identifiers (a quoted segment in
|
|
1852
|
-
* a dotted link path is outside the verified spelling
|
|
1869
|
+
* a dotted link path is outside the verified spelling, fall back); and a
|
|
1853
1870
|
* DECLARED link that verifiably matches (`findMatchingLink`).
|
|
1854
1871
|
*/
|
|
1855
1872
|
async planLinkPathRelation(relName, rel, opt, includePii, parentCols, index) {
|
|
@@ -1880,7 +1897,7 @@ class PowqlInterface {
|
|
|
1880
1897
|
});
|
|
1881
1898
|
if (!hasCarrierBlockedCol)
|
|
1882
1899
|
return null;
|
|
1883
|
-
// All cheap checks passed: NOW fetch (and cache) the link snapshot
|
|
1900
|
+
// All cheap checks passed: NOW fetch (and cache) the link snapshot, never
|
|
1884
1901
|
// before, so a query with no link-path candidate issues no `schema links`.
|
|
1885
1902
|
const link = this.findMatchingLink(await this.linksSnapshot(), rel);
|
|
1886
1903
|
if (!link)
|
|
@@ -1911,7 +1928,7 @@ class PowqlInterface {
|
|
|
1911
1928
|
}
|
|
1912
1929
|
/**
|
|
1913
1930
|
* Reconstruct each link-path relation's child entity from its flat hop fields
|
|
1914
|
-
* and attach it under the relation name
|
|
1931
|
+
* and attach it under the relation name, output indistinguishable from the
|
|
1915
1932
|
* loader (same keys, same coercions). Presence: the target PK cell arriving
|
|
1916
1933
|
* Empty (a null/dangling FK at the hop) means no linked row → `null`, matching
|
|
1917
1934
|
* the loader's `matches[0] ?? null`. Otherwise the gathered snake cells go
|
|
@@ -1941,7 +1958,7 @@ class PowqlInterface {
|
|
|
1941
1958
|
}
|
|
1942
1959
|
}
|
|
1943
1960
|
// -------------------------------------------------------------------------
|
|
1944
|
-
// Writes (reselect
|
|
1961
|
+
// Writes (reselect, PowDB has no RETURNING)
|
|
1945
1962
|
// -------------------------------------------------------------------------
|
|
1946
1963
|
/** Split `data` into scalar assignments; reject relation (nested-write) keys. */
|
|
1947
1964
|
scalarData(data) {
|
|
@@ -1950,7 +1967,7 @@ class PowqlInterface {
|
|
|
1950
1967
|
if (value === undefined)
|
|
1951
1968
|
continue;
|
|
1952
1969
|
if (this.meta.relations[field]) {
|
|
1953
|
-
throw new errors_js_1.UnsupportedFeatureError('nested writes', 'PowDB', `relation "${field}"
|
|
1970
|
+
throw new errors_js_1.UnsupportedFeatureError('nested writes', 'PowDB', `relation "${field}", nested writes need create()/update(), not createMany()/upsert()`);
|
|
1954
1971
|
}
|
|
1955
1972
|
out.push({ col: this.column(field), value });
|
|
1956
1973
|
}
|
|
@@ -1959,8 +1976,8 @@ class PowqlInterface {
|
|
|
1959
1976
|
/**
|
|
1960
1977
|
* Fill in a client-generated UUID for a defaulted **string** PK that wasn't
|
|
1961
1978
|
* supplied. A server-generated PK ({@link ColumnMetadata.isGenerated}, e.g. an
|
|
1962
|
-
* `int` column with PowDB's `auto` modifier) is left untouched
|
|
1963
|
-
* it and the trailing `returning` reads it back
|
|
1979
|
+
* `int` column with PowDB's `auto` modifier) is left untouched, PowDB assigns
|
|
1980
|
+
* it and the trailing `returning` reads it back, as is any non-string PK.
|
|
1964
1981
|
*/
|
|
1965
1982
|
applyPkDefault(data) {
|
|
1966
1983
|
const out = { ...data };
|
|
@@ -1977,7 +1994,7 @@ class PowqlInterface {
|
|
|
1977
1994
|
return out;
|
|
1978
1995
|
}
|
|
1979
1996
|
/**
|
|
1980
|
-
* The table name as a PowQL type reference
|
|
1997
|
+
* The table name as a PowQL type reference, backtick-quoted when it is a
|
|
1981
1998
|
* reserved word (e.g. a table named `order`). Used in every emitted
|
|
1982
1999
|
* statement; plain `this.table` stays in error messages.
|
|
1983
2000
|
*/
|
|
@@ -2060,7 +2077,7 @@ class PowqlInterface {
|
|
|
2060
2077
|
if (value === undefined)
|
|
2061
2078
|
continue;
|
|
2062
2079
|
if (this.meta.relations[field]) {
|
|
2063
|
-
throw new errors_js_1.UnsupportedFeatureError('nested writes', 'PowDB', `relation "${field}"
|
|
2080
|
+
throw new errors_js_1.UnsupportedFeatureError('nested writes', 'PowDB', `relation "${field}", nested writes need create()/update(), not updateMany()/upsert()`);
|
|
2064
2081
|
}
|
|
2065
2082
|
const colMeta = this.column(field);
|
|
2066
2083
|
const ref = this.ref(field);
|
|
@@ -2089,11 +2106,11 @@ class PowqlInterface {
|
|
|
2089
2106
|
return parts.join(', ');
|
|
2090
2107
|
}
|
|
2091
2108
|
// -------------------------------------------------------------------------
|
|
2092
|
-
// Nested writes
|
|
2109
|
+
// Nested writes, create/update whose `data` carries relation ops (create,
|
|
2093
2110
|
// connect, connectOrCreate, disconnect, set, delete, update, upsert). Reuses
|
|
2094
2111
|
// the engine-agnostic nested-write engine (it only needs ctx.schema + the
|
|
2095
2112
|
// table accessors PowqlInterface already provides). Runs as ONE flat top-level
|
|
2096
|
-
// PowDB transaction
|
|
2113
|
+
// PowDB transaction, single global write lock, no savepoints, so the whole
|
|
2097
2114
|
// tree commits or rolls back together (mirrors the SQL path's coverage:
|
|
2098
2115
|
// hasMany / hasOne / belongsTo; manyToMany nested writes are not handled by
|
|
2099
2116
|
// the shared engine on any backend).
|
|
@@ -2136,9 +2153,13 @@ class PowqlInterface {
|
|
|
2136
2153
|
// Pass the PowDB pool so its read-only guard + capabilities carry into
|
|
2137
2154
|
// the transaction-scoped proxy pool (see createTxPool).
|
|
2138
2155
|
this.pool);
|
|
2139
|
-
const ctx = {
|
|
2156
|
+
const ctx = {
|
|
2157
|
+
schema: this.schema,
|
|
2158
|
+
tx: tx,
|
|
2159
|
+
scopedConnect: this.options?.scopedConnect === true,
|
|
2160
|
+
};
|
|
2140
2161
|
// Plant the single-writer re-entrancy marker for the implicit tx's
|
|
2141
|
-
// subtree (same seam TurbineClient.$transaction uses)
|
|
2162
|
+
// subtree (same seam TurbineClient.$transaction uses), user code that
|
|
2142
2163
|
// fires db.$transaction from inside (e.g. $use middleware around a
|
|
2143
2164
|
// nested-write child op) must fast-fail E017, not queue into deadlock.
|
|
2144
2165
|
const wrap = client
|
|
@@ -2148,7 +2169,7 @@ class PowqlInterface {
|
|
|
2148
2169
|
return result;
|
|
2149
2170
|
}
|
|
2150
2171
|
catch (err) {
|
|
2151
|
-
// Only roll back a transaction we actually opened
|
|
2172
|
+
// Only roll back a transaction we actually opened, a failed BEGIN
|
|
2152
2173
|
// (queue timeout, re-entrancy E017) must not emit a stray ROLLBACK that
|
|
2153
2174
|
// could land inside another transaction on a shared engine handle.
|
|
2154
2175
|
if (began) {
|
|
@@ -2156,7 +2177,7 @@ class PowqlInterface {
|
|
|
2156
2177
|
await client.query(d?.rollbackStatement?.() ?? 'rollback');
|
|
2157
2178
|
}
|
|
2158
2179
|
catch {
|
|
2159
|
-
/* best-effort
|
|
2180
|
+
/* best-effort, the connection may be gone */
|
|
2160
2181
|
}
|
|
2161
2182
|
}
|
|
2162
2183
|
throw err;
|
|
@@ -2179,7 +2200,7 @@ class PowqlInterface {
|
|
|
2179
2200
|
const resolvedWhere = await this.resolveRelationFilters(args.where, args.timeout);
|
|
2180
2201
|
const where = this.buildWhere(resolvedWhere, params);
|
|
2181
2202
|
this.assertCompiledWhere(where, false, 'delete');
|
|
2182
|
-
// `returning` hands back the deleted row(s)
|
|
2203
|
+
// `returning` hands back the deleted row(s), no separate pre-image reselect needed.
|
|
2183
2204
|
const { rows, native } = await this.exec(`${this.qt} filter ${where} delete returning`, params, args.timeout, 'delete');
|
|
2184
2205
|
const row = rows.length ? this.stripWritePii(this.shape(rows, native)[0]) : null;
|
|
2185
2206
|
if (!row)
|
|
@@ -2267,7 +2288,7 @@ class PowqlInterface {
|
|
|
2267
2288
|
}
|
|
2268
2289
|
async aggregate(args) {
|
|
2269
2290
|
return this.withMiddleware('aggregate', args, async () => {
|
|
2270
|
-
// One scalar query per aggregate
|
|
2291
|
+
// One scalar query per aggregate, PowDB's bare-projection aggregate is broken.
|
|
2271
2292
|
const result = {};
|
|
2272
2293
|
const filterParams = [];
|
|
2273
2294
|
const resolvedWhere = await this.resolveRelationFilters(args.where, args.timeout);
|
|
@@ -2636,12 +2657,12 @@ class PowqlInterface {
|
|
|
2636
2657
|
// -------------------------------------------------------------------------
|
|
2637
2658
|
// Streaming / unsupported
|
|
2638
2659
|
// -------------------------------------------------------------------------
|
|
2639
|
-
// biome-ignore lint/correctness/useYield: intentionally throws before yielding
|
|
2660
|
+
// biome-ignore lint/correctness/useYield: intentionally throws before yielding, PowDB has no server cursor.
|
|
2640
2661
|
async *findManyStream() {
|
|
2641
2662
|
throw new errors_js_1.UnsupportedFeatureError('cursor streaming (findManyStream)', 'PowDB', 'PowDB has no server-side cursor; page with findMany({ limit, offset }) instead');
|
|
2642
2663
|
}
|
|
2643
2664
|
// -------------------------------------------------------------------------
|
|
2644
|
-
// Reselect helper (upsert only
|
|
2665
|
+
// Reselect helper (upsert only, PowDB's upsert has no `returning`)
|
|
2645
2666
|
// -------------------------------------------------------------------------
|
|
2646
2667
|
/** Reselect a single row by its single-column primary key value. */
|
|
2647
2668
|
async reselectByPk(pkValue, timeout) {
|
|
@@ -2654,12 +2675,12 @@ class PowqlInterface {
|
|
|
2654
2675
|
return rows.length ? this.shape(rows, native)[0] : null;
|
|
2655
2676
|
}
|
|
2656
2677
|
/**
|
|
2657
|
-
* Empty-where guard
|
|
2678
|
+
* Empty-where guard, blocks accidental whole-table writes. Mirrors the SQL
|
|
2658
2679
|
* path's `assertMutationHasPredicate` (query/builder.ts): it gates on the
|
|
2659
2680
|
* *compiled* PowQL filter fragment, NOT the shape of the `where` object. A
|
|
2660
|
-
* `where` whose conditions all evaporate during compilation
|
|
2681
|
+
* `where` whose conditions all evaporate during compilation, `{}`,
|
|
2661
2682
|
* `{ id: undefined }`, `{ OR: [] }`, `{ AND: [] }`, `{ NOT: {} }`,
|
|
2662
|
-
* `{ OR: [{ f: undefined }] }
|
|
2683
|
+
* `{ OR: [{ f: undefined }] }`, compiles to the empty string and is refused,
|
|
2663
2684
|
* because emitting a filter-less write would hit every row.
|
|
2664
2685
|
*/
|
|
2665
2686
|
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
|
|
@@ -107,7 +107,7 @@ function buildGroupBy(qi, args) {
|
|
|
107
107
|
? buildDistinctOnSource(qi, args.distinctOn, whereSql, params)
|
|
108
108
|
: `${qi.q(qi.table)}${whereSql}`;
|
|
109
109
|
// Group keys: plain columns and/or JSON-path keys. Output-name collisions
|
|
110
|
-
// are rejected up front
|
|
110
|
+
// are rejected up front, and the check runs over the EMITTED SQL output
|
|
111
111
|
// column names (snake_case column / JSON alias / `_agg_key` aggregate
|
|
112
112
|
// alias), not just the given arg keys: the driver keeps only the LAST
|
|
113
113
|
// duplicate field per row object, so a JSON alias equal to another key's
|
|
@@ -254,7 +254,7 @@ function buildGroupBy(qi, args) {
|
|
|
254
254
|
buildAggregates('_min', 'MIN', args._min);
|
|
255
255
|
buildAggregates('_max', 'MAX', args._max);
|
|
256
256
|
let sql = `SELECT ${selectExprs.join(', ')} FROM ${fromSql} GROUP BY ${groupExprs.join(', ')}`;
|
|
257
|
-
// HAVING
|
|
257
|
+
// HAVING, filter whole groups by their aggregate values.
|
|
258
258
|
// Appends to the same `params` array, so placeholders continue from the
|
|
259
259
|
// WHERE clause's parameter positions (qi.p(params.length) below).
|
|
260
260
|
if (args.having) {
|
|
@@ -510,7 +510,7 @@ function buildDistinctOnSource(qi, distinctOn, whereSql, params) {
|
|
|
510
510
|
* throws {@link ValidationError} for unknown fields and `qi.q()` quotes via
|
|
511
511
|
* the dialect, so no unvalidated identifier ever reaches the SQL string. Every
|
|
512
512
|
* comparison value is pushed onto the shared `params` array and referenced by
|
|
513
|
-
* a `$N` placeholder via {@link buildHavingNumericClauses}
|
|
513
|
+
* a `$N` placeholder via {@link buildHavingNumericClauses}, there is no string
|
|
514
514
|
* interpolation of user values.
|
|
515
515
|
*
|
|
516
516
|
* `jsonAggExprs` (from {@link buildGroupBy}) maps `alias:aggKey` to the
|
|
@@ -522,7 +522,7 @@ function buildDistinctOnSource(qi, distinctOn, whereSql, params) {
|
|
|
522
522
|
function buildHavingClauses(qi, having, params, jsonAggExprs) {
|
|
523
523
|
const clauses = [];
|
|
524
524
|
// Maps the per-field aggregate key to its SQL function name. The set of
|
|
525
|
-
// allowed keys is fixed here
|
|
525
|
+
// allowed keys is fixed here, any other key on a field's filter object is
|
|
526
526
|
// rejected by ValidationError below (never interpolated).
|
|
527
527
|
const aggFnByKey = {
|
|
528
528
|
_sum: 'SUM',
|
|
@@ -545,7 +545,7 @@ function buildHavingClauses(qi, having, params, jsonAggExprs) {
|
|
|
545
545
|
`expected an aggregate object like { _sum: { gt: 100 } }.`);
|
|
546
546
|
}
|
|
547
547
|
// toColumn validates the field against schema metadata (throws
|
|
548
|
-
// ValidationError on unknown columns) and q() quotes the identifier
|
|
548
|
+
// ValidationError on unknown columns) and q() quotes the identifier, no
|
|
549
549
|
// unvalidated identifier ever reaches the SQL string. Resolution is lazy:
|
|
550
550
|
// a JSON-path aggregate alias is not a column, so it must not hit
|
|
551
551
|
// 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
|
*/
|