turbine-orm 0.70.0 → 0.71.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 +164 -1041
- package/dist/cjs/cli/compile-query.d.ts +198 -0
- package/dist/cjs/cli/compile-query.js +529 -0
- package/dist/cjs/cli/index.d.ts +25 -1
- package/dist/cjs/cli/index.js +49 -1
- package/dist/cjs/cli/mcp.js +198 -16
- package/dist/cjs/client.d.ts +45 -10
- package/dist/cjs/client.js +21 -3
- package/dist/cjs/connection-url.d.ts +160 -0
- package/dist/cjs/connection-url.js +296 -0
- package/dist/cjs/index-stats.d.ts +4 -1
- package/dist/cjs/index-stats.js +27 -11
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/plan-flip-probe.js +17 -1
- package/dist/cjs/powql.d.ts +1 -0
- package/dist/cjs/powql.js +9 -0
- package/dist/cjs/query/builder.d.ts +133 -2
- package/dist/cjs/query/builder.js +288 -64
- package/dist/cjs/query/deferred.d.ts +12 -6
- package/dist/cjs/query/index.d.ts +1 -1
- package/dist/cjs/query/option-surface.js +6 -0
- package/dist/cjs/query/types.d.ts +47 -0
- package/dist/cjs/query/where.d.ts +11 -2
- package/dist/cli/compile-query.d.ts +198 -0
- package/dist/cli/compile-query.js +522 -0
- package/dist/cli/index.d.ts +25 -1
- package/dist/cli/index.js +48 -1
- package/dist/cli/mcp.js +198 -16
- package/dist/client.d.ts +45 -10
- package/dist/client.js +19 -1
- package/dist/connection-url.d.ts +160 -0
- package/dist/connection-url.js +289 -0
- package/dist/index-stats.d.ts +4 -1
- package/dist/index-stats.js +27 -11
- package/dist/index.d.ts +1 -1
- package/dist/plan-flip-probe.js +17 -1
- package/dist/powql.d.ts +1 -0
- package/dist/powql.js +9 -0
- package/dist/query/builder.d.ts +133 -2
- package/dist/query/builder.js +288 -64
- package/dist/query/deferred.d.ts +12 -6
- package/dist/query/index.d.ts +1 -1
- package/dist/query/option-surface.js +6 -0
- package/dist/query/types.d.ts +47 -0
- package/dist/query/where.d.ts +11 -2
- package/package.json +8 -6
|
@@ -450,6 +450,44 @@ function isEmptyOrderBy(orderBy) {
|
|
|
450
450
|
}
|
|
451
451
|
return orderBy === undefined || orderBy === null;
|
|
452
452
|
}
|
|
453
|
+
/** The two accepted {@link JsonEncoding} values, frozen so the check is total. */
|
|
454
|
+
const JSON_ENCODINGS = Object.freeze(['object', 'positional']);
|
|
455
|
+
/**
|
|
456
|
+
* The relation JSON encoding a client on `dialect` gets when it names none.
|
|
457
|
+
*
|
|
458
|
+
* `'positional'` (`json_build_array`) on PostgreSQL, `'object'`
|
|
459
|
+
* (`json_build_object`) everywhere else. The positional form drops the repeated
|
|
460
|
+
* key names from every relation row: measured on a 50-parent / ~10-child read
|
|
461
|
+
* against local PostgreSQL 17, server time 0.685 ms → 0.350 ms and 152 KB →
|
|
462
|
+
* 100 KB on the wire, returning byte-identical parsed rows.
|
|
463
|
+
*
|
|
464
|
+
* ## Why the test is `dialect.name`, not "does the dialect have buildJsonArray"
|
|
465
|
+
*
|
|
466
|
+
* Because a presence test does not distinguish engines HERE. `buildJsonArray`
|
|
467
|
+
* is declared on `postgresDialect`, and sqlite.ts / mysql.ts / mssql.ts /
|
|
468
|
+
* powdb.ts each build their dialect by SPREADING it, so every engine inherits
|
|
469
|
+
* the hook and an "is it absent" fallback never fires. That is the documented
|
|
470
|
+
* inheritance trap (see `buildPartitionLimit` in {@link
|
|
471
|
+
* QueryInterface.batchedContext} and the `distinct` gate in
|
|
472
|
+
* {@link QueryInterface.buildFindMany}), and it is exactly the shape that
|
|
473
|
+
* silently turned the partition-limit window on for SQLite.
|
|
474
|
+
*
|
|
475
|
+
* A new capability flag would work, but only if every engine set it
|
|
476
|
+
* explicitly, and it would be a SECOND authority on the same question:
|
|
477
|
+
* `buildSelectWithRelations` (relations.ts) already refuses `'positional'` with
|
|
478
|
+
* E017 on `dialect.name !== 'postgresql'`. Deriving the default from the
|
|
479
|
+
* identical predicate is what makes it impossible for the default to select an
|
|
480
|
+
* encoding the builder then refuses. A flag could drift from that refusal; this
|
|
481
|
+
* cannot.
|
|
482
|
+
*
|
|
483
|
+
* A wire-compatible engine that reaches this on `postgresDialect` itself
|
|
484
|
+
* (CockroachDB, YugabyteDB, AlloyDB, Timescale, all of which are ADAPTERS over
|
|
485
|
+
* the Postgres dialect rather than dialects of their own) gets `'positional'`,
|
|
486
|
+
* which is correct: they speak `json_build_array`.
|
|
487
|
+
*/
|
|
488
|
+
function defaultJsonEncoding(dialect) {
|
|
489
|
+
return dialect.name === 'postgresql' ? 'positional' : 'object';
|
|
490
|
+
}
|
|
453
491
|
// biome-ignore lint/complexity/noBannedTypes: {} means "no relations known", intentional for untyped table access
|
|
454
492
|
class QueryInterface {
|
|
455
493
|
pool;
|
|
@@ -529,8 +567,31 @@ class QueryInterface {
|
|
|
529
567
|
* is derived. See {@link autoToOneThreshold}.
|
|
530
568
|
*/
|
|
531
569
|
autoRoundTripMs;
|
|
532
|
-
/**
|
|
570
|
+
/**
|
|
571
|
+
* The CLIENT-level nested-relation JSON encoding: what a query that names no
|
|
572
|
+
* `jsonEncoding` of its own gets. `'positional'` on PostgreSQL, `'object'`
|
|
573
|
+
* everywhere else (see {@link defaultJsonEncoding}).
|
|
574
|
+
*
|
|
575
|
+
* Read through {@link QueryInterface.currentJsonEncoding}, never directly, so
|
|
576
|
+
* a per-query override cannot be missed by one reader.
|
|
577
|
+
*/
|
|
533
578
|
jsonEncoding;
|
|
579
|
+
/**
|
|
580
|
+
* The encoding the query BEING BUILT resolved to, i.e. its own
|
|
581
|
+
* `jsonEncoding` or {@link QueryInterface.jsonEncoding}.
|
|
582
|
+
*
|
|
583
|
+
* Reassigned per `build*` call and exposed on the {@link BuilderCtx} as a live
|
|
584
|
+
* getter, exactly like {@link QueryInterface.currentSkip} and for the same
|
|
585
|
+
* reason: relations.ts reads the encoding from four places deep inside the
|
|
586
|
+
* SELECT walk, and threading it through every one of them as a parameter
|
|
587
|
+
* would be four chances to forget it.
|
|
588
|
+
*
|
|
589
|
+
* Safe because a build is SYNCHRONOUS from the assignment to the last read:
|
|
590
|
+
* `buildFindMany` / `buildFindUnique` return a fully-formed DeferredQuery
|
|
591
|
+
* whose parser closure already captured the shapes, so nothing reads this
|
|
592
|
+
* field after the build returns and no two builds can interleave on it.
|
|
593
|
+
*/
|
|
594
|
+
currentJsonEncoding;
|
|
534
595
|
/**
|
|
535
596
|
* `parseRow` decode plans, keyed by table plus the exact column list. Bounded
|
|
536
597
|
* like the SQL template cache and for the same reason: the shapes come from
|
|
@@ -699,7 +760,8 @@ class QueryInterface {
|
|
|
699
760
|
autoToOne !== undefined && Number.isFinite(autoToOne) && autoToOne >= 0 ? Math.floor(autoToOne) : undefined;
|
|
700
761
|
const rtt = options?.autoRoundTripMs;
|
|
701
762
|
this.autoRoundTripMs = rtt !== undefined && Number.isFinite(rtt) && rtt > 0 ? rtt : undefined;
|
|
702
|
-
this.jsonEncoding = options?.jsonEncoding ??
|
|
763
|
+
this.jsonEncoding = options?.jsonEncoding ?? defaultJsonEncoding(this.dialect);
|
|
764
|
+
this.currentJsonEncoding = this.jsonEncoding;
|
|
703
765
|
// Only retain the map when it has at least one entry, so `globalFilters`
|
|
704
766
|
// stays `undefined` (and every merge path a no-op) for the common case.
|
|
705
767
|
this.globalFilters =
|
|
@@ -806,7 +868,13 @@ class QueryInterface {
|
|
|
806
868
|
mutationInsertId: (result) => this.mutationInsertId(result),
|
|
807
869
|
acquireSql: (cacheKey, build) => this.acquireSql(cacheKey, build),
|
|
808
870
|
crossCheckCache: (op, cacheKey, entry, build, collectedParams) => this.crossCheckCache(op, cacheKey, entry, build, collectedParams),
|
|
809
|
-
|
|
871
|
+
// LIVE getter, not a copied value: `jsonEncoding` is now a per-query
|
|
872
|
+
// option, so the module-facing view must see the encoding of the query
|
|
873
|
+
// being built rather than the one the client was constructed with. Same
|
|
874
|
+
// shape and same reason as `currentSkip` above.
|
|
875
|
+
get jsonEncoding() {
|
|
876
|
+
return self.currentJsonEncoding;
|
|
877
|
+
},
|
|
810
878
|
camelDateFieldCache: this.camelDateFieldCache,
|
|
811
879
|
relationEntryCache: this.relationEntryCache,
|
|
812
880
|
limitOneClause: () => this.limitOneClause(),
|
|
@@ -1005,6 +1073,35 @@ class QueryInterface {
|
|
|
1005
1073
|
resolveStableOrder(argFlag) {
|
|
1006
1074
|
return argFlag ?? this.stableRelationOrder;
|
|
1007
1075
|
}
|
|
1076
|
+
/**
|
|
1077
|
+
* Resolve one query's relation JSON encoding and PIN it for the build, so
|
|
1078
|
+
* every reader inside relations.ts sees the same answer.
|
|
1079
|
+
*
|
|
1080
|
+
* Called at the TOP of each entry point that can emit or decode relation JSON
|
|
1081
|
+
* (`buildFindMany`, `buildFindUnique`, `makeStreamRowParser`), before the
|
|
1082
|
+
* cache key is assembled and before the flatten plan is consulted, because
|
|
1083
|
+
* both of those depend on the answer.
|
|
1084
|
+
*
|
|
1085
|
+
* An unrecognized value THROWS (E003) rather than falling back. A per-query
|
|
1086
|
+
* option that is silently ignored when misspelled is the exact failure this
|
|
1087
|
+
* package has been bitten by before (see query/option-surface.ts), and here it
|
|
1088
|
+
* would be invisible: the wrong encoding still returns correct rows, just
|
|
1089
|
+
* without the saving the caller asked for, or with the flatten plan they were
|
|
1090
|
+
* trying to re-enable still refused. Thrown before the SQL cache is consulted
|
|
1091
|
+
* so a warm template can never serve a call the cold path would refuse.
|
|
1092
|
+
*/
|
|
1093
|
+
resolveJsonEncoding(argEncoding) {
|
|
1094
|
+
if (argEncoding === undefined) {
|
|
1095
|
+
this.currentJsonEncoding = this.jsonEncoding;
|
|
1096
|
+
return this.currentJsonEncoding;
|
|
1097
|
+
}
|
|
1098
|
+
if (!JSON_ENCODINGS.includes(argEncoding)) {
|
|
1099
|
+
throw new errors_js_1.ValidationError(`[turbine] Invalid \`jsonEncoding\` on "${this.table}": ${JSON.stringify(argEncoding)}. ` +
|
|
1100
|
+
`Expected ${JSON_ENCODINGS.map((e) => `'${e}'`).join(' or ')}.`);
|
|
1101
|
+
}
|
|
1102
|
+
this.currentJsonEncoding = argEncoding;
|
|
1103
|
+
return argEncoding;
|
|
1104
|
+
}
|
|
1008
1105
|
/**
|
|
1009
1106
|
* Fill a PK-ascending `orderBy` into every to-many `with` relation that has no
|
|
1010
1107
|
* explicit one, recursing into nested `with`. Returns a CLONED clause (user
|
|
@@ -2117,6 +2214,8 @@ class QueryInterface {
|
|
|
2117
2214
|
}
|
|
2118
2215
|
buildFindUnique(args) {
|
|
2119
2216
|
this.currentSkip = (0, types_js_1.resolveSkipGlobalFilters)(args.skipGlobalFilters);
|
|
2217
|
+
// Pinned before the cache key, which carries it (see buildFindMany).
|
|
2218
|
+
const jsonEncoding = this.resolveJsonEncoding(args.jsonEncoding);
|
|
2120
2219
|
// Prisma compound-unique selector expansion (before global-filter merge and
|
|
2121
2220
|
// fingerprinting, so the cache only ever sees the canonical expanded where).
|
|
2122
2221
|
args = maybeExpandCompoundUnique(this.tableMeta, args);
|
|
@@ -2170,7 +2269,10 @@ class QueryInterface {
|
|
|
2170
2269
|
// See buildFindMany: `includePii` is its own cache-key segment so a no-PII
|
|
2171
2270
|
// statement can never serve an `includePii` call (relation projections that
|
|
2172
2271
|
// `withFp` does not capture also flip on it).
|
|
2173
|
-
|
|
2272
|
+
// `|je=`: same rule and same hazard as in buildFindMany. findUnique is never
|
|
2273
|
+
// flatten-planned, so this is the only plan-shape segment it needs.
|
|
2274
|
+
const encodingFp = `|je=${jsonEncoding === 'positional' ? 'p' : 'o'}`;
|
|
2275
|
+
const ck = `fu:${whereFingerprint}|c=${colKey}|w=${withFp}|pii=${includePii ? 1 : 0}${encodingFp}${this.globalFilterCacheSegment()}`;
|
|
2174
2276
|
const params = [];
|
|
2175
2277
|
// Check if all where values are simple (plain equality, no operators/null/OR).
|
|
2176
2278
|
// Keys are sorted to match fingerprintWhere, insertion order here would let
|
|
@@ -2455,6 +2557,8 @@ class QueryInterface {
|
|
|
2455
2557
|
}
|
|
2456
2558
|
buildFindMany(args) {
|
|
2457
2559
|
this.currentSkip = (0, types_js_1.resolveSkipGlobalFilters)(args?.skipGlobalFilters);
|
|
2560
|
+
// Pinned before the flatten plan and the cache key, both of which read it.
|
|
2561
|
+
const jsonEncoding = this.resolveJsonEncoding(args?.jsonEncoding);
|
|
2458
2562
|
// Stable relation order (opt-in): fill PK-asc orderBy into unordered to-many
|
|
2459
2563
|
// relations BEFORE fingerprinting, so the two orderings get distinct cache
|
|
2460
2564
|
// entries and every downstream path (SQL build, collect, parser) inherits it.
|
|
@@ -2603,7 +2707,19 @@ class QueryInterface {
|
|
|
2603
2707
|
// key is byte-identical to before.
|
|
2604
2708
|
const flattenPlan = this.planFlatten(args, includePii);
|
|
2605
2709
|
const flattenFp = flattenPlan ? `|fl=${flattenPlan.signature}` : '';
|
|
2606
|
-
|
|
2710
|
+
// `jsonEncoding` decides whether every relation subquery emits
|
|
2711
|
+
// `json_build_object` or `json_build_array`, and the parser built alongside
|
|
2712
|
+
// the statement decodes exactly one of those two. It is invisible to
|
|
2713
|
+
// `withFp` (which fingerprints the `with` SHAPE, not its rendering), so it
|
|
2714
|
+
// needs its own segment for the same reason `pii=` and `fl=` do, and the
|
|
2715
|
+
// consequence of getting it wrong is worse than either: a positional
|
|
2716
|
+
// template served to an object-planned call hands `parseNestedRow` bare
|
|
2717
|
+
// arrays where it expects keyed objects, which is silent data corruption
|
|
2718
|
+
// rather than an error. Emitted for BOTH values rather than only the
|
|
2719
|
+
// non-default one, so the key never depends on what the client default
|
|
2720
|
+
// happens to be.
|
|
2721
|
+
const encodingFp = `|je=${jsonEncoding === 'positional' ? 'p' : 'o'}`;
|
|
2722
|
+
const ck = `fm:${whereFp}|c=${colKey}|o=${orderFp}|l=${limitFp}|off=${offsetFp}|cur=${cursorFp}|d=${distinctFp}|w=${withFp}|pii=${includePii ? 1 : 0}${flattenFp}${encodingFp}${this.globalFilterCacheSegment()}`;
|
|
2607
2723
|
const params = [];
|
|
2608
2724
|
const buildSql = (freshParams) => {
|
|
2609
2725
|
// Fresh build: generates SQL and populates freshParams
|
|
@@ -2780,58 +2896,66 @@ class QueryInterface {
|
|
|
2780
2896
|
};
|
|
2781
2897
|
}
|
|
2782
2898
|
// -------------------------------------------------------------------------
|
|
2783
|
-
// findManyStream, async
|
|
2899
|
+
// findManyStream / findManyStreamBatches, async iterables using PostgreSQL cursors
|
|
2784
2900
|
// -------------------------------------------------------------------------
|
|
2785
2901
|
/**
|
|
2786
|
-
*
|
|
2787
|
-
* Returns an AsyncIterable that yields individual rows, fetching in batches internally.
|
|
2902
|
+
* Build the one row parser a whole drain uses.
|
|
2788
2903
|
*
|
|
2789
|
-
*
|
|
2790
|
-
*
|
|
2791
|
-
*
|
|
2792
|
-
*
|
|
2793
|
-
* method fall back to the full cursor path.
|
|
2904
|
+
* Shared by {@link findManyStream} and {@link findManyStreamBatches} so the
|
|
2905
|
+
* two can never disagree about a row's SHAPE while agreeing about its
|
|
2906
|
+
* contents: the flatten plan, the positional/object relation decode and the
|
|
2907
|
+
* PII projection are all decided here, once, before a statement is issued.
|
|
2794
2908
|
*
|
|
2795
|
-
*
|
|
2796
|
-
*
|
|
2797
|
-
*
|
|
2798
|
-
*
|
|
2799
|
-
*
|
|
2909
|
+
* The plan is a pure function of the schema, the `with` shape and
|
|
2910
|
+
* `includePii`, never of `limit`, so the batch-size override the speculative
|
|
2911
|
+
* fetch applies cannot change it and the parser always matches the emitted
|
|
2912
|
+
* SQL. Reading the raw `includePii` sentinel with `=== true` here would have
|
|
2913
|
+
* quietly planned a no-PII parser over a with-PII statement, which is why it
|
|
2914
|
+
* goes through `resolveUnsafeFlag` on every path, `with` clause or not.
|
|
2915
|
+
*/
|
|
2916
|
+
makeStreamRowParser(args) {
|
|
2917
|
+
const streamPii = (0, types_js_1.resolveUnsafeFlag)(args?.includePii, 'includePii');
|
|
2918
|
+
// THIS CALL IS ORDER-CRITICAL, and it is why the resolution is a method
|
|
2919
|
+
// rather than an expression inlined into the two build sites. The parser is
|
|
2920
|
+
// built HERE, before `streamRaw` reaches `buildFindMany` and pins the
|
|
2921
|
+
// encoding itself, so without this the parser would be planned against the
|
|
2922
|
+
// CLIENT default while the statement was built against the query's
|
|
2923
|
+
// override: positional SQL decoded as objects, or the reverse. Both build
|
|
2924
|
+
// from the same `args`, so both resolve to the same value; the invalid-value
|
|
2925
|
+
// throw also lands here first, before a connection is taken.
|
|
2926
|
+
this.resolveJsonEncoding(args?.jsonEncoding);
|
|
2927
|
+
if (!args?.with) {
|
|
2928
|
+
const table = this.table;
|
|
2929
|
+
return (row) => this.parseRow(row, table);
|
|
2930
|
+
}
|
|
2931
|
+
const streamFlattenPlan = this.planFlatten(args, streamPii);
|
|
2932
|
+
return this.makeNestedParser(args.with, streamPii, streamFlattenPlan);
|
|
2933
|
+
}
|
|
2934
|
+
/**
|
|
2935
|
+
* Everything a stream does to the DATABASE, and nothing it does to a row.
|
|
2800
2936
|
*
|
|
2801
|
-
*
|
|
2802
|
-
*
|
|
2803
|
-
*
|
|
2804
|
-
*
|
|
2805
|
-
* speculative fetch and the cursor then run on the caller's connection
|
|
2806
|
-
* inside the caller's transaction (the cursor path issues no BEGIN/COMMIT of
|
|
2807
|
-
* its own and releases nothing, so the caller's transaction is intact when
|
|
2808
|
-
* iteration finishes).
|
|
2937
|
+
* {@link findManyStream} and {@link findManyStreamBatches} are the same drain
|
|
2938
|
+
* handed out at two granularities, so the statement they issue, when the
|
|
2939
|
+
* cursor opens, and how the connection is released all live here once. Only
|
|
2940
|
+
* the yielding differs, which is the entire point of having both.
|
|
2809
2941
|
*
|
|
2810
|
-
*
|
|
2811
|
-
*
|
|
2812
|
-
*
|
|
2813
|
-
*
|
|
2814
|
-
*
|
|
2815
|
-
*
|
|
2942
|
+
* Parsing deliberately stays in the callers: they parse at different
|
|
2943
|
+
* granularities, and the per-row method must keep parsing LAZILY, one row at
|
|
2944
|
+
* a time, exactly as it always has. Hoisting the parse in here would make a
|
|
2945
|
+
* consumer that breaks after the first row pay for the rest of its batch.
|
|
2946
|
+
*
|
|
2947
|
+
* `action` is the tag query events carry. It is a parameter rather than a
|
|
2948
|
+
* constant so each public method reports its own name instead of the name of
|
|
2949
|
+
* whichever one happens to be implemented over the other.
|
|
2950
|
+
*
|
|
2951
|
+
* An EMPTY batch is never yielded: the dialect's cursor loop breaks on a
|
|
2952
|
+
* zero-row FETCH, and the speculative path below yields nothing at all when
|
|
2953
|
+
* the result set is empty. Callers may therefore treat a yielded batch as
|
|
2954
|
+
* non-empty.
|
|
2816
2955
|
*/
|
|
2817
|
-
async *
|
|
2956
|
+
async *streamRaw(args, action) {
|
|
2818
2957
|
const batchSize = Math.max(1, Math.floor(Number(args?.batchSize ?? 1000)));
|
|
2819
|
-
|
|
2820
|
-
// Build the positional-aware relation parser once for the whole stream.
|
|
2821
|
-
// Same flatten plan buildFindMany compiles below. The plan is a pure
|
|
2822
|
-
// function of the schema, the `with` shape and `includePii`, never of
|
|
2823
|
-
// `limit`, so the batch-size override the speculative fetch applies cannot
|
|
2824
|
-
// change it, and the stream's parser matches the emitted SQL.
|
|
2825
|
-
// Resolved once for the whole stream: the flatten plan and the row parser
|
|
2826
|
-
// MUST agree with the SQL buildFindMany emits below, and reading the raw
|
|
2827
|
-
// sentinel with `=== true` here would have quietly planned a no-PII parser
|
|
2828
|
-
// over a with-PII statement.
|
|
2829
|
-
const streamPii = (0, types_js_1.resolveUnsafeFlag)(args?.includePii, 'includePii');
|
|
2830
|
-
const streamFlattenPlan = hasRelations ? this.planFlatten(args, streamPii) : null;
|
|
2831
|
-
const parseWith = hasRelations
|
|
2832
|
-
? this.makeNestedParser(args.with, streamPii, streamFlattenPlan)
|
|
2833
|
-
: null;
|
|
2834
|
-
this.currentAction = 'findManyStream';
|
|
2958
|
+
this.currentAction = action;
|
|
2835
2959
|
// Streaming is ALREADY immune to the generic-plan cliff: the speculative
|
|
2836
2960
|
// fetch has never passed a prepared name, and the cursor path runs through
|
|
2837
2961
|
// DECLARE, so neither statement enters the plan cache. `preparedNameFor` is
|
|
@@ -2874,10 +2998,9 @@ class QueryInterface {
|
|
|
2874
2998
|
});
|
|
2875
2999
|
const speculativeResult = await this.queryWithTimeout(speculativeDeferred.sql, speculativeDeferred.params, args?.timeout, streamPreparedName);
|
|
2876
3000
|
if (speculativeResult.rows.length <= batchSize) {
|
|
2877
|
-
// Small drain,
|
|
2878
|
-
|
|
2879
|
-
yield
|
|
2880
|
-
}
|
|
3001
|
+
// Small drain, hand over the whole result and return, no cursor needed.
|
|
3002
|
+
if (speculativeResult.rows.length > 0)
|
|
3003
|
+
yield speculativeResult.rows;
|
|
2881
3004
|
return;
|
|
2882
3005
|
}
|
|
2883
3006
|
}
|
|
@@ -2886,7 +3009,7 @@ class QueryInterface {
|
|
|
2886
3009
|
// Acquire a dedicated connection: cursors require a single connection in a
|
|
2887
3010
|
// transaction. The dialect owns the streaming SQL (Postgres: BEGIN → DECLARE
|
|
2888
3011
|
// … NO SCROLL CURSOR FOR → FETCH n → CLOSE → COMMIT, ROLLBACK on error); we
|
|
2889
|
-
// just
|
|
3012
|
+
// just yield the row batches it produces.
|
|
2890
3013
|
//
|
|
2891
3014
|
// Inside a caller-owned transaction there is nothing to check out: the
|
|
2892
3015
|
// transaction-scoped pool pins every query to the transaction's own
|
|
@@ -2898,13 +3021,9 @@ class QueryInterface {
|
|
|
2898
3021
|
query: async (text, values) => (await this.pool.query(text, values)),
|
|
2899
3022
|
};
|
|
2900
3023
|
try {
|
|
2901
|
-
|
|
3024
|
+
yield* this.dialect.openStream(conn, deferred.sql, deferred.params, batchSize, {
|
|
2902
3025
|
ambientTransaction: this.txScoped,
|
|
2903
|
-
})
|
|
2904
|
-
for (const row of batch) {
|
|
2905
|
-
yield (parseWith ? parseWith(row) : this.parseRow(row, this.table));
|
|
2906
|
-
}
|
|
2907
|
-
}
|
|
3026
|
+
});
|
|
2908
3027
|
}
|
|
2909
3028
|
catch (err) {
|
|
2910
3029
|
// Wrap pg constraint errors so streaming surfaces typed errors like the rest of the API
|
|
@@ -2914,6 +3033,97 @@ class QueryInterface {
|
|
|
2914
3033
|
client?.release();
|
|
2915
3034
|
}
|
|
2916
3035
|
}
|
|
3036
|
+
/**
|
|
3037
|
+
* Stream rows from a findMany query using PostgreSQL cursors, one BATCH of
|
|
3038
|
+
* rows at a time.
|
|
3039
|
+
*
|
|
3040
|
+
* The same drain {@link findManyStream} performs, the same statements, the
|
|
3041
|
+
* same cursor, the same rows in the same order, handed out as arrays instead
|
|
3042
|
+
* of one row at a time. That is worth having because the per-row form costs
|
|
3043
|
+
* one promise resolution and one microtask turn PER ROW: measured over 50,000
|
|
3044
|
+
* rows on a local PostgreSQL, per-row yielding costs ~7 ms (~140 ns/row) that
|
|
3045
|
+
* batch yielding does not, which is roughly half of the streaming overhead
|
|
3046
|
+
* over a hand-written cursor loop. Nothing else about the two paths differs,
|
|
3047
|
+
* so the entire saving is the yielding.
|
|
3048
|
+
*
|
|
3049
|
+
* Prefer this whenever the consumer can work on an array; keep
|
|
3050
|
+
* {@link findManyStream} when a row at a time is what the code actually wants,
|
|
3051
|
+
* since flattening a batch by hand costs exactly what it saves.
|
|
3052
|
+
*
|
|
3053
|
+
* A yielded batch is never empty, and its length is NOT a contract: it is at
|
|
3054
|
+
* most `batchSize`, the final batch is usually shorter, and a result set that
|
|
3055
|
+
* fits within one batch arrives as a single array from the speculative fetch
|
|
3056
|
+
* with no cursor involved. Do not use batch boundaries to infer anything
|
|
3057
|
+
* about the data.
|
|
3058
|
+
*
|
|
3059
|
+
* Every other streaming behaviour is shared and documented on
|
|
3060
|
+
* {@link findManyStream}: the speculative fast path, the cursor path and its
|
|
3061
|
+
* cleanup, the snapshot semantics, and early `break`.
|
|
3062
|
+
*
|
|
3063
|
+
* @example
|
|
3064
|
+
* ```ts
|
|
3065
|
+
* for await (const batch of db.users.findManyStreamBatches({ where: { orgId: 1 }, batchSize: 500 })) {
|
|
3066
|
+
* await sink.writeAll(batch);
|
|
3067
|
+
* }
|
|
3068
|
+
* ```
|
|
3069
|
+
*/
|
|
3070
|
+
async *findManyStreamBatches(args) {
|
|
3071
|
+
const parse = this.makeStreamRowParser(args);
|
|
3072
|
+
for await (const batch of this.streamRaw(args, 'findManyStreamBatches')) {
|
|
3073
|
+
const parsed = new Array(batch.length);
|
|
3074
|
+
for (let i = 0; i < batch.length; i++) {
|
|
3075
|
+
parsed[i] = parse(batch[i]);
|
|
3076
|
+
}
|
|
3077
|
+
yield parsed;
|
|
3078
|
+
}
|
|
3079
|
+
}
|
|
3080
|
+
/**
|
|
3081
|
+
* Stream rows from a findMany query using PostgreSQL cursors.
|
|
3082
|
+
* Returns an AsyncIterable that yields individual rows, fetching in batches internally.
|
|
3083
|
+
*
|
|
3084
|
+
* See {@link findManyStreamBatches} for the same drain yielded a batch at a
|
|
3085
|
+
* time, which is measurably cheaper when the consumer can take an array.
|
|
3086
|
+
*
|
|
3087
|
+
* **Speculative fast-path:** Before opening a cursor, issues a single
|
|
3088
|
+
* `SELECT ... LIMIT batchSize+1`. If the result fits within `batchSize`,
|
|
3089
|
+
* all rows are yielded immediately with zero cursor overhead (no BEGIN /
|
|
3090
|
+
* DECLARE / CLOSE / COMMIT). Only when the result overflows does the
|
|
3091
|
+
* method fall back to the full cursor path.
|
|
3092
|
+
*
|
|
3093
|
+
* **Cursor path:** Uses DECLARE CURSOR within a dedicated transaction on a
|
|
3094
|
+
* single pooled connection. The cursor is CLOSEd (in the dialect's `finally`)
|
|
3095
|
+
* and the connection released both when iteration completes normally and when
|
|
3096
|
+
* it ends early (`break` from `for await`). An error mid-stream skips the
|
|
3097
|
+
* CLOSE and rolls back instead, which drops the cursor with the transaction.
|
|
3098
|
+
*
|
|
3099
|
+
* **Snapshot semantics note:** Outside a transaction the speculative
|
|
3100
|
+
* fast-path runs unwrapped, and an overflow opens the cursor in its own
|
|
3101
|
+
* transaction, so the two fetches span two separate snapshots. Wrapping the
|
|
3102
|
+
* call in `$transaction` gives strict single-snapshot semantics: both the
|
|
3103
|
+
* speculative fetch and the cursor then run on the caller's connection
|
|
3104
|
+
* inside the caller's transaction (the cursor path issues no BEGIN/COMMIT of
|
|
3105
|
+
* its own and releases nothing, so the caller's transaction is intact when
|
|
3106
|
+
* iteration finishes).
|
|
3107
|
+
*
|
|
3108
|
+
* @example
|
|
3109
|
+
* ```ts
|
|
3110
|
+
* for await (const user of db.users.findManyStream({ where: { orgId: 1 }, batchSize: 500 })) {
|
|
3111
|
+
* process.stdout.write(`${user.email}\n`);
|
|
3112
|
+
* }
|
|
3113
|
+
* ```
|
|
3114
|
+
*/
|
|
3115
|
+
async *findManyStream(args) {
|
|
3116
|
+
const parse = this.makeStreamRowParser(args);
|
|
3117
|
+
// Parsed one row at a time, INSIDE the yield loop rather than a batch ahead
|
|
3118
|
+
// of it, so a consumer that breaks early still pays only for the rows it
|
|
3119
|
+
// took. That laziness is the reason this loop is not written over
|
|
3120
|
+
// `findManyStreamBatches`.
|
|
3121
|
+
for await (const batch of this.streamRaw(args, 'findManyStream')) {
|
|
3122
|
+
for (let i = 0; i < batch.length; i++) {
|
|
3123
|
+
yield parse(batch[i]);
|
|
3124
|
+
}
|
|
3125
|
+
}
|
|
3126
|
+
}
|
|
2917
3127
|
// -------------------------------------------------------------------------
|
|
2918
3128
|
// findFirst, like findMany but returns a single row or null
|
|
2919
3129
|
// -------------------------------------------------------------------------
|
|
@@ -3337,7 +3547,11 @@ class QueryInterface {
|
|
|
3337
3547
|
* strategy, silently and byte-identically):
|
|
3338
3548
|
* - the resolved strategy is not `'flatten'`;
|
|
3339
3549
|
* - `jsonEncoding: 'positional'` (a flattened relation emits no JSON at all,
|
|
3340
|
-
* so the two
|
|
3550
|
+
* so the two are not composed in this version). NOTE that this is the
|
|
3551
|
+
* PostgreSQL DEFAULT, so on Postgres `'flatten'` engages only when the
|
|
3552
|
+
* caller ALSO passes `jsonEncoding: 'object'`. The fallback is silent and
|
|
3553
|
+
* byte-identical, and `warnFlattenBlocked` names the encoding and the
|
|
3554
|
+
* escape hatch in dev;
|
|
3341
3555
|
* - the dialect owns relation-subquery generation
|
|
3342
3556
|
* (`dialect.buildRelationSubquery`, i.e. SQL Server's `FOR JSON PATH`);
|
|
3343
3557
|
* - `distinct` (the `DISTINCT ON` rewrite re-orders in an outer wrapper, and
|
|
@@ -3362,8 +3576,9 @@ class QueryInterface {
|
|
|
3362
3576
|
// Query-level refusals: the whole plan is off, so name the reason once for
|
|
3363
3577
|
// the query rather than once per relation (relations.ts warns per relation
|
|
3364
3578
|
// for the eligibility rules it owns).
|
|
3365
|
-
const queryLevelBlock = this.
|
|
3366
|
-
? "`jsonEncoding: 'positional'` is active, and a flattened relation emits no
|
|
3579
|
+
const queryLevelBlock = this.currentJsonEncoding === 'positional'
|
|
3580
|
+
? "`jsonEncoding: 'positional'` is active (the PostgreSQL default), and a flattened relation emits no " +
|
|
3581
|
+
"JSON to encode. Pass `jsonEncoding: 'object'` alongside the strategy to get the flatten plan"
|
|
3367
3582
|
: this.dialect.buildRelationSubquery
|
|
3368
3583
|
? `the ${this.dialect.name} dialect generates relation subqueries itself`
|
|
3369
3584
|
: args?.distinct && args.distinct.length > 0
|
|
@@ -3682,9 +3897,18 @@ class QueryInterface {
|
|
|
3682
3897
|
* Build the decode plan for one exact column list, and remember it as this
|
|
3683
3898
|
* table's most recent shape so the next row of the same result set hits the
|
|
3684
3899
|
* fast path in {@link parseRow}.
|
|
3900
|
+
*
|
|
3901
|
+
* The shape key's delimiter is NUL because it is the one byte a Postgres
|
|
3902
|
+
* identifier cannot contain, even quoted, so no table or column name can
|
|
3903
|
+
* forge a collision. It MUST be written as the six-character escape, never
|
|
3904
|
+
* as a literal NUL byte: a raw NUL makes byte-oriented tools classify this
|
|
3905
|
+
* file as binary, and `grep` then reports ZERO matches for a term that is
|
|
3906
|
+
* present rather than saying it declined to look. This is the largest file
|
|
3907
|
+
* in the repo, and that silent empty result has already sent more than one
|
|
3908
|
+
* search down the wrong path.
|
|
3685
3909
|
*/
|
|
3686
3910
|
buildRowDecodePlan(table, meta, keys) {
|
|
3687
|
-
const shapeKey = `${table}
|
|
3911
|
+
const shapeKey = `${table}\u0000${keys.join('\u0000')}`;
|
|
3688
3912
|
let plan = this.rowPlanCache.get(shapeKey);
|
|
3689
3913
|
if (plan === undefined) {
|
|
3690
3914
|
const reverseMap = meta.reverseColumnMap;
|
|
@@ -7,7 +7,7 @@ import type { Dialect } from '../dialect.js';
|
|
|
7
7
|
import type { PgCompatPool, PgCompatQueryResult } from '../pg-types.js';
|
|
8
8
|
import type { SchemaMetadata } from '../schema.js';
|
|
9
9
|
import type { QueryInterface } from './builder.js';
|
|
10
|
-
import type { GlobalFilters, RelationLoadStrategy } from './types.js';
|
|
10
|
+
import type { GlobalFilters, JsonEncoding, RelationLoadStrategy } from './types.js';
|
|
11
11
|
/**
|
|
12
12
|
* Runs a SQL statement and resolves its raw result. Passed to a
|
|
13
13
|
* {@link DeferredQuery.reselect} plan so it can run the write and the follow-up
|
|
@@ -245,12 +245,18 @@ export interface QueryInterfaceOptions {
|
|
|
245
245
|
*/
|
|
246
246
|
autoRoundTripMs?: number;
|
|
247
247
|
/**
|
|
248
|
-
* How nested-relation subqueries encode each row's JSON: `'object'`
|
|
249
|
-
* `json_build_object`) or `'positional'` (`json_build_array`, key-less, see
|
|
250
|
-
* {@link Dialect.buildJsonArray}).
|
|
251
|
-
*
|
|
248
|
+
* How nested-relation subqueries encode each row's JSON: `'object'`
|
|
249
|
+
* (`json_build_object`) or `'positional'` (`json_build_array`, key-less, see
|
|
250
|
+
* {@link Dialect.buildJsonArray}).
|
|
251
|
+
*
|
|
252
|
+
* DEFAULTS BY ENGINE: `'positional'` on PostgreSQL, `'object'` on every other
|
|
253
|
+
* dialect. Positional is Postgres-only; setting it elsewhere makes a `with`
|
|
254
|
+
* clause throw `UnsupportedFeatureError` (E017).
|
|
255
|
+
*
|
|
256
|
+
* Overridable per query (`findMany({ jsonEncoding })`), which is also how a
|
|
257
|
+
* caller re-enables `relationLoadStrategy: 'flatten'` on PostgreSQL.
|
|
252
258
|
*/
|
|
253
|
-
jsonEncoding?:
|
|
259
|
+
jsonEncoding?: JsonEncoding;
|
|
254
260
|
/**
|
|
255
261
|
* Automatic WHERE filters keyed by table accessor, AND-merged into every
|
|
256
262
|
* query on that table and every relation subquery targeting it (soft-delete /
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* `import { … } from './query/index.js'` is a drop-in replacement for the
|
|
6
6
|
* former monolithic `import { … } from './query.js'`.
|
|
7
7
|
*/
|
|
8
|
-
export type { AggregateArgs, AggregateResult, ArrayFilter, ColumnRef, ConnectOrCreateOp, CountArgs, CreateArgs, CreateDataInput, CreateManyArgs, DeleteArgs, DeleteManyArgs, FieldResult, FindManyArgs, FindManyStreamArgs, FindUniqueArgs, GlobalFilters, GroupByAggregateSpec, GroupByArgs, GroupByDistinctOn, GroupByResult, HavingClause, JsonFilter, JsonPathAggregateTarget, JsonPathGroupKey, JsonPathOrderBy, NestedCreateOp, NestedUpdateOp, NestedUpdateOpItem, NestedUpsertOpItem, OmitResult, OrderByClause, OrderByObject, OrderDirection, QueryResult, RelationDescriptor, RelationFilter, RelationLoadStrategy, RelationPickBy, RelationPickOrderBy, ResolvedSkipGlobalFilters, SelectResult, SkipGlobalFilters, TextSearchFilter, TypedWithClause, UpdateArgs, UpdateDataInput, UpdateInput, UpdateManyArgs, UpdateOperatorInput, UpsertArgs, VectorDistanceFilter, VectorFilter, VectorMetric, VectorOrderBy, VectorOrderByDistance, WhereClause, WhereOperator, WhereValue, WithClause, WithOptions, WithOrderByObject, WithResult, } from './types.js';
|
|
8
|
+
export type { AggregateArgs, AggregateResult, ArrayFilter, ColumnRef, ConnectOrCreateOp, CountArgs, CreateArgs, CreateDataInput, CreateManyArgs, DeleteArgs, DeleteManyArgs, FieldResult, FindManyArgs, FindManyStreamArgs, FindUniqueArgs, GlobalFilters, GroupByAggregateSpec, GroupByArgs, GroupByDistinctOn, GroupByResult, HavingClause, JsonEncoding, JsonFilter, JsonPathAggregateTarget, JsonPathGroupKey, JsonPathOrderBy, NestedCreateOp, NestedUpdateOp, NestedUpdateOpItem, NestedUpsertOpItem, OmitResult, OrderByClause, OrderByObject, OrderDirection, QueryResult, RelationDescriptor, RelationFilter, RelationLoadStrategy, RelationPickBy, RelationPickOrderBy, ResolvedSkipGlobalFilters, SelectResult, SkipGlobalFilters, TextSearchFilter, TypedWithClause, UpdateArgs, UpdateDataInput, UpdateInput, UpdateManyArgs, UpdateOperatorInput, UpsertArgs, VectorDistanceFilter, VectorFilter, VectorMetric, VectorOrderBy, VectorOrderByDistance, WhereClause, WhereOperator, WhereValue, WithClause, WithOptions, WithOrderByObject, WithResult, } from './types.js';
|
|
9
9
|
export type { BuiltStatement, BulkInsertStatementInput, ColumnDefinitionInput, ColumnTypeInput, CreateIndexStatementInput, CreateTableStatementInput, Dialect, InsertStatementInput, UpsertStatementInput, } from '../dialect.js';
|
|
10
10
|
export { postgresDialect } from '../dialect.js';
|
|
11
11
|
export type { OptionKind, OptionTable } from './option-surface.js';
|
|
@@ -73,6 +73,10 @@ exports.FIND_UNIQUE_OPTIONS = {
|
|
|
73
73
|
skipGlobalFilters: 'native',
|
|
74
74
|
includePii: 'native',
|
|
75
75
|
forceCustomPlan: 'native',
|
|
76
|
+
// `'object' | 'positional'`: a wire ENCODING, not a name. It cannot reach the
|
|
77
|
+
// schema's naming space, so THE ONE RULE puts it here rather than in the
|
|
78
|
+
// hand-translated set.
|
|
79
|
+
jsonEncoding: 'native',
|
|
76
80
|
};
|
|
77
81
|
exports.FIND_MANY_OPTIONS = {
|
|
78
82
|
where: 'prisma',
|
|
@@ -92,6 +96,8 @@ exports.FIND_MANY_OPTIONS = {
|
|
|
92
96
|
warnOnUnlimited: 'native',
|
|
93
97
|
includePii: 'native',
|
|
94
98
|
forceCustomPlan: 'native',
|
|
99
|
+
// See FIND_UNIQUE_OPTIONS: an encoding name carries no schema name.
|
|
100
|
+
jsonEncoding: 'native',
|
|
95
101
|
};
|
|
96
102
|
exports.FIND_MANY_STREAM_OPTIONS = {
|
|
97
103
|
...exports.FIND_MANY_OPTIONS,
|
|
@@ -599,7 +599,23 @@ export interface FindUniqueArgs<T, R extends object = {}, W extends TypedWithCla
|
|
|
599
599
|
includePii?: Unsafe;
|
|
600
600
|
/** Plan this query with its real parameter values. See {@link FindManyArgs.forceCustomPlan}. */
|
|
601
601
|
forceCustomPlan?: boolean;
|
|
602
|
+
/** Override the relation JSON wire encoding for this query. See {@link FindManyArgs.jsonEncoding}. */
|
|
603
|
+
jsonEncoding?: JsonEncoding;
|
|
602
604
|
}
|
|
605
|
+
/**
|
|
606
|
+
* How a nested relation's rows are encoded on the wire.
|
|
607
|
+
*
|
|
608
|
+
* - `'object'`, `json_build_object('id', …, 'title', …)`: every key name is
|
|
609
|
+
* repeated on every row. Readable in a query log, and the only shape the
|
|
610
|
+
* non-PostgreSQL engines emit.
|
|
611
|
+
* - `'positional'`, `json_build_array(…)`: the keys are dropped and each row is
|
|
612
|
+
* a bare array whose positions are mapped back client-side. Fewer bytes and
|
|
613
|
+
* materially less server CPU, and the parsed rows are identical.
|
|
614
|
+
*
|
|
615
|
+
* PostgreSQL defaults to `'positional'`; every other engine is `'object'` and
|
|
616
|
+
* refuses `'positional'` with {@link UnsupportedFeatureError} (E017).
|
|
617
|
+
*/
|
|
618
|
+
export type JsonEncoding = 'object' | 'positional';
|
|
603
619
|
export interface FindManyArgs<T, R extends object = {}, W extends TypedWithClause<R> = TypedWithClause<R>, S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined> {
|
|
604
620
|
/** Row filter. Keys are checked against `T` and `R` (see {@link WhereClause}). */
|
|
605
621
|
where?: WhereClause<T, R>;
|
|
@@ -788,6 +804,37 @@ export interface FindManyArgs<T, R extends object = {}, W extends TypedWithClaus
|
|
|
788
804
|
* made. Omitting it (or `false`) is accepted everywhere.
|
|
789
805
|
*/
|
|
790
806
|
forceCustomPlan?: boolean;
|
|
807
|
+
/**
|
|
808
|
+
* Override {@link TurbineConfig.jsonEncoding} for this query: how a `with`
|
|
809
|
+
* clause's relation rows are encoded on the wire.
|
|
810
|
+
*
|
|
811
|
+
* No effect on a query without a `with` clause, which emits no relation JSON
|
|
812
|
+
* at all, and none on `relationLoadStrategy: 'batched'`, whose follow-up
|
|
813
|
+
* queries are flat.
|
|
814
|
+
*
|
|
815
|
+
* PostgreSQL defaults to `'positional'`, which drops the repeated key names
|
|
816
|
+
* from every relation row. Measured on a 50-parent, ~10-child-per-parent read
|
|
817
|
+
* against local PostgreSQL 17: server time 0.685 ms → 0.350 ms and 152 KB →
|
|
818
|
+
* 100 KB on the wire, for byte-identical parsed rows. Every other engine
|
|
819
|
+
* defaults to `'object'` and refuses `'positional'` with
|
|
820
|
+
* {@link UnsupportedFeatureError} (E017).
|
|
821
|
+
*
|
|
822
|
+
* Two reasons to ask for `'object'` on PostgreSQL:
|
|
823
|
+
*
|
|
824
|
+
* 1. READABILITY. A logged positional statement carries no key names, so a
|
|
825
|
+
* query log or a Studio SQL pane shows `json_build_array(t0."id", …)`
|
|
826
|
+
* where the object form names each field.
|
|
827
|
+
* 2. `relationLoadStrategy: 'flatten'`. A flattened relation emits no JSON
|
|
828
|
+
* to encode, so the two are not composed: with `'positional'` active the
|
|
829
|
+
* flatten plan is refused for the whole query and every relation falls
|
|
830
|
+
* back to the correlated subquery (same rows, different plan). Setting
|
|
831
|
+
* `jsonEncoding: 'object'` alongside `'flatten'` is what makes the
|
|
832
|
+
* flatten plan run.
|
|
833
|
+
*
|
|
834
|
+
* A value that is neither `'object'` nor `'positional'` throws
|
|
835
|
+
* {@link ValidationError} (E003) rather than being ignored.
|
|
836
|
+
*/
|
|
837
|
+
jsonEncoding?: JsonEncoding;
|
|
791
838
|
}
|
|
792
839
|
export interface FindManyStreamArgs<T, R extends object = {}, W extends TypedWithClause<R> = TypedWithClause<R>, S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined> extends FindManyArgs<T, R, W, S, O> {
|
|
793
840
|
/**
|
|
@@ -15,7 +15,7 @@ import { ValidationError } from '../errors.js';
|
|
|
15
15
|
import type { PgCompatQueryResult } from '../pg-types.js';
|
|
16
16
|
import type { RelationDef, SchemaMetadata, TableMetadata } from '../schema.js';
|
|
17
17
|
import type { TemporalInfinityReading } from './deferred.js';
|
|
18
|
-
import type { ArrayFilter, ColumnRef, GlobalFilters, JsonFilter, JsonPathOrderBy, ResolvedSkipGlobalFilters, TextSearchFilter, VectorFilter, WhereClause, WhereOperator } from './types.js';
|
|
18
|
+
import type { ArrayFilter, ColumnRef, GlobalFilters, JsonEncoding, JsonFilter, JsonPathOrderBy, ResolvedSkipGlobalFilters, TextSearchFilter, VectorFilter, WhereClause, WhereOperator } from './types.js';
|
|
19
19
|
import { type SqlCacheEntry } from './utils.js';
|
|
20
20
|
import { type WhereHost, type WhereRecord } from './where-compile.js';
|
|
21
21
|
/**
|
|
@@ -130,7 +130,16 @@ export interface BuilderCtx {
|
|
|
130
130
|
mutationInsertId(result: PgCompatQueryResult): unknown;
|
|
131
131
|
acquireSql(cacheKey: string, build: (params: unknown[]) => string): SqlCacheEntry;
|
|
132
132
|
crossCheckCache(op: string, cacheKey: string, entry: SqlCacheEntry, build: (params: unknown[]) => string, collectedParams: unknown[]): void;
|
|
133
|
-
|
|
133
|
+
/**
|
|
134
|
+
* The relation JSON encoding of the query BEING BUILT: its own `jsonEncoding`
|
|
135
|
+
* arg, else the client's, which is `'positional'` on PostgreSQL and
|
|
136
|
+
* `'object'` on every other engine.
|
|
137
|
+
*
|
|
138
|
+
* A live getter on the concrete ctx (like `currentSkip`), not a value copied
|
|
139
|
+
* at construction, because it is now a per-query option. `readonly` here says
|
|
140
|
+
* the modules may not write it, not that it cannot change between builds.
|
|
141
|
+
*/
|
|
142
|
+
readonly jsonEncoding: JsonEncoding;
|
|
134
143
|
readonly camelDateFieldCache: Map<string, Set<string>>;
|
|
135
144
|
/**
|
|
136
145
|
* Per-table memo of `Object.entries(meta.relations)`. See
|