turbine-orm 0.79.1 → 0.80.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 +4 -4
- package/dist/checkout.d.ts +53 -0
- package/dist/checkout.js +78 -0
- package/dist/cjs/checkout.d.ts +53 -0
- package/dist/cjs/checkout.js +82 -0
- package/dist/cjs/cli/index.js +4 -0
- package/dist/cjs/cli/mcp.js +4 -0
- package/dist/cjs/cli/migrate.js +6 -0
- package/dist/cjs/cli/observe.js +8 -0
- package/dist/cjs/cli/studio.js +13 -1
- package/dist/cjs/client.d.ts +12 -2
- package/dist/cjs/client.js +61 -75
- package/dist/cjs/connection-guard.d.ts +120 -0
- package/dist/cjs/connection-guard.js +191 -0
- package/dist/cjs/errors.d.ts +26 -0
- package/dist/cjs/errors.js +85 -1
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/nested-write.d.ts +12 -2
- package/dist/cjs/nested-write.js +4 -10
- package/dist/cjs/pipeline.js +12 -7
- package/dist/cjs/plan-flip-probe.js +4 -0
- package/dist/cjs/powdb-shared.d.ts +22 -2
- package/dist/cjs/powdb-shared.js +27 -2
- package/dist/cjs/powdb.js +36 -37
- package/dist/cjs/powql.d.ts +51 -6
- package/dist/cjs/powql.js +199 -45
- package/dist/cjs/prisma-compat.js +28 -4
- package/dist/cjs/query/builder.d.ts +44 -24
- package/dist/cjs/query/builder.js +125 -66
- package/dist/cjs/query/deferred.d.ts +9 -0
- package/dist/cjs/query/option-surface.js +12 -0
- package/dist/cjs/query/types.d.ts +68 -4
- package/dist/cjs/query/writes.d.ts +39 -9
- package/dist/cjs/query/writes.js +72 -34
- package/dist/cjs/realtime.d.ts +46 -2
- package/dist/cjs/realtime.js +125 -20
- package/dist/cjs/schema-sql.js +6 -0
- package/dist/cli/index.js +4 -0
- package/dist/cli/mcp.js +4 -0
- package/dist/cli/migrate.js +6 -0
- package/dist/cli/observe.js +8 -0
- package/dist/cli/studio.js +13 -1
- package/dist/client.d.ts +12 -2
- package/dist/client.js +62 -76
- package/dist/connection-guard.d.ts +120 -0
- package/dist/connection-guard.js +183 -0
- package/dist/errors.d.ts +26 -0
- package/dist/errors.js +83 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/nested-write.d.ts +12 -2
- package/dist/nested-write.js +4 -10
- package/dist/pipeline.js +13 -8
- package/dist/plan-flip-probe.js +4 -0
- package/dist/powdb-shared.d.ts +22 -2
- package/dist/powdb-shared.js +25 -2
- package/dist/powdb.js +23 -24
- package/dist/powql.d.ts +51 -6
- package/dist/powql.js +200 -46
- package/dist/prisma-compat.js +28 -4
- package/dist/query/builder.d.ts +44 -24
- package/dist/query/builder.js +126 -67
- package/dist/query/deferred.d.ts +9 -0
- package/dist/query/option-surface.js +12 -0
- package/dist/query/types.d.ts +68 -4
- package/dist/query/writes.d.ts +39 -9
- package/dist/query/writes.js +71 -34
- package/dist/realtime.d.ts +46 -2
- package/dist/realtime.js +125 -20
- package/dist/schema-sql.js +6 -0
- package/package.json +5 -3
package/dist/powql.js
CHANGED
|
@@ -35,9 +35,10 @@
|
|
|
35
35
|
* @module
|
|
36
36
|
*/
|
|
37
37
|
import { randomUUID } from 'node:crypto';
|
|
38
|
+
import { guardCheckout } from './connection-guard.js';
|
|
38
39
|
import { NotFoundError, OptimisticLockError, ReadOnlyError, TimeoutError, UnsupportedFeatureError, ValidationError, } from './errors.js';
|
|
39
40
|
import { executeNestedCreate, executeNestedUpdate, hasRelationFields, } from './nested-write.js';
|
|
40
|
-
import { ALL_POWDB_CAPABILITIES, baseTsType, coerceNativeValue, isJsonColumn, isPowdbDatetimeColumn, isStaleFramePowdbError, PowdbFloatParam, PowdbJsonParam, powqlColumnType, quotePowqlDotted, quotePowqlIdent, requireCapability, rowToEntity, } from './powdb-shared.js';
|
|
41
|
+
import { ALL_POWDB_CAPABILITIES, atLeastVersion, baseTsType, coerceNativeValue, isJsonColumn, isPowdbDatetimeColumn, isStaleFramePowdbError, PowdbFloatParam, PowdbJsonParam, parsePowdbSemver, powqlColumnType, quotePowqlDotted, quotePowqlIdent, requireCapability, rowToEntity, } from './powdb-shared.js';
|
|
41
42
|
import { assertAggregatePiiOptIn } from './query/aggregates.js';
|
|
42
43
|
import { assertMutationWhereIdentifiesOneRow, assertWhereIdentifiesOneRow, expandCompoundUniqueWhere, } from './query/compound-unique.js';
|
|
43
44
|
import { ARRAY_OPERATOR_KEYS, isJsonFilter, isRelationPickOrderBy, orderByEntries } from './query/filters.js';
|
|
@@ -1236,6 +1237,34 @@ export class PowqlInterface {
|
|
|
1236
1237
|
}
|
|
1237
1238
|
return entity;
|
|
1238
1239
|
}
|
|
1240
|
+
/**
|
|
1241
|
+
* A single-row write's `select` / `omit`, resolved by {@link projectionPlan}
|
|
1242
|
+
* so the rules and messages are the read path's, and the SQL engines' (an
|
|
1243
|
+
* unknown name is E003, `select` must name something, the pair is refused).
|
|
1244
|
+
* `undefined` for the default return shape. Resolved BEFORE the write is sent,
|
|
1245
|
+
* so a bad projection writes nothing.
|
|
1246
|
+
*
|
|
1247
|
+
* The narrowing itself happens on the returned row ({@link shapeWriteRow}):
|
|
1248
|
+
* PowQL's `returning` takes no column list (see {@link stripWritePii}), so
|
|
1249
|
+
* unlike the SQL engines the unselected columns still cross the wire here.
|
|
1250
|
+
* The RESULT is identical across engines; the byte saving is SQL-only.
|
|
1251
|
+
*/
|
|
1252
|
+
writeReturnPlan(args) {
|
|
1253
|
+
if (args.select === undefined && args.omit === undefined)
|
|
1254
|
+
return undefined;
|
|
1255
|
+
return this.projectionPlan(args.select, args.omit, false);
|
|
1256
|
+
}
|
|
1257
|
+
/** Apply a write's return plan to its row, or the default PII strip without one. */
|
|
1258
|
+
shapeWriteRow(entity, plan) {
|
|
1259
|
+
if (!entity || !plan)
|
|
1260
|
+
return this.stripWritePii(entity);
|
|
1261
|
+
const keep = new Set(plan.cols.filter((c) => !plan.forcedPk.includes(c)).map((c) => this.meta.reverseColumnMap[c] ?? c));
|
|
1262
|
+
for (const key of Object.keys(entity)) {
|
|
1263
|
+
if (!keep.has(key))
|
|
1264
|
+
delete entity[key];
|
|
1265
|
+
}
|
|
1266
|
+
return entity;
|
|
1267
|
+
}
|
|
1239
1268
|
/** `{ .c1, .c2, … }` projection clause. */
|
|
1240
1269
|
projection(cols) {
|
|
1241
1270
|
return `{ ${cols.map((c) => this.colRefName(c)).join(', ')} }`;
|
|
@@ -2676,6 +2705,7 @@ export class PowqlInterface {
|
|
|
2676
2705
|
}
|
|
2677
2706
|
async create(args) {
|
|
2678
2707
|
return this.withMiddleware('create', args, async () => {
|
|
2708
|
+
const plan = this.writeReturnPlan(args);
|
|
2679
2709
|
if (hasRelationFields(args.data, this.meta)) {
|
|
2680
2710
|
return this.nestedCreate(args);
|
|
2681
2711
|
}
|
|
@@ -2687,7 +2717,7 @@ export class PowqlInterface {
|
|
|
2687
2717
|
.join(', ');
|
|
2688
2718
|
// `returning` surfaces the inserted row (all columns, schema order) in one round-trip.
|
|
2689
2719
|
const { rows, native } = await this.exec(`insert ${this.qt} { ${body} } returning`, params, args.timeout, 'create');
|
|
2690
|
-
const row = rows.length ? this.
|
|
2720
|
+
const row = rows.length ? this.shapeWriteRow(this.shape(rows, native)[0], plan) : null;
|
|
2691
2721
|
if (!row)
|
|
2692
2722
|
throw new NotFoundError({ table: this.table, where: data });
|
|
2693
2723
|
return row;
|
|
@@ -2762,6 +2792,7 @@ export class PowqlInterface {
|
|
|
2762
2792
|
}
|
|
2763
2793
|
async update(args) {
|
|
2764
2794
|
return this.withMiddleware('update', args, async () => {
|
|
2795
|
+
const plan = this.writeReturnPlan(args);
|
|
2765
2796
|
if (hasRelationFields(args.data, this.meta)) {
|
|
2766
2797
|
// The nested engine re-enters `update` / `findUnique` for the parent
|
|
2767
2798
|
// row before it writes anything, so the identity rule below still runs
|
|
@@ -2799,7 +2830,7 @@ export class PowqlInterface {
|
|
|
2799
2830
|
}
|
|
2800
2831
|
// `returning` hands back the post-update row(s); take the first (single-row contract).
|
|
2801
2832
|
const { rows, native } = await this.exec(`${this.qt} filter ${where} update { ${setClause} } returning`, params, args.timeout, 'update');
|
|
2802
|
-
const row = rows.length ? this.
|
|
2833
|
+
const row = rows.length ? this.shapeWriteRow(this.shape(rows, native)[0], plan) : null;
|
|
2803
2834
|
if (!row) {
|
|
2804
2835
|
if (lock) {
|
|
2805
2836
|
throw new OptimisticLockError({
|
|
@@ -2876,18 +2907,21 @@ export class PowqlInterface {
|
|
|
2876
2907
|
}
|
|
2877
2908
|
async nestedCreate(args) {
|
|
2878
2909
|
const data = args.data;
|
|
2910
|
+
// Applied by the nested engine's final read-back, as on the SQL engines.
|
|
2911
|
+
const shape = { select: args.select, omit: args.omit };
|
|
2879
2912
|
if (this.isTxScoped()) {
|
|
2880
|
-
return executeNestedCreate(this.buildNestedCtx(), this.table, data);
|
|
2913
|
+
return executeNestedCreate(this.buildNestedCtx(), this.table, data, 0, [], shape);
|
|
2881
2914
|
}
|
|
2882
|
-
return this.runInImplicitTx((ctx) => executeNestedCreate(ctx, this.table, data));
|
|
2915
|
+
return this.runInImplicitTx((ctx) => executeNestedCreate(ctx, this.table, data, 0, [], shape));
|
|
2883
2916
|
}
|
|
2884
2917
|
async nestedUpdate(args) {
|
|
2885
2918
|
const data = args.data;
|
|
2886
2919
|
const where = args.where;
|
|
2920
|
+
const shape = { select: args.select, omit: args.omit };
|
|
2887
2921
|
if (this.isTxScoped()) {
|
|
2888
|
-
return executeNestedUpdate(this.buildNestedCtx(), this.table, where, data);
|
|
2922
|
+
return executeNestedUpdate(this.buildNestedCtx(), this.table, where, data, 0, [], shape);
|
|
2889
2923
|
}
|
|
2890
|
-
return this.runInImplicitTx((ctx) => executeNestedUpdate(ctx, this.table, where, data));
|
|
2924
|
+
return this.runInImplicitTx((ctx) => executeNestedUpdate(ctx, this.table, where, data, 0, [], shape));
|
|
2891
2925
|
}
|
|
2892
2926
|
/** Open a flat PowDB transaction on a pinned connection and run `fn` inside it. */
|
|
2893
2927
|
async runInImplicitTx(fn) {
|
|
@@ -2900,6 +2934,9 @@ export class PowqlInterface {
|
|
|
2900
2934
|
// drifts from `powdbDialect`; falls back to the literal lowercase keywords.
|
|
2901
2935
|
const d = this.options.dialect;
|
|
2902
2936
|
const client = await this.pool.connect();
|
|
2937
|
+
// A no-op today (PowDB checkouts have no event surface), kept so this path
|
|
2938
|
+
// cannot become the one unguarded checkout if a transport ever emits 'error'.
|
|
2939
|
+
const checkout = guardCheckout(client);
|
|
2903
2940
|
let began = false;
|
|
2904
2941
|
try {
|
|
2905
2942
|
await client.query(d?.beginStatement?.() ?? 'begin');
|
|
@@ -2939,7 +2976,7 @@ export class PowqlInterface {
|
|
|
2939
2976
|
throw err;
|
|
2940
2977
|
}
|
|
2941
2978
|
finally {
|
|
2942
|
-
|
|
2979
|
+
checkout.release();
|
|
2943
2980
|
}
|
|
2944
2981
|
}
|
|
2945
2982
|
/** Already inside a transaction: build a context whose table accessors reuse the pinned pool. */
|
|
@@ -2952,6 +2989,7 @@ export class PowqlInterface {
|
|
|
2952
2989
|
}
|
|
2953
2990
|
async delete(args) {
|
|
2954
2991
|
return this.withMiddleware('delete', args, async () => {
|
|
2992
|
+
const plan = this.writeReturnPlan(args);
|
|
2955
2993
|
const allowFullTableScan = resolveUnsafeFlag(args.allowFullTableScan, 'allowFullTableScan');
|
|
2956
2994
|
const userWhere = this.expandedWhere(args.where);
|
|
2957
2995
|
const params = [];
|
|
@@ -2962,7 +3000,7 @@ export class PowqlInterface {
|
|
|
2962
3000
|
where = this.applyGlobalFilter(where, params, args.skipGlobalFilters);
|
|
2963
3001
|
// `returning` hands back the deleted row(s), no separate pre-image reselect needed.
|
|
2964
3002
|
const { rows, native } = await this.exec(`${this.qt} filter ${where} delete returning`, params, args.timeout, 'delete');
|
|
2965
|
-
const row = rows.length ? this.
|
|
3003
|
+
const row = rows.length ? this.shapeWriteRow(this.shape(rows, native)[0], plan) : null;
|
|
2966
3004
|
if (!row)
|
|
2967
3005
|
throw new NotFoundError({ table: this.table, where: args.where });
|
|
2968
3006
|
return row;
|
|
@@ -2988,13 +3026,45 @@ export class PowqlInterface {
|
|
|
2988
3026
|
// disagreeing about whether a query is VALID, which is the failure class
|
|
2989
3027
|
// 0.64.0 and 0.72.0 were both spent on. Before `applyPkDefault`, so a
|
|
2990
3028
|
// refused upsert mints no client-side UUID and sends nothing.
|
|
2991
|
-
|
|
3029
|
+
const upsertWhere = (this.expandedWhere(args.where) ?? {});
|
|
3030
|
+
this.assertMutationIdentifiesOneRow(upsertWhere, false, 'upsert');
|
|
3031
|
+
const plan = this.writeReturnPlan(args);
|
|
2992
3032
|
const createData = this.applyPkDefault(args.create);
|
|
2993
3033
|
const pkCol = this.meta.primaryKey[0];
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
3034
|
+
// The conflict target is the columns `where` names, as on the SQL
|
|
3035
|
+
// engines (`ON CONFLICT (<where keys>)`), with the conflicting VALUES
|
|
3036
|
+
// taken from `create`. This path used to conflict on the primary key
|
|
3037
|
+
// whatever `where` named, so an upsert keyed on another unique column
|
|
3038
|
+
// (`where: { email }`) never found the existing row.
|
|
3039
|
+
const conflictColumns = Object.keys(upsertWhere)
|
|
3040
|
+
.filter((k) => upsertWhere[k] !== undefined)
|
|
3041
|
+
.map((k) => this.column(k).name);
|
|
3042
|
+
// PowQL's native `upsert … on .col` expresses exactly one shape: a single
|
|
3043
|
+
// conflict column that is the primary key, with no predicate on its
|
|
3044
|
+
// conflict branch. Everything else is an atomic reselect-or-write
|
|
3045
|
+
// transaction.
|
|
3046
|
+
//
|
|
3047
|
+
// That includes a table under a global filter. The native statement's
|
|
3048
|
+
// `on conflict` branch carries no predicate, so a tenant-scoped client
|
|
3049
|
+
// whose `create` key matched ANOTHER tenant's row updated that row, and
|
|
3050
|
+
// the tenant-filtered reselect then reported the write it had just made
|
|
3051
|
+
// as a NotFoundError. The SQL engines guard the same branch with the
|
|
3052
|
+
// filter (`DO UPDATE … WHERE tenant_id = $n`). The lookup-first path runs
|
|
3053
|
+
// its find and its update through the filtered interface, so another
|
|
3054
|
+
// tenant's row is invisible to it: the insert then collides on the key
|
|
3055
|
+
// and fails, as it does on the SQL engines, and nothing is overwritten.
|
|
3056
|
+
const filtered = this.applyGlobalFilter('', [], args.skipGlobalFilters) !== '';
|
|
3057
|
+
const native = this.meta.primaryKey.length === 1 &&
|
|
3058
|
+
pkCol !== undefined &&
|
|
3059
|
+
conflictColumns.length === 1 &&
|
|
3060
|
+
conflictColumns[0] === pkCol &&
|
|
3061
|
+
!filtered;
|
|
3062
|
+
if (!native) {
|
|
3063
|
+
return this.upsertLookupFirst(createData, args.update, conflictColumns, {
|
|
3064
|
+
select: args.select,
|
|
3065
|
+
omit: args.omit,
|
|
3066
|
+
skipGlobalFilters: args.skipGlobalFilters,
|
|
3067
|
+
});
|
|
2998
3068
|
}
|
|
2999
3069
|
const params = [];
|
|
3000
3070
|
const createBody = this.scalarData(createData)
|
|
@@ -3007,40 +3077,61 @@ export class PowqlInterface {
|
|
|
3007
3077
|
// reselect-by-PK fetch; create/update/delete all use `returning`.
|
|
3008
3078
|
await this.exec(`upsert ${this.qt} on ${this.colRefName(pkCol)} { ${createBody} } on conflict { ${updateBody} }`, params, args.timeout, 'upsert');
|
|
3009
3079
|
const pkField = this.meta.reverseColumnMap[pkCol] ?? pkCol;
|
|
3010
|
-
// Either spelling, as `
|
|
3080
|
+
// Either spelling, as `upsertLookupFirst` already does below: reading the
|
|
3011
3081
|
// camelCase field alone reselected `undefined` for a snake-spelled PK and
|
|
3012
3082
|
// reported a write that had SUCCEEDED as a NotFoundError.
|
|
3013
|
-
|
|
3083
|
+
// `args` carries skipGlobalFilters too: an upsert that opted out of the
|
|
3084
|
+
// filter must not have its own write filtered out of the reselect.
|
|
3085
|
+
const row = await this.reselectByPk(createData[pkField] ?? createData[pkCol], args.timeout, args);
|
|
3014
3086
|
if (!row)
|
|
3015
3087
|
throw new NotFoundError({ table: this.table, where: createData });
|
|
3016
|
-
|
|
3088
|
+
// Without a plan the reselect's default projection already left PII out.
|
|
3089
|
+
return plan ? this.shapeWriteRow(row, plan) : row;
|
|
3017
3090
|
});
|
|
3018
3091
|
}
|
|
3019
3092
|
/**
|
|
3020
|
-
*
|
|
3021
|
-
*
|
|
3022
|
-
*
|
|
3023
|
-
*
|
|
3024
|
-
|
|
3025
|
-
|
|
3093
|
+
* Upsert as a reselect-or-write inside one flat transaction, for every shape
|
|
3094
|
+
* the native `upsert … on .col` statement cannot express: a conflict target
|
|
3095
|
+
* other than a single-column primary key (it takes one column and PowDB has
|
|
3096
|
+
* no composite unique), and a table under a global filter (its conflict
|
|
3097
|
+
* branch takes no predicate). PowDB's single writer makes the read-then-write
|
|
3098
|
+
* safe from concurrent writers; the transaction makes it atomic with the
|
|
3099
|
+
* write.
|
|
3100
|
+
*
|
|
3101
|
+
* The row is looked up by the conflict columns with `create`'s values for
|
|
3102
|
+
* them, which is what `ON CONFLICT (<cols>)` compares on the SQL engines. The
|
|
3103
|
+
* find and the update run through the transaction's table interface, so a
|
|
3104
|
+
* configured global filter applies to both exactly as it does to any other
|
|
3105
|
+
* read or write, and `skipGlobalFilters` is forwarded to both.
|
|
3106
|
+
*/
|
|
3107
|
+
async upsertLookupFirst(createData, updateData, conflictColumns, shape) {
|
|
3108
|
+
const { skipGlobalFilters, ...projection } = shape;
|
|
3109
|
+
const skip = skipGlobalFilters === undefined ? {} : { skipGlobalFilters };
|
|
3026
3110
|
// Accept either the camelCase field or the snake_case column in `create`
|
|
3027
3111
|
// (create() resolves both), and key the where by field name.
|
|
3028
|
-
const
|
|
3029
|
-
const field = this.meta.reverseColumnMap[
|
|
3030
|
-
return { field, value: createData[field] ?? createData[
|
|
3112
|
+
const keyPairs = conflictColumns.map((col) => {
|
|
3113
|
+
const field = this.meta.reverseColumnMap[col] ?? col;
|
|
3114
|
+
return { field, value: createData[field] ?? createData[col] };
|
|
3031
3115
|
});
|
|
3032
|
-
|
|
3033
|
-
|
|
3116
|
+
const keyless = keyPairs.some((p) => p.value == null);
|
|
3117
|
+
const isCompositePk = keyPairs.length > 1 &&
|
|
3118
|
+
keyPairs.length === this.meta.primaryKey.length &&
|
|
3119
|
+
conflictColumns.every((c) => this.meta.primaryKey.includes(c));
|
|
3120
|
+
if (keyless && isCompositePk) {
|
|
3121
|
+
throw new ValidationError(`upsert on "${this.table}" needs every composite-PK field in \`create\` (${keyPairs
|
|
3034
3122
|
.map((p) => p.field)
|
|
3035
3123
|
.join(', ')}).`);
|
|
3036
3124
|
}
|
|
3037
|
-
const
|
|
3125
|
+
const keyWhere = Object.fromEntries(keyPairs.map((p) => [p.field, p.value]));
|
|
3038
3126
|
const run = async (ctx) => {
|
|
3039
3127
|
const tbl = ctx.tx.table(this.table);
|
|
3040
|
-
|
|
3128
|
+
// A row whose `create` leaves a conflict column unset cannot collide on
|
|
3129
|
+
// it (NULL never equals NULL in a unique index, and a server-assigned key
|
|
3130
|
+
// is new), which is what `ON CONFLICT` concludes on the SQL engines too.
|
|
3131
|
+
const existing = keyless ? null : await tbl.findUnique({ where: keyWhere, ...skip });
|
|
3041
3132
|
return existing
|
|
3042
|
-
? (await tbl.update({ where:
|
|
3043
|
-
: (await tbl.create({ data: createData }));
|
|
3133
|
+
? (await tbl.update({ where: keyWhere, data: updateData, ...projection, ...skip }))
|
|
3134
|
+
: (await tbl.create({ data: createData, ...projection }));
|
|
3044
3135
|
};
|
|
3045
3136
|
return this.isTxScoped() ? run(this.buildNestedCtx()) : this.runInImplicitTx(run);
|
|
3046
3137
|
}
|
|
@@ -3087,33 +3178,64 @@ export class PowqlInterface {
|
|
|
3087
3178
|
'every SQL engine exactly when the column is nullable. `_count: true` (a row count) and a per-field ' +
|
|
3088
3179
|
'`_count` of a NOT NULL column are correct on every version and are never refused.');
|
|
3089
3180
|
}
|
|
3181
|
+
/**
|
|
3182
|
+
* Can `aggregate()` compute several aggregates in ONE statement on this engine?
|
|
3183
|
+
*
|
|
3184
|
+
* PowQL refuses a bare multi-aggregate projection (`T { a: sum(.x), b: sum(.y) }`
|
|
3185
|
+
* is "aggregate function in an unsupported position" on every engine version),
|
|
3186
|
+
* so the only one-statement form is a grouping over a literal key,
|
|
3187
|
+
* `T filter … group 1 { agg_0: sum(.x), agg_1: sum(.y) }`, which yields a
|
|
3188
|
+
* single group holding every row the filter matched. Measured against the
|
|
3189
|
+
* embedded addon at 0.7.1 through 0.28: below 0.13 a literal group key does not
|
|
3190
|
+
* parse, from 0.13 to 0.19.1 the grouped per-field `count` disagrees with the
|
|
3191
|
+
* scalar `count(T { .col })` on a nullable column, and from 0.20 every
|
|
3192
|
+
* aggregate kind (count / sum / avg / min / max over int, float, str, an
|
|
3193
|
+
* all-null column, and several filters) answers identically on both wires.
|
|
3194
|
+
*
|
|
3195
|
+
* Derived from the PROBED engine version rather than from a capability flag,
|
|
3196
|
+
* so an unprobed pool (`engineVersion: null`, e.g. an injected pool carrying
|
|
3197
|
+
* {@link ALL_POWDB_CAPABILITIES}) keeps the per-field statements: this changes
|
|
3198
|
+
* the emitted PowQL, and an older engine would reject it outright, the same
|
|
3199
|
+
* probe-only discipline `nestedProjections` follows.
|
|
3200
|
+
*/
|
|
3201
|
+
get groupsAggregatesInOneStatement() {
|
|
3202
|
+
const sem = parsePowdbSemver(this.capabilities.engineVersion);
|
|
3203
|
+
return sem !== null && atLeastVersion(sem, 0, 20);
|
|
3204
|
+
}
|
|
3090
3205
|
async aggregate(args) {
|
|
3091
3206
|
this.assertNoForceCustomPlan(args);
|
|
3092
3207
|
return this.withMiddleware('aggregate', args, async () => {
|
|
3093
|
-
// One scalar query per aggregate, PowDB's bare-projection aggregate is broken.
|
|
3094
3208
|
const result = {};
|
|
3095
3209
|
const filterParams = [];
|
|
3096
3210
|
const resolvedWhere = await this.resolveRelationFilters(args.where, args.timeout);
|
|
3097
3211
|
let where = this.buildWhere(resolvedWhere, filterParams);
|
|
3098
3212
|
where = this.applyGlobalFilter(where, filterParams, args.skipGlobalFilters);
|
|
3099
3213
|
const filter = where ? ` filter ${where}` : '';
|
|
3100
|
-
const
|
|
3101
|
-
const params = [...filterParams];
|
|
3102
|
-
const { rows } = await this.exec(expr, params, args.timeout, 'aggregate');
|
|
3103
|
-
const v = rows[0]?.value;
|
|
3104
|
-
return v == null || v === 'null' ? null : Number(v);
|
|
3105
|
-
};
|
|
3214
|
+
const plan = [];
|
|
3106
3215
|
if (args._count) {
|
|
3107
3216
|
if (args._count === true) {
|
|
3108
|
-
|
|
3217
|
+
plan.push({
|
|
3218
|
+
scalar: `count(${this.qt}${filter})`,
|
|
3219
|
+
inner: 'count(*)',
|
|
3220
|
+
assign: (v) => {
|
|
3221
|
+
result._count = v ?? 0;
|
|
3222
|
+
},
|
|
3223
|
+
});
|
|
3109
3224
|
}
|
|
3110
3225
|
else {
|
|
3111
3226
|
const counts = {};
|
|
3227
|
+
result._count = counts;
|
|
3112
3228
|
for (const field of Object.keys(args._count).filter((f) => args._count[f])) {
|
|
3113
3229
|
this.assertProjectedCountSupported(field);
|
|
3114
|
-
|
|
3230
|
+
const ref = this.ref(field);
|
|
3231
|
+
plan.push({
|
|
3232
|
+
scalar: `count(${this.qt}${filter} { ${ref} })`,
|
|
3233
|
+
inner: `count(${ref})`,
|
|
3234
|
+
assign: (v) => {
|
|
3235
|
+
counts[field] = v ?? 0;
|
|
3236
|
+
},
|
|
3237
|
+
});
|
|
3115
3238
|
}
|
|
3116
|
-
result._count = counts;
|
|
3117
3239
|
}
|
|
3118
3240
|
}
|
|
3119
3241
|
for (const fn of ['_sum', '_avg', '_min', '_max']) {
|
|
@@ -3121,15 +3243,44 @@ export class PowqlInterface {
|
|
|
3121
3243
|
if (!spec)
|
|
3122
3244
|
continue;
|
|
3123
3245
|
const acc = {};
|
|
3246
|
+
result[fn] = acc;
|
|
3247
|
+
const powfn = fn.slice(1); // sum/avg/min/max
|
|
3124
3248
|
for (const field of Object.keys(spec).filter((f) => spec[f])) {
|
|
3125
|
-
const powfn = fn.slice(1); // sum/avg/min/max
|
|
3126
3249
|
// Same PII contract as the SQL engines: _min/_max return a stored cell.
|
|
3127
3250
|
if (fn === '_min' || fn === '_max') {
|
|
3128
3251
|
assertAggregatePiiOptIn(this.table, this.meta, field, this.column(field).name, `aggregate ${fn}`, resolveUnsafeFlag(args.includePii, 'includePii'));
|
|
3129
3252
|
}
|
|
3130
|
-
|
|
3253
|
+
const ref = this.ref(field);
|
|
3254
|
+
plan.push({
|
|
3255
|
+
scalar: `${powfn}(${this.qt}${filter} { ${ref} })`,
|
|
3256
|
+
inner: `${powfn}(${ref})`,
|
|
3257
|
+
assign: (v) => {
|
|
3258
|
+
acc[field] = v;
|
|
3259
|
+
},
|
|
3260
|
+
});
|
|
3131
3261
|
}
|
|
3132
|
-
|
|
3262
|
+
}
|
|
3263
|
+
// A scalar cell and a grouped cell decode through the same wire decoder,
|
|
3264
|
+
// so one coercion serves both forms.
|
|
3265
|
+
const toNumber = (v) => (v == null || v === 'null' ? null : Number(v));
|
|
3266
|
+
if (plan.length > 1 && this.groupsAggregatesInOneStatement) {
|
|
3267
|
+
const proj = plan.map((p, i) => `agg_${i}: ${p.inner}`).join(', ');
|
|
3268
|
+
const { rows } = await this.exec(`${this.qt}${filter} group 1 { ${proj} }`, [...filterParams], args.timeout, 'aggregate');
|
|
3269
|
+
// Zero groups means the filter matched no row. The per-field statements
|
|
3270
|
+
// below answer that case instead of a synthesized one, because what an
|
|
3271
|
+
// aggregate over NO rows returns is engine-versioned (`sum` answered 0
|
|
3272
|
+
// before PowDB 0.28 and null since), and the scalar form is the one
|
|
3273
|
+
// whose answer this method has always returned.
|
|
3274
|
+
const row = rows.length === 1 ? rows[0] : undefined;
|
|
3275
|
+
if (row) {
|
|
3276
|
+
for (const [i, p] of plan.entries())
|
|
3277
|
+
p.assign(toNumber(row[`agg_${i}`]));
|
|
3278
|
+
return result;
|
|
3279
|
+
}
|
|
3280
|
+
}
|
|
3281
|
+
for (const p of plan) {
|
|
3282
|
+
const { rows } = await this.exec(p.scalar, [...filterParams], args.timeout, 'aggregate');
|
|
3283
|
+
p.assign(toNumber(rows[0]?.value));
|
|
3133
3284
|
}
|
|
3134
3285
|
return result;
|
|
3135
3286
|
});
|
|
@@ -3532,12 +3683,15 @@ export class PowqlInterface {
|
|
|
3532
3683
|
// Reselect helper (upsert only, PowDB's upsert has no `returning`)
|
|
3533
3684
|
// -------------------------------------------------------------------------
|
|
3534
3685
|
/** Reselect a single row by its single-column primary key value. */
|
|
3535
|
-
async reselectByPk(pkValue, timeout) {
|
|
3686
|
+
async reselectByPk(pkValue, timeout, shape) {
|
|
3536
3687
|
const pkField = this.meta.reverseColumnMap[this.meta.primaryKey[0]] ?? this.meta.primaryKey[0];
|
|
3537
3688
|
const { rows, native } = await this.runFind({
|
|
3538
3689
|
where: { [pkField]: pkValue },
|
|
3539
3690
|
limit: 1,
|
|
3540
3691
|
timeout,
|
|
3692
|
+
select: shape?.select,
|
|
3693
|
+
omit: shape?.omit,
|
|
3694
|
+
...(shape?.skipGlobalFilters === undefined ? {} : { skipGlobalFilters: shape.skipGlobalFilters }),
|
|
3541
3695
|
});
|
|
3542
3696
|
return rows.length ? this.shape(rows, native)[0] : null;
|
|
3543
3697
|
}
|
package/dist/prisma-compat.js
CHANGED
|
@@ -968,6 +968,22 @@ function resolveWriteProjection(ctx, mm, op, args) {
|
|
|
968
968
|
}
|
|
969
969
|
return NO_WRITE_PROJECTION;
|
|
970
970
|
}
|
|
971
|
+
/**
|
|
972
|
+
* The same projection in core's spelling, for the write itself. Passed DOWN so
|
|
973
|
+
* the SQL engines narrow the RETURNING list rather than fetching the whole row
|
|
974
|
+
* for {@link applyWriteProjection} to trim afterwards, which on a table with a
|
|
975
|
+
* large JSON column was most of the bytes the write moved. The trim stays: it
|
|
976
|
+
* is what keeps the result Prisma-shaped on every path, including the
|
|
977
|
+
* lookup-first upsert and PowDB, whose `returning` takes no column list.
|
|
978
|
+
*/
|
|
979
|
+
function coreWriteProjection(mm, proj) {
|
|
980
|
+
const spell = (keys) => Object.fromEntries([...keys].map((k) => [renameField(mm, k), true]));
|
|
981
|
+
if (proj.pick)
|
|
982
|
+
return { select: spell(proj.pick) };
|
|
983
|
+
if (proj.omit)
|
|
984
|
+
return { omit: spell(proj.omit) };
|
|
985
|
+
return {};
|
|
986
|
+
}
|
|
971
987
|
/** Apply a {@link WriteProjection} to one already-reshaped write result row. */
|
|
972
988
|
function applyWriteProjection(proj, row) {
|
|
973
989
|
if (!isPlainObject(row))
|
|
@@ -1762,9 +1778,12 @@ function upsertKeysMatch(t) {
|
|
|
1762
1778
|
/** Prisma upsert semantics: look up by where; update the found row, else insert create. */
|
|
1763
1779
|
async function upsertLookupFirst(qi, t) {
|
|
1764
1780
|
const existing = await qi.findUnique({ where: t.where });
|
|
1781
|
+
// The projection rides along so both branches return what the native upsert
|
|
1782
|
+
// path returns (an explicitly selected PII field included).
|
|
1783
|
+
const shape = { select: t.select, omit: t.omit };
|
|
1765
1784
|
if (existing)
|
|
1766
|
-
return qi.update({ where: t.where, data: t.update });
|
|
1767
|
-
return qi.create({ data: t.create });
|
|
1785
|
+
return qi.update({ where: t.where, data: t.update, ...shape });
|
|
1786
|
+
return qi.create({ data: t.create, ...shape });
|
|
1768
1787
|
}
|
|
1769
1788
|
/**
|
|
1770
1789
|
* The row shapes a translated `createMany` has to insert, as contiguous runs
|
|
@@ -1872,7 +1891,10 @@ function makeDelegate(ctx, mm, getQI, runInTx) {
|
|
|
1872
1891
|
const shape = (raw) => applyWriteProjection(proj, reshapeRow(ctx, mm, raw));
|
|
1873
1892
|
return defer('create', args, () => {
|
|
1874
1893
|
proj = resolveWriteProjection(ctx, mm, 'create', args);
|
|
1875
|
-
const t = {
|
|
1894
|
+
const t = {
|
|
1895
|
+
data: translateWriteData(ctx, mm, applyCreateDefaults(mm, args.data)),
|
|
1896
|
+
...coreWriteProjection(mm, proj),
|
|
1897
|
+
};
|
|
1876
1898
|
applyNativeOptions(CREATE_OPTIONS, args, t);
|
|
1877
1899
|
return t;
|
|
1878
1900
|
}, (qi, t) => qi.create(t).then(shape), {
|
|
@@ -1917,6 +1939,7 @@ function makeDelegate(ctx, mm, getQI, runInTx) {
|
|
|
1917
1939
|
const t = {
|
|
1918
1940
|
where: translateWhere(ctx, mm, a.where),
|
|
1919
1941
|
data: translateWriteData(ctx, mm, applyUpdateTouch(mm, a.data)),
|
|
1942
|
+
...coreWriteProjection(mm, proj),
|
|
1920
1943
|
};
|
|
1921
1944
|
applyNativeOptions(UPDATE_OPTIONS, a, t);
|
|
1922
1945
|
// `optimisticLock.field` is a FIELD NAME, so it is renamed rather than
|
|
@@ -1956,7 +1979,7 @@ function makeDelegate(ctx, mm, getQI, runInTx) {
|
|
|
1956
1979
|
return defer('delete', args, () => {
|
|
1957
1980
|
const a = requireWhere(args, 'delete');
|
|
1958
1981
|
proj = resolveWriteProjection(ctx, mm, 'delete', a);
|
|
1959
|
-
const t = { where: translateWhere(ctx, mm, a.where) };
|
|
1982
|
+
const t = { where: translateWhere(ctx, mm, a.where), ...coreWriteProjection(mm, proj) };
|
|
1960
1983
|
applyNativeOptions(DELETE_OPTIONS, a, t);
|
|
1961
1984
|
return t;
|
|
1962
1985
|
}, (qi, t) => qi.delete(t).then(shape), { build: (qi, t) => qi.buildDelete(t), reshape: shape });
|
|
@@ -1981,6 +2004,7 @@ function makeDelegate(ctx, mm, getQI, runInTx) {
|
|
|
1981
2004
|
where: translateWhere(ctx, mm, a.where),
|
|
1982
2005
|
create: translateWriteData(ctx, mm, applyCreateDefaults(mm, a.create)),
|
|
1983
2006
|
update: translateWriteData(ctx, mm, applyUpdateTouch(mm, a.update)),
|
|
2007
|
+
...coreWriteProjection(mm, proj),
|
|
1984
2008
|
};
|
|
1985
2009
|
applyNativeOptions(UPSERT_OPTIONS, a, t);
|
|
1986
2010
|
return t;
|
package/dist/query/builder.d.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import type { PgCompatPool } from '../pg-types.js';
|
|
14
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';
|
|
15
|
+
import type { AggregateArgs, AggregateResult, CountArgs, CreateArgs, CreateManyArgs, DeleteArgs, DeleteManyArgs, FieldResult, FindManyArgs, FindManyStreamArgs, FindUniqueArgs, GroupByArgs, GroupByResult, QueryResult, TypedWithClause, UpdateArgs, UpdateManyArgs, UpsertArgs, WithClause } from './types.js';
|
|
16
16
|
/**
|
|
17
17
|
* Discard the memoized cross-check environment so the next cache hit re-reads
|
|
18
18
|
* `process.env`.
|
|
@@ -993,10 +993,34 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
|
|
|
993
993
|
* here and is not refused.
|
|
994
994
|
*/
|
|
995
995
|
private preparedNameFor;
|
|
996
|
+
/**
|
|
997
|
+
* {@link queryWithTimeout} for a statement that only READS, which makes it
|
|
998
|
+
* safe to send twice. Outside a transaction, a read that fails because the
|
|
999
|
+
* connection it went out on had already been closed by the server
|
|
1000
|
+
* ({@link isStaleConnectionError}) is sent once more, after one event-loop
|
|
1001
|
+
* turn, on whatever connection the pool hands out next.
|
|
1002
|
+
*
|
|
1003
|
+
* Reads only, and the distinction is the whole design. For a write, "the
|
|
1004
|
+
* connection died" does not say whether the statement committed first: a
|
|
1005
|
+
* connection can drop after the server commits and before the reply
|
|
1006
|
+
* arrives, so resending an INSERT can insert it twice. A read has no such
|
|
1007
|
+
* outcome. Nor inside a transaction, where the connection that died WAS the
|
|
1008
|
+
* transaction and a retry on another one would run outside it.
|
|
1009
|
+
*
|
|
1010
|
+
* The loop turn is what makes a second attempt worth making: pg-pool evicts
|
|
1011
|
+
* an idle connection whose close it has read, so after one poll phase every
|
|
1012
|
+
* other connection that died alongside this one is gone from the idle list
|
|
1013
|
+
* rather than lent out to the retry.
|
|
1014
|
+
*
|
|
1015
|
+
* Every read call site uses this and every write uses `queryWithTimeout`
|
|
1016
|
+
* directly; `src/test/stale-connection-retry.test.ts` pins which is which.
|
|
1017
|
+
*/
|
|
1018
|
+
private readWithTimeout;
|
|
996
1019
|
/**
|
|
997
1020
|
* Execute a pool.query with an optional timeout.
|
|
998
1021
|
* If timeout is set, races the query against a timer and rejects on expiry.
|
|
999
1022
|
* pg driver errors are translated to typed Turbine errors via wrapPgError.
|
|
1023
|
+
* `retryIfStale` is {@link readWithTimeout}'s; nothing else sets it.
|
|
1000
1024
|
*/
|
|
1001
1025
|
private queryWithTimeout;
|
|
1002
1026
|
/**
|
|
@@ -1013,11 +1037,18 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
|
|
|
1013
1037
|
* write and a follow-up SELECT; the SELECT's rows feed the transform.
|
|
1014
1038
|
*/
|
|
1015
1039
|
private executeMutation;
|
|
1016
|
-
buildCreate(args: CreateArgs<T>): DeferredQuery<T
|
|
1040
|
+
buildCreate<S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined>(args: CreateArgs<T, R, S, O>): DeferredQuery<FieldResult<T, S, O>>;
|
|
1041
|
+
/**
|
|
1042
|
+
* A single-row write's `select` / `omit`, resolved through the SAME
|
|
1043
|
+
* `resolveProjection` reads use (see WriteProjection in writes.ts), or
|
|
1044
|
+
* `undefined` for the default return shape. Resolved here rather than in
|
|
1045
|
+
* writes.ts because relations.ts imports writes.ts.
|
|
1046
|
+
*/
|
|
1047
|
+
private resolveWriteProjection;
|
|
1017
1048
|
buildCreateMany(args: CreateManyArgs<T>): DeferredQuery<T[]>;
|
|
1018
|
-
buildUpdate(args: UpdateArgs<T>): DeferredQuery<T
|
|
1019
|
-
buildDelete(args: DeleteArgs<T>): DeferredQuery<T
|
|
1020
|
-
buildUpsert(args: UpsertArgs<T>): DeferredQuery<T
|
|
1049
|
+
buildUpdate<S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined>(args: UpdateArgs<T, R, S, O>): DeferredQuery<FieldResult<T, S, O>>;
|
|
1050
|
+
buildDelete<S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined>(args: DeleteArgs<T, R, S, O>): DeferredQuery<FieldResult<T, S, O>>;
|
|
1051
|
+
buildUpsert<S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined>(args: UpsertArgs<T, R, S, O>): DeferredQuery<FieldResult<T, S, O>>;
|
|
1021
1052
|
buildUpdateMany(args: UpdateManyArgs<T>): DeferredQuery<{
|
|
1022
1053
|
count: number;
|
|
1023
1054
|
}>;
|
|
@@ -1235,34 +1266,23 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
|
|
|
1235
1266
|
buildFindFirstOrThrow<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>): DeferredQuery<QueryResult<T, R, W, S, O>>;
|
|
1236
1267
|
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>>;
|
|
1237
1268
|
buildFindUniqueOrThrow<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>): DeferredQuery<QueryResult<T, R, W, S, O>>;
|
|
1238
|
-
create(args: CreateArgs<T, R>): Promise<T
|
|
1269
|
+
create<S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined>(args: CreateArgs<T, R, S, O>): Promise<FieldResult<T, S, O>>;
|
|
1239
1270
|
createMany(args: CreateManyArgs<T>): Promise<T[]>;
|
|
1240
|
-
update(args: UpdateArgs<T, R>): Promise<T
|
|
1271
|
+
update<S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined>(args: UpdateArgs<T, R, S, O>): Promise<FieldResult<T, S, O>>;
|
|
1241
1272
|
private nestedCreate;
|
|
1242
1273
|
private nestedUpdate;
|
|
1243
1274
|
/**
|
|
1244
|
-
* Check out a pooled connection,
|
|
1245
|
-
*
|
|
1246
|
-
*
|
|
1247
|
-
*
|
|
1248
|
-
* (SQLSTATE 28P01), no such database (3D000), nothing listening
|
|
1249
|
-
* (ECONNREFUSED), an unverifiable TLS certificate. Unwrapped, every one of
|
|
1250
|
-
* those surfaces from an ordinary `db.users.create({ data: { ...nested } })`
|
|
1251
|
-
* as a raw pg `DatabaseError` whose `.code` is a SQLSTATE, on the same
|
|
1252
|
-
* property Turbine puts `TURBINE_E0NN` in.
|
|
1253
|
-
*
|
|
1254
|
-
* client.ts has its own copy for `$transaction` / `connect()`; this one
|
|
1255
|
-
* exists because `query/` must not import client.ts (circular dependency).
|
|
1256
|
-
* The query paths need no equivalent: `pool.query()` opens the connection
|
|
1257
|
-
* itself and rejects with the connect error, which the query boundary
|
|
1258
|
-
* already wraps.
|
|
1275
|
+
* Check out a guarded pooled connection, as a typed error when that fails.
|
|
1276
|
+
* Shared with client.ts through checkout.ts (see there for why each part
|
|
1277
|
+
* matters); the cursor stream holds it, and a nested write opens its
|
|
1278
|
+
* implicit transaction on one through `openCheckout`.
|
|
1259
1279
|
*/
|
|
1260
1280
|
private acquireConnection;
|
|
1261
1281
|
private runInImplicitTx;
|
|
1262
1282
|
private buildNestedCtx;
|
|
1263
1283
|
private makeTxProxy;
|
|
1264
|
-
delete(args: DeleteArgs<T, R>): Promise<T
|
|
1265
|
-
upsert(args: UpsertArgs<T, R>): Promise<T
|
|
1284
|
+
delete<S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined>(args: DeleteArgs<T, R, S, O>): Promise<FieldResult<T, S, O>>;
|
|
1285
|
+
upsert<S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined>(args: UpsertArgs<T, R, S, O>): Promise<FieldResult<T, S, O>>;
|
|
1266
1286
|
updateMany(args: UpdateManyArgs<T, R>): Promise<{
|
|
1267
1287
|
count: number;
|
|
1268
1288
|
}>;
|