turbine-orm 0.51.0 → 0.52.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 +33 -5
- package/dist/cjs/cli/studio-ui.generated.js +1 -1
- package/dist/cjs/client.d.ts +106 -2
- package/dist/cjs/client.js +111 -5
- package/dist/cjs/dialect.d.ts +33 -0
- package/dist/cjs/dialect.js +14 -0
- package/dist/cjs/engine-config.d.ts +49 -0
- package/dist/cjs/engine-config.js +19 -0
- package/dist/cjs/index-advisor.js +0 -0
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.js +3 -2
- package/dist/cjs/mssql.d.ts +8 -3
- package/dist/cjs/mssql.js +22 -3
- package/dist/cjs/mysql.d.ts +7 -3
- package/dist/cjs/mysql.js +20 -3
- package/dist/cjs/nested-write.d.ts +31 -0
- package/dist/cjs/nested-write.js +80 -2
- package/dist/cjs/powdb-introspect.d.ts +10 -1
- package/dist/cjs/powdb-introspect.js +10 -1
- package/dist/cjs/powdb.d.ts +116 -6
- package/dist/cjs/powdb.js +169 -10
- package/dist/cjs/powql.d.ts +161 -1
- package/dist/cjs/powql.js +299 -19
- package/dist/cjs/prisma-compat.d.ts +54 -8
- package/dist/cjs/prisma-compat.js +136 -20
- package/dist/cjs/query/batched-loader.d.ts +7 -0
- package/dist/cjs/query/batched-loader.js +97 -15
- package/dist/cjs/query/builder.d.ts +131 -5
- package/dist/cjs/query/builder.js +223 -19
- package/dist/cjs/query/compound-unique.js +0 -0
- package/dist/cjs/query/index.d.ts +1 -1
- package/dist/cjs/query/index.js +2 -1
- package/dist/cjs/query/warn-registry.d.ts +10 -0
- package/dist/cjs/query/warn-registry.js +10 -0
- package/dist/cjs/query/writes.js +115 -7
- package/dist/cjs/sqlite.d.ts +10 -4
- package/dist/cjs/sqlite.js +18 -4
- package/dist/cli/studio-ui.generated.js +1 -1
- package/dist/client.d.ts +106 -2
- package/dist/client.js +111 -5
- package/dist/dialect.d.ts +33 -0
- package/dist/dialect.js +14 -0
- package/dist/engine-config.d.ts +49 -0
- package/dist/engine-config.js +18 -0
- package/dist/index-advisor.js +0 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/mssql.d.ts +8 -3
- package/dist/mssql.js +22 -3
- package/dist/mysql.d.ts +7 -3
- package/dist/mysql.js +20 -3
- package/dist/nested-write.d.ts +31 -0
- package/dist/nested-write.js +79 -2
- package/dist/powdb-introspect.d.ts +10 -1
- package/dist/powdb-introspect.js +10 -1
- package/dist/powdb.d.ts +116 -6
- package/dist/powdb.js +167 -9
- package/dist/powql.d.ts +161 -1
- package/dist/powql.js +299 -19
- package/dist/prisma-compat.d.ts +54 -8
- package/dist/prisma-compat.js +136 -20
- package/dist/query/batched-loader.d.ts +7 -0
- package/dist/query/batched-loader.js +98 -16
- package/dist/query/builder.d.ts +131 -5
- package/dist/query/builder.js +222 -18
- package/dist/query/compound-unique.js +0 -0
- package/dist/query/index.d.ts +1 -1
- package/dist/query/index.js +1 -1
- package/dist/query/warn-registry.d.ts +10 -0
- package/dist/query/warn-registry.js +10 -0
- package/dist/query/writes.js +116 -8
- package/dist/sqlite.d.ts +10 -4
- package/dist/sqlite.js +19 -5
- package/package.json +3 -3
package/dist/powql.js
CHANGED
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
import { randomUUID } from 'node:crypto';
|
|
38
38
|
import { NotFoundError, ReadOnlyError, TimeoutError, UnsupportedFeatureError, ValidationError } from './errors.js';
|
|
39
39
|
import { executeNestedCreate, executeNestedUpdate, hasRelationFields, } from './nested-write.js';
|
|
40
|
-
import { ALL_POWDB_CAPABILITIES, coerceNativeValue, isJsonColumn, isStaleFramePowdbError, PowdbFloatParam, PowdbJsonParam, powqlColumnType, quotePowqlIdent, requireCapability, rowToEntity, } from './powdb.js';
|
|
40
|
+
import { ALL_POWDB_CAPABILITIES, coerceNativeValue, isJsonColumn, isPowdbDatetimeColumn, isStaleFramePowdbError, PowdbFloatParam, PowdbJsonParam, powqlColumnType, quotePowqlIdent, requireCapability, rowToEntity, } from './powdb.js';
|
|
41
41
|
import { assertAggregatePiiOptIn } from './query/aggregates.js';
|
|
42
42
|
import { expandCompoundUniqueWhere } from './query/compound-unique.js';
|
|
43
43
|
import { isJsonFilter, isRelationPickOrderBy, orderByEntries } from './query/filters.js';
|
|
@@ -51,6 +51,18 @@ import { normalizeKeyColumns, snakeToCamel, } from './schema.js';
|
|
|
51
51
|
* before grouping. Mirrors the chunking the parity matrix documents.
|
|
52
52
|
*/
|
|
53
53
|
const MAX_RELATION_KEYS = 1000;
|
|
54
|
+
/**
|
|
55
|
+
* Max values one `in` / `notIn` on a PowDB-native `datetime` column may carry.
|
|
56
|
+
*
|
|
57
|
+
* Such a list is never sent as a list: it is expanded into an equality chain
|
|
58
|
+
* (see {@link PowqlInterface.buildDatetimeInList}), and PowQL spends one level
|
|
59
|
+
* of its 64-level nesting budget per chain term. Measured on the 0.20.0 addon:
|
|
60
|
+
* 63 terms parse at the top level, 61 one level deep, whatever else the
|
|
61
|
+
* predicate contains. 32 leaves room for the surrounding filter, and is also the
|
|
62
|
+
* key-chunk size the relation loaders use for a datetime correlation column, so
|
|
63
|
+
* a loader can never build a chain the engine will reject.
|
|
64
|
+
*/
|
|
65
|
+
export const MAX_POWQL_DATETIME_TERMS = 32;
|
|
54
66
|
/**
|
|
55
67
|
* Read-shaped actions whose statement may be transparently replayed once on a
|
|
56
68
|
* stale wire frame when `retryStaleReads` is enabled (see
|
|
@@ -260,6 +272,36 @@ export class PowqlInterface {
|
|
|
260
272
|
get capabilities() {
|
|
261
273
|
return this.pool.capabilities ?? ALL_POWDB_CAPABILITIES;
|
|
262
274
|
}
|
|
275
|
+
/**
|
|
276
|
+
* The `limit` a query actually emits: the explicit `limit`, Prisma's `take`
|
|
277
|
+
* alias, then the client-level `defaultLimit`. Shared by {@link buildFind} and
|
|
278
|
+
* the {@link findMany} zero short-circuit so the two can never disagree about
|
|
279
|
+
* which limit is in force.
|
|
280
|
+
*/
|
|
281
|
+
effectiveLimit(args) {
|
|
282
|
+
return args.limit ?? args.take ?? this.defaultLimit;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Reject a negative `limit` / `offset` before it reaches the engine. PowDB
|
|
286
|
+
* casts both with `as usize` at execution, so below engine 0.20 a negative
|
|
287
|
+
* limit wrapped to `usize::MAX` and silently returned EVERY row, the opposite
|
|
288
|
+
* of what the caller asked for; 0.20 refuses it. Validating client-side makes
|
|
289
|
+
* the refusal identical on every engine version and names the argument.
|
|
290
|
+
*
|
|
291
|
+
* `limit: 0` is legal and means "no rows" (SQL `LIMIT 0`). It is not emitted:
|
|
292
|
+
* callers short-circuit it, because PowDB's projection fast path returned ONE
|
|
293
|
+
* row for `limit 0` below 0.20.
|
|
294
|
+
*/
|
|
295
|
+
assertPagination(limit, offset, context) {
|
|
296
|
+
for (const [name, value] of [
|
|
297
|
+
['limit', limit],
|
|
298
|
+
['offset', offset],
|
|
299
|
+
]) {
|
|
300
|
+
if (value !== undefined && value !== null && value < 0) {
|
|
301
|
+
throw new ValidationError(`[turbine] ${context} on "${this.table}": \`${name}\` must not be negative (got ${value}).`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
263
305
|
/** A predicate that is always false, the empty-`in` / contradiction sentinel. */
|
|
264
306
|
alwaysFalse() {
|
|
265
307
|
const pk = this.meta.primaryKey[0] ?? this.meta.columns[0]?.name;
|
|
@@ -317,6 +359,43 @@ export class PowqlInterface {
|
|
|
317
359
|
}
|
|
318
360
|
return parts.join(' and ');
|
|
319
361
|
}
|
|
362
|
+
/**
|
|
363
|
+
* Gate a predicate on a PowDB-native `datetime` column. Turbine binds a JS
|
|
364
|
+
* `Date` as an integer count of microseconds, and PowDB writes a timestamp
|
|
365
|
+
* literal as a plain integer, so every such predicate is a `DateTime` vs `Int`
|
|
366
|
+
* comparison. Below engine 0.20 that pairing was unhandled and fell back to
|
|
367
|
+
* comparing TYPE TAGS, so `>` matched every non-null row, `=` and `<` matched
|
|
368
|
+
* none, and the answer additionally changed with the column's access path
|
|
369
|
+
* (indexed vs scanned). 0.20 compares microseconds and every binary operator
|
|
370
|
+
* (`=`, `!=`, `<`, `<=`, `>`, `>=`) is correct.
|
|
371
|
+
*
|
|
372
|
+
* ONE gate covers the whole family, whatever spelling the caller used. `in` /
|
|
373
|
+
* `not in` are included because their still-broken LIST form is never emitted:
|
|
374
|
+
* {@link buildInList} expands a datetime list into the equality chain the
|
|
375
|
+
* engine does answer correctly, which is the same binary comparison this flag
|
|
376
|
+
* governs. So on >= 0.20 every path is served (direct predicates, relation
|
|
377
|
+
* filters, the batched loaders, nested projections, native joins, and the
|
|
378
|
+
* findUnique / update / delete / upsert by-key paths), and below 0.20 every
|
|
379
|
+
* path that compares against a literal is refused with one message, which
|
|
380
|
+
* names the read paths that do not.
|
|
381
|
+
*
|
|
382
|
+
* `is null` / `is not null` are never gated: they compare no literal and are
|
|
383
|
+
* correct on every version. Ordering, grouping and `min`/`max` are likewise
|
|
384
|
+
* unaffected (they compare datetimes against each other, never against an int),
|
|
385
|
+
* as are nested-projection and join correlations, which are column-to-column.
|
|
386
|
+
*
|
|
387
|
+
* Only PowDB's native `datetime` type is affected. Turbine's own DDL emits
|
|
388
|
+
* `int` epoch micros for a `Date` column, so a Turbine-provisioned database
|
|
389
|
+
* never reaches this: the exposed shape is a table created outside Turbine and
|
|
390
|
+
* read through `introspectPowdbDatabase`, or hand-written metadata.
|
|
391
|
+
*/
|
|
392
|
+
assertDatetimePredicateSupported(col) {
|
|
393
|
+
if (!isPowdbDatetimeColumn(col))
|
|
394
|
+
return;
|
|
395
|
+
requireCapability(this.capabilities, 'datetimeCompare', `comparisons on the PowDB datetime column "${col.name}"`, 'Reading that column is unaffected, and so are ordering, grouping and `is null` checks; only comparing it ' +
|
|
396
|
+
'against a bound timestamp needs the fix. A nested `with` (the default nested-projection / join paths) ' +
|
|
397
|
+
'correlates column to column, so it loads the relation without any such comparison.');
|
|
398
|
+
}
|
|
320
399
|
/** Build a single `field: value | operator` condition. */
|
|
321
400
|
buildFieldCondition(field, value, params, alias) {
|
|
322
401
|
const colMeta = this.column(field);
|
|
@@ -324,6 +403,7 @@ export class PowqlInterface {
|
|
|
324
403
|
if (value === null)
|
|
325
404
|
return `${ref} is null`;
|
|
326
405
|
if (value instanceof Date || typeof value !== 'object') {
|
|
406
|
+
this.assertDatetimePredicateSupported(colMeta);
|
|
327
407
|
return `${ref} = ${this.param(value, params, colMeta)}`;
|
|
328
408
|
}
|
|
329
409
|
const op = value;
|
|
@@ -338,6 +418,7 @@ export class PowqlInterface {
|
|
|
338
418
|
rejectUnsupportedFilter(op, field);
|
|
339
419
|
if (!Object.keys(op).some((k) => OPERATOR_KEYS.has(k))) {
|
|
340
420
|
// A bare object that is not an operator set, equality by value.
|
|
421
|
+
this.assertDatetimePredicateSupported(colMeta);
|
|
341
422
|
return `${ref} = ${this.param(value, params)}`;
|
|
342
423
|
}
|
|
343
424
|
const insensitive = op.mode === 'insensitive';
|
|
@@ -346,6 +427,17 @@ export class PowqlInterface {
|
|
|
346
427
|
for (const [opName, opVal] of Object.entries(op)) {
|
|
347
428
|
if (opVal === undefined || opName === 'mode')
|
|
348
429
|
continue;
|
|
430
|
+
// Every operator below compares the column against a bound literal, which
|
|
431
|
+
// is the shape a PowDB `datetime` column can only answer from engine 0.20
|
|
432
|
+
// (see assertDatetimePredicateSupported; `in`/`notIn` reach the engine as
|
|
433
|
+
// an equality chain, so they are the same comparison). The two exceptions
|
|
434
|
+
// compare nothing and stay allowed on every version: a null operand
|
|
435
|
+
// (`equals`/`not: null` → `is [not] null`) and an empty `in`/`notIn` list
|
|
436
|
+
// (a compile-time constant, never sent).
|
|
437
|
+
const comparesNothing = (opVal === null && (opName === 'equals' || opName === 'not')) ||
|
|
438
|
+
((opName === 'in' || opName === 'notIn') && Array.isArray(opVal) && opVal.length === 0);
|
|
439
|
+
if (!comparesNothing)
|
|
440
|
+
this.assertDatetimePredicateSupported(colMeta);
|
|
349
441
|
switch (opName) {
|
|
350
442
|
case 'equals':
|
|
351
443
|
conds.push(opVal === null ? `${ref} is null` : `${lhs} = ${this.bind(opVal, params, insensitive)}`);
|
|
@@ -370,10 +462,10 @@ export class PowqlInterface {
|
|
|
370
462
|
conds.push(`${lhs} <= ${this.bind(opVal, params, insensitive)}`);
|
|
371
463
|
break;
|
|
372
464
|
case 'in':
|
|
373
|
-
conds.push(this.buildInList(lhs, opVal, params, insensitive, false));
|
|
465
|
+
conds.push(this.buildInList(lhs, opVal, params, insensitive, false, colMeta));
|
|
374
466
|
break;
|
|
375
467
|
case 'notIn':
|
|
376
|
-
conds.push(this.buildInList(lhs, opVal, params, insensitive, true));
|
|
468
|
+
conds.push(this.buildInList(lhs, opVal, params, insensitive, true, colMeta));
|
|
377
469
|
break;
|
|
378
470
|
case 'contains':
|
|
379
471
|
conds.push(`${lhs} like ${this.bindLike(`%${escapeLike(String(opVal))}%`, params, insensitive)}`);
|
|
@@ -510,13 +602,20 @@ export class PowqlInterface {
|
|
|
510
602
|
const ph = this.param(pattern, params);
|
|
511
603
|
return insensitive ? `lower(${ph})` : ph;
|
|
512
604
|
}
|
|
513
|
-
/**
|
|
514
|
-
|
|
605
|
+
/**
|
|
606
|
+
* `lhs [not] in ($1, $2, …)`, empty list collapses to a constant.
|
|
607
|
+
*
|
|
608
|
+
* A PowDB-native `datetime` column takes the expanded form instead (see
|
|
609
|
+
* {@link buildDatetimeInList}): its `in` list is still broken upstream at 0.20.
|
|
610
|
+
*/
|
|
611
|
+
buildInList(lhs, values, params, insensitive, negate, col) {
|
|
515
612
|
if (!Array.isArray(values) || values.length === 0) {
|
|
516
613
|
// `in []` matches nothing; `not in []` matches everything (SQL parity requires a
|
|
517
614
|
// missing-value row to match `notIn []`, so NO presence guard is appended here).
|
|
518
615
|
return negate ? '(1 = 1)' : this.alwaysFalse();
|
|
519
616
|
}
|
|
617
|
+
if (col && isPowdbDatetimeColumn(col))
|
|
618
|
+
return this.buildDatetimeInList(lhs, values, params, negate, col);
|
|
520
619
|
const items = values.map((v) => this.bind(v, params, insensitive)).join(', ');
|
|
521
620
|
if (negate) {
|
|
522
621
|
// PowQL `not in` matches missing-value rows, so append `and lhs is not null`
|
|
@@ -525,6 +624,78 @@ export class PowqlInterface {
|
|
|
525
624
|
}
|
|
526
625
|
return `${lhs} in (${items})`;
|
|
527
626
|
}
|
|
627
|
+
/**
|
|
628
|
+
* `in` / `not in` on a PowDB-native `datetime` column, expanded into the
|
|
629
|
+
* equality chain the engine answers correctly:
|
|
630
|
+
*
|
|
631
|
+
* `in` → `(.ts = $1 or .ts = $2 …)`
|
|
632
|
+
* `notIn` → `(.ts != $1 and .ts != $2 … and .ts is not null)`
|
|
633
|
+
*
|
|
634
|
+
* PowQL's LIST form compares a datetime column against integer timestamp
|
|
635
|
+
* literals by TYPE TAG as of engine 0.20 (the 0.20 timestamp fix covered the
|
|
636
|
+
* binary operators only): measured on the 0.20.0 addon, `filter .ts in
|
|
637
|
+
* (<micros>, …)` matches nothing and `not in` matches everything, while the
|
|
638
|
+
* identical lists against an `int` control column answer correctly. The
|
|
639
|
+
* expanded chain uses the operators 0.20 DID fix, so it answers correctly and
|
|
640
|
+
* matches the int control exactly.
|
|
641
|
+
*
|
|
642
|
+
* This is what keeps the relation family coherent: relation filters and the
|
|
643
|
+
* batched loaders both compile to a key `in` list, so without the rewrite the
|
|
644
|
+
* same relation was refused through one strategy and served through another
|
|
645
|
+
* (nested projections and joins correlate column to column and never emit a
|
|
646
|
+
* list at all).
|
|
647
|
+
*
|
|
648
|
+
* The cost is the chain's width. PowQL bounds the SHAPE of the predicate tree,
|
|
649
|
+
* and a flat `or` / `and` chain spends one level per term against the same
|
|
650
|
+
* 64-level budget as nested parens (measured on 0.20: 63 terms at the top
|
|
651
|
+
* level, 61 one level deep), so the expansion is capped at
|
|
652
|
+
* {@link MAX_POWQL_DATETIME_TERMS} with headroom for whatever predicate it
|
|
653
|
+
* sits inside. The loaders chunk their key lists to that cap, so only a
|
|
654
|
+
* caller-written list (or a relation filter matching very many distinct
|
|
655
|
+
* timestamps) can exceed it, and that raises a typed E017 saying so rather
|
|
656
|
+
* than an engine parse failure.
|
|
657
|
+
*/
|
|
658
|
+
buildDatetimeInList(lhs, values, params, negate, col) {
|
|
659
|
+
if (values.length > MAX_POWQL_DATETIME_TERMS) {
|
|
660
|
+
throw new UnsupportedFeatureError(`\`${negate ? 'notIn' : 'in'}\` with ${values.length} values on the PowDB datetime column "${col.name}"`, 'PowDB', `PowQL's \`in\` list still compares a datetime column against integer timestamp literals by type tag as of ` +
|
|
661
|
+
`engine 0.20, so Turbine expands it into an equality chain, which PowQL's nesting budget caps at ` +
|
|
662
|
+
`${MAX_POWQL_DATETIME_TERMS} terms. Narrow the list (a \`gte\`/\`lte\` range over the same timestamps is ` +
|
|
663
|
+
'one comparison), split the call and merge the results, or store the column as a PowQL `int` of epoch ' +
|
|
664
|
+
"microseconds, which is what Turbine's own DDL emits for a `Date` column. A relation filter reaches this " +
|
|
665
|
+
'when the inner predicate matches more than that many distinct key timestamps.');
|
|
666
|
+
}
|
|
667
|
+
const terms = values.map((v) => `${lhs} ${negate ? '!=' : '='} ${this.param(v, params, col)}`);
|
|
668
|
+
// `!=` already excludes a missing-value row, but the trailing presence guard
|
|
669
|
+
// keeps the emitted predicate identical in meaning to the plain `not in`
|
|
670
|
+
// branch above (and to SQL null semantics) on every engine version.
|
|
671
|
+
if (negate)
|
|
672
|
+
terms.push(`${lhs} is not null`);
|
|
673
|
+
return `(${terms.join(negate ? ' and ' : ' or ')})`;
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* A literal `ref in (…)` clause for the hand-built key lists the relation
|
|
677
|
+
* loaders emit (they bypass {@link buildWhere}). Routes a PowDB-native
|
|
678
|
+
* `datetime` key column through the same equality-chain expansion the
|
|
679
|
+
* where-builder uses, so a datetime junction / correlation key behaves
|
|
680
|
+
* identically however the statement was assembled.
|
|
681
|
+
*/
|
|
682
|
+
inClause(ref, values, params, col) {
|
|
683
|
+
if (col && isPowdbDatetimeColumn(col)) {
|
|
684
|
+
this.assertDatetimePredicateSupported(col);
|
|
685
|
+
return this.buildDatetimeInList(ref, values, params, false, col);
|
|
686
|
+
}
|
|
687
|
+
return `${ref} in (${values.map((v) => this.param(v, params, col)).join(', ')})`;
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Key-chunk size for a relation loader. A PowDB-native `datetime` correlation
|
|
691
|
+
* column's `in` list is expanded into an equality chain, which PowQL's nesting
|
|
692
|
+
* budget bounds, so those keys chunk at {@link MAX_POWQL_DATETIME_TERMS}
|
|
693
|
+
* (more, smaller round-trips) instead of {@link MAX_RELATION_KEYS}.
|
|
694
|
+
*/
|
|
695
|
+
keyChunkSize(meta, colName) {
|
|
696
|
+
const col = meta?.columns.find((c) => c.name === colName);
|
|
697
|
+
return col && isPowdbDatetimeColumn(col) ? MAX_POWQL_DATETIME_TERMS : MAX_RELATION_KEYS;
|
|
698
|
+
}
|
|
528
699
|
/**
|
|
529
700
|
* Pre-resolve every relation filter (`some`/`none`/`every`) in a where clause
|
|
530
701
|
* into a plain scalar `in`/`notIn` condition on the **local key**, by running
|
|
@@ -639,15 +810,18 @@ export class PowqlInterface {
|
|
|
639
810
|
return [...new Set(rows.map((r) => r[targetPkField]).filter((v) => v != null))];
|
|
640
811
|
};
|
|
641
812
|
// Junction source keys linking any of `targetPks` (literal IN-list, never a subquery).
|
|
813
|
+
const junctionMeta = this.schema.tables[through.table];
|
|
814
|
+
const targetJColMeta = junctionMeta?.columns.find((c) => c.name === targetJCol);
|
|
815
|
+
const junctionChunk = this.keyChunkSize(junctionMeta, targetJCol);
|
|
642
816
|
const sourcesForTargets = async (targetPks) => {
|
|
643
817
|
if (!targetPks.length)
|
|
644
818
|
return [];
|
|
645
819
|
const out = new Set();
|
|
646
|
-
for (let i = 0; i < targetPks.length; i +=
|
|
647
|
-
const chunk = targetPks.slice(i, i +
|
|
820
|
+
for (let i = 0; i < targetPks.length; i += junctionChunk) {
|
|
821
|
+
const chunk = targetPks.slice(i, i + junctionChunk);
|
|
648
822
|
const params = [];
|
|
649
|
-
const
|
|
650
|
-
const { rows } = await this.exec(`${quotePowqlIdent(through.table)} filter
|
|
823
|
+
const keyClause = this.inClause(`.${targetJCol}`, chunk, params, targetJColMeta);
|
|
824
|
+
const { rows } = await this.exec(`${quotePowqlIdent(through.table)} filter ${keyClause} { .${sourceJCol} }`, params, timeout, 'findMany');
|
|
651
825
|
for (const r of rows) {
|
|
652
826
|
const v = r[sourceJCol];
|
|
653
827
|
if (v != null)
|
|
@@ -948,6 +1122,13 @@ export class PowqlInterface {
|
|
|
948
1122
|
// -------------------------------------------------------------------------
|
|
949
1123
|
async findMany(args = {}) {
|
|
950
1124
|
return this.withMiddleware('findMany', args, async () => {
|
|
1125
|
+
// `limit: 0` means "no rows" (SQL `LIMIT 0`), and answering it client-side
|
|
1126
|
+
// is correct on every engine version: PowDB's projection fast path returned
|
|
1127
|
+
// ONE row for `limit 0` below 0.20. Validate first so a negative limit still
|
|
1128
|
+
// raises instead of falling through to a query.
|
|
1129
|
+
this.assertPagination(this.effectiveLimit(args), args.offset, 'findMany');
|
|
1130
|
+
if (this.effectiveLimit(args) === 0)
|
|
1131
|
+
return [];
|
|
951
1132
|
const { rows, native, resolvedWhere, nestedPlans, linkPlans, residualWith } = await this.runFind(args, 'findMany');
|
|
952
1133
|
const entities = this.shape(rows, native);
|
|
953
1134
|
if (nestedPlans.length)
|
|
@@ -1019,7 +1200,8 @@ export class PowqlInterface {
|
|
|
1019
1200
|
const distinct = args.distinct?.length ? ' distinct' : '';
|
|
1020
1201
|
const filter = where ? ` filter ${where}` : '';
|
|
1021
1202
|
const order = this.buildOrder(args.orderBy, params, alias);
|
|
1022
|
-
const limit =
|
|
1203
|
+
const limit = this.effectiveLimit(args);
|
|
1204
|
+
this.assertPagination(limit, args.offset, 'findMany');
|
|
1023
1205
|
if (limit === undefined && this.warnOnUnlimited && !this.warnedUnlimited) {
|
|
1024
1206
|
this.warnedUnlimited = true;
|
|
1025
1207
|
console.warn(`[turbine] findMany on "${this.table}" has no limit: this scans the whole table.`);
|
|
@@ -1218,8 +1400,9 @@ export class PowqlInterface {
|
|
|
1218
1400
|
// cell) so a datetime correlation column stitches instead of silently
|
|
1219
1401
|
// returning [].
|
|
1220
1402
|
const childByKey = new Map();
|
|
1221
|
-
|
|
1222
|
-
|
|
1403
|
+
const chunkSize = this.keyChunkSize(targetMeta, childKeyCol);
|
|
1404
|
+
for (let i = 0; i < keys.length; i += chunkSize) {
|
|
1405
|
+
const chunk = keys.slice(i, i + chunkSize);
|
|
1223
1406
|
const childWhere = {
|
|
1224
1407
|
...fetchOptions.where,
|
|
1225
1408
|
[childKeyField]: { in: chunk },
|
|
@@ -1295,13 +1478,16 @@ export class PowqlInterface {
|
|
|
1295
1478
|
return;
|
|
1296
1479
|
}
|
|
1297
1480
|
// (1) Junction rows: sourceKeyVal(String) → [targetKeyVal(String)].
|
|
1481
|
+
const junctionMeta = this.schema.tables[through.table];
|
|
1482
|
+
const sourceJColMeta = junctionMeta?.columns.find((c) => c.name === sourceJCol);
|
|
1298
1483
|
const targetsBySource = new Map();
|
|
1299
1484
|
const allTargetVals = new Set();
|
|
1300
|
-
|
|
1301
|
-
|
|
1485
|
+
const junctionChunk = this.keyChunkSize(junctionMeta, sourceJCol);
|
|
1486
|
+
for (let i = 0; i < parentKeys.length; i += junctionChunk) {
|
|
1487
|
+
const chunk = parentKeys.slice(i, i + junctionChunk);
|
|
1302
1488
|
const params = [];
|
|
1303
|
-
const
|
|
1304
|
-
const powql = `${quotePowqlIdent(through.table)} filter
|
|
1489
|
+
const keyClause = this.inClause(`.${sourceJCol}`, chunk, params, sourceJColMeta);
|
|
1490
|
+
const powql = `${quotePowqlIdent(through.table)} filter ${keyClause} { .${sourceJCol}, .${targetJCol} }`;
|
|
1305
1491
|
const { rows } = await this.exec(powql, params, timeout, 'findMany');
|
|
1306
1492
|
for (const row of rows) {
|
|
1307
1493
|
const sv = String(row[sourceJCol]);
|
|
@@ -1319,8 +1505,9 @@ export class PowqlInterface {
|
|
|
1319
1505
|
const targetQi = new PowqlInterface(this.pool, rel.to, this.schema, [], this.options);
|
|
1320
1506
|
const targetByPk = new Map();
|
|
1321
1507
|
const targetValList = [...allTargetVals].map((v) => targetPkColMeta ? coerceScalar(v, targetPkColMeta.tsType) : v);
|
|
1322
|
-
|
|
1323
|
-
|
|
1508
|
+
const targetChunk = this.keyChunkSize(targetMeta, targetPkCol);
|
|
1509
|
+
for (let i = 0; i < targetValList.length; i += targetChunk) {
|
|
1510
|
+
const chunk = targetValList.slice(i, i + targetChunk);
|
|
1324
1511
|
const where = {
|
|
1325
1512
|
...options.where,
|
|
1326
1513
|
[targetPkField]: { in: chunk },
|
|
@@ -1430,6 +1617,11 @@ export class PowqlInterface {
|
|
|
1430
1617
|
if ((options.limit !== undefined || options.offset) && (single || parentCount > MAX_RELATION_KEYS)) {
|
|
1431
1618
|
return false;
|
|
1432
1619
|
}
|
|
1620
|
+
// A `limit 0` relation stays off the join statement for the same reason it
|
|
1621
|
+
// stays off a nested projection: PowDB answered `limit 0` with one row below
|
|
1622
|
+
// engine 0.20. The loader resolves it client-side, correctly on every version.
|
|
1623
|
+
if (options.limit === 0)
|
|
1624
|
+
return false;
|
|
1433
1625
|
return true;
|
|
1434
1626
|
}
|
|
1435
1627
|
/**
|
|
@@ -1471,6 +1663,7 @@ export class PowqlInterface {
|
|
|
1471
1663
|
const childCols = this.joinChildCols(targetQi, options, includePii);
|
|
1472
1664
|
const filter = await this.joinFilter(targetQi, parent.resolvedWhere, options.where, 'c', params, options.timeout ?? timeout);
|
|
1473
1665
|
const order = targetQi.buildOrder(options.orderBy, params, 'c');
|
|
1666
|
+
this.assertPagination(options.limit, options.offset, `relation "${relName}"`);
|
|
1474
1667
|
const limitClause = options.limit !== undefined ? ` limit ${this.param(options.limit, params)}` : '';
|
|
1475
1668
|
const offsetClause = options.offset ? ` offset ${this.param(options.offset, params)}` : '';
|
|
1476
1669
|
const proj = this.joinProjection(childCols, `p.${quotePowqlIdent(parentKeyCol)}`, 'c');
|
|
@@ -1625,6 +1818,10 @@ export class PowqlInterface {
|
|
|
1625
1818
|
* - m2m (the block takes exactly one child table; the junction-order
|
|
1626
1819
|
* stitch has no nested equivalent), and composite relation keys;
|
|
1627
1820
|
* - a to-one relation carrying `limit`/`offset` (the loaders' semantics);
|
|
1821
|
+
* - a relation `limit` of exactly 0 (a nested block would emit `limit 0`,
|
|
1822
|
+
* which PowDB's projection fast path answered with ONE row below engine
|
|
1823
|
+
* 0.20; the loader path resolves it client-side and is correct on every
|
|
1824
|
+
* version). A NEGATIVE relation limit is refused outright, not fallen back;
|
|
1628
1825
|
* - `distinct` inside the relation options (no nested grammar for it);
|
|
1629
1826
|
* - a projected child column whose tsType is `bigint` or `Uint8Array`
|
|
1630
1827
|
* (values ride a JSON array, which cannot carry them losslessly);
|
|
@@ -1650,6 +1847,9 @@ export class PowqlInterface {
|
|
|
1650
1847
|
const single = rel.type === 'belongsTo' || rel.type === 'hasOne';
|
|
1651
1848
|
if (single && (options.limit !== undefined || options.offset))
|
|
1652
1849
|
return null;
|
|
1850
|
+
this.assertPagination(options.limit, options.offset, `relation "${relName}"`);
|
|
1851
|
+
if (options.limit === 0)
|
|
1852
|
+
return null;
|
|
1653
1853
|
const targetQi = new PowqlInterface(this.pool, rel.to, this.schema, [], this.options);
|
|
1654
1854
|
const cols = targetQi.projectedColumns(options.select, options.omit, includePii);
|
|
1655
1855
|
const byName = new Map(targetQi.meta.columns.map((c) => [c.name, c]));
|
|
@@ -1984,6 +2184,52 @@ export class PowqlInterface {
|
|
|
1984
2184
|
return row;
|
|
1985
2185
|
});
|
|
1986
2186
|
}
|
|
2187
|
+
/**
|
|
2188
|
+
* Refuse a `createMany` whose rows do not all name the SAME fields.
|
|
2189
|
+
*
|
|
2190
|
+
* PowQL could express it: a multi-row insert carries one `{ col := … }` tuple
|
|
2191
|
+
* per row, each with its own column list, so a ragged call inserts every
|
|
2192
|
+
* value. The SQL engines cannot, their single statement takes its column list
|
|
2193
|
+
* from the first row, so a field only a later row names is dropped and a field
|
|
2194
|
+
* only the first row names is written as NULL over that column's default. They
|
|
2195
|
+
* refuse it (ValidationError, query/writes.ts), and PowDB matching that
|
|
2196
|
+
* refusal is what makes the call portable: a shape accepted here and rejected
|
|
2197
|
+
* by every other engine turns a PowDB-to-Postgres move into a hard error found
|
|
2198
|
+
* in production rather than at the first run.
|
|
2199
|
+
*
|
|
2200
|
+
* Runs AFTER `applyPkDefault`, on the rows as they will actually be written:
|
|
2201
|
+
* a defaulted string PK is filled in on every row, so `[{}, { name }]` is
|
|
2202
|
+
* refused for the missing `name`, not for the PK the client supplied itself.
|
|
2203
|
+
* Rows that all name the same fields (the overwhelmingly common shape,
|
|
2204
|
+
* including N rows of pure defaults) cost one `Object.keys` pass and emit
|
|
2205
|
+
* byte-identical PowQL.
|
|
2206
|
+
*/
|
|
2207
|
+
assertUniformCreateManyRows(rows) {
|
|
2208
|
+
const definedKeys = (row) => Object.keys(row).filter((k) => row[k] !== undefined);
|
|
2209
|
+
const firstKeys = definedKeys(rows[0]);
|
|
2210
|
+
const expected = new Set(firstKeys);
|
|
2211
|
+
const quoteList = (names) => names.map((n) => `"${n}"`).join(', ');
|
|
2212
|
+
for (let i = 1; i < rows.length; i++) {
|
|
2213
|
+
const rowKeys = definedKeys(rows[i]);
|
|
2214
|
+
// No stranger and the same count means the same set (object keys are unique).
|
|
2215
|
+
const unexpected = rowKeys.filter((k) => !expected.has(k));
|
|
2216
|
+
if (unexpected.length === 0 && rowKeys.length === expected.size)
|
|
2217
|
+
continue;
|
|
2218
|
+
const present = new Set(rowKeys);
|
|
2219
|
+
const missing = firstKeys.filter((k) => !present.has(k));
|
|
2220
|
+
const parts = [];
|
|
2221
|
+
if (missing.length > 0)
|
|
2222
|
+
parts.push(`does not supply ${quoteList(missing)}`);
|
|
2223
|
+
if (unexpected.length > 0)
|
|
2224
|
+
parts.push(`supplies ${quoteList(unexpected)}, which the first row does not`);
|
|
2225
|
+
throw new ValidationError(`[turbine] createMany on "${this.table}": row ${i} ${parts.join(' and ')}. ` +
|
|
2226
|
+
'Every row must supply the same fields (a field set to `undefined` counts as omitted, exactly as it does ' +
|
|
2227
|
+
'in `create`). PowQL itself would insert the ragged rows, but the SQL engines build ONE statement whose ' +
|
|
2228
|
+
"column list comes from the first row, so there a later row's extra field is dropped and a field it " +
|
|
2229
|
+
"omits is written as NULL over that column's default. Supply the field explicitly on every row, or " +
|
|
2230
|
+
'split the call into one createMany per field set.');
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
1987
2233
|
async createMany(args) {
|
|
1988
2234
|
return this.withMiddleware('createMany', args, async () => {
|
|
1989
2235
|
if (args.skipDuplicates) {
|
|
@@ -1994,6 +2240,7 @@ export class PowqlInterface {
|
|
|
1994
2240
|
const inputs = args.data.map((d) => this.applyPkDefault(d));
|
|
1995
2241
|
if (!inputs.length)
|
|
1996
2242
|
return [];
|
|
2243
|
+
this.assertUniformCreateManyRows(inputs);
|
|
1997
2244
|
const params = [];
|
|
1998
2245
|
const tuples = inputs.map((d) => {
|
|
1999
2246
|
const assigns = this.scalarData(d);
|
|
@@ -2250,6 +2497,34 @@ export class PowqlInterface {
|
|
|
2250
2497
|
return Number((rows[0]?.value ?? rows[0]?.count ?? 0));
|
|
2251
2498
|
});
|
|
2252
2499
|
}
|
|
2500
|
+
/**
|
|
2501
|
+
* Gate ONE field of a per-field `_count`.
|
|
2502
|
+
*
|
|
2503
|
+
* `_count: { col: true }` compiles to `count(T { .col })`, which counts
|
|
2504
|
+
* NON-NULL values of the column (SQL's `COUNT(col)`) only from engine 0.20 on.
|
|
2505
|
+
* Below 0.20 both PowDB frontends ignored the projection and returned the ROW
|
|
2506
|
+
* count.
|
|
2507
|
+
*
|
|
2508
|
+
* That divergence only EXISTS on a nullable column: where the column is NOT
|
|
2509
|
+
* NULL the row count and the non-null count are the same number, so the
|
|
2510
|
+
* pre-0.20 answer was already the right one and the query keeps working. The
|
|
2511
|
+
* gate is therefore per-column, not per-call: refusing the whole feature would
|
|
2512
|
+
* take a correct, working call away from every user on the engine line Turbine
|
|
2513
|
+
* shipped against and hand them nothing.
|
|
2514
|
+
*
|
|
2515
|
+
* Nullability comes from the column metadata (`introspectPowdbDatabase` reads
|
|
2516
|
+
* PowDB's `required` modifier; `defineSchema` declares it). Metadata that
|
|
2517
|
+
* claims NOT NULL for a column the live catalog lets be null would count rows
|
|
2518
|
+
* instead of values below 0.20, the same drift any stale-metadata query has.
|
|
2519
|
+
*/
|
|
2520
|
+
assertProjectedCountSupported(field) {
|
|
2521
|
+
const col = this.column(field);
|
|
2522
|
+
if (!col.nullable)
|
|
2523
|
+
return;
|
|
2524
|
+
requireCapability(this.capabilities, 'projectedCountNonNull', `per-field \`_count\` of the nullable column "${col.name}"`, 'Below that version the engine ignored the column projection and returned the ROW count, which differs from ' +
|
|
2525
|
+
'every SQL engine exactly when the column is nullable. `_count: true` (a row count) and a per-field ' +
|
|
2526
|
+
'`_count` of a NOT NULL column are correct on every version and are never refused.');
|
|
2527
|
+
}
|
|
2253
2528
|
async aggregate(args) {
|
|
2254
2529
|
return this.withMiddleware('aggregate', args, async () => {
|
|
2255
2530
|
// One scalar query per aggregate, PowDB's bare-projection aggregate is broken.
|
|
@@ -2271,6 +2546,7 @@ export class PowqlInterface {
|
|
|
2271
2546
|
else {
|
|
2272
2547
|
const counts = {};
|
|
2273
2548
|
for (const field of Object.keys(args._count).filter((f) => args._count[f])) {
|
|
2549
|
+
this.assertProjectedCountSupported(field);
|
|
2274
2550
|
counts[field] = (await scalar(`count(${this.qt}${filter} { ${this.ref(field)} })`)) ?? 0;
|
|
2275
2551
|
}
|
|
2276
2552
|
result._count = counts;
|
|
@@ -2440,7 +2716,11 @@ export class PowqlInterface {
|
|
|
2440
2716
|
const having = this.buildHaving(args.having, params, aggInner);
|
|
2441
2717
|
const order = this.buildGroupOrder(args.orderBy, byOrderExprs, aggOrderExprs);
|
|
2442
2718
|
// LIMIT / OFFSET over the result groups, applied after ORDER BY (mirrors
|
|
2443
|
-
// the SQL groupBy). offset 0 is a no-op, matching findMany
|
|
2719
|
+
// the SQL groupBy). offset 0 is a no-op, matching findMany; `limit: 0` is
|
|
2720
|
+
// answered client-side (PowDB returned one row for it below engine 0.20).
|
|
2721
|
+
this.assertPagination(args.limit, args.offset, 'groupBy');
|
|
2722
|
+
if (args.limit === 0)
|
|
2723
|
+
return [];
|
|
2444
2724
|
const limitClause = args.limit !== undefined ? ` limit ${this.param(args.limit, params)}` : '';
|
|
2445
2725
|
const offsetClause = args.offset ? ` offset ${this.param(args.offset, params)}` : '';
|
|
2446
2726
|
const powql = `${this.qt}${filter} group ${groupExprs.join(', ')}${having}${order}${limitClause}${offsetClause} { ${proj.join(', ')} }`;
|
package/dist/prisma-compat.d.ts
CHANGED
|
@@ -25,7 +25,10 @@
|
|
|
25
25
|
* , the un-awaited delegate calls defer to Turbine's `build*()` methods and
|
|
26
26
|
* run atomically through the core batch `$transaction([...])` path.
|
|
27
27
|
* - **Raw SQL**: `$queryRaw` / `$executeRaw` tagged templates (with
|
|
28
|
-
* `Prisma.sql`-style nested-fragment flattening) and the `*Unsafe` variants
|
|
28
|
+
* `Prisma.sql`-style nested-fragment flattening) and the `*Unsafe` variants,
|
|
29
|
+
* on the client AND on the transaction client, where they run on the
|
|
30
|
+
* transaction's own connection so a mixed raw + delegate `$transaction` stays
|
|
31
|
+
* atomic.
|
|
29
32
|
* - **Result reshaping**: `_count` objects keyed back to Prisma relation names,
|
|
30
33
|
* and to-one relations surfaced as `object | null`.
|
|
31
34
|
*
|
|
@@ -128,6 +131,18 @@ export interface CompatQueryInterface {
|
|
|
128
131
|
/** A transaction-scoped client handed to a `$transaction(callback)`. */
|
|
129
132
|
export interface CompatTransactionClient {
|
|
130
133
|
table(name: string): CompatQueryInterface;
|
|
134
|
+
/**
|
|
135
|
+
* Execute a prebuilt `(text, params)` statement on the TRANSACTION's own
|
|
136
|
+
* connection (core `TransactionClient.rawQuery`). It is what backs the
|
|
137
|
+
* transaction-scoped `$queryRaw` / `$executeRaw`; without it those methods
|
|
138
|
+
* would have to reach around the transaction to the pool, which silently
|
|
139
|
+
* breaks atomicity. Optional only so a test stub can omit it: when it is
|
|
140
|
+
* absent the raw methods throw instead of escaping the transaction.
|
|
141
|
+
*/
|
|
142
|
+
rawQuery?(text: string, params?: readonly unknown[]): Promise<{
|
|
143
|
+
rows: unknown[];
|
|
144
|
+
rowCount: number | null;
|
|
145
|
+
}>;
|
|
131
146
|
}
|
|
132
147
|
/** The minimal `TurbineClient` surface the adapter consumes. */
|
|
133
148
|
export interface CompatTurbineClient extends CompatTransactionClient {
|
|
@@ -135,6 +150,21 @@ export interface CompatTurbineClient extends CompatTransactionClient {
|
|
|
135
150
|
$transaction<R>(fn: (tx: CompatTransactionClient) => Promise<R>, options?: unknown): Promise<R>;
|
|
136
151
|
$transaction(queries: readonly DeferredQuery<unknown>[]): Promise<unknown[]>;
|
|
137
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* Brand marking an object as a raw-SQL fragment whose `strings` are spliced
|
|
155
|
+
* VERBATIM into the emitted statement (see `flattenTemplate`). Deliberately a
|
|
156
|
+
* module-private `Symbol()` and NOT `Symbol.for(...)`: a registry symbol is
|
|
157
|
+
* reachable by name from anywhere in the process, so any dependency could mint
|
|
158
|
+
* an object that flattens as trusted SQL. With a private symbol the only way to
|
|
159
|
+
* obtain a fragment is to call `Prisma.sql` / `Prisma.join` / `Prisma.raw` from
|
|
160
|
+
* this module.
|
|
161
|
+
*
|
|
162
|
+
* The fragment check is fail-CLOSED: an object that does not carry this exact
|
|
163
|
+
* symbol is bound as a `$N` parameter, never spliced. That is also what makes
|
|
164
|
+
* the (contrived) dual-package case safe rather than dangerous, a fragment
|
|
165
|
+
* built by the ESM copy of this module and executed by the CJS copy binds as a
|
|
166
|
+
* parameter instead of composing.
|
|
167
|
+
*/
|
|
138
168
|
declare const SQL_FRAGMENT: unique symbol;
|
|
139
169
|
/** A composable SQL fragment, the local stand-in for `Prisma.Sql`. */
|
|
140
170
|
export interface Sql {
|
|
@@ -227,16 +257,24 @@ export interface PrismaModelDelegate<M extends PrismaModelTypes> {
|
|
|
227
257
|
aggregate(args: Args): Promise<Record<string, unknown>>;
|
|
228
258
|
groupBy(args: Args): Promise<Record<string, unknown>[]>;
|
|
229
259
|
}
|
|
260
|
+
/**
|
|
261
|
+
* The four Prisma raw-SQL methods. Present on BOTH the client and the
|
|
262
|
+
* transaction-scoped client, exactly as in Prisma, so a migrated call site that
|
|
263
|
+
* mixes `$transaction` with raw SQL keeps working, and the raw statement runs on
|
|
264
|
+
* the transaction's own connection.
|
|
265
|
+
*/
|
|
266
|
+
export interface PrismaCompatRawSurface {
|
|
267
|
+
$queryRaw<T = unknown>(strings: TemplateStringsArray, ...values: unknown[]): Promise<T[]>;
|
|
268
|
+
$queryRawUnsafe<T = unknown>(sql: string, ...params: unknown[]): Promise<T[]>;
|
|
269
|
+
$executeRaw(strings: TemplateStringsArray, ...values: unknown[]): Promise<number>;
|
|
270
|
+
$executeRawUnsafe(sql: string, ...params: unknown[]): Promise<number>;
|
|
271
|
+
}
|
|
230
272
|
/** The client-level surface (`$transaction` / raw), added to the model map. */
|
|
231
|
-
export interface PrismaCompatClientBase<S extends Record<string, PrismaModelTypes> = Record<string, PrismaModelTypes>> {
|
|
273
|
+
export interface PrismaCompatClientBase<S extends Record<string, PrismaModelTypes> = Record<string, PrismaModelTypes>> extends PrismaCompatRawSurface {
|
|
232
274
|
$transaction<R>(fn: (tx: PrismaCompatTransactionClient<S>) => Promise<R>, options?: PrismaCompatTxOptions): Promise<R>;
|
|
233
275
|
$transaction<P extends readonly PromiseLike<unknown>[]>(promises: readonly [...P]): Promise<{
|
|
234
276
|
[K in keyof P]: Awaited<P[K]>;
|
|
235
277
|
}>;
|
|
236
|
-
$queryRaw<T = unknown>(strings: TemplateStringsArray, ...values: unknown[]): Promise<T[]>;
|
|
237
|
-
$queryRawUnsafe<T = unknown>(sql: string, ...params: unknown[]): Promise<T[]>;
|
|
238
|
-
$executeRaw(strings: TemplateStringsArray, ...values: unknown[]): Promise<number>;
|
|
239
|
-
$executeRawUnsafe(sql: string, ...params: unknown[]): Promise<number>;
|
|
240
278
|
$connect(): Promise<void>;
|
|
241
279
|
$disconnect(): Promise<void>;
|
|
242
280
|
}
|
|
@@ -246,10 +284,18 @@ export interface PrismaCompatTxOptions {
|
|
|
246
284
|
timeout?: number;
|
|
247
285
|
maxWait?: number;
|
|
248
286
|
}
|
|
249
|
-
/**
|
|
287
|
+
/**
|
|
288
|
+
* The transaction-scoped client handed to a `$transaction(callback)`: a model
|
|
289
|
+
* delegate per Prisma model (under both spellings, as on the client) plus the
|
|
290
|
+
* raw-SQL surface, every one of them bound to the transaction's connection.
|
|
291
|
+
* Prisma's transaction client has no `$transaction` / `$connect` /
|
|
292
|
+
* `$disconnect`, and neither does this one.
|
|
293
|
+
*/
|
|
250
294
|
export type PrismaCompatTransactionClient<S extends Record<string, PrismaModelTypes> = Record<string, PrismaModelTypes>> = {
|
|
251
295
|
[K in keyof S]: PrismaModelDelegate<S[K]>;
|
|
252
|
-
}
|
|
296
|
+
} & {
|
|
297
|
+
[K in keyof S as Uncapitalize<K & string>]: PrismaModelDelegate<S[K]>;
|
|
298
|
+
} & PrismaCompatRawSurface;
|
|
253
299
|
/**
|
|
254
300
|
* The full typed compat client: a model delegate per Prisma model name, plus the
|
|
255
301
|
* client-level `$transaction` / raw surface. Parameterize `S` with your
|