turbine-orm 0.65.0 → 0.66.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 +34 -32
- package/dist/adapters/cockroachdb.js +21 -3
- package/dist/adapters/index.d.ts +15 -0
- package/dist/adapters/yugabytedb.js +20 -3
- package/dist/cjs/adapters/cockroachdb.js +21 -3
- package/dist/cjs/adapters/index.d.ts +15 -0
- package/dist/cjs/adapters/yugabytedb.js +20 -3
- package/dist/cjs/cli/destructive.d.ts +18 -4
- package/dist/cjs/cli/destructive.js +230 -122
- package/dist/cjs/cli/index.d.ts +21 -4
- package/dist/cjs/cli/index.js +119 -22
- package/dist/cjs/cli/mcp.d.ts +28 -8
- package/dist/cjs/cli/mcp.js +170 -127
- package/dist/cjs/cli/migrate.d.ts +134 -13
- package/dist/cjs/cli/migrate.js +349 -241
- package/dist/cjs/cli/pii-predicate-guard.d.ts +112 -0
- package/dist/cjs/cli/pii-predicate-guard.js +390 -0
- package/dist/cjs/cli/prisma-resolve.js +75 -4
- package/dist/cjs/cli/prisma-schema.d.ts +17 -1
- package/dist/cjs/cli/prisma-schema.js +83 -17
- package/dist/cjs/cli/sql-statements.d.ts +125 -0
- package/dist/cjs/cli/sql-statements.js +378 -0
- package/dist/cjs/cli/studio.js +49 -118
- package/dist/cjs/cli/ui.d.ts +1 -1
- package/dist/cjs/client.d.ts +43 -0
- package/dist/cjs/client.js +125 -6
- package/dist/cjs/dialect.d.ts +123 -0
- package/dist/cjs/dialect.js +33 -0
- package/dist/cjs/errors.d.ts +74 -1
- package/dist/cjs/errors.js +239 -25
- package/dist/cjs/index-advisor.d.ts +33 -1
- package/dist/cjs/index-advisor.js +32 -1
- package/dist/cjs/introspect.d.ts +48 -0
- package/dist/cjs/introspect.js +222 -91
- package/dist/cjs/mssql.js +43 -1
- package/dist/cjs/mysql.d.ts +5 -2
- package/dist/cjs/mysql.js +202 -17
- package/dist/cjs/nested-write.js +6 -1
- package/dist/cjs/pipeline-submittable.js +17 -3
- package/dist/cjs/pipeline.js +75 -9
- package/dist/cjs/powdb.d.ts +23 -0
- package/dist/cjs/powdb.js +33 -1
- package/dist/cjs/powql.d.ts +61 -9
- package/dist/cjs/powql.js +186 -49
- package/dist/cjs/prisma-compat.js +160 -41
- package/dist/cjs/query/aggregates.d.ts +1 -1
- package/dist/cjs/query/aggregates.js +80 -18
- package/dist/cjs/query/batched-loader.d.ts +10 -0
- package/dist/cjs/query/batched-loader.js +268 -7
- package/dist/cjs/query/builder.d.ts +73 -0
- package/dist/cjs/query/builder.js +225 -28
- package/dist/cjs/query/filters.d.ts +162 -0
- package/dist/cjs/query/filters.js +250 -1
- package/dist/cjs/query/relations.d.ts +10 -10
- package/dist/cjs/query/relations.js +93 -12
- package/dist/cjs/query/types.d.ts +14 -1
- package/dist/cjs/query/utils.d.ts +146 -2
- package/dist/cjs/query/utils.js +210 -4
- package/dist/cjs/query/warn-registry.d.ts +10 -0
- package/dist/cjs/query/warn-registry.js +10 -0
- package/dist/cjs/query/where-compile.d.ts +30 -0
- package/dist/cjs/query/where-compile.js +41 -0
- package/dist/cjs/query/where.d.ts +128 -13
- package/dist/cjs/query/where.js +215 -77
- package/dist/cjs/query/writes.d.ts +1 -1
- package/dist/cjs/query/writes.js +39 -15
- package/dist/cjs/schema-builder.d.ts +2 -1
- package/dist/cjs/schema-sql.d.ts +94 -4
- package/dist/cjs/schema-sql.js +506 -30
- package/dist/cjs/schema.d.ts +3 -1
- package/dist/cjs/sqlite.d.ts +6 -0
- package/dist/cjs/sqlite.js +151 -10
- package/dist/cjs/typed-sql.d.ts +29 -1
- package/dist/cjs/typed-sql.js +30 -12
- package/dist/cli/destructive.d.ts +18 -4
- package/dist/cli/destructive.js +229 -121
- package/dist/cli/index.d.ts +21 -4
- package/dist/cli/index.js +120 -24
- package/dist/cli/mcp.d.ts +28 -8
- package/dist/cli/mcp.js +172 -129
- package/dist/cli/migrate.d.ts +134 -13
- package/dist/cli/migrate.js +347 -238
- package/dist/cli/pii-predicate-guard.d.ts +112 -0
- package/dist/cli/pii-predicate-guard.js +386 -0
- package/dist/cli/prisma-resolve.js +75 -4
- package/dist/cli/prisma-schema.d.ts +17 -1
- package/dist/cli/prisma-schema.js +83 -17
- package/dist/cli/sql-statements.d.ts +125 -0
- package/dist/cli/sql-statements.js +373 -0
- package/dist/cli/studio.js +49 -118
- package/dist/cli/ui.d.ts +1 -1
- package/dist/client.d.ts +43 -0
- package/dist/client.js +126 -7
- package/dist/dialect.d.ts +123 -0
- package/dist/dialect.js +33 -0
- package/dist/errors.d.ts +74 -1
- package/dist/errors.js +228 -19
- package/dist/index-advisor.d.ts +33 -1
- package/dist/index-advisor.js +31 -1
- package/dist/introspect.d.ts +48 -0
- package/dist/introspect.js +221 -91
- package/dist/mssql.js +44 -2
- package/dist/mysql.d.ts +5 -2
- package/dist/mysql.js +203 -18
- package/dist/nested-write.js +7 -2
- package/dist/pipeline-submittable.js +18 -4
- package/dist/pipeline.js +76 -10
- package/dist/powdb.d.ts +23 -0
- package/dist/powdb.js +33 -2
- package/dist/powql.d.ts +61 -9
- package/dist/powql.js +187 -50
- package/dist/prisma-compat.js +160 -41
- package/dist/query/aggregates.d.ts +1 -1
- package/dist/query/aggregates.js +82 -20
- package/dist/query/batched-loader.d.ts +10 -0
- package/dist/query/batched-loader.js +270 -9
- package/dist/query/builder.d.ts +73 -0
- package/dist/query/builder.js +226 -30
- package/dist/query/filters.d.ts +162 -0
- package/dist/query/filters.js +246 -1
- package/dist/query/relations.d.ts +10 -10
- package/dist/query/relations.js +94 -14
- package/dist/query/types.d.ts +14 -1
- package/dist/query/utils.d.ts +146 -2
- package/dist/query/utils.js +204 -3
- package/dist/query/warn-registry.d.ts +10 -0
- package/dist/query/warn-registry.js +10 -0
- package/dist/query/where-compile.d.ts +30 -0
- package/dist/query/where-compile.js +40 -1
- package/dist/query/where.d.ts +128 -13
- package/dist/query/where.js +216 -80
- package/dist/query/writes.d.ts +1 -1
- package/dist/query/writes.js +40 -16
- package/dist/schema-builder.d.ts +2 -1
- package/dist/schema-sql.d.ts +94 -4
- package/dist/schema-sql.js +505 -30
- package/dist/schema.d.ts +3 -1
- package/dist/sqlite.d.ts +6 -0
- package/dist/sqlite.js +151 -10
- package/dist/typed-sql.d.ts +29 -1
- package/dist/typed-sql.js +30 -12
- package/package.json +6 -4
|
@@ -162,6 +162,7 @@ const prisma_schema_fingerprint_js_1 = require("./prisma-schema-fingerprint.js")
|
|
|
162
162
|
const index_js_1 = require("./query/index.js");
|
|
163
163
|
const utils_js_1 = require("./query/utils.js");
|
|
164
164
|
const warn_registry_js_1 = require("./query/warn-registry.js");
|
|
165
|
+
const where_compile_js_1 = require("./query/where-compile.js");
|
|
165
166
|
// ---------------------------------------------------------------------------
|
|
166
167
|
// Prisma.sql-style raw fragments (local, minimal, never imports @prisma/client)
|
|
167
168
|
// ---------------------------------------------------------------------------
|
|
@@ -307,7 +308,63 @@ function relTargetModel(ctx, mm, turbineRel) {
|
|
|
307
308
|
// Argument translation
|
|
308
309
|
// ---------------------------------------------------------------------------
|
|
309
310
|
const COMBINATORS = new Set(['AND', 'OR', 'NOT']);
|
|
311
|
+
/**
|
|
312
|
+
* The to-many half of the relation-filter wrapper list.
|
|
313
|
+
*
|
|
314
|
+
* DRIFT RISK, and this is the third copy of that list in the tree. The canonical
|
|
315
|
+
* NAMED one is `RELATION_FILTER_WRAPPERS` in `cli/pii-predicate-guard.ts`
|
|
316
|
+
* (`some`/`none`/`every`/`is`/`isNot`), and a second is inlined in
|
|
317
|
+
* `query/where-compile.ts` as `'some' in x || 'every' in x || …`. This copy is
|
|
318
|
+
* the worst of the three because it is SPLIT: the to-many quantifiers are here,
|
|
319
|
+
* and `is` / `isNot` are two inline `k === …` tests in
|
|
320
|
+
* {@link translateRelationFilter}, so half of it does not read as a list and a
|
|
321
|
+
* grep for the list will not find it. A wrapper the compiler learns and this set
|
|
322
|
+
* does not is a relation body that reaches core untranslated, i.e. with Prisma
|
|
323
|
+
* field names still in it.
|
|
324
|
+
*
|
|
325
|
+
* The fix is an export from `query/filters.ts` that all three import; until that
|
|
326
|
+
* exists, these comments are what hold them in step.
|
|
327
|
+
*/
|
|
310
328
|
const RELATION_QUANTIFIERS = new Set(['some', 'every', 'none']);
|
|
329
|
+
/**
|
|
330
|
+
* Refuse a translation walk that has nested past {@link MAX_WHERE_DEPTH}.
|
|
331
|
+
*
|
|
332
|
+
* Core caps every WHERE / HAVING walk (`assertWhereDepth`), but this layer runs
|
|
333
|
+
* BEFORE core sees anything: `translateWhere` rebuilds the whole clause into
|
|
334
|
+
* turbine field names first, so on a deeply nested predicate the stack overflows
|
|
335
|
+
* HERE and core's guard is unreachable for every compat consumer. Measured on
|
|
336
|
+
* Node 24 against a wire-realistic body: a 1,000-deep `NOT` chain translated
|
|
337
|
+
* fine and a 4,000-deep one (a 32 KB body, comfortably under `express.json()`'s
|
|
338
|
+
* 100 KB default) threw `RangeError: Maximum call stack size exceeded`, which is
|
|
339
|
+
* not a {@link TurbineError} and so walks straight past the typed-error surface
|
|
340
|
+
* callers catch on.
|
|
341
|
+
*
|
|
342
|
+
* FIVE families needed the cap, not one. `where` and `having` have a core
|
|
343
|
+
* counterpart and DELEGATE to it, so a compat caller reads the byte-identical
|
|
344
|
+
* message a core caller reads at the byte-identical nesting. The other three
|
|
345
|
+
* have no core counterpart at all and are the sharper half:
|
|
346
|
+
*
|
|
347
|
+
* - `orderBy` recurses on nested ARRAYS, and core ignores those entirely
|
|
348
|
+
* (`normalizeOrderBy` reads one level), so there is no second line of
|
|
349
|
+
* defense: `{"orderBy":[[[…]]]}` at 2,000 (a 4 KB body) was the cheapest
|
|
350
|
+
* `RangeError` on the whole surface.
|
|
351
|
+
* - `include` / `select` recurse through `translateWithOption`
|
|
352
|
+
* (45 KB), and
|
|
353
|
+
* - nested-write `data` recurses through `translateNestedWrite` (43 KB).
|
|
354
|
+
*
|
|
355
|
+
* The depth is core's, not a second convention: the same {@link MAX_WHERE_DEPTH}
|
|
356
|
+
* for all five, so nothing core would accept is refused here for depth alone.
|
|
357
|
+
*/
|
|
358
|
+
function assertTranslateDepth(depth, clause) {
|
|
359
|
+
if (depth <= where_compile_js_1.MAX_WHERE_DEPTH)
|
|
360
|
+
return;
|
|
361
|
+
// The two clauses core also walks raise core's own error, verbatim.
|
|
362
|
+
if (clause === 'where' || clause === 'having')
|
|
363
|
+
(0, where_compile_js_1.assertWhereDepth)(depth, clause);
|
|
364
|
+
throw new errors_js_1.ValidationError(`[turbine] \`${clause}\` nests more than ${where_compile_js_1.MAX_WHERE_DEPTH} levels deep. That is far past anything a real ` +
|
|
365
|
+
`query needs, and an unbounded walk over caller-supplied nesting is a stack-overflow surface, so it is ` +
|
|
366
|
+
`refused. If this is a generated argument, flatten it: a single array of N entries is one level, not N.`);
|
|
367
|
+
}
|
|
311
368
|
/** Whether a value is a plain object usable as a compound-unique selector. */
|
|
312
369
|
function isPlainObject(v) {
|
|
313
370
|
return (typeof v === 'object' &&
|
|
@@ -568,14 +625,25 @@ function mapRelationLoadStrategy(value) {
|
|
|
568
625
|
* `none`) against the target model, and rewrites compound-unique selectors -
|
|
569
626
|
* including custom `@@unique(name:)` names, into the core-derived selector form
|
|
570
627
|
* so Turbine's `findUnique`-family expansion handles them uniformly.
|
|
628
|
+
*
|
|
629
|
+
* `depth` is incremented at exactly the two places core's `walkWhere` increments
|
|
630
|
+
* it, a combinator branch and a relation-filter descent (the wrapper body counts
|
|
631
|
+
* as its own level there, so it does here too). That is what makes the refusal
|
|
632
|
+
* land on the same clause at the same nesting whether a caller arrives through
|
|
633
|
+
* this layer or straight through core.
|
|
571
634
|
*/
|
|
572
|
-
function translateWhere(ctx, mm, where) {
|
|
635
|
+
function translateWhere(ctx, mm, where, depth = 0) {
|
|
636
|
+
assertTranslateDepth(depth, 'where');
|
|
573
637
|
if (!isPlainObject(where))
|
|
574
638
|
return where;
|
|
575
639
|
const out = {};
|
|
576
640
|
for (const [key, val] of Object.entries(where)) {
|
|
577
641
|
if (COMBINATORS.has(key)) {
|
|
578
|
-
|
|
642
|
+
// An `AND` / `OR` array of N conditions is ONE level, not N: the elements
|
|
643
|
+
// are siblings, so they all translate at the same incremented depth.
|
|
644
|
+
out[key] = Array.isArray(val)
|
|
645
|
+
? val.map((v) => translateWhere(ctx, mm, v, depth + 1))
|
|
646
|
+
: translateWhere(ctx, mm, val, depth + 1);
|
|
579
647
|
continue;
|
|
580
648
|
}
|
|
581
649
|
// Compound-unique selector (custom or default Prisma name).
|
|
@@ -593,7 +661,7 @@ function translateWhere(ctx, mm, where) {
|
|
|
593
661
|
const rel = mm.relations[key];
|
|
594
662
|
if (rel) {
|
|
595
663
|
const target = relTargetModel(ctx, mm, rel.name);
|
|
596
|
-
out[rel.name] = translateRelationFilter(ctx, target, val);
|
|
664
|
+
out[rel.name] = translateRelationFilter(ctx, target, val, depth + 1);
|
|
597
665
|
continue;
|
|
598
666
|
}
|
|
599
667
|
// Scalar field, key renamed, value (literal or operator object) passes
|
|
@@ -602,7 +670,7 @@ function translateWhere(ctx, mm, where) {
|
|
|
602
670
|
}
|
|
603
671
|
return out;
|
|
604
672
|
}
|
|
605
|
-
function translateRelationFilter(ctx, target, val) {
|
|
673
|
+
function translateRelationFilter(ctx, target, val, depth) {
|
|
606
674
|
if (!isPlainObject(val))
|
|
607
675
|
return val;
|
|
608
676
|
const keys = Object.keys(val);
|
|
@@ -611,17 +679,32 @@ function translateRelationFilter(ctx, target, val) {
|
|
|
611
679
|
const out = {};
|
|
612
680
|
for (const [k, v] of Object.entries(val)) {
|
|
613
681
|
out[k] =
|
|
614
|
-
target && (RELATION_QUANTIFIERS.has(k) || k === 'is' || k === 'isNot')
|
|
682
|
+
target && (RELATION_QUANTIFIERS.has(k) || k === 'is' || k === 'isNot')
|
|
683
|
+
? translateWhere(ctx, target, v, depth + 1)
|
|
684
|
+
: v;
|
|
615
685
|
}
|
|
616
686
|
return out;
|
|
617
687
|
}
|
|
618
|
-
// A bare object filter on a to-one relation: translate its body.
|
|
619
|
-
|
|
688
|
+
// A bare object filter on a to-one relation: translate its body. Core wraps
|
|
689
|
+
// this shape in `{ is: … }` (`normalizeRelationFilter`) and charges it the
|
|
690
|
+
// same level the explicit wrapper above costs, so charge it here too.
|
|
691
|
+
return target ? translateWhere(ctx, target, val, depth + 1) : val;
|
|
620
692
|
}
|
|
621
|
-
/**
|
|
622
|
-
|
|
693
|
+
/**
|
|
694
|
+
* Translate a Prisma `orderBy` (object / array) into a Turbine `orderBy`.
|
|
695
|
+
*
|
|
696
|
+
* The ARRAY branch is the reason this walker has its own cap rather than relying
|
|
697
|
+
* on core's: `normalizeOrderBy` reads ONE level of array and never recurses, so
|
|
698
|
+
* core has no second line of defense here at all, and `[[[…]]]` at 2,000 (a 4 KB
|
|
699
|
+
* body) overflowed the stack. Nesting is what counts, not width: the elements of
|
|
700
|
+
* one array are siblings and all translate at the same incremented depth, so a
|
|
701
|
+
* legal `orderBy: [{ a: 'asc' }, { b: 'desc' }]` of any length is one level and
|
|
702
|
+
* only an array INSIDE an array pays for another.
|
|
703
|
+
*/
|
|
704
|
+
function translateOrderBy(ctx, mm, ob, depth = 0) {
|
|
705
|
+
assertTranslateDepth(depth, 'orderBy');
|
|
623
706
|
if (Array.isArray(ob))
|
|
624
|
-
return ob.map((o) => translateOrderBy(ctx, mm, o));
|
|
707
|
+
return ob.map((o) => translateOrderBy(ctx, mm, o, depth + 1));
|
|
625
708
|
if (!isPlainObject(ob))
|
|
626
709
|
return ob;
|
|
627
710
|
const out = {};
|
|
@@ -633,7 +716,8 @@ function translateOrderBy(ctx, mm, ob) {
|
|
|
633
716
|
const rel = mm.relations[key];
|
|
634
717
|
if (rel) {
|
|
635
718
|
const target = relTargetModel(ctx, mm, rel.name);
|
|
636
|
-
out[rel.name] =
|
|
719
|
+
out[rel.name] =
|
|
720
|
+
isPlainObject(val) && !('_count' in val) && target ? translateOrderBy(ctx, target, val, depth + 1) : val;
|
|
637
721
|
continue;
|
|
638
722
|
}
|
|
639
723
|
out[renameField(mm, key)] = val;
|
|
@@ -682,8 +766,14 @@ function translateOmit(ctx, mm, omit) {
|
|
|
682
766
|
* `select` narrows scalars and may also pull relations + `_count`. `include`
|
|
683
767
|
* and `select` are mutually exclusive, and so are `select` and `omit` (a
|
|
684
768
|
* narrowed projection minus fields is ambiguous; Prisma refuses the pair too).
|
|
769
|
+
*
|
|
770
|
+
* Mutually recursive with {@link translateWithOption}, one level per relation
|
|
771
|
+
* hop, and it was unbounded: a 1,000-hop `include` chain (a 45 KB body) threw
|
|
772
|
+
* `RangeError` here, before core's own depth-10 relation cap
|
|
773
|
+
* (`CircularRelationError`) could ever see the `with` clause it was building.
|
|
685
774
|
*/
|
|
686
|
-
function translateProjection(ctx, mm, args) {
|
|
775
|
+
function translateProjection(ctx, mm, args, depth = 0) {
|
|
776
|
+
assertTranslateDepth(depth, 'include');
|
|
687
777
|
const include = args.include;
|
|
688
778
|
const select = args.select;
|
|
689
779
|
const omitArg = args.omit;
|
|
@@ -710,7 +800,7 @@ function translateProjection(ctx, mm, args) {
|
|
|
710
800
|
if (!rel) {
|
|
711
801
|
throw new errors_js_1.ValidationError(`[turbine] prisma-compat: unknown relation "${key}" in include on model "${modelName(ctx, mm)}".`);
|
|
712
802
|
}
|
|
713
|
-
withClause[rel.name] = translateWithOption(ctx, mm, rel.name, val);
|
|
803
|
+
withClause[rel.name] = translateWithOption(ctx, mm, rel.name, val, depth + 1);
|
|
714
804
|
hasWith = true;
|
|
715
805
|
}
|
|
716
806
|
return { omit, with: hasWith ? withClause : undefined };
|
|
@@ -728,7 +818,7 @@ function translateProjection(ctx, mm, args) {
|
|
|
728
818
|
}
|
|
729
819
|
const rel = mm.relations[key];
|
|
730
820
|
if (rel) {
|
|
731
|
-
withClause[rel.name] = translateWithOption(ctx, mm, rel.name, val);
|
|
821
|
+
withClause[rel.name] = translateWithOption(ctx, mm, rel.name, val, depth + 1);
|
|
732
822
|
hasWith = true;
|
|
733
823
|
continue;
|
|
734
824
|
}
|
|
@@ -862,8 +952,15 @@ function applyWriteProjection(proj, row) {
|
|
|
862
952
|
}
|
|
863
953
|
return row;
|
|
864
954
|
}
|
|
865
|
-
/**
|
|
866
|
-
|
|
955
|
+
/**
|
|
956
|
+
* Translate a Prisma relation include payload into a Turbine `WithOptions`.
|
|
957
|
+
*
|
|
958
|
+
* `depth` is the relation hop count carried down from {@link translateProjection}
|
|
959
|
+
* and is charged to the nested `where` / `orderBy` too, so a payload that pads
|
|
960
|
+
* one arg with relation hops and then nests the other cannot spend two separate
|
|
961
|
+
* budgets to reach the same stack depth.
|
|
962
|
+
*/
|
|
963
|
+
function translateWithOption(ctx, mm, turbineRel, val, depth = 0) {
|
|
867
964
|
if (val === true)
|
|
868
965
|
return true;
|
|
869
966
|
if (!isPlainObject(val))
|
|
@@ -871,16 +968,16 @@ function translateWithOption(ctx, mm, turbineRel, val) {
|
|
|
871
968
|
const target = relTargetModel(ctx, mm, turbineRel);
|
|
872
969
|
const opt = {};
|
|
873
970
|
if (val.where !== undefined)
|
|
874
|
-
opt.where = target ? translateWhere(ctx, target, val.where) : val.where;
|
|
971
|
+
opt.where = target ? translateWhere(ctx, target, val.where, depth) : val.where;
|
|
875
972
|
if (val.orderBy !== undefined)
|
|
876
|
-
opt.orderBy = target ? translateOrderBy(ctx, target, val.orderBy) : val.orderBy;
|
|
973
|
+
opt.orderBy = target ? translateOrderBy(ctx, target, val.orderBy, depth) : val.orderBy;
|
|
877
974
|
if (val.take !== undefined)
|
|
878
975
|
opt.limit = mapTake(val.take);
|
|
879
976
|
if (val.skip !== undefined) {
|
|
880
977
|
throw new errors_js_1.UnsupportedFeatureError('skip (offset) on a nested relation include', 'prisma-compat', "Turbine's `with` clause has no offset, page the relation with a separate query.");
|
|
881
978
|
}
|
|
882
979
|
if (target && (val.select !== undefined || val.include !== undefined || val.omit !== undefined)) {
|
|
883
|
-
const proj = translateProjection(ctx, target, val);
|
|
980
|
+
const proj = translateProjection(ctx, target, val, depth);
|
|
884
981
|
if (proj.select)
|
|
885
982
|
opt.select = proj.select;
|
|
886
983
|
// Same silent drop as the top level: a nested `omit` names the sensitive
|
|
@@ -1150,7 +1247,8 @@ const NESTED_WRITE_OPS = new Set([
|
|
|
1150
1247
|
* renamed to their Turbine relation name and their nested-write payloads are
|
|
1151
1248
|
* translated against the target model (op names match Prisma's).
|
|
1152
1249
|
*/
|
|
1153
|
-
function translateWriteData(ctx, mm, data) {
|
|
1250
|
+
function translateWriteData(ctx, mm, data, depth = 0) {
|
|
1251
|
+
assertTranslateDepth(depth, 'data');
|
|
1154
1252
|
if (!isPlainObject(data))
|
|
1155
1253
|
return data;
|
|
1156
1254
|
const out = {};
|
|
@@ -1158,39 +1256,50 @@ function translateWriteData(ctx, mm, data) {
|
|
|
1158
1256
|
const rel = mm.relations[key];
|
|
1159
1257
|
if (rel && isPlainObject(val) && Object.keys(val).some((k) => NESTED_WRITE_OPS.has(k))) {
|
|
1160
1258
|
const target = relTargetModel(ctx, mm, rel.name);
|
|
1161
|
-
out[rel.name] = translateNestedWrite(ctx, target, val);
|
|
1259
|
+
out[rel.name] = translateNestedWrite(ctx, target, val, depth + 1);
|
|
1162
1260
|
continue;
|
|
1163
1261
|
}
|
|
1164
1262
|
out[renameField(mm, key)] = val;
|
|
1165
1263
|
}
|
|
1166
1264
|
return out;
|
|
1167
1265
|
}
|
|
1168
|
-
|
|
1266
|
+
/**
|
|
1267
|
+
* One relation's nested-write ops. `depth` is threaded into EVERY payload
|
|
1268
|
+
* translator, the `where` ones included, so the budget is spent on total
|
|
1269
|
+
* nesting rather than per-arg: a chain that alternates relation hops with
|
|
1270
|
+
* combinator nesting reaches the same stack depth as either alone.
|
|
1271
|
+
*
|
|
1272
|
+
* Core's own nested-write engine caps at depth 10 (`nested-write.ts`), so this
|
|
1273
|
+
* cap can only ever fire on a payload core was going to refuse anyway. What it
|
|
1274
|
+
* changes is HOW: a typed refusal instead of a `RangeError` raised before core
|
|
1275
|
+
* is reached at all (measured: a 1,000-hop `create` chain, a 43 KB body).
|
|
1276
|
+
*/
|
|
1277
|
+
function translateNestedWrite(ctx, target, ops, depth) {
|
|
1169
1278
|
const out = {};
|
|
1170
1279
|
for (const [op, payload] of Object.entries(ops)) {
|
|
1171
1280
|
switch (op) {
|
|
1172
1281
|
case 'create':
|
|
1173
1282
|
case 'createMany':
|
|
1174
|
-
out[op] = mapMaybeArray(payload, (p) => (target ? translateWriteData(ctx, target, p) : p));
|
|
1283
|
+
out[op] = mapMaybeArray(payload, (p) => (target ? translateWriteData(ctx, target, p, depth + 1) : p));
|
|
1175
1284
|
break;
|
|
1176
1285
|
case 'connect':
|
|
1177
1286
|
case 'disconnect':
|
|
1178
1287
|
case 'delete':
|
|
1179
1288
|
case 'set':
|
|
1180
|
-
out[op] = mapMaybeArray(payload, (p) => (target ? translateWhere(ctx, target, p) : p));
|
|
1289
|
+
out[op] = mapMaybeArray(payload, (p) => (target ? translateWhere(ctx, target, p, depth + 1) : p));
|
|
1181
1290
|
break;
|
|
1182
1291
|
case 'deleteMany':
|
|
1183
1292
|
case 'updateMany':
|
|
1184
|
-
out[op] = mapMaybeArray(payload, (p) => translateWhereDataPair(ctx, target, p));
|
|
1293
|
+
out[op] = mapMaybeArray(payload, (p) => translateWhereDataPair(ctx, target, p, depth + 1));
|
|
1185
1294
|
break;
|
|
1186
1295
|
case 'update':
|
|
1187
|
-
out[op] = mapMaybeArray(payload, (p) => translateWhereDataPair(ctx, target, p));
|
|
1296
|
+
out[op] = mapMaybeArray(payload, (p) => translateWhereDataPair(ctx, target, p, depth + 1));
|
|
1188
1297
|
break;
|
|
1189
1298
|
case 'connectOrCreate':
|
|
1190
|
-
out[op] = mapMaybeArray(payload, (p) => translateConnectOrCreate(ctx, target, p));
|
|
1299
|
+
out[op] = mapMaybeArray(payload, (p) => translateConnectOrCreate(ctx, target, p, depth + 1));
|
|
1191
1300
|
break;
|
|
1192
1301
|
case 'upsert':
|
|
1193
|
-
out[op] = mapMaybeArray(payload, (p) => translateUpsertNested(ctx, target, p));
|
|
1302
|
+
out[op] = mapMaybeArray(payload, (p) => translateUpsertNested(ctx, target, p, depth + 1));
|
|
1194
1303
|
break;
|
|
1195
1304
|
default:
|
|
1196
1305
|
out[op] = payload;
|
|
@@ -1202,39 +1311,39 @@ function mapMaybeArray(val, fn) {
|
|
|
1202
1311
|
return Array.isArray(val) ? val.map(fn) : fn(val);
|
|
1203
1312
|
}
|
|
1204
1313
|
/** A `{ where?, data }` pair (nested update/updateMany), or a bare data object. */
|
|
1205
|
-
function translateWhereDataPair(ctx, target, p) {
|
|
1314
|
+
function translateWhereDataPair(ctx, target, p, depth) {
|
|
1206
1315
|
if (!isPlainObject(p))
|
|
1207
1316
|
return p;
|
|
1208
1317
|
if ('data' in p || 'where' in p) {
|
|
1209
1318
|
const out = {};
|
|
1210
1319
|
if (p.where !== undefined)
|
|
1211
|
-
out.where = target ? translateWhere(ctx, target, p.where) : p.where;
|
|
1320
|
+
out.where = target ? translateWhere(ctx, target, p.where, depth) : p.where;
|
|
1212
1321
|
if (p.data !== undefined)
|
|
1213
|
-
out.data = target ? translateWriteData(ctx, target, p.data) : p.data;
|
|
1322
|
+
out.data = target ? translateWriteData(ctx, target, p.data, depth) : p.data;
|
|
1214
1323
|
return out;
|
|
1215
1324
|
}
|
|
1216
|
-
return target ? translateWriteData(ctx, target, p) : p;
|
|
1325
|
+
return target ? translateWriteData(ctx, target, p, depth) : p;
|
|
1217
1326
|
}
|
|
1218
|
-
function translateConnectOrCreate(ctx, target, p) {
|
|
1327
|
+
function translateConnectOrCreate(ctx, target, p, depth) {
|
|
1219
1328
|
if (!isPlainObject(p))
|
|
1220
1329
|
return p;
|
|
1221
1330
|
const out = {};
|
|
1222
1331
|
if (p.where !== undefined)
|
|
1223
|
-
out.where = target ? translateWhere(ctx, target, p.where) : p.where;
|
|
1332
|
+
out.where = target ? translateWhere(ctx, target, p.where, depth) : p.where;
|
|
1224
1333
|
if (p.create !== undefined)
|
|
1225
|
-
out.create = target ? translateWriteData(ctx, target, p.create) : p.create;
|
|
1334
|
+
out.create = target ? translateWriteData(ctx, target, p.create, depth) : p.create;
|
|
1226
1335
|
return out;
|
|
1227
1336
|
}
|
|
1228
|
-
function translateUpsertNested(ctx, target, p) {
|
|
1337
|
+
function translateUpsertNested(ctx, target, p, depth) {
|
|
1229
1338
|
if (!isPlainObject(p))
|
|
1230
1339
|
return p;
|
|
1231
1340
|
const out = {};
|
|
1232
1341
|
if (p.where !== undefined)
|
|
1233
|
-
out.where = target ? translateWhere(ctx, target, p.where) : p.where;
|
|
1342
|
+
out.where = target ? translateWhere(ctx, target, p.where, depth) : p.where;
|
|
1234
1343
|
if (p.create !== undefined)
|
|
1235
|
-
out.create = target ? translateWriteData(ctx, target, p.create) : p.create;
|
|
1344
|
+
out.create = target ? translateWriteData(ctx, target, p.create, depth) : p.create;
|
|
1236
1345
|
if (p.update !== undefined)
|
|
1237
|
-
out.update = target ? translateWriteData(ctx, target, p.update) : p.update;
|
|
1346
|
+
out.update = target ? translateWriteData(ctx, target, p.update, depth) : p.update;
|
|
1238
1347
|
return out;
|
|
1239
1348
|
}
|
|
1240
1349
|
// --- aggregate / groupBy translation --------------------------------------
|
|
@@ -1297,13 +1406,23 @@ function renameAggBlock(mm, block, isCount) {
|
|
|
1297
1406
|
}
|
|
1298
1407
|
return out;
|
|
1299
1408
|
}
|
|
1300
|
-
|
|
1409
|
+
/**
|
|
1410
|
+
* Rename a groupBy `having` into turbine field names.
|
|
1411
|
+
*
|
|
1412
|
+
* Capped for the same reason and at the same depth as {@link translateWhere}:
|
|
1413
|
+
* core caps its own HAVING walk (`assertWhereDepth(depth, 'having')` in
|
|
1414
|
+
* `query/aggregates.ts`), but this rename runs first and was unbounded, so a
|
|
1415
|
+
* 8,000-deep `NOT` chain (a 64 KB body) overflowed here and core's guard never
|
|
1416
|
+
* ran.
|
|
1417
|
+
*/
|
|
1418
|
+
function renameHaving(mm, having, depth = 0) {
|
|
1419
|
+
assertTranslateDepth(depth, 'having');
|
|
1301
1420
|
if (!isPlainObject(having))
|
|
1302
1421
|
return having;
|
|
1303
1422
|
const out = {};
|
|
1304
1423
|
for (const [key, val] of Object.entries(having)) {
|
|
1305
1424
|
if (COMBINATORS.has(key)) {
|
|
1306
|
-
out[key] = Array.isArray(val) ? val.map((v) => renameHaving(mm, v)) : renameHaving(mm, val);
|
|
1425
|
+
out[key] = Array.isArray(val) ? val.map((v) => renameHaving(mm, v, depth + 1)) : renameHaving(mm, val, depth + 1);
|
|
1307
1426
|
continue;
|
|
1308
1427
|
}
|
|
1309
1428
|
if (key === '_count') {
|
|
@@ -104,7 +104,7 @@ export type HavingGroupKey = {
|
|
|
104
104
|
* {@link HavingGroupKey}): a scalar filter is legal ONLY on a group key,
|
|
105
105
|
* because a non-grouped column cannot be referenced in HAVING at all.
|
|
106
106
|
*/
|
|
107
|
-
export declare function buildHavingClauses<T extends object>(qi: BuilderCtx, having: HavingClause<T>, params: unknown[], jsonAggExprs?: Map<string, string>, groupKeys?: Map<string, HavingGroupKey
|
|
107
|
+
export declare function buildHavingClauses<T extends object>(qi: BuilderCtx, having: HavingClause<T>, params: unknown[], jsonAggExprs?: Map<string, string>, groupKeys?: Map<string, HavingGroupKey>, depth?: number): string[];
|
|
108
108
|
/**
|
|
109
109
|
* Convert a single having aggregate filter into one or more parameterized SQL
|
|
110
110
|
* comparisons against the given aggregate expression. A bare value is
|
|
@@ -57,6 +57,7 @@ const filters_js_1 = require("./filters.js");
|
|
|
57
57
|
const types_js_1 = require("./types.js");
|
|
58
58
|
const utils_js_1 = require("./utils.js");
|
|
59
59
|
const whereMod = __importStar(require("./where.js"));
|
|
60
|
+
const where_compile_js_1 = require("./where-compile.js");
|
|
60
61
|
/**
|
|
61
62
|
* Enforce the PII contract on the aggregate surface. A PII-tagged
|
|
62
63
|
* (`defineSchema` `pii: true`) column is excluded from every default
|
|
@@ -407,6 +408,32 @@ function buildGroupByOrderBy(qi, orderBy, byOrderExprs, aggOrderExprs) {
|
|
|
407
408
|
return keys.join(', ') || '(none)';
|
|
408
409
|
};
|
|
409
410
|
const parts = [];
|
|
411
|
+
// Redundant-sort-term dropping, groupBy's spelling of it (see
|
|
412
|
+
// `dedupeOrderEntries` in filters.ts for why this drops rather than refuses).
|
|
413
|
+
// The findMany rule compares the RESOLVED COLUMN because its keys are field
|
|
414
|
+
// names; here the keys are RESULT keys (by-fields, JSON group-key aliases,
|
|
415
|
+
// aggregate blocks) that have already been resolved into the exact SELECT
|
|
416
|
+
// expression they re-emit, so the expression IS the identity, and comparing
|
|
417
|
+
// it is both cheaper and stricter: an alias and a JSON group key that extract
|
|
418
|
+
// the same path collapse onto one term, which is what "sorts by the same
|
|
419
|
+
// thing" means. Direction is excluded for the same reason as in filters.ts: a
|
|
420
|
+
// second term on an expression the first already ordered by cannot move a row
|
|
421
|
+
// whichever way it points.
|
|
422
|
+
//
|
|
423
|
+
// Done in place rather than up front because groupBy does not go through the
|
|
424
|
+
// SQL-template cache at all, so there is no fingerprint for this to stay in
|
|
425
|
+
// step with; the compile path is the only path.
|
|
426
|
+
const seen = new Map();
|
|
427
|
+
const pushOrderTerm = (expr, label, value) => {
|
|
428
|
+
const first = seen.get(expr);
|
|
429
|
+
if (first !== undefined) {
|
|
430
|
+
(0, utils_js_1.warnRedundantSortTerm)(qi.table, 'groupBy orderBy', [{ key: label, first, resolved: expr }]);
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
seen.set(expr, label);
|
|
434
|
+
const { dir, nulls } = (0, filters_js_1.normalizeOrderBy)(value);
|
|
435
|
+
parts.push(`${expr} ${dir}${qi.nullsSuffix(nulls)}`);
|
|
436
|
+
};
|
|
410
437
|
for (const [key, value] of (0, filters_js_1.orderByEntries)(orderBy)) {
|
|
411
438
|
if (value === undefined)
|
|
412
439
|
continue;
|
|
@@ -421,8 +448,7 @@ function buildGroupByOrderBy(qi, orderBy, byOrderExprs, aggOrderExprs) {
|
|
|
421
448
|
// Refuse a direction that is neither asc nor desc BEFORE normalizeOrderBy,
|
|
422
449
|
// whose `=== 'desc' ? DESC : ASC` would silently sort ascending.
|
|
423
450
|
(0, types_js_1.assertOrderDirection)(value, `groupBy orderBy "_count" on table "${qi.table}"`);
|
|
424
|
-
|
|
425
|
-
parts.push(`${expr} ${dir}${qi.nullsSuffix(nulls)}`);
|
|
451
|
+
pushOrderTerm(expr, '_count', value);
|
|
426
452
|
continue;
|
|
427
453
|
}
|
|
428
454
|
// `_sum` / `_avg` / `_min` / `_max`: an object of field → direction/spec.
|
|
@@ -439,8 +465,7 @@ function buildGroupByOrderBy(qi, orderBy, byOrderExprs, aggOrderExprs) {
|
|
|
439
465
|
`that aggregate is not requested in this call. Orderable keys: ${validKeys()}.`);
|
|
440
466
|
}
|
|
441
467
|
(0, types_js_1.assertOrderDirection)(dirSpec, `groupBy orderBy "${key}.${field}" on table "${qi.table}"`);
|
|
442
|
-
|
|
443
|
-
parts.push(`${expr} ${dir}${qi.nullsSuffix(nulls)}`);
|
|
468
|
+
pushOrderTerm(expr, `${key}.${field}`, dirSpec);
|
|
444
469
|
}
|
|
445
470
|
continue;
|
|
446
471
|
}
|
|
@@ -451,8 +476,7 @@ function buildGroupByOrderBy(qi, orderBy, byOrderExprs, aggOrderExprs) {
|
|
|
451
476
|
`Orderable keys: ${validKeys()}.`);
|
|
452
477
|
}
|
|
453
478
|
(0, types_js_1.assertOrderDirection)(value, `groupBy orderBy "${key}" on table "${qi.table}"`);
|
|
454
|
-
|
|
455
|
-
parts.push(`${expr} ${dir}${qi.nullsSuffix(nulls)}`);
|
|
479
|
+
pushOrderTerm(expr, key, value);
|
|
456
480
|
}
|
|
457
481
|
return parts.join(', ');
|
|
458
482
|
}
|
|
@@ -505,7 +529,14 @@ function buildDistinctOnSource(qi, distinctOn, whereSql, params) {
|
|
|
505
529
|
throw new errors_js_1.ValidationError(`[turbine] groupBy distinctOn on table "${qi.table}" requires \`orderBy\` to pick ONE row per ` +
|
|
506
530
|
"column combination deterministically (e.g. orderBy: { createdAt: 'desc' }).");
|
|
507
531
|
}
|
|
508
|
-
|
|
532
|
+
// A repeated DISTINCT ON column is a no-op, so drop it rather than emit it
|
|
533
|
+
// twice (see `dedupeColumnList`). The orderBy below needs no equivalent: its
|
|
534
|
+
// keys come from an object, and the DISTINCT ON columns lead the ORDER BY, so
|
|
535
|
+
// a key repeating one of them is already merged by `orderParts`' construction.
|
|
536
|
+
const dedupedCols = (0, filters_js_1.dedupeColumnList)(qi.tableMeta, distinctOn.columns);
|
|
537
|
+
if (dedupedCols)
|
|
538
|
+
(0, utils_js_1.warnRedundantSortTerm)(qi.table, 'groupBy distinctOn.columns', dedupedCols.dropped);
|
|
539
|
+
const distinctCols = (dedupedCols?.columns ?? distinctOn.columns).map((c) => qi.q(qi.toColumn(c)));
|
|
509
540
|
// DISTINCT ON expressions must lead the ORDER BY; the user's orderBy then
|
|
510
541
|
// decides which row survives per combination.
|
|
511
542
|
const orderParts = [...distinctCols];
|
|
@@ -564,7 +595,8 @@ const HAVING_AGGREGATE_FNS = {
|
|
|
564
595
|
* {@link HavingGroupKey}): a scalar filter is legal ONLY on a group key,
|
|
565
596
|
* because a non-grouped column cannot be referenced in HAVING at all.
|
|
566
597
|
*/
|
|
567
|
-
function buildHavingClauses(qi, having, params, jsonAggExprs, groupKeys) {
|
|
598
|
+
function buildHavingClauses(qi, having, params, jsonAggExprs, groupKeys, depth = 0) {
|
|
599
|
+
(0, where_compile_js_1.assertWhereDepth)(depth, 'having');
|
|
568
600
|
const clauses = [];
|
|
569
601
|
for (const [key, value] of Object.entries(having)) {
|
|
570
602
|
if (value === undefined)
|
|
@@ -576,7 +608,7 @@ function buildHavingClauses(qi, having, params, jsonAggExprs, groupKeys) {
|
|
|
576
608
|
}
|
|
577
609
|
// AND / OR / NOT, mixing scalar and aggregate predicates at any depth.
|
|
578
610
|
if (key === 'AND' || key === 'OR' || key === 'NOT') {
|
|
579
|
-
clauses.push(...buildHavingCombinator(qi, key, value, params, jsonAggExprs, groupKeys));
|
|
611
|
+
clauses.push(...buildHavingCombinator(qi, key, value, params, jsonAggExprs, groupKeys, depth));
|
|
580
612
|
continue;
|
|
581
613
|
}
|
|
582
614
|
// Otherwise `key` is a field name. Split its aggregate keys from its
|
|
@@ -646,15 +678,28 @@ function splitHavingField(qi, field, value) {
|
|
|
646
678
|
* (the caller ANDs them), mirroring {@link buildWhereClause}'s combinator
|
|
647
679
|
* shapes so HAVING and WHERE read the same way.
|
|
648
680
|
*/
|
|
649
|
-
function buildHavingCombinator(qi, key, value, params, jsonAggExprs, groupKeys) {
|
|
681
|
+
function buildHavingCombinator(qi, key, value, params, jsonAggExprs, groupKeys, depth = 0) {
|
|
650
682
|
const conditions = Array.isArray(value) ? value : [value];
|
|
683
|
+
// Same variable-arity shape as the WHERE combinators: an ARRAY branch writes
|
|
684
|
+
// one parenthesized condition per element into the SQL text, so a
|
|
685
|
+
// caller-sized `having.OR` is a new statement per length.
|
|
686
|
+
//
|
|
687
|
+
// DEFENSIVE, not load-bearing, today: `buildGroupBy` and `buildAggregate`
|
|
688
|
+
// assemble their SQL directly and never go through `acquireSql`, so they
|
|
689
|
+
// carry no prepared-statement name for the mark to clear (asserted in
|
|
690
|
+
// prepared-statement-arity.test.ts). It is marked anyway so that routing
|
|
691
|
+
// them through the cache later cannot silently reopen the hole, and so the
|
|
692
|
+
// rule reads the same in both clause compilers. The DEPTH cap above is the
|
|
693
|
+
// half of this that bites here and now.
|
|
694
|
+
if (Array.isArray(value) && key !== 'NOT')
|
|
695
|
+
qi.markVariableArity();
|
|
651
696
|
const parts = [];
|
|
652
697
|
for (const condition of conditions) {
|
|
653
698
|
if (!(0, filters_js_1.isUnmatchedPlainObject)(condition)) {
|
|
654
699
|
throw new errors_js_1.ValidationError(`[turbine] Invalid having "${key}" on table "${qi.table}": expected ` +
|
|
655
700
|
`${key === 'OR' ? 'an array of having objects' : 'a having object (or an array of them)'}.`);
|
|
656
701
|
}
|
|
657
|
-
const sub = buildHavingClauses(qi, condition, params, jsonAggExprs, groupKeys);
|
|
702
|
+
const sub = buildHavingClauses(qi, condition, params, jsonAggExprs, groupKeys, depth + 1);
|
|
658
703
|
if (sub.length === 0)
|
|
659
704
|
continue;
|
|
660
705
|
parts.push(sub.length === 1 ? sub[0] : `(${sub.join(' AND ')})`);
|
|
@@ -806,13 +851,30 @@ function buildHavingNumericClauses(qi, expr, filter, params) {
|
|
|
806
851
|
function temporalAggValue(qi, col, value) {
|
|
807
852
|
if (!qi.tableMeta.dateColumns.has(col))
|
|
808
853
|
return value;
|
|
809
|
-
if (
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
return value;
|
|
815
|
-
|
|
854
|
+
if ((0, utils_js_1.isTemporalInfinity)(value)) {
|
|
855
|
+
if (qi.temporalInfinity === 'null')
|
|
856
|
+
return null;
|
|
857
|
+
if (typeof value === 'number')
|
|
858
|
+
return value;
|
|
859
|
+
return value === '-infinity' ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY;
|
|
860
|
+
}
|
|
861
|
+
// A STRING on a temporal column means the driver handed the value back
|
|
862
|
+
// untyped, which is what every engine without pg's OID-keyed type parsers
|
|
863
|
+
// does. Verified on SQLite: `findMany` returned a `Date` for `at` and
|
|
864
|
+
// `groupBy` returned a `Date` for the same column used as a group key, while
|
|
865
|
+
// `aggregate({ _max: { at: true } })` returned the raw string
|
|
866
|
+
// '2024-01-15 12:00:00', so ONE column disagreed with itself across three
|
|
867
|
+
// read paths and with the `Date` the generated types promise. `_min`/`_max`
|
|
868
|
+
// are assembled from the RAW row here (they cannot go through `parseRow`,
|
|
869
|
+
// whose snake→camel mapping would collide with the `_min_` alias), so the
|
|
870
|
+
// coercion `parseRow` applies has to be applied here as well, on the same
|
|
871
|
+
// terms: offset-less text is pinned to UTC unless `utcTimestamps: false`.
|
|
872
|
+
//
|
|
873
|
+
// On PostgreSQL this branch is unreachable and the emitted values are
|
|
874
|
+
// unchanged: the driver's date/timestamp parsers already produce a `Date`.
|
|
875
|
+
if (typeof value === 'string')
|
|
876
|
+
return qi.utcTimestamps !== false ? (0, utils_js_1.parseDbDate)(value) : new Date(value);
|
|
877
|
+
return value;
|
|
816
878
|
}
|
|
817
879
|
function buildAggregate(qi, args) {
|
|
818
880
|
qi.currentSkip = (0, types_js_1.resolveSkipGlobalFilters)(args.skipGlobalFilters);
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
* @module
|
|
55
55
|
*/
|
|
56
56
|
import type pg from 'pg';
|
|
57
|
+
import type { PartitionLimitInput } from '../dialect.js';
|
|
57
58
|
import { type RelationDef, type SchemaMetadata, type TableMetadata } from '../schema.js';
|
|
58
59
|
import type { ReselectExecutor } from './builder.js';
|
|
59
60
|
import type { SkipGlobalFilters, Unsafe, WithClause, WithCount } from './types.js';
|
|
@@ -123,6 +124,15 @@ export interface RelationLoadContext {
|
|
|
123
124
|
clause: string;
|
|
124
125
|
params: unknown[];
|
|
125
126
|
} | null;
|
|
127
|
+
/**
|
|
128
|
+
* The active dialect's {@link Dialect.buildPartitionLimit}, when it has one.
|
|
129
|
+
* Absent means this engine cannot bound the follow-up per correlation key, so
|
|
130
|
+
* the loader falls back to fetching every child and slicing client-side. The
|
|
131
|
+
* same fallback runs, on every engine, for a relation whose ordering does not
|
|
132
|
+
* force which rows the limit keeps. See {@link partitionOrderBy} and
|
|
133
|
+
* {@link boundedChildQuery}.
|
|
134
|
+
*/
|
|
135
|
+
buildPartitionLimit?: (input: PartitionLimitInput) => string;
|
|
126
136
|
}
|
|
127
137
|
/**
|
|
128
138
|
* The default projection of `meta` expressed in FIELD names: which fields the
|