turbine-orm 0.49.0 → 0.50.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 +122 -39
- package/dist/cjs/adapters/cockroachdb.d.ts +39 -0
- package/dist/cjs/adapters/index.d.ts +110 -0
- package/dist/cjs/adapters/yugabytedb.d.ts +51 -0
- package/dist/cjs/cli/config.d.ts +181 -0
- package/dist/cjs/cli/config.js +32 -6
- package/dist/cjs/cli/destructive.d.ts +38 -0
- package/dist/cjs/cli/index.d.ts +359 -0
- package/dist/cjs/cli/index.js +228 -56
- package/dist/cjs/cli/loader.d.ts +61 -0
- package/dist/cjs/cli/mcp.d.ts +42 -0
- package/dist/cjs/cli/migrate.d.ts +356 -0
- package/dist/cjs/cli/migrate.js +131 -40
- package/dist/cjs/cli/observe-ui.d.ts +1 -0
- package/dist/cjs/cli/observe-ui.js +14 -5
- package/dist/cjs/cli/observe.d.ts +25 -0
- package/dist/cjs/cli/observe.js +49 -12
- package/dist/cjs/cli/pii-tags.d.ts +53 -0
- package/dist/cjs/cli/prisma-report.d.ts +33 -0
- package/dist/cjs/cli/prisma-report.js +73 -0
- package/dist/cjs/cli/prisma-resolve.d.ts +106 -0
- package/dist/cjs/cli/prisma-resolve.js +1 -0
- package/dist/cjs/cli/prisma-schema.d.ts +176 -0
- package/dist/cjs/cli/prisma-schema.js +82 -4
- package/dist/cjs/cli/rate-limit.d.ts +32 -0
- package/dist/cjs/cli/rate-limit.js +45 -0
- package/dist/cjs/cli/studio-demo.d.ts +43 -0
- package/dist/cjs/cli/studio-ui.generated.d.ts +1 -0
- package/dist/cjs/cli/studio.d.ts +207 -0
- package/dist/cjs/cli/studio.js +136 -71
- package/dist/cjs/cli/ui.d.ts +73 -0
- package/dist/cjs/cli/ui.js +51 -9
- package/dist/cjs/client.d.ts +837 -0
- package/dist/cjs/client.js +3 -0
- package/dist/cjs/dialect.d.ts +516 -0
- package/dist/cjs/dialect.js +37 -12
- package/dist/cjs/errors.d.ts +370 -0
- package/dist/cjs/generate.d.ts +137 -0
- package/dist/cjs/generate.js +39 -6
- package/dist/cjs/index-advisor.d.ts +153 -0
- package/dist/cjs/index-stats.d.ts +384 -0
- package/dist/cjs/index.d.ts +55 -0
- package/dist/cjs/index.js +7 -2
- package/dist/cjs/introspect.d.ts +269 -0
- package/dist/cjs/mssql.d.ts +232 -0
- package/dist/cjs/mssql.js +6 -0
- package/dist/cjs/mysql.d.ts +173 -0
- package/dist/cjs/mysql.js +16 -0
- package/dist/cjs/nested-write.d.ts +96 -0
- package/dist/cjs/nested-write.js +414 -24
- package/dist/cjs/observe.d.ts +115 -0
- package/dist/cjs/optional-peer-import.d.cts +72 -0
- package/dist/cjs/pipeline-submittable.d.ts +93 -0
- package/dist/cjs/pipeline.d.ts +71 -0
- package/dist/cjs/powdb-introspect.d.ts +84 -0
- package/dist/cjs/powdb.d.ts +931 -0
- package/dist/cjs/powdb.js +106 -21
- package/dist/cjs/powql.d.ts +592 -0
- package/dist/cjs/powql.js +42 -6
- package/dist/cjs/prisma-compat.d.ts +283 -0
- package/dist/cjs/prisma-compat.js +167 -9
- package/dist/cjs/query/aggregates.d.ts +92 -0
- package/dist/cjs/query/aggregates.js +7 -3
- package/dist/cjs/query/batched-loader.d.ts +193 -0
- package/dist/cjs/query/builder.d.ts +849 -0
- package/dist/cjs/query/builder.js +571 -65
- package/dist/cjs/query/compound-unique.d.ts +51 -0
- package/dist/cjs/query/deferred.d.ts +223 -0
- package/dist/cjs/query/filters.d.ts +201 -0
- package/dist/cjs/query/index.d.ts +14 -0
- package/dist/cjs/query/index.js +6 -1
- package/dist/cjs/query/relations.d.ts +609 -0
- package/dist/cjs/query/relations.js +693 -46
- package/dist/cjs/query/types.d.ts +1300 -0
- package/dist/cjs/query/utils.d.ts +209 -0
- package/dist/cjs/query/utils.js +208 -1
- package/dist/cjs/query/warn-registry.d.ts +68 -0
- package/dist/cjs/query/warn-registry.js +9 -0
- package/dist/cjs/query/where-compile.d.ts +139 -0
- package/dist/cjs/query/where.d.ts +548 -0
- package/dist/cjs/query/where.js +58 -22
- package/dist/cjs/query/writes.d.ts +172 -0
- package/dist/cjs/query/writes.js +105 -12
- package/dist/cjs/realtime.d.ts +70 -0
- package/dist/cjs/schema-builder.d.ts +354 -0
- package/dist/cjs/schema-metadata.d.ts +83 -0
- package/dist/cjs/schema-sql.d.ts +217 -0
- package/dist/cjs/schema-sql.js +23 -5
- package/dist/cjs/schema.d.ts +356 -0
- package/dist/cjs/schema.js +125 -0
- package/dist/cjs/seed.d.ts +15 -0
- package/dist/cjs/serverless.d.ts +142 -0
- package/dist/cjs/sqlite.d.ts +143 -0
- package/dist/cjs/sqlite.js +4 -0
- package/dist/cjs/typed-sql.d.ts +102 -0
- package/dist/cli/config.d.ts +18 -4
- package/dist/cli/config.js +31 -6
- package/dist/cli/index.d.ts +123 -0
- package/dist/cli/index.js +223 -58
- package/dist/cli/migrate.d.ts +59 -10
- package/dist/cli/migrate.js +128 -41
- package/dist/cli/observe-ui.d.ts +1 -1
- package/dist/cli/observe-ui.js +14 -5
- package/dist/cli/observe.d.ts +7 -1
- package/dist/cli/observe.js +48 -12
- package/dist/cli/prisma-report.d.ts +14 -0
- package/dist/cli/prisma-report.js +72 -0
- package/dist/cli/prisma-resolve.d.ts +6 -0
- package/dist/cli/prisma-resolve.js +1 -0
- package/dist/cli/prisma-schema.d.ts +62 -2
- package/dist/cli/prisma-schema.js +81 -4
- package/dist/cli/rate-limit.d.ts +32 -0
- package/dist/cli/rate-limit.js +40 -0
- package/dist/cli/studio.d.ts +5 -5
- package/dist/cli/studio.js +135 -70
- package/dist/cli/ui.d.ts +1 -1
- package/dist/cli/ui.js +51 -9
- package/dist/client.d.ts +40 -0
- package/dist/client.js +3 -0
- package/dist/dialect.d.ts +17 -1
- package/dist/dialect.js +37 -12
- package/dist/generate.js +40 -7
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/mssql.js +6 -0
- package/dist/mysql.js +16 -0
- package/dist/nested-write.d.ts +2 -0
- package/dist/nested-write.js +415 -25
- package/dist/powdb.d.ts +4 -2
- package/dist/powdb.js +106 -21
- package/dist/powql.d.ts +5 -0
- package/dist/powql.js +42 -6
- package/dist/prisma-compat.d.ts +2 -0
- package/dist/prisma-compat.js +166 -8
- package/dist/query/aggregates.js +7 -3
- package/dist/query/builder.d.ts +292 -21
- package/dist/query/builder.js +570 -64
- package/dist/query/deferred.d.ts +39 -0
- package/dist/query/index.d.ts +1 -1
- package/dist/query/index.js +1 -1
- package/dist/query/relations.d.ts +173 -5
- package/dist/query/relations.js +688 -47
- package/dist/query/types.d.ts +123 -39
- package/dist/query/utils.d.ts +116 -0
- package/dist/query/utils.js +198 -0
- package/dist/query/warn-registry.d.ts +9 -0
- package/dist/query/warn-registry.js +9 -0
- package/dist/query/where.d.ts +38 -1
- package/dist/query/where.js +58 -23
- package/dist/query/writes.d.ts +42 -1
- package/dist/query/writes.js +104 -13
- package/dist/schema-sql.d.ts +14 -0
- package/dist/schema-sql.js +23 -5
- package/dist/schema.d.ts +38 -0
- package/dist/schema.js +123 -0
- package/dist/sqlite.js +4 -0
- package/package.json +77 -28
|
@@ -0,0 +1,849 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* turbine-orm — Query builder
|
|
3
|
+
*
|
|
4
|
+
* Each table accessor (db.users, db.posts, etc.) returns a QueryInterface<T>
|
|
5
|
+
* that builds parameterized SQL and executes it through the connection pool.
|
|
6
|
+
*
|
|
7
|
+
* Nested relations use json_build_object + json_agg subqueries for single-query
|
|
8
|
+
* resolution — a PostgreSQL-native approach that eliminates N+1 query patterns.
|
|
9
|
+
*
|
|
10
|
+
* Schema-driven: all column names, types, and relations come from introspected
|
|
11
|
+
* metadata — nothing is hardcoded.
|
|
12
|
+
*/
|
|
13
|
+
import type pg from 'pg';
|
|
14
|
+
import type { SchemaMetadata } from '../schema.js';
|
|
15
|
+
import type { AggregateArgs, AggregateResult, CountArgs, CreateArgs, CreateManyArgs, DeleteArgs, DeleteManyArgs, FindManyArgs, FindManyStreamArgs, FindUniqueArgs, GroupByArgs, GroupByResult, QueryResult, TypedWithClause, UpdateArgs, UpdateManyArgs, UpsertArgs, WithClause } from './types.js';
|
|
16
|
+
/**
|
|
17
|
+
* Marginal cost of keeping a to-one relation on the JOIN plan, per parent row.
|
|
18
|
+
*
|
|
19
|
+
* A to-one relation compiled into the join plan is a CORRELATED subquery: the
|
|
20
|
+
* engine re-evaluates it once per parent row, so the join plan costs roughly
|
|
21
|
+
* `AUTO_JOIN_PENALTY_MS_PER_ROW * parentRows` more CPU than one flat follow-up
|
|
22
|
+
* query, no matter how well indexed the correlation column is. The batched plan
|
|
23
|
+
* pays that back as a second statement, i.e. one extra round trip.
|
|
24
|
+
*
|
|
25
|
+
* Break-even is therefore, to a first approximation:
|
|
26
|
+
*
|
|
27
|
+
* parentRows = roundTripMs / AUTO_JOIN_PENALTY_MS_PER_ROW
|
|
28
|
+
*
|
|
29
|
+
* Measured (PostgreSQL 17, hasOne over a UNIQUE FK, 10K-row parent table,
|
|
30
|
+
* median of 15 reps per point) at two very different link speeds:
|
|
31
|
+
*
|
|
32
|
+
* link RTT penalty/row break-even observed crossover
|
|
33
|
+
* ─────────────────── ──────── ───────────── ──────────── ──────────────────
|
|
34
|
+
* loopback TCP 0.118ms 0.000711ms ~166-236 between 200 and 400
|
|
35
|
+
* +1ms/direction 2.683ms 0.000717ms ~3744-3993 between 3000 and 5000
|
|
36
|
+
*
|
|
37
|
+
* The two things that matters most in that table: the per-row penalty is
|
|
38
|
+
* essentially IDENTICAL across the two links (it is a property of the plan, not
|
|
39
|
+
* the wire), while the break-even moved by 17x. So the break-even is a function
|
|
40
|
+
* of the deployment's round-trip time and NOTHING ELSE that is knowable at plan
|
|
41
|
+
* time. That is why this is expressed as a per-row cost and a round-trip time
|
|
42
|
+
* rather than as a hard-coded row count: a row count tuned on a Unix socket is
|
|
43
|
+
* off by ~20x for a cross-region deployment, and vice versa. Concretely, the
|
|
44
|
+
* previously shipped flat `1000` was simultaneously too HIGH on loopback
|
|
45
|
+
* (up to 1.44x slower than the better plan just under the cliff) and too LOW
|
|
46
|
+
* over a 2.7ms link (1.26x slower just above it).
|
|
47
|
+
*/
|
|
48
|
+
export declare const AUTO_JOIN_PENALTY_MS_PER_ROW = 0.0007;
|
|
49
|
+
/**
|
|
50
|
+
* Round-trip time assumed before this process has observed a real one, chosen
|
|
51
|
+
* as a typical same-region managed-Postgres latency (app and database in one
|
|
52
|
+
* region over TCP). It is stated as a LATENCY rather than a row count so the
|
|
53
|
+
* assumption is visible and re-derivable: at
|
|
54
|
+
* {@link AUTO_JOIN_PENALTY_MS_PER_ROW} it yields exactly the 1000-row default
|
|
55
|
+
* this heuristic has always shipped, so an unmeasured process behaves exactly
|
|
56
|
+
* as before.
|
|
57
|
+
*/
|
|
58
|
+
export declare const AUTO_ASSUMED_ROUND_TRIP_MS = 0.7;
|
|
59
|
+
/**
|
|
60
|
+
* Default parent-row ceiling under which `'auto'` keeps a to-one relation on
|
|
61
|
+
* the single-statement join plan: {@link AUTO_ASSUMED_ROUND_TRIP_MS} divided by
|
|
62
|
+
* {@link AUTO_JOIN_PENALTY_MS_PER_ROW}. Used until the process has measured its
|
|
63
|
+
* own round-trip time, and whenever measurement is unavailable.
|
|
64
|
+
*/
|
|
65
|
+
export declare const AUTO_TO_ONE_JOIN_MAX_ROWS: number;
|
|
66
|
+
/**
|
|
67
|
+
* Clamps on the MEASURED threshold (an explicit `autoToOneJoinMaxRows` is an
|
|
68
|
+
* instruction, not an estimate, and bypasses both).
|
|
69
|
+
*
|
|
70
|
+
* The lower clamp matters: on a very fast link the formula can drop the
|
|
71
|
+
* threshold to a few dozen rows, and the sweep shows the join plan winning by
|
|
72
|
+
* up to 1.83x on a handful of parent rows, where the second statement's fixed
|
|
73
|
+
* cost dwarfs everything. Holding the floor at 100 rows keeps those small
|
|
74
|
+
* queries on the join plan; the cost of doing so, in the band where batched has
|
|
75
|
+
* just started to win, is under 1.2x. The upper clamp is a sanity bound for a
|
|
76
|
+
* pathological latency reading (a 70ms measurement would otherwise ask for
|
|
77
|
+
* 100K rows).
|
|
78
|
+
*/
|
|
79
|
+
export declare const AUTO_TO_ONE_JOIN_ROWS_MIN = 100;
|
|
80
|
+
export declare const AUTO_TO_ONE_JOIN_ROWS_MAX = 100000;
|
|
81
|
+
export type { DeferredQuery, MiddlewareFn, QueryEvent, QueryEventListener, QueryInterfaceOptions, ReselectExecutor, } from './deferred.js';
|
|
82
|
+
import type { DeferredQuery, MiddlewareFn, QueryInterfaceOptions } from './deferred.js';
|
|
83
|
+
export declare class QueryInterface<T extends object, R extends object = {}> {
|
|
84
|
+
private readonly pool;
|
|
85
|
+
private readonly table;
|
|
86
|
+
private readonly schema;
|
|
87
|
+
private readonly tableMeta;
|
|
88
|
+
/**
|
|
89
|
+
* SQL template cache: cacheKey → SqlCacheEntry (sql + prepared statement name).
|
|
90
|
+
* Capacity is set once in the constructor from `options.sqlCacheSize`
|
|
91
|
+
* (default 1000). See {@link QueryInterfaceOptions.sqlCacheSize}.
|
|
92
|
+
*/
|
|
93
|
+
private readonly sqlTemplateCache;
|
|
94
|
+
/**
|
|
95
|
+
* Whether the most recent {@link acquireSql} call was a cache HIT. Read by
|
|
96
|
+
* {@link crossCheckCache} to decide whether to run the dev-mode lockstep
|
|
97
|
+
* cross-check. Safe as a single mutable flag: each `build*()` method calls
|
|
98
|
+
* `acquireSql` then `crossCheckCache` synchronously with no intervening
|
|
99
|
+
* `await` and no re-entrant `acquireSql` (relation subqueries are built
|
|
100
|
+
* inline, not through the top-level cache).
|
|
101
|
+
*/
|
|
102
|
+
private lastCacheHit;
|
|
103
|
+
private readonly middlewares;
|
|
104
|
+
private readonly defaultLimit?;
|
|
105
|
+
private readonly warnOnUnlimited;
|
|
106
|
+
private readonly utcTimestamps;
|
|
107
|
+
private readonly preparedStatementsEnabled;
|
|
108
|
+
/**
|
|
109
|
+
* Whether the SQL template cache is active. Set once in the constructor.
|
|
110
|
+
* Mutable (not `readonly`) only so {@link withSqlCacheDisabled} can flip it
|
|
111
|
+
* off around a single synchronous compile (see {@link explain}).
|
|
112
|
+
*/
|
|
113
|
+
private sqlCacheEnabled;
|
|
114
|
+
private readonly dialect;
|
|
115
|
+
/**
|
|
116
|
+
* Client-level default relation-loading strategy. When nothing is configured
|
|
117
|
+
* this is `'auto'` (the implicit default): per-relation, keep the single-
|
|
118
|
+
* statement join unless the introspected metadata proves a probe is unindexed,
|
|
119
|
+
* in which case that relation falls back to the batched loader. An explicit
|
|
120
|
+
* `'join'`/`'batched'` (client or query level) always wins.
|
|
121
|
+
*/
|
|
122
|
+
private readonly relationLoadStrategy;
|
|
123
|
+
/** Client-level default for {@link applyStableRelationOrder} (off unless configured). */
|
|
124
|
+
private readonly stableRelationOrder;
|
|
125
|
+
/**
|
|
126
|
+
* Client-level opt-in: apply an implicit primary-key ascending `ORDER BY` to a
|
|
127
|
+
* `findMany` that paginates (`limit`/`take`/`offset`) but declares no
|
|
128
|
+
* `orderBy`. OFF by default in core, see {@link applyImplicitPkOrdering}.
|
|
129
|
+
*/
|
|
130
|
+
private readonly implicitPkOrdering;
|
|
131
|
+
/**
|
|
132
|
+
* Explicitly configured parent-row ceiling for the `'auto'` to-one rule, or
|
|
133
|
+
* `undefined` to derive it from the observed round-trip time. See
|
|
134
|
+
* {@link autoToOneThreshold}.
|
|
135
|
+
*/
|
|
136
|
+
private readonly autoToOneJoinMaxRowsOption;
|
|
137
|
+
/**
|
|
138
|
+
* Deployment round-trip time in milliseconds, from which the to-one threshold
|
|
139
|
+
* is derived. See {@link autoToOneThreshold}.
|
|
140
|
+
*/
|
|
141
|
+
private readonly autoRoundTripMs;
|
|
142
|
+
/** Nested-relation JSON encoding: 'object' (default) or 'positional'. */
|
|
143
|
+
private readonly jsonEncoding;
|
|
144
|
+
/**
|
|
145
|
+
* Client-level automatic WHERE filters keyed by table accessor (soft-delete /
|
|
146
|
+
* multi-tenancy). AND-merged into every query on the keyed table and every
|
|
147
|
+
* relation subquery targeting it. Undefined when none are configured, in
|
|
148
|
+
* which case every path is byte-identical to the pre-0.28 behavior.
|
|
149
|
+
*/
|
|
150
|
+
private readonly globalFilters?;
|
|
151
|
+
/**
|
|
152
|
+
* Tracks tables that have already triggered an unlimited-query warning so
|
|
153
|
+
* the user is not spammed once per row. Per-instance state — each
|
|
154
|
+
* QueryInterface is bound to a single table, so this set will only ever
|
|
155
|
+
* contain at most one entry, but using a Set keeps the API consistent with
|
|
156
|
+
* the audit's "Set<string>" guidance and leaves room for future
|
|
157
|
+
* cross-table sharing.
|
|
158
|
+
*/
|
|
159
|
+
private readonly warnedTables;
|
|
160
|
+
/** Cache hit/miss counters for diagnostics */
|
|
161
|
+
private cacheHits;
|
|
162
|
+
private cacheMisses;
|
|
163
|
+
/** Pre-computed column type lookups (avoids linear scans per query) */
|
|
164
|
+
private readonly columnPgTypeMap;
|
|
165
|
+
private readonly columnArrayTypeMap;
|
|
166
|
+
/**
|
|
167
|
+
* Columns whose type lives in a DIFFERENT schema than the introspected one
|
|
168
|
+
* (ColumnMetadata.pgTypeSchema is recorded only in that case) — such columns
|
|
169
|
+
* must never receive this schema's `::"enum"` cast (see enumTypeForColumn).
|
|
170
|
+
*/
|
|
171
|
+
private readonly crossSchemaTypeColumns;
|
|
172
|
+
/**
|
|
173
|
+
* Per-table memo of date columns keyed by their camelCase FIELD name.
|
|
174
|
+
* `meta.dateColumns` is keyed by raw snake_case column name, which matches
|
|
175
|
+
* top-level rows from pg. Nested relation rows arrive from json_build_object
|
|
176
|
+
* with camelCase keys, so they need this camelCase-keyed set to be coerced
|
|
177
|
+
* to Date as well (otherwise nested dates leak through as strings).
|
|
178
|
+
*/
|
|
179
|
+
private readonly camelDateFieldCache;
|
|
180
|
+
/** True when this QI runs inside an active transaction (set via _txScoped option). */
|
|
181
|
+
private readonly txScoped;
|
|
182
|
+
/** Original options reference — forwarded to child QIs in nested writes. */
|
|
183
|
+
private readonly options?;
|
|
184
|
+
/** Set by executeWithMiddleware so queryWithTimeout can include it in events. */
|
|
185
|
+
private currentAction;
|
|
186
|
+
/**
|
|
187
|
+
* Tags the query events of an in-flight `relationLoadStrategy: 'auto'` query
|
|
188
|
+
* that engaged the batched fallback (`'auto-batched'`), so observability sees
|
|
189
|
+
* which queries the auto default re-planned. Same transient-instance-state
|
|
190
|
+
* caveat as {@link currentAction}: set for the whole auto-split operation and
|
|
191
|
+
* cleared afterward; a concurrent unrelated query on the same accessor during
|
|
192
|
+
* that window could read it (a best-effort diagnostic tag, not load-bearing).
|
|
193
|
+
*/
|
|
194
|
+
private currentStrategyTag;
|
|
195
|
+
/**
|
|
196
|
+
* The active query's `skipGlobalFilters` opt-out, set at the top of each
|
|
197
|
+
* `build*` method and read deep in the (synchronous) SQL-build + param-collect
|
|
198
|
+
* tree — so relation subqueries, relation filters, `_count`, and relation
|
|
199
|
+
* `orderBy` all see it without threading it through dozens of signatures.
|
|
200
|
+
* Only load-bearing when {@link globalFilters} is configured; build+collect are
|
|
201
|
+
* synchronous per call, so this transient is never observed across an await.
|
|
202
|
+
*/
|
|
203
|
+
private currentSkip;
|
|
204
|
+
/**
|
|
205
|
+
* The bound view of this instance passed to the shared WHERE walk
|
|
206
|
+
* (`where-compile.ts`). Built once in the constructor so `fingerprintWhere` /
|
|
207
|
+
* `buildWhereClause` / `collectWhereParams` all drive ONE enumeration + ONE
|
|
208
|
+
* scalar classifier without widening the class's public surface or allocating
|
|
209
|
+
* per call. See {@link WhereHost}.
|
|
210
|
+
*/
|
|
211
|
+
private readonly whereHost;
|
|
212
|
+
/**
|
|
213
|
+
* Per-target-table {@link WhereHost} memo for scoped sub-wheres (relation
|
|
214
|
+
* `EXISTS` filters + relation `with`-clause `where`s). Keyed by table name;
|
|
215
|
+
* the host depends only on the target table's metadata, so it is shared across
|
|
216
|
+
* every alias/qualifier for that table. Lazily filled by {@link scopedWhereHost}.
|
|
217
|
+
*/
|
|
218
|
+
private readonly scopedHostCache;
|
|
219
|
+
/**
|
|
220
|
+
* The privacy-preserving view of this instance handed to the extracted WHERE
|
|
221
|
+
* module (`where.ts`). Built once in the constructor (mirroring the
|
|
222
|
+
* `whereHost` precedent) so the free functions there reach exactly the
|
|
223
|
+
* class-resident primitives they need without widening the public surface.
|
|
224
|
+
*/
|
|
225
|
+
private readonly ctx;
|
|
226
|
+
constructor(pool: pg.Pool, table: string, schema: SchemaMetadata, middlewares?: MiddlewareFn[], options?: QueryInterfaceOptions);
|
|
227
|
+
/** Quote an identifier through the active SQL dialect. */
|
|
228
|
+
private q;
|
|
229
|
+
/** Return the active dialect's placeholder for a 1-indexed parameter position. */
|
|
230
|
+
private p;
|
|
231
|
+
/**
|
|
232
|
+
* Cast an aggregate expression to an integer/float result type through the
|
|
233
|
+
* active dialect. PostgreSQL keeps the historical postfix cast (`expr::int` /
|
|
234
|
+
* `expr::float`); SQLite (no `::` operator) maps to `CAST(expr AS INTEGER/REAL)`.
|
|
235
|
+
* Falls back to the Postgres postfix cast for dialects without the hook.
|
|
236
|
+
*/
|
|
237
|
+
private castAgg;
|
|
238
|
+
/**
|
|
239
|
+
* Build the trailing pagination clause for an OUTER SELECT. PostgreSQL/MySQL/
|
|
240
|
+
* SQLite use ` LIMIT <ph>` and/or ` OFFSET <ph>`. SQL Server has no `LIMIT`, so
|
|
241
|
+
* its dialect implements {@link Dialect.buildLimitOffset} to emit
|
|
242
|
+
* `[ORDER BY (SELECT NULL)] OFFSET <off> ROWS [FETCH NEXT <lim> ROWS ONLY]`.
|
|
243
|
+
* Param-push order (limit before offset) is owned by the caller and unchanged —
|
|
244
|
+
* this only varies the SQL text, so PG output stays byte-identical.
|
|
245
|
+
*/
|
|
246
|
+
private buildPagination;
|
|
247
|
+
/**
|
|
248
|
+
* The single-row limit appended to findUnique / findFirst-style lookups. ` LIMIT 1`
|
|
249
|
+
* for PG/MySQL/SQLite; SQL Server routes through {@link Dialect.buildLimitOffset}
|
|
250
|
+
* (literal `1`, no params) → ` ORDER BY (SELECT NULL) OFFSET 0 ROWS FETCH NEXT 1
|
|
251
|
+
* ROWS ONLY`. No params are pushed, so the collect path is unaffected.
|
|
252
|
+
*/
|
|
253
|
+
private limitOneClause;
|
|
254
|
+
/**
|
|
255
|
+
* Coerce a LIMIT/OFFSET argument and validate it as a non-negative safe
|
|
256
|
+
* integer. Numeric strings (`'5'`) coerce; everything else (`NaN`, a
|
|
257
|
+
* non-numeric string, a negative, a fractional or out-of-safe-range number)
|
|
258
|
+
* throws {@link ValidationError} (E003) naming the argument and the table.
|
|
259
|
+
*
|
|
260
|
+
* This runs on EVERY pagination path, parameterized as well as inlined: a
|
|
261
|
+
* bound `NaN` serializes as SQL NULL, and Postgres reads `LIMIT NULL` as
|
|
262
|
+
* "no limit", so an unvalidated value silently turns a paginated query into
|
|
263
|
+
* a full-table read (and a bad OFFSET silently disappears).
|
|
264
|
+
*/
|
|
265
|
+
private paginationValue;
|
|
266
|
+
/**
|
|
267
|
+
* Validate a LIMIT/OFFSET value as a non-negative integer and return it as an
|
|
268
|
+
* inline SQL literal. Used only on `dialect.inlineLimitOffset` engines (MySQL).
|
|
269
|
+
* The input is always a Turbine-controlled pagination value, never a raw user
|
|
270
|
+
* string, and this guard guarantees the output is `String` of a validated
|
|
271
|
+
* integer, so inlining cannot inject SQL.
|
|
272
|
+
*/
|
|
273
|
+
private limitOffsetLiteral;
|
|
274
|
+
/**
|
|
275
|
+
* Resolve a LIMIT/OFFSET value to either an inline literal (no param pushed, on
|
|
276
|
+
* `dialect.inlineLimitOffset` engines) or a bound placeholder (the value is
|
|
277
|
+
* pushed to `params`). Build and collect paths both gate on the same flag so
|
|
278
|
+
* the param order stays mirrored; PG/SQLite/SQL Server keep parameterizing and
|
|
279
|
+
* stay byte-identical.
|
|
280
|
+
*/
|
|
281
|
+
private paginationRef;
|
|
282
|
+
/**
|
|
283
|
+
* Build an `IN` / `NOT IN` predicate through the active dialect. PostgreSQL
|
|
284
|
+
* keeps the array-param form (`expr = ANY($n)` / `expr != ALL($n)`); other
|
|
285
|
+
* engines (SQLite) use a length-independent single-placeholder form. Paired
|
|
286
|
+
* with {@link inParam}, which supplies the single bound value.
|
|
287
|
+
*/
|
|
288
|
+
private inClause;
|
|
289
|
+
/** The single bound parameter for an `IN` list (PG: the array; SQLite: a JSON string). */
|
|
290
|
+
private inParam;
|
|
291
|
+
/**
|
|
292
|
+
* Resolve the effective relation-loading strategy for a query: the per-query
|
|
293
|
+
* arg wins, then the client-level default, then `'auto'`. Only meaningful when
|
|
294
|
+
* a `with` clause is present; the callers gate on that.
|
|
295
|
+
*/
|
|
296
|
+
private resolveLoadStrategy;
|
|
297
|
+
/**
|
|
298
|
+
* The effective {@link QueryInterfaceOptions.stableRelationOrder} for a query:
|
|
299
|
+
* the per-query arg wins, then the client-level default (off).
|
|
300
|
+
*/
|
|
301
|
+
private resolveStableOrder;
|
|
302
|
+
/**
|
|
303
|
+
* Fill a PK-ascending `orderBy` into every to-many `with` relation that has no
|
|
304
|
+
* explicit one, recursing into nested `with`. Returns a CLONED clause (user
|
|
305
|
+
* args are never mutated); when nothing needs filling it returns the input
|
|
306
|
+
* object unchanged, so the byte-identical fast path stays free. Only called
|
|
307
|
+
* when {@link resolveStableOrder} is true; runs BEFORE `withFingerprint`, so
|
|
308
|
+
* the two orderings get distinct SQL-cache entries automatically. To-one
|
|
309
|
+
* relations are single rows (no array to order) and PK-less targets have
|
|
310
|
+
* nothing stable to order by, so both are left untouched.
|
|
311
|
+
*/
|
|
312
|
+
private applyStableRelationOrder;
|
|
313
|
+
/**
|
|
314
|
+
* The primary key of this table as an ascending `orderBy`, in DECLARATION
|
|
315
|
+
* order (a composite PK orders on every column), or `undefined` for a PK-less
|
|
316
|
+
* table. Field names are the camelCase accessor names, so the emitted SQL goes
|
|
317
|
+
* through the normal column mapping.
|
|
318
|
+
*/
|
|
319
|
+
private pkOrderBy;
|
|
320
|
+
/**
|
|
321
|
+
* The field names a `cursor` actually seeks on (its own keys with a defined
|
|
322
|
+
* value), in the canonical sorted order the cursor conditions are built in.
|
|
323
|
+
* Empty for a missing cursor or one whose every value is `undefined` (which
|
|
324
|
+
* emits no seek condition at all, so it does not paginate).
|
|
325
|
+
*/
|
|
326
|
+
private cursorFields;
|
|
327
|
+
/**
|
|
328
|
+
* The ascending ordering implied by a `cursor`, or `undefined` when the shape
|
|
329
|
+
* is too ambiguous to order safely.
|
|
330
|
+
*
|
|
331
|
+
* A cursor seek emits `col > $n` per field (`<` when the orderBy says desc),
|
|
332
|
+
* so the ONLY ordering coherent with it is on the cursor's own field: ordering
|
|
333
|
+
* a seek on column X by column Y walks the table in an order the seek does not
|
|
334
|
+
* follow, which skips and repeats rows just as badly as no order at all. That
|
|
335
|
+
* is why this orders on the cursor field rather than blindly on the primary
|
|
336
|
+
* key when the two differ.
|
|
337
|
+
*
|
|
338
|
+
* Returns `undefined` (warn, inject nothing) for two shapes:
|
|
339
|
+
* - a MULTI-field cursor. `a > $1 AND b > $2` is a conjunction, not a proper
|
|
340
|
+
* composite keyset seek (`(a, b) > ($1, $2)`), so no single ORDER BY makes
|
|
341
|
+
* it correct. Injecting `(a asc, b asc)` would dress a broken seek up as a
|
|
342
|
+
* sound one.
|
|
343
|
+
* - a field that does not resolve to a real column. Column validation belongs
|
|
344
|
+
* to the normal build path, which raises a precise error; synthesizing an
|
|
345
|
+
* ORDER BY on it here would only change which error the caller sees.
|
|
346
|
+
*/
|
|
347
|
+
private cursorOrderBy;
|
|
348
|
+
/**
|
|
349
|
+
* Whether a findMany paginates (`limit` / `take` / `offset` / `cursor`) but
|
|
350
|
+
* declares no ordering, which makes the returned page NON-DETERMINISTIC:
|
|
351
|
+
* Postgres is free to return different rows for the same unordered `LIMIT`
|
|
352
|
+
* once the heap changes underneath it, so a row can appear on two pages or on
|
|
353
|
+
* none.
|
|
354
|
+
*
|
|
355
|
+
* `cursor` counts, and is the worst case rather than an exception: a keyset
|
|
356
|
+
* seek with no ORDER BY is exactly this bug (`WHERE id > $1 LIMIT $2` walks
|
|
357
|
+
* the heap in whatever order the plan happens to produce). An empty orderBy
|
|
358
|
+
* (`[]`, or an object whose every value is `undefined`) counts as absent,
|
|
359
|
+
* because it emits no ordering.
|
|
360
|
+
*
|
|
361
|
+
* `distinct` is still excluded: that path re-orders in an outer wrapper around
|
|
362
|
+
* a `DISTINCT ON` whose ordering picks the representative row, so an implicit
|
|
363
|
+
* key would change which rows come back, not just their order.
|
|
364
|
+
*/
|
|
365
|
+
private isUnorderedPage;
|
|
366
|
+
/**
|
|
367
|
+
* Opt-in (`implicitPkOrdering`) primary-key ascending ordering for a paginating
|
|
368
|
+
* findMany that declares no `orderBy`, making its pages deterministic.
|
|
369
|
+
*
|
|
370
|
+
* OFF by default in CORE, deliberately: turning it on would add an `ORDER BY`
|
|
371
|
+
* to SQL that existing applications already emit, changing both the rows a
|
|
372
|
+
* given page returns and the plan the engine picks. That is a breaking change
|
|
373
|
+
* in everything but the type signature, so it waits for a major. The
|
|
374
|
+
* `turbine-orm/prisma-compat` layer defaults it ON instead, because reproducing
|
|
375
|
+
* Prisma's semantics is that layer's whole contract.
|
|
376
|
+
*
|
|
377
|
+
* An explicit `orderBy` always wins, a PK-less table is left alone (nothing
|
|
378
|
+
* stable to order by), and a composite PK orders on every column in
|
|
379
|
+
* declaration order. A `cursor` query orders on the CURSOR's field instead
|
|
380
|
+
* (see {@link cursorOrderBy}), and is left alone when that shape is ambiguous.
|
|
381
|
+
* `distinct` shapes are skipped (see {@link isUnorderedPage}). With the flag
|
|
382
|
+
* off this returns `undefined` before touching anything, so the emitted SQL is
|
|
383
|
+
* byte-identical to before.
|
|
384
|
+
*/
|
|
385
|
+
private implicitPkOrderBy;
|
|
386
|
+
/**
|
|
387
|
+
* Dev-only, once per query shape: an unordered paginating findMany returns a
|
|
388
|
+
* non-deterministic page (see {@link isUnorderedPage}), and on real data it is
|
|
389
|
+
* also usually the slower plan (an unordered `LIMIT` can discard tens of
|
|
390
|
+
* thousands of heap rows that an index scan on the key would have skipped).
|
|
391
|
+
*
|
|
392
|
+
* Gated exactly like the other dev diagnostics (silent under
|
|
393
|
+
* `NODE_ENV=production`) and consistent with `warnOnUnlimited`: a per-call
|
|
394
|
+
* `warnOnUnlimited: false` silences it, `true` forces it past a config-level
|
|
395
|
+
* opt-out, and a config/per-table `warnOnUnlimited: false` silences it. Deduped
|
|
396
|
+
* process-wide through the shared warn registry, so it can never spam.
|
|
397
|
+
*
|
|
398
|
+
* Suppressed only when `implicitPkOrdering` will ACTUALLY order this query.
|
|
399
|
+
* The flag being on is not enough: a PK-less table and an ambiguous
|
|
400
|
+
* multi-field cursor both get no injected ordering, and those are precisely
|
|
401
|
+
* the shapes that still need saying out loud.
|
|
402
|
+
*/
|
|
403
|
+
private maybeWarnUnorderedPage;
|
|
404
|
+
/**
|
|
405
|
+
* Whether a relation can be served by the batched loader, i.e. all its
|
|
406
|
+
* correlation keys are single-column (the loader throws E017 on composite
|
|
407
|
+
* keys). Composite-key relations therefore always stay on the join plan under
|
|
408
|
+
* `'auto'` (and keep the existing unindexed-probe dev warning).
|
|
409
|
+
*/
|
|
410
|
+
private relationBatchEligible;
|
|
411
|
+
/**
|
|
412
|
+
* The verdict for one relation SUBTREE under `'auto'`: is any probe in the
|
|
413
|
+
* subtree unindexed, is EVERY relation in the subtree batched-eligible, and
|
|
414
|
+
* the first unindexed probe found (for the dev note). Subtree-atomic: a whole
|
|
415
|
+
* top-level relation falls back only when its entire subtree is eligible,
|
|
416
|
+
* mirroring the batched loader recursing the same tree.
|
|
417
|
+
*/
|
|
418
|
+
private autoSubtreeVerdict;
|
|
419
|
+
/** The `_count` verdict under `'auto'`: any counted probe unindexed + all single-key. */
|
|
420
|
+
private autoCountVerdict;
|
|
421
|
+
/**
|
|
422
|
+
* Whether a query's parent set is potentially large at plan time, which is the
|
|
423
|
+
* only cardinality signal available before the base query runs. A `findMany`
|
|
424
|
+
* with no `limit`/`take` (or one above {@link autoToOneJoinMaxRows}) can return
|
|
425
|
+
* an arbitrary number of parent rows; a small `limit` bounds it. `findUnique` /
|
|
426
|
+
* `findFirst` pass `false` explicitly (their parent set is one row).
|
|
427
|
+
*/
|
|
428
|
+
private autoParentSetLarge;
|
|
429
|
+
/**
|
|
430
|
+
* The parent-row count at which `'auto'` stops preferring the single-statement
|
|
431
|
+
* join for a to-one relation.
|
|
432
|
+
*
|
|
433
|
+
* Resolution order:
|
|
434
|
+
* 1. an explicit `autoToOneJoinMaxRows` — an instruction, used verbatim
|
|
435
|
+
* (no clamping: the caller has measured their own workload);
|
|
436
|
+
* 2. the configured `autoRoundTripMs` divided by
|
|
437
|
+
* {@link AUTO_JOIN_PENALTY_MS_PER_ROW}, clamped to
|
|
438
|
+
* [{@link AUTO_TO_ONE_JOIN_ROWS_MIN}, {@link AUTO_TO_ONE_JOIN_ROWS_MAX}];
|
|
439
|
+
* 3. {@link AUTO_TO_ONE_JOIN_MAX_ROWS}, which is that same division applied
|
|
440
|
+
* to {@link AUTO_ASSUMED_ROUND_TRIP_MS}.
|
|
441
|
+
*
|
|
442
|
+
* Deriving it rather than hard-coding a row count is the whole point: the
|
|
443
|
+
* sweep in {@link AUTO_JOIN_PENALTY_MS_PER_ROW} shows the break-even moving
|
|
444
|
+
* 17x between a loopback link and a 2.7ms one while the per-row penalty stays
|
|
445
|
+
* put, so any single constant is wrong for someone by more than the margin it
|
|
446
|
+
* is trying to save. Placing the switch AT the break-even is also what removes
|
|
447
|
+
* the old cliff: two plans that cost the same at the boundary make the regret
|
|
448
|
+
* there ~1.0x, rising only as the true row count moves away from it — where
|
|
449
|
+
* the previous fixed 1000 put its WORST case (1.44x measured) immediately
|
|
450
|
+
* below its own switch point.
|
|
451
|
+
*
|
|
452
|
+
* Cheap enough to recompute per call (a division and two comparisons over
|
|
453
|
+
* readonly fields), so there is no cached copy to invalidate.
|
|
454
|
+
*/
|
|
455
|
+
private autoToOneThreshold;
|
|
456
|
+
/**
|
|
457
|
+
* Partition a top-level `with` clause under `'auto'`. A relation routes to
|
|
458
|
+
* `batchedWith` when it is fully batched-eligible AND either
|
|
459
|
+
*
|
|
460
|
+
* 1. its subtree has a PROVEN unindexed probe (index metadata only), or
|
|
461
|
+
* 2. it is TO-ONE and the parent set is potentially large
|
|
462
|
+
* ({@link AUTO_TO_ONE_JOIN_MAX_ROWS}), since a correlated to-one subquery
|
|
463
|
+
* is re-evaluated per parent row no matter how well indexed it is.
|
|
464
|
+
*
|
|
465
|
+
* Everything else (indexed to-many, composite-key, unknown) stays in `joinWith`
|
|
466
|
+
* (byte-identical join). The reserved `_count` key falls back on rule 1 only,
|
|
467
|
+
* and only for a large parent set: an inline `_count` is one correlated
|
|
468
|
+
* `COUNT(*)` per parent row, so the grouped follow-up wins exactly when there
|
|
469
|
+
* are many parents, while for a handful of parents the extra round-trip costs
|
|
470
|
+
* more than the repeated (small) scans. Also returns the engaged relations for
|
|
471
|
+
* the dev note.
|
|
472
|
+
*/
|
|
473
|
+
private partitionWithForAuto;
|
|
474
|
+
/**
|
|
475
|
+
* Plan the `'auto'` split for a query's `with` clause: normalize stable order,
|
|
476
|
+
* partition, and return the split ONLY when at least one relation falls back
|
|
477
|
+
* to batched. Returns `null` (→ run the plain join path, byte-identical, same
|
|
478
|
+
* cache keys) when nothing qualifies.
|
|
479
|
+
*/
|
|
480
|
+
private planAuto;
|
|
481
|
+
/** Dev-only once-per-relation note that `'auto'` engaged the batched fallback. */
|
|
482
|
+
private emitAutoNotes;
|
|
483
|
+
/**
|
|
484
|
+
* Execute a findMany/findUnique `'auto'` split: run the base query with the
|
|
485
|
+
* residual `joinWith` (plus any parent stitch keys the batched subset needs),
|
|
486
|
+
* then load `batchedWith` via the batched loader and stitch. `single` returns
|
|
487
|
+
* the first entity (findUnique) instead of the array. Output is identical in
|
|
488
|
+
* shape to the pure join plan.
|
|
489
|
+
*/
|
|
490
|
+
private runAutoSplit;
|
|
491
|
+
/**
|
|
492
|
+
* Build the {@link RelationLoadContext} the batched loader needs, closing over
|
|
493
|
+
* this interface's pool/dialect/executor. Child readers are constructed on the
|
|
494
|
+
* SAME pool (so they join an active transaction) with `defaultLimit` cleared
|
|
495
|
+
* and unlimited-warnings silenced — a relation load must fetch every matching
|
|
496
|
+
* child, and the per-relation `limit` is applied client-side by the loader.
|
|
497
|
+
*/
|
|
498
|
+
private batchedContext;
|
|
499
|
+
/**
|
|
500
|
+
* Run a findMany with the batched strategy: execute the base query WITHOUT
|
|
501
|
+
* relation subqueries (all other clauses intact), then load each relation via
|
|
502
|
+
* one flat follow-up query and stitch client-side. Parent stitch keys the
|
|
503
|
+
* caller's `select`/`omit` excluded are added for the base query and stripped
|
|
504
|
+
* from the returned rows, so the shape matches the join strategy exactly.
|
|
505
|
+
*/
|
|
506
|
+
private runFindManyBatched;
|
|
507
|
+
/**
|
|
508
|
+
* Build the base findMany args for a batched run: drop `with`, and ensure every
|
|
509
|
+
* parent correlation key needed for stitching is projected (returning the list
|
|
510
|
+
* of keys that must be stripped from the output afterwards).
|
|
511
|
+
*/
|
|
512
|
+
private prepareBatchedBase;
|
|
513
|
+
/**
|
|
514
|
+
* Return cache hit/miss statistics for this QueryInterface instance.
|
|
515
|
+
* Useful for monitoring and benchmarking.
|
|
516
|
+
*/
|
|
517
|
+
cacheStats(): {
|
|
518
|
+
hits: number;
|
|
519
|
+
misses: number;
|
|
520
|
+
hitRate: number;
|
|
521
|
+
size: number;
|
|
522
|
+
};
|
|
523
|
+
/**
|
|
524
|
+
* Look up or build a SQL template in the cache.
|
|
525
|
+
* On miss, calls `build()` to generate the SQL, stores the entry, and returns it.
|
|
526
|
+
* On hit, increments counters and returns the cached entry.
|
|
527
|
+
*
|
|
528
|
+
* When `sqlCache` is disabled, always calls `build()` without caching.
|
|
529
|
+
*
|
|
530
|
+
* `build` receives a fresh `$N` param scratch array. On a miss those params
|
|
531
|
+
* are discarded (the returned params come from each call site's dedicated
|
|
532
|
+
* collect path); the array exists so the build path can number placeholders
|
|
533
|
+
* via `params.length` exactly as it does today. On a HIT, `build` is skipped
|
|
534
|
+
* here but re-run by {@link crossCheckCache} (dev only) with a fresh array to
|
|
535
|
+
* verify the collect path stayed in lockstep with the build path.
|
|
536
|
+
*
|
|
537
|
+
* Sets {@link lastCacheHit} so the caller's `crossCheckCache` knows whether a
|
|
538
|
+
* cross-check is warranted.
|
|
539
|
+
*/
|
|
540
|
+
private acquireSql;
|
|
541
|
+
/**
|
|
542
|
+
* Dev-mode SQL-cache lockstep cross-check (see {@link cacheCrossCheckEnabled}).
|
|
543
|
+
*
|
|
544
|
+
* Runs only when the most recent {@link acquireSql} was a cache HIT and the
|
|
545
|
+
* check is enabled. Rebuilds the SQL + `$N` params fresh via the same `build`
|
|
546
|
+
* closure the caller passed to `acquireSql`, then compares:
|
|
547
|
+
* (a) the cached SQL string byte-for-byte against the fresh SQL, and
|
|
548
|
+
* (b) the params the cache-hit collect path produced against the fresh
|
|
549
|
+
* build-path params (length and element-wise strict deep-equal).
|
|
550
|
+
*
|
|
551
|
+
* A mismatch means the fingerprint / build / collect paths have drifted out
|
|
552
|
+
* of lockstep (the exact class of bug that has silently corrupted results
|
|
553
|
+
* before), so it throws a {@link ValidationError} (E003) naming the
|
|
554
|
+
* fingerprint, the operation, and both SQL strings (truncated). Failing loud
|
|
555
|
+
* in dev/test is the point. Production never reaches the comparison.
|
|
556
|
+
*
|
|
557
|
+
* @param op human label of the calling build method (for the error message).
|
|
558
|
+
* @param cacheKey the cache fingerprint that HIT.
|
|
559
|
+
* @param entry the cached SQL entry that will be executed.
|
|
560
|
+
* @param build the same closure passed to `acquireSql`; re-run here to
|
|
561
|
+
* capture the fresh build-path SQL + params.
|
|
562
|
+
* @param collectedParams the params the caller's collect path produced.
|
|
563
|
+
*/
|
|
564
|
+
private crossCheckCache;
|
|
565
|
+
/**
|
|
566
|
+
* Reset the per-instance unlimited-query warning dedupe set.
|
|
567
|
+
* Exposed for tests so a single test process can verify the warning fires
|
|
568
|
+
* exactly once per table without bleeding state between assertions.
|
|
569
|
+
*/
|
|
570
|
+
resetUnlimitedWarnings(): void;
|
|
571
|
+
private emitQueryEvent;
|
|
572
|
+
/**
|
|
573
|
+
* Execute a pool.query with an optional timeout.
|
|
574
|
+
* If timeout is set, races the query against a timer and rejects on expiry.
|
|
575
|
+
* pg driver errors are translated to typed Turbine errors via wrapPgError.
|
|
576
|
+
*/
|
|
577
|
+
private queryWithTimeout;
|
|
578
|
+
/**
|
|
579
|
+
* Execute a write `DeferredQuery` (create/update/delete/upsert) according to
|
|
580
|
+
* the active dialect's {@link Dialect.resultStrategy}, then apply its
|
|
581
|
+
* transform.
|
|
582
|
+
*
|
|
583
|
+
* - `'returning'` / `'output'`: the statement returns its own affected rows
|
|
584
|
+
* (`RETURNING *` / `OUTPUT INSERTED.*`). Byte-identical to the historical
|
|
585
|
+
* single `queryWithTimeout` + `transform(result)` path — the PostgreSQL
|
|
586
|
+
* route is unchanged.
|
|
587
|
+
* - `'reselect'`: the engine cannot return rows from a write, so the build
|
|
588
|
+
* method attached a {@link DeferredQuery.reselect} plan that runs the
|
|
589
|
+
* write and a follow-up SELECT; the SELECT's rows feed the transform.
|
|
590
|
+
*/
|
|
591
|
+
private executeMutation;
|
|
592
|
+
buildCreate(args: CreateArgs<T>): DeferredQuery<T>;
|
|
593
|
+
buildCreateMany(args: CreateManyArgs<T>): DeferredQuery<T[]>;
|
|
594
|
+
buildUpdate(args: UpdateArgs<T>): DeferredQuery<T>;
|
|
595
|
+
buildDelete(args: DeleteArgs<T>): DeferredQuery<T>;
|
|
596
|
+
buildUpsert(args: UpsertArgs<T>): DeferredQuery<T>;
|
|
597
|
+
buildUpdateMany(args: UpdateManyArgs<T>): DeferredQuery<{
|
|
598
|
+
count: number;
|
|
599
|
+
}>;
|
|
600
|
+
buildDeleteMany(args: DeleteManyArgs<T>): DeferredQuery<{
|
|
601
|
+
count: number;
|
|
602
|
+
}>;
|
|
603
|
+
/**
|
|
604
|
+
* Best-effort extraction of an auto-generated primary key from a write
|
|
605
|
+
* result for `'reselect'` engines (e.g. mysql2's `insertId`). Returns
|
|
606
|
+
* `undefined` when the driver exposes no such field.
|
|
607
|
+
*/
|
|
608
|
+
private mutationInsertId;
|
|
609
|
+
/**
|
|
610
|
+
* Execute a query through the middleware chain.
|
|
611
|
+
* If no middlewares are registered, executes directly.
|
|
612
|
+
*
|
|
613
|
+
* Middleware can inspect and log query parameters, measure timing, and
|
|
614
|
+
* transform the result returned by `next()`. Note: query SQL is generated
|
|
615
|
+
* BEFORE middleware runs — `params.args` is a read-only snapshot, and
|
|
616
|
+
* mutating it does NOT change the executed SQL. Cross-cutting filters
|
|
617
|
+
* (e.g. soft deletes) belong in the query itself: pass an explicit
|
|
618
|
+
* `where: { deletedAt: null }` or wrap the table accessor in a small helper.
|
|
619
|
+
*/
|
|
620
|
+
private executeWithMiddleware;
|
|
621
|
+
findUnique<W extends TypedWithClause<R> = {}, S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined>(args: FindUniqueArgs<T, R, W, S, O>): Promise<QueryResult<T, R, W, S, O> | null>;
|
|
622
|
+
/**
|
|
623
|
+
* Batched-strategy findUnique: fetch the single base row without relation
|
|
624
|
+
* subqueries (adding any parent stitch keys the projection excluded), then load
|
|
625
|
+
* its relations via one follow-up query each and stitch. Mirrors the join
|
|
626
|
+
* strategy's shape for the one row.
|
|
627
|
+
*/
|
|
628
|
+
private runFindUniqueBatched;
|
|
629
|
+
buildFindUnique<W extends TypedWithClause<R> = {}>(args: FindUniqueArgs<T, R, W, Record<string, boolean> | undefined, Record<string, boolean> | undefined>): DeferredQuery<T | null>;
|
|
630
|
+
findMany<W extends TypedWithClause<R> = {}, S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined>(args?: FindManyArgs<T, R, W, S, O>): Promise<QueryResult<T, R, W, S, O>[]>;
|
|
631
|
+
/**
|
|
632
|
+
* Return the engine's query plan for a {@link findMany}-shaped query as plain
|
|
633
|
+
* text lines: a diagnostic surface for inspecting how the database will run a
|
|
634
|
+
* query (index usage, join strategy, scan type).
|
|
635
|
+
*
|
|
636
|
+
* The compiled SELECT is prefixed with the dialect's explain syntax
|
|
637
|
+
* (Postgres `EXPLAIN`, SQLite `EXPLAIN QUERY PLAN`, MySQL `EXPLAIN
|
|
638
|
+
* FORMAT=TREE`) and run as a read. The findMany args are compiled with the
|
|
639
|
+
* SQL template cache disabled, so an explain never reads or writes the shared
|
|
640
|
+
* cache. Middleware is NOT applied: the returned rows are plan text, not
|
|
641
|
+
* entity rows. Each result row is flattened to one line by joining its column
|
|
642
|
+
* values with a single space (Postgres returns one `QUERY PLAN` text column,
|
|
643
|
+
* SQLite's `EXPLAIN QUERY PLAN` returns four, MySQL's tree format one).
|
|
644
|
+
*
|
|
645
|
+
* Only `findMany` shapes are supported (where / orderBy / with / limit /
|
|
646
|
+
* pagination). Engines whose plan cannot be requested in-band from a compiled
|
|
647
|
+
* query (SQL Server, whose SHOWPLAN is a session toggle) throw
|
|
648
|
+
* {@link UnsupportedFeatureError} (E017).
|
|
649
|
+
*
|
|
650
|
+
* The plan text itself is engine-owned and NOT covered by semver: its content
|
|
651
|
+
* and formatting can change with the underlying database version.
|
|
652
|
+
*/
|
|
653
|
+
explain(args?: FindManyArgs<T, R>): Promise<string[]>;
|
|
654
|
+
/**
|
|
655
|
+
* Run `fn` with the SQL template cache forced off, restoring the prior state
|
|
656
|
+
* afterward. Used by {@link explain}, whose one-off prefixed statement must
|
|
657
|
+
* neither read nor write the shared cache. `fn` is synchronous, so no query
|
|
658
|
+
* interleaves between the toggle and its restore.
|
|
659
|
+
*/
|
|
660
|
+
private withSqlCacheDisabled;
|
|
661
|
+
/**
|
|
662
|
+
* Emit a one-time `console.warn` when {@link findMany} is called without an
|
|
663
|
+
* explicit `limit`/`take` and `warnOnUnlimited` has not been disabled.
|
|
664
|
+
*
|
|
665
|
+
* Deduped per QueryInterface instance via {@link warnedTables} so a busy
|
|
666
|
+
* loop calling `db.users.findMany()` thousands of times only logs once.
|
|
667
|
+
* Suppressed when `defaultLimit` is configured (the caller has already
|
|
668
|
+
* opted in to a bounded query) and when the user passed an explicit
|
|
669
|
+
* `limit`, `take`, or `cursor`. A per-call `warnOnUnlimited` overrides the
|
|
670
|
+
* config-level setting in either direction (`false` silences a call that
|
|
671
|
+
* intentionally reads the full set; `true` forces the warning even when
|
|
672
|
+
* disabled in config).
|
|
673
|
+
*/
|
|
674
|
+
private maybeWarnUnlimited;
|
|
675
|
+
/**
|
|
676
|
+
* Recursively measure the maximum depth of a `with` clause tree.
|
|
677
|
+
* Used by the dev-only deep-with warning guard.
|
|
678
|
+
*/
|
|
679
|
+
private measureWithDepth;
|
|
680
|
+
buildFindMany<W extends TypedWithClause<R> = {}>(args?: FindManyArgs<T, R, W, Record<string, boolean> | undefined, Record<string, boolean> | undefined>): DeferredQuery<T[]>;
|
|
681
|
+
/**
|
|
682
|
+
* Stream rows from a findMany query using PostgreSQL cursors.
|
|
683
|
+
* Returns an AsyncIterable that yields individual rows, fetching in batches internally.
|
|
684
|
+
*
|
|
685
|
+
* **Speculative fast-path:** Before opening a cursor, issues a single
|
|
686
|
+
* `SELECT ... LIMIT batchSize+1`. If the result fits within `batchSize`,
|
|
687
|
+
* all rows are yielded immediately with zero cursor overhead (no BEGIN /
|
|
688
|
+
* DECLARE / CLOSE / COMMIT). Only when the result overflows does the
|
|
689
|
+
* method fall back to the full cursor path.
|
|
690
|
+
*
|
|
691
|
+
* **Cursor path:** Uses DECLARE CURSOR within a dedicated transaction on a
|
|
692
|
+
* single pooled connection. The cursor is CLOSEd (in the dialect's `finally`)
|
|
693
|
+
* and the connection released both when iteration completes normally and when
|
|
694
|
+
* it ends early (`break` from `for await`). An error mid-stream skips the
|
|
695
|
+
* CLOSE and rolls back instead, which drops the cursor with the transaction.
|
|
696
|
+
*
|
|
697
|
+
* **Snapshot semantics note:** Outside a transaction the speculative
|
|
698
|
+
* fast-path runs unwrapped, and an overflow opens the cursor in its own
|
|
699
|
+
* transaction, so the two fetches span two separate snapshots. Wrapping the
|
|
700
|
+
* call in `$transaction` gives strict single-snapshot semantics: both the
|
|
701
|
+
* speculative fetch and the cursor then run on the caller's connection
|
|
702
|
+
* inside the caller's transaction (the cursor path issues no BEGIN/COMMIT of
|
|
703
|
+
* its own and releases nothing, so the caller's transaction is intact when
|
|
704
|
+
* iteration finishes).
|
|
705
|
+
*
|
|
706
|
+
* @example
|
|
707
|
+
* ```ts
|
|
708
|
+
* for await (const user of db.users.findManyStream({ where: { orgId: 1 }, batchSize: 500 })) {
|
|
709
|
+
* process.stdout.write(`${user.email}\n`);
|
|
710
|
+
* }
|
|
711
|
+
* ```
|
|
712
|
+
*/
|
|
713
|
+
findManyStream<W extends TypedWithClause<R> = {}, S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined>(args?: FindManyStreamArgs<T, R, W, S, O>): AsyncGenerator<QueryResult<T, R, W, S, O>, void, undefined>;
|
|
714
|
+
findFirst<W extends TypedWithClause<R> = {}, S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined>(args?: FindManyArgs<T, R, W, S, O>): Promise<QueryResult<T, R, W, S, O> | null>;
|
|
715
|
+
buildFindFirst<W extends TypedWithClause<R> = {}>(args?: FindManyArgs<T, R, W, Record<string, boolean> | undefined, Record<string, boolean> | undefined>): DeferredQuery<T | null>;
|
|
716
|
+
findFirstOrThrow<W extends TypedWithClause<R> = {}, S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined>(args?: FindManyArgs<T, R, W, S, O>): Promise<QueryResult<T, R, W, S, O>>;
|
|
717
|
+
buildFindFirstOrThrow<W extends TypedWithClause<R> = {}>(args?: FindManyArgs<T, R, W, Record<string, boolean> | undefined, Record<string, boolean> | undefined>): DeferredQuery<T>;
|
|
718
|
+
findUniqueOrThrow<W extends TypedWithClause<R> = {}, S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined>(args: FindUniqueArgs<T, R, W, S, O>): Promise<QueryResult<T, R, W, S, O>>;
|
|
719
|
+
buildFindUniqueOrThrow<W extends TypedWithClause<R> = {}>(args: FindUniqueArgs<T, R, W, Record<string, boolean> | undefined, Record<string, boolean> | undefined>): DeferredQuery<T>;
|
|
720
|
+
create(args: CreateArgs<T, R>): Promise<T>;
|
|
721
|
+
createMany(args: CreateManyArgs<T>): Promise<T[]>;
|
|
722
|
+
update(args: UpdateArgs<T, R>): Promise<T>;
|
|
723
|
+
private nestedCreate;
|
|
724
|
+
private nestedUpdate;
|
|
725
|
+
private runInImplicitTx;
|
|
726
|
+
private buildNestedCtx;
|
|
727
|
+
private makeTxProxy;
|
|
728
|
+
delete(args: DeleteArgs<T, R>): Promise<T>;
|
|
729
|
+
upsert(args: UpsertArgs<T, R>): Promise<T>;
|
|
730
|
+
updateMany(args: UpdateManyArgs<T, R>): Promise<{
|
|
731
|
+
count: number;
|
|
732
|
+
}>;
|
|
733
|
+
deleteMany(args: DeleteManyArgs<T, R>): Promise<{
|
|
734
|
+
count: number;
|
|
735
|
+
}>;
|
|
736
|
+
count(args?: CountArgs<T, R>): Promise<number>;
|
|
737
|
+
buildCount(args?: CountArgs<T>): DeferredQuery<number>;
|
|
738
|
+
/**
|
|
739
|
+
* Group rows and compute per-group aggregates (Prisma-style). The result row
|
|
740
|
+
* type is INFERRED from the args: each `by` field carries its entity field
|
|
741
|
+
* type, `_count` is always a number, and each requested `_sum` / `_avg` /
|
|
742
|
+
* `_min` / `_max` block maps its fields to properly typed values (see
|
|
743
|
+
* {@link GroupByResult}). Grouping by a JSON-path key yields a runtime alias
|
|
744
|
+
* that cannot be typed, so those columns are not projected onto the row type.
|
|
745
|
+
*/
|
|
746
|
+
groupBy<A extends GroupByArgs<T, R>>(args: A): Promise<GroupByResult<T, A>[]>;
|
|
747
|
+
buildGroupBy(args: GroupByArgs<T>): DeferredQuery<Record<string, unknown>[]>;
|
|
748
|
+
buildAggregate(args: AggregateArgs<T>): DeferredQuery<AggregateResult<T>>;
|
|
749
|
+
aggregate(args: AggregateArgs<T, R>): Promise<AggregateResult<T>>;
|
|
750
|
+
private resolveColumns;
|
|
751
|
+
withFingerprint(withClause: WithClause | undefined, table?: string, depth?: number): string;
|
|
752
|
+
private collectWithParams;
|
|
753
|
+
private orderByEntryFingerprint;
|
|
754
|
+
private buildOrderBy;
|
|
755
|
+
private isRelationOrderByValue;
|
|
756
|
+
private nullsSuffix;
|
|
757
|
+
private resolveOrderByColumn;
|
|
758
|
+
private validateJsonPathOrderBy;
|
|
759
|
+
private buildJsonPathOrderEntry;
|
|
760
|
+
private collectRelationPickOrderParams;
|
|
761
|
+
private collectRelationCountParams;
|
|
762
|
+
private getCamelDateFields;
|
|
763
|
+
private makeNestedParser;
|
|
764
|
+
private buildSelectWithRelations;
|
|
765
|
+
/**
|
|
766
|
+
* Compile the `relationLoadStrategy: 'flatten'` plan for a findMany-shaped
|
|
767
|
+
* query, or `null` to emit exactly the SQL (and cache key) the default
|
|
768
|
+
* strategy emits.
|
|
769
|
+
*
|
|
770
|
+
* `'flatten'` compiles an eligible to-one relation to a `LEFT JOIN` with a
|
|
771
|
+
* prefixed scalar projection instead of a correlated `json_build_object`
|
|
772
|
+
* subquery. The correlated form is re-evaluated once per parent row, so its
|
|
773
|
+
* cost scales with the parent set no matter how well the FK is indexed; the
|
|
774
|
+
* join does not, and unlike `'batched'` it stays a single round trip.
|
|
775
|
+
*
|
|
776
|
+
* It is an EXPLICIT opt-in: `'auto'` is unchanged and never selects it.
|
|
777
|
+
*
|
|
778
|
+
* Query-shape gates (any of these routes the WHOLE query back to the default
|
|
779
|
+
* strategy, silently and byte-identically):
|
|
780
|
+
* - the resolved strategy is not `'flatten'`;
|
|
781
|
+
* - `jsonEncoding: 'positional'` (a flattened relation emits no JSON at all,
|
|
782
|
+
* so the two encodings are not composed in this version);
|
|
783
|
+
* - the dialect owns relation-subquery generation
|
|
784
|
+
* (`dialect.buildRelationSubquery`, i.e. SQL Server's `FOR JSON PATH`);
|
|
785
|
+
* - `distinct` (the `DISTINCT ON` rewrite re-orders in an outer wrapper, and
|
|
786
|
+
* the extra projected columns have not been proven safe there).
|
|
787
|
+
*
|
|
788
|
+
* `limit` / `offset` / `cursor` / `orderBy` need no gate: every flattened join
|
|
789
|
+
* is over a PROVABLY UNIQUE target key, so it matches at most one row per
|
|
790
|
+
* parent and cannot change the parent row count that pagination applies to.
|
|
791
|
+
*
|
|
792
|
+
* Per-relation eligibility lives in `planFlattenWith` / `planFlattenNode`.
|
|
793
|
+
* Only the findMany family is planned (`findMany`, `findFirst`,
|
|
794
|
+
* `findManyStream`, and pipelined `buildFindMany`); `findUnique` reads a single
|
|
795
|
+
* parent row, where the correlated subquery runs exactly once, so it stays on
|
|
796
|
+
* the default path.
|
|
797
|
+
*/
|
|
798
|
+
private planFlatten;
|
|
799
|
+
/** Dev-only once-only note that `'flatten'` was refused for the whole query. */
|
|
800
|
+
private warnFlattenBlocked;
|
|
801
|
+
/** Convert camelCase field name to snake_case column name (unquoted, for non-SQL uses) */
|
|
802
|
+
private toColumn;
|
|
803
|
+
/** Convert camelCase field name to a double-quoted SQL identifier */
|
|
804
|
+
private toSqlColumn;
|
|
805
|
+
fingerprintWhere(where: Record<string, unknown>): string;
|
|
806
|
+
collectWhereParams(where: Record<string, unknown>, params: unknown[]): void;
|
|
807
|
+
private resolveGlobalFilter;
|
|
808
|
+
private mergeGlobalFilter;
|
|
809
|
+
private collectTargetGlobalFilterAlias;
|
|
810
|
+
private globalFilterCacheSegment;
|
|
811
|
+
private buildWhereClause;
|
|
812
|
+
private buildAliasWhere;
|
|
813
|
+
private vectorOperator;
|
|
814
|
+
private pushVectorParam;
|
|
815
|
+
private normalizeRelationFilter;
|
|
816
|
+
private isJsonColumnType;
|
|
817
|
+
private getColumnPgType;
|
|
818
|
+
private jsonPathParam;
|
|
819
|
+
/**
|
|
820
|
+
* Collect params for an orderBy clause. Vector KNN ordering pushes the
|
|
821
|
+
* `$n::vector` query vector and JSON-path ordering pushes its text[] path;
|
|
822
|
+
* plain direction ordering is parameterless. Mirrors buildOrderBy's push
|
|
823
|
+
* order exactly so the cached-SQL param re-collection stays in lockstep.
|
|
824
|
+
*/
|
|
825
|
+
private collectOrderByParams;
|
|
826
|
+
/**
|
|
827
|
+
* The {@link WhereHost} for a scoped sub-where over `meta`'s table. Memoized
|
|
828
|
+
* per table (see {@link scopedHostCache}); the host depends only on the target
|
|
829
|
+
* metadata, not on the caller's alias/qualifier.
|
|
830
|
+
*/
|
|
831
|
+
/**
|
|
832
|
+
* Build ORDER BY clause from an object.
|
|
833
|
+
*
|
|
834
|
+
* Each value is either a plain direction (`'asc'`/`'desc'`) or — for pgvector
|
|
835
|
+
* columns — a `{ distance: { to, metric, direction? } }` KNN ordering object.
|
|
836
|
+
* Vector ordering binds the query vector as a `$n::vector` param, so a `params`
|
|
837
|
+
* array MUST be supplied when a vector ordering may be present (top-level
|
|
838
|
+
* findMany path). When `params` is omitted (groupBy / relation path) a vector
|
|
839
|
+
* ordering throws — KNN ordering is only supported at the top level.
|
|
840
|
+
*/
|
|
841
|
+
/** Parse a flat row: convert snake_case to camelCase + Date coercion */
|
|
842
|
+
/**
|
|
843
|
+
* Returns the set of camelCase field names for a table's date columns,
|
|
844
|
+
* derived once from `meta.dateColumns` (snake_case) via reverseColumnMap and
|
|
845
|
+
* memoized per table. Used so nested relation rows (camelCase keys) coerce
|
|
846
|
+
* dates the same way top-level rows do.
|
|
847
|
+
*/
|
|
848
|
+
private parseRow;
|
|
849
|
+
}
|