turbine-orm 0.90.2 → 0.91.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.
Files changed (45) hide show
  1. package/README.md +3 -3
  2. package/dist/cjs/dialect.d.ts +26 -1
  3. package/dist/cjs/dialect.js +4 -0
  4. package/dist/cjs/index.d.ts +2 -2
  5. package/dist/cjs/mssql.js +2 -0
  6. package/dist/cjs/mysql.js +3 -0
  7. package/dist/cjs/powdb.js +2 -0
  8. package/dist/cjs/powql.d.ts +5 -2
  9. package/dist/cjs/powql.js +49 -28
  10. package/dist/cjs/prisma-compat.d.ts +2 -2
  11. package/dist/cjs/prisma-compat.js +8 -5
  12. package/dist/cjs/query/aggregates.js +74 -4
  13. package/dist/cjs/query/builder.d.ts +20 -7
  14. package/dist/cjs/query/builder.js +31 -22
  15. package/dist/cjs/query/expr.d.ts +47 -0
  16. package/dist/cjs/query/expr.js +129 -3
  17. package/dist/cjs/query/index.d.ts +1 -1
  18. package/dist/cjs/query/option-surface.js +3 -0
  19. package/dist/cjs/query/types.d.ts +107 -7
  20. package/dist/cjs/query/writes.d.ts +14 -2
  21. package/dist/cjs/query/writes.js +65 -18
  22. package/dist/cjs/sqlite.js +6 -0
  23. package/dist/dialect.d.ts +26 -1
  24. package/dist/dialect.js +4 -0
  25. package/dist/index.d.ts +2 -2
  26. package/dist/mssql.js +2 -0
  27. package/dist/mysql.js +3 -0
  28. package/dist/powdb.js +2 -0
  29. package/dist/powql.d.ts +5 -2
  30. package/dist/powql.js +49 -28
  31. package/dist/prisma-compat.d.ts +2 -2
  32. package/dist/prisma-compat.js +8 -5
  33. package/dist/query/aggregates.js +74 -4
  34. package/dist/query/builder.d.ts +20 -7
  35. package/dist/query/builder.js +31 -22
  36. package/dist/query/expr.d.ts +47 -0
  37. package/dist/query/expr.js +128 -3
  38. package/dist/query/index.d.ts +1 -1
  39. package/dist/query/option-surface.js +3 -0
  40. package/dist/query/types.d.ts +107 -7
  41. package/dist/query/writes.d.ts +14 -2
  42. package/dist/query/writes.js +63 -18
  43. package/dist/sqlite.js +6 -0
  44. package/package.json +1 -1
  45. package/skills/turbine-orm/SKILL.md +8 -0
@@ -3048,7 +3048,24 @@ class QueryInterface {
3048
3048
  }
3049
3049
  buildCreateMany(args) {
3050
3050
  args = this.normalizeArgs(args);
3051
- return writesMod.buildCreateMany(this.ctx, args, 'refuse', this.resolveWriteProjection(args));
3051
+ return writesMod.buildCreateMany(this.ctx, args, 'refuse', this.createManyProjection(args));
3052
+ }
3053
+ /**
3054
+ * `createMany`'s projection: the caller's `select` / `omit`, or
3055
+ * {@link writesMod.COUNT_ONLY} for `returning: false`, which takes neither.
3056
+ */
3057
+ createManyProjection(args) {
3058
+ const { returning } = args;
3059
+ if (returning === undefined || returning === true)
3060
+ return this.resolveWriteProjection(args);
3061
+ if (returning !== false) {
3062
+ throw new errors_js_1.ValidationError(`createMany on "${this.table}": returning must be true or false, received ${typeof returning}.`);
3063
+ }
3064
+ if (args.select !== undefined || args.omit !== undefined) {
3065
+ throw new errors_js_1.ValidationError(`createMany on "${this.table}": returning: false returns { count } and no rows, so it takes no ` +
3066
+ `\`${args.select !== undefined ? 'select' : 'omit'}\`. Drop one of them.`);
3067
+ }
3068
+ return writesMod.COUNT_ONLY;
3052
3069
  }
3053
3070
  buildUpdate(args) {
3054
3071
  args = this.normalizeArgs(args);
@@ -4441,13 +4458,17 @@ class QueryInterface {
4441
4458
  async createMany(args) {
4442
4459
  args = this.normalizeArgs(args);
4443
4460
  return this.executeWithMiddleware('createMany', args, async () => {
4444
- const projection = this.resolveWriteProjection(args);
4461
+ const projection = this.createManyProjection(args);
4462
+ const countOnly = writesMod.isCountOnly(projection);
4445
4463
  const base = args;
4446
4464
  // One statement when the rows fit the engine's per-statement limits,
4447
4465
  // else chunks that do, all in one transaction so the rows land together
4448
4466
  // or not at all. (`buildCreateMany`, one statement for a pipeline,
4449
4467
  // refuses instead.)
4450
- const { chunks, rowsPerChunk } = writesMod.planCreateMany(this.ctx, base, projection);
4468
+ // Splitting a call that fits one statement needs a transaction, which a
4469
+ // pool without connect() cannot open outside one.
4470
+ const canSplit = this.txScoped || typeof this.pool.connect === 'function';
4471
+ const { chunks, rowsPerChunk } = writesMod.planCreateMany(this.ctx, base, projection, canSplit);
4451
4472
  const run = async () => {
4452
4473
  // An engine that applies two rows whose onConflict keys are equal only
4453
4474
  // under the column's collation (SQLite's NOCASE) as an insert and an
@@ -4463,13 +4484,18 @@ class QueryInterface {
4463
4484
  const reselects = this.dialect.resultStrategy === 'reselect';
4464
4485
  return this.inOneTransaction(async (exec) => {
4465
4486
  const rows = [];
4487
+ let count = 0;
4466
4488
  for (const chunk of chunks) {
4467
4489
  const result = reselects && chunk.reselect
4468
4490
  ? await chunk.reselect(exec)
4469
4491
  : await exec(chunk.sql, chunk.params, chunk.preparedName);
4470
- rows.push(...chunk.transform(result));
4492
+ const out = chunk.transform(result);
4493
+ if (countOnly)
4494
+ count += out.count;
4495
+ else
4496
+ rows.push(...out);
4471
4497
  }
4472
- return rows;
4498
+ return (countOnly ? { count } : rows);
4473
4499
  }, args.timeout, `createMany of ${base.data.length} rows`, `Insert at most ${rowsPerChunk} rows per call, which fits one statement, or pass a pool that can check ` +
4474
4500
  'out a connection.');
4475
4501
  };
@@ -4759,17 +4785,6 @@ class QueryInterface {
4759
4785
  preparedName: entry.name,
4760
4786
  };
4761
4787
  }
4762
- // -------------------------------------------------------------------------
4763
- // groupBy (with aggregate functions)
4764
- // -------------------------------------------------------------------------
4765
- /**
4766
- * Group rows and compute per-group aggregates (Prisma-style). The result row
4767
- * type is INFERRED from the args: each `by` field carries its entity field
4768
- * type, `_count` is always a number, and each requested `_sum` / `_avg` /
4769
- * `_min` / `_max` block maps its fields to properly typed values (see
4770
- * {@link GroupByResult}). Grouping by a JSON-path key yields a runtime alias
4771
- * that cannot be typed, so those columns are not projected onto the row type.
4772
- */
4773
4788
  async groupBy(args) {
4774
4789
  args = this.normalizeArgs(args);
4775
4790
  return this.executeWithMiddleware('groupBy', args, async () => {
@@ -4778,9 +4793,6 @@ class QueryInterface {
4778
4793
  return deferred.transform(result);
4779
4794
  });
4780
4795
  }
4781
- // ---------------------------------------------------------------------------
4782
- // Aggregate / groupBy compilation (extracted to aggregates.ts).
4783
- // ---------------------------------------------------------------------------
4784
4796
  buildGroupBy(args) {
4785
4797
  const normalized = this.normalizeArgs(args);
4786
4798
  return this.withPreparedName(() => aggMod.buildGroupBy(this.ctx, normalized));
@@ -4789,9 +4801,6 @@ class QueryInterface {
4789
4801
  const normalized = this.normalizeArgs(args);
4790
4802
  return this.withPreparedName(() => aggMod.buildAggregate(this.ctx, normalized));
4791
4803
  }
4792
- // -------------------------------------------------------------------------
4793
- // aggregate, standalone aggregation without groupBy
4794
- // -------------------------------------------------------------------------
4795
4804
  async aggregate(args) {
4796
4805
  args = this.normalizeArgs(args);
4797
4806
  return this.executeWithMiddleware('aggregate', args, async () => {
@@ -124,6 +124,19 @@ export declare const expr: Readonly<{
124
124
  * value). Refused anywhere else.
125
125
  */
126
126
  excluded<F extends string>(field: F): ExcludedRef<F>;
127
+ /**
128
+ * The seconds from `from` to `to`, two timestamp columns of the row:
129
+ * `to - from`, fractional, negative when `to` is earlier, NULL when either is.
130
+ * Both columns must be timestamptz, or both zone-less timestamp. Accepted in
131
+ * `_percentile`'s `of` (PostgreSQL); refused in update data in this release.
132
+ *
133
+ * ```ts
134
+ * await db.jobs.aggregate({
135
+ * _percentile: { wait: { of: expr.secondsBetween(col('requestedAt'), col('readyAt')), p: [0.5, 0.95] } },
136
+ * });
137
+ * ```
138
+ */
139
+ secondsBetween(from: ColumnRef, to: ColumnRef): SqlExpr<number>;
127
140
  }>;
128
141
  /** True only for a node built by {@link expr} (any loaded copy of the package). */
129
142
  export declare function isSqlExpr(value: unknown): value is SqlExpr;
@@ -167,6 +180,12 @@ export interface ExprCompileEnv {
167
180
  readonly insertColumns?: ReadonlySet<string> | undefined;
168
181
  /** Compile a `case` arm's predicate (already pruned and validated) against the target row. */
169
182
  predicate(when: Record<string, unknown>): string;
183
+ /**
184
+ * The expression is READ by a query (`_percentile`'s `of`), not assigned:
185
+ * there is no target column, `targetKey` is the result's name, and
186
+ * `expr.secondsBetween` is accepted while `expr.now` is not.
187
+ */
188
+ readonly read?: true | undefined;
170
189
  }
171
190
  /** One compiled expression. */
172
191
  export interface CompiledSetExpr {
@@ -197,6 +216,34 @@ export interface CompiledSetExpr {
197
216
  * cache key does not capture.
198
217
  */
199
218
  export declare function compileSetExpr(env: ExprCompileEnv, value: SqlExpr | ColumnRef): CompiledSetExpr;
219
+ /** What {@link compileReadExpr} needs from its caller (the aggregate module). */
220
+ export interface ReadExprEnv {
221
+ readonly qi: BuilderCtx;
222
+ /** The result's name, for messages. */
223
+ readonly name: string;
224
+ /** For messages: `aggregate _percentile`, `groupBy _percentile`. */
225
+ readonly operation: string;
226
+ /** Bind one number, bigint or null and return its placeholder, cast to its class's type. */
227
+ bind(value: number | bigint | null, cls: 'int' | 'dec'): string;
228
+ /** Compile a `case` arm's predicate (already pruned and validated) against the row. */
229
+ predicate(when: Record<string, unknown>): string;
230
+ }
231
+ /** One compiled read expression. */
232
+ export interface CompiledReadExpr {
233
+ readonly sql: string;
234
+ /** Value-free shape, as {@link CompiledSetExpr.fingerprint}. */
235
+ readonly fingerprint: string;
236
+ /** Columns the expression reads as a value (columns only a case arm's `when` reads are not). */
237
+ readonly values: ReadonlySet<string>;
238
+ }
239
+ /**
240
+ * Compile a numeric expression a query READS (`_percentile`'s `of`): the walk
241
+ * {@link compileSetExpr} runs, with no target column. Bound values must be
242
+ * numbers or bigints and are cast by their own class, the result must be
243
+ * numeric, and `col()` alone is refused because a field key already says it.
244
+ * Not gated on `updateExpressions`: the caller's own engine check applies.
245
+ */
246
+ export declare function compileReadExpr(read: ReadExprEnv, value: unknown): CompiledReadExpr;
200
247
  /**
201
248
  * Rewrite every `expr.excluded(f)` in a single-row upsert's `update` to the
202
249
  * value `create` supplies for `f`. The incoming row of a one-row upsert IS
@@ -32,6 +32,7 @@ exports.plainExprShapeMessage = plainExprShapeMessage;
32
32
  exports.exprNotAllowedMessage = exprNotAllowedMessage;
33
33
  exports.assertExprTarget = assertExprTarget;
34
34
  exports.compileSetExpr = compileSetExpr;
35
+ exports.compileReadExpr = compileReadExpr;
35
36
  exports.resolveExcludedFromCreate = resolveExcludedFromCreate;
36
37
  const errors_js_1 = require("../errors.js");
37
38
  const column_ref_js_1 = require("./column-ref.js");
@@ -55,6 +56,7 @@ const EXPR_KINDS = new Set([
55
56
  'case',
56
57
  'now',
57
58
  'excluded',
59
+ 'secondsBetween',
58
60
  ]);
59
61
  /** Operand count bounds for coalesce / greatest / least. */
60
62
  const MIN_VARIADIC = 2;
@@ -172,6 +174,20 @@ function caseNode(arms, otherwise) {
172
174
  assertOperand('case', arms.length, otherwise);
173
175
  return makeNode('case', frozen, otherwise === undefined ? undefined : { else: otherwise });
174
176
  }
177
+ function secondsBetweenNode(from, to) {
178
+ [from, to].forEach((operand, i) => {
179
+ if ((0, column_ref_js_1.isColumnRef)(operand))
180
+ return;
181
+ const at = `expr.secondsBetween(): operand ${i + 1}`;
182
+ if ((0, column_ref_js_1.isPlainColumnRefShape)(operand)) {
183
+ const name = JSON.stringify(operand.col);
184
+ throw new errors_js_1.ValidationError(`${at} is a plain { col: ${name} } object, not a column reference. Build one with col(${name}).`);
185
+ }
186
+ throw new errors_js_1.ValidationError(`${at} is ${operand === undefined ? 'undefined' : describeOperand(operand)}. Both operands are col() of a ` +
187
+ 'timestamp column of the row.');
188
+ });
189
+ return makeNode('secondsBetween', [from, to]);
190
+ }
175
191
  /**
176
192
  * Builders for SQL expressions in update data. Each returns a frozen, branded
177
193
  * node; the SQL is generated per engine when the update compiles.
@@ -257,6 +273,21 @@ exports.expr = Object.freeze({
257
273
  Object.defineProperty(node, exports.EXCLUDED_REF, { value: true, enumerable: false });
258
274
  return Object.freeze(node);
259
275
  },
276
+ /**
277
+ * The seconds from `from` to `to`, two timestamp columns of the row:
278
+ * `to - from`, fractional, negative when `to` is earlier, NULL when either is.
279
+ * Both columns must be timestamptz, or both zone-less timestamp. Accepted in
280
+ * `_percentile`'s `of` (PostgreSQL); refused in update data in this release.
281
+ *
282
+ * ```ts
283
+ * await db.jobs.aggregate({
284
+ * _percentile: { wait: { of: expr.secondsBetween(col('requestedAt'), col('readyAt')), p: [0.5, 0.95] } },
285
+ * });
286
+ * ```
287
+ */
288
+ secondsBetween(from, to) {
289
+ return secondsBetweenNode(from, to);
290
+ },
260
291
  });
261
292
  /** True only for a node built by {@link expr} (any loaded copy of the package). */
262
293
  function isSqlExpr(value) {
@@ -448,6 +479,12 @@ function isBareStructuredCopy(qi, column, value) {
448
479
  const type = columnType(qi, column);
449
480
  return source !== undefined && type !== undefined && columnType(qi, source) === type;
450
481
  }
482
+ /** Where an expression sits, for messages. */
483
+ function site(env) {
484
+ return env.read
485
+ ? `${env.operation} "${env.targetKey}" on "${env.qi.table}"`
486
+ : `${env.operation} data on "${env.qi.table}.${env.targetKey}"`;
487
+ }
451
488
  /**
452
489
  * Compile one expression (or `col()` reference) assigned to `env.targetKey`.
453
490
  * The single traversal: SQL, params (through `env.bind`, in emission order)
@@ -468,7 +505,7 @@ function compileSetExpr(env, value) {
468
505
  const { qi } = env;
469
506
  assertEngine(qi);
470
507
  const column = assertExprTarget(qi, env.targetKey, value, env.operation);
471
- const state = { env, targetType: columnType(qi, column), reads: new Set() };
508
+ const state = { env, targetType: columnType(qi, column), reads: new Set(), values: new Set() };
472
509
  const part = walkOperand(state, value, false, 0);
473
510
  if (part.cls === 'dec' && numClassOfType(state.targetType) === 'int') {
474
511
  const what = (0, column_ref_js_1.isColumnRef)(value) ? `col(${JSON.stringify(value.col)})` : `expr.${value.kind}()`;
@@ -480,6 +517,48 @@ function compileSetExpr(env, value) {
480
517
  }
481
518
  return { sql: part.sql, fingerprint: part.fp, reads: state.reads };
482
519
  }
520
+ /**
521
+ * Compile a numeric expression a query READS (`_percentile`'s `of`): the walk
522
+ * {@link compileSetExpr} runs, with no target column. Bound values must be
523
+ * numbers or bigints and are cast by their own class, the result must be
524
+ * numeric, and `col()` alone is refused because a field key already says it.
525
+ * Not gated on `updateExpressions`: the caller's own engine check applies.
526
+ */
527
+ function compileReadExpr(read, value) {
528
+ const { qi, name, operation } = read;
529
+ const at = `${operation} "${name}" on "${qi.table}"`;
530
+ if ((0, column_ref_js_1.isColumnRef)(value)) {
531
+ throw new errors_js_1.ValidationError(`${at}: \`of\` is col(${JSON.stringify(value.col)}) alone. Use the field as the key instead: ` +
532
+ `{ ${JSON.stringify(value.col)}: { p } }.`);
533
+ }
534
+ if (!isSqlExpr(value)) {
535
+ throw new errors_js_1.ValidationError(`${at}: \`of\` is ${value === undefined ? 'undefined' : describeOperand(value)}. Build it with expr ` +
536
+ "(import { col, expr } from 'turbine-orm'); a plain object is never an expression.");
537
+ }
538
+ const env = {
539
+ qi,
540
+ targetKey: name,
541
+ operation,
542
+ read: true,
543
+ bind: (leaf, arithmetic) => {
544
+ if (leaf !== null && typeof leaf !== 'number' && typeof leaf !== 'bigint') {
545
+ throw new errors_js_1.ValidationError(`${at}: a bound value is ${leaf instanceof Date ? 'a Date' : leaf instanceof Uint8Array ? 'a Uint8Array' : `a ${typeof leaf}`}. ` +
546
+ 'An expression a query reads binds numbers and bigints only.');
547
+ }
548
+ const integer = (arithmetic ?? numClassOfValue(leaf)) !== 'dec' && (typeof leaf !== 'number' || Number.isSafeInteger(leaf));
549
+ return read.bind(leaf, integer ? 'int' : 'dec');
550
+ },
551
+ column: (column) => qi.q(column),
552
+ predicate: (when) => read.predicate(when),
553
+ };
554
+ const state = { env, targetType: undefined, reads: new Set(), values: new Set() };
555
+ const part = walkOperand(state, value, false, 0);
556
+ if (part.cls === 'other' || part.cls === 'null') {
557
+ throw new errors_js_1.ValidationError(`${at}: \`of\` is ${part.cls === 'null' ? 'NULL for every row' : 'not numeric (it reads a non-numeric column)'}. ` +
558
+ 'A percentile orders numbers: numeric columns, bound numbers, and expr.secondsBetween() of two timestamps.');
559
+ }
560
+ return { sql: part.sql, fingerprint: part.fp, values: state.values };
561
+ }
483
562
  function assertEngine(qi) {
484
563
  if (qi.dialect.capabilities.updateExpressions)
485
564
  return;
@@ -492,6 +571,7 @@ function walkOperand(state, value, inArithmetic, depth) {
492
571
  if ((0, column_ref_js_1.isColumnRef)(value)) {
493
572
  const column = resolveOperandColumn(state, value.col, 'col', inArithmetic);
494
573
  state.reads.add(column);
574
+ state.values.add(column);
495
575
  return { sql: state.env.column(column), fp: `c:${column}`, cls: numClassOfType(columnType(state.env.qi, column)) };
496
576
  }
497
577
  if (isSqlExpr(value))
@@ -503,7 +583,7 @@ function walkOperand(state, value, inArithmetic, depth) {
503
583
  // greatest, least or a case arm (`expr.add(expr.coalesce('x', 1), 2)`).
504
584
  if (inArithmetic &&
505
585
  (typeof value === 'string' || typeof value === 'boolean' || value instanceof Date || value instanceof Uint8Array)) {
506
- throw new errors_js_1.ValidationError(`An arithmetic operand in ${state.env.operation} data on "${state.env.qi.table}.${state.env.targetKey}" is a ` +
586
+ throw new errors_js_1.ValidationError(`An arithmetic operand in ${site(state.env)} is a ` +
507
587
  `${value instanceof Date ? 'Date' : value instanceof Uint8Array ? 'Uint8Array' : typeof value}. ` +
508
588
  'Arithmetic takes numbers, bigints, col() of a numeric column, or numeric expressions.');
509
589
  }
@@ -520,7 +600,7 @@ function resolveOperandColumn(state, field, how, inArithmetic) {
520
600
  const call = how === 'col' ? `col(${JSON.stringify(field)})` : `expr.excluded(${JSON.stringify(field)})`;
521
601
  const column = (0, utils_js_1.resolveColumnName)(qi.tableMeta, field);
522
602
  if (column === undefined) {
523
- throw new errors_js_1.ValidationError(`${call} in ${state.env.operation} data on "${qi.table}" names no column. ` +
603
+ throw new errors_js_1.ValidationError(`${call} in ${site(state.env)} names no column. ` +
524
604
  `Known fields: ${Object.keys(qi.tableMeta.columnMap).join(', ') || '(none)'}.`);
525
605
  }
526
606
  const cls = columnClass(columnType(qi, column));
@@ -580,6 +660,9 @@ function walkNode(state, node, inArithmetic, depth) {
580
660
  case 'case':
581
661
  return walkCase(state, node, inArithmetic, depth);
582
662
  case 'now': {
663
+ if (env.read) {
664
+ throw new errors_js_1.ValidationError(`expr.now() in ${site(env)}: an expression a query reads takes the row's columns and bound numbers only.`);
665
+ }
583
666
  if (inArithmetic) {
584
667
  throw new errors_js_1.ValidationError('expr.now() cannot be an arithmetic operand: date arithmetic is not supported.');
585
668
  }
@@ -603,6 +686,8 @@ function walkNode(state, node, inArithmetic, depth) {
603
686
  }
604
687
  return { sql: env.excluded(column), fp: `x:${column}`, cls: numClassOfType(columnType(qi, column)) };
605
688
  }
689
+ case 'secondsBetween':
690
+ return walkSecondsBetween(state, node);
606
691
  default:
607
692
  throw new errors_js_1.ValidationError(`Unknown expression kind "${String(node.kind)}".`);
608
693
  }
@@ -634,6 +719,47 @@ function walkCase(state, node, inArithmetic, depth) {
634
719
  cls: combineClasses(classes),
635
720
  };
636
721
  }
722
+ /**
723
+ * `expr.secondsBetween(from, to)`: `EXTRACT(EPOCH FROM (to - from))`, the
724
+ * PostgreSQL spelling. It is read only by `_percentile`, which only an engine
725
+ * with `supportsPercentileAggregates` runs, and that SQL (`percentile_cont ...
726
+ * WITHIN GROUP`) is PostgreSQL's too. Both columns must have one temporal
727
+ * kind: subtracting a timestamptz from a zone-less timestamp converts through
728
+ * the session time zone.
729
+ */
730
+ function walkSecondsBetween(state, node) {
731
+ const { env } = state;
732
+ const { qi } = env;
733
+ if (!env.read) {
734
+ throw new errors_js_1.ValidationError(`expr.secondsBetween() in ${site(env)}: it is accepted in _percentile's \`of\` in this release, not in ` +
735
+ 'update data. Compute the value in JavaScript and write it as a plain value.');
736
+ }
737
+ const kinds = [];
738
+ const columns = node.args.map((arg) => {
739
+ const field = arg.col;
740
+ const column = resolveOperandColumn(state, field, 'col', false);
741
+ const type = columnType(qi, column);
742
+ const kind = temporalKind(type);
743
+ if (kind !== 'tz' && kind !== 'timestamp') {
744
+ throw new errors_js_1.ValidationError(`expr.secondsBetween() in ${site(env)}: col(${JSON.stringify(field)}) is ${type === undefined ? 'a column of unknown type' : `a ${type} column`}. ` +
745
+ 'Both operands must be timestamp (or both timestamptz) columns.');
746
+ }
747
+ kinds.push(kind);
748
+ state.reads.add(column);
749
+ state.values.add(column);
750
+ return column;
751
+ });
752
+ if (kinds[0] !== kinds[1]) {
753
+ throw new errors_js_1.ValidationError(`expr.secondsBetween() in ${site(env)} mixes a timestamptz column with a zone-less timestamp column, ` +
754
+ 'whose difference depends on the session time zone. Use two columns of one kind.');
755
+ }
756
+ const [from, to] = columns;
757
+ return {
758
+ sql: `EXTRACT(EPOCH FROM (${env.column(to)} - ${env.column(from)}))`,
759
+ fp: `secs(${from},${to})`,
760
+ cls: 'dec',
761
+ };
762
+ }
637
763
  /**
638
764
  * Prune and validate one arm's `when`: `undefined` keys drop out (the C1 rule
639
765
  * for every `where` position), an arm left with no predicate is refused, and
@@ -5,7 +5,7 @@
5
5
  * `import { … } from './query/index.js'` is a drop-in replacement for the
6
6
  * former monolithic `import { … } from './query.js'`.
7
7
  */
8
- export type { AggregateArgs, AggregateResult, ArrayFilter, ColumnRef, ConnectOrCreateOp, CountArgs, CreateArgs, CreateDataInput, CreateManyArgs, DeleteArgs, DeleteManyArgs, FieldResult, FindManyArgs, FindManyStreamArgs, FindUniqueArgs, GlobalFilters, GroupByAggregateSpec, GroupByArgs, GroupByDistinctOn, GroupByResult, HavingClause, JsonEncoding, JsonFilter, JsonPathAggregateTarget, JsonPathGroupKey, JsonPathOrderBy, NestedCreateOp, NestedUpdateOp, NestedUpdateOpItem, NestedUpsertOpItem, OmitResult, OrderByClause, OrderByObject, OrderBySpec, OrderDirection, QueryResult, RelationDescriptor, RelationFilter, RelationLoadStrategy, RelationOrderBy, RelationOrderByChain, RelationPickBy, RelationPickOrderBy, ResolvedSkipGlobalFilters, SelectResult, SkipGlobalFilters, TextSearchFilter, TypedWithClause, UpdateArgs, UpdateDataInput, UpdateInput, UpdateManyArgs, UpdateOperatorInput, UpsertArgs, VectorDistanceFilter, VectorFilter, VectorMetric, VectorOrderBy, VectorOrderByDistance, WhereClause, WhereOperator, WhereValue, WithClause, WithOptions, WithOrderByObject, WithResult, } from './types.js';
8
+ export type { AggregateArgs, AggregateResult, ArrayFilter, ColumnRef, ConnectOrCreateOp, CountArgs, CreateArgs, CreateDataInput, CreateManyArgs, CreateManyCount, CreateManyResult, DeleteArgs, DeleteManyArgs, FieldResult, FindManyArgs, FindManyStreamArgs, FindUniqueArgs, GlobalFilters, GroupByAggregateSpec, GroupByArgs, GroupByDistinctOn, GroupByResult, HavingClause, JsonEncoding, JsonFilter, JsonPathAggregateTarget, JsonPathGroupKey, JsonPathOrderBy, NestedCreateOp, NestedUpdateOp, NestedUpdateOpItem, NestedUpsertOpItem, OmitResult, OrderByClause, OrderByObject, OrderBySpec, OrderDirection, QueryResult, RelationDescriptor, RelationFilter, RelationLoadStrategy, RelationOrderBy, RelationOrderByChain, RelationPickBy, RelationPickOrderBy, ResolvedSkipGlobalFilters, SelectResult, SkipGlobalFilters, TextSearchFilter, TypedWithClause, UpdateArgs, UpdateDataInput, UpdateInput, UpdateManyArgs, UpdateOperatorInput, UpsertArgs, VectorDistanceFilter, VectorFilter, VectorMetric, VectorOrderBy, VectorOrderByDistance, WhereClause, WhereOperator, WhereValue, WithClause, WithOptions, WithOrderByObject, WithResult, } from './types.js';
9
9
  export type { BuiltStatement, BulkInsertStatementInput, ColumnDefinitionInput, ColumnTypeInput, CreateIndexStatementInput, CreateTableStatementInput, Dialect, InsertStatementInput, UpsertStatementInput, } from '../dialect.js';
10
10
  export { postgresDialect } from '../dialect.js';
11
11
  export { col } from './column-ref.js';
@@ -142,6 +142,9 @@ exports.CREATE_MANY_OPTIONS = {
142
142
  onConflict: 'internal',
143
143
  select: 'internal',
144
144
  omit: 'internal',
145
+ // Compat's createMany already resolves to Prisma's { count } and sets this
146
+ // itself; a caller-supplied value would change the shape it relies on.
147
+ returning: 'internal',
145
148
  timeout: 'native',
146
149
  skipGlobalFilters: 'native',
147
150
  };
@@ -1034,7 +1034,7 @@ export interface CreateArgs<T, R extends object = {}, S extends Record<string, b
1034
1034
  /** Query timeout in milliseconds. Rejects with an error if exceeded. */
1035
1035
  timeout?: number | undefined;
1036
1036
  }
1037
- export interface CreateManyArgs<T, D = OptionalInput<T>, S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined> {
1037
+ export interface CreateManyArgs<T, D = OptionalInput<T>, S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined, C extends boolean | undefined = true | undefined> {
1038
1038
  /** The rows. A generated client passes its `XCreate` as `D`. */
1039
1039
  data: D[];
1040
1040
  /** Skip rows that conflict on ANY unique constraint (ON CONFLICT DO NOTHING). */
@@ -1052,6 +1052,22 @@ export interface CreateManyArgs<T, D = OptionalInput<T>, S extends Record<string
1052
1052
  select?: FlagsArg<T, S>;
1053
1053
  /** Return the written rows without these fields. Same rules as `select`. */
1054
1054
  omit?: FlagsArg<T, O>;
1055
+ /**
1056
+ * `false` resolves to `{ count }`, the number of rows written, instead of the
1057
+ * rows. A plain insert then sends no `RETURNING` / `OUTPUT` at all; an
1058
+ * `onConflict` call returns only the key columns it needs to refuse two input
1059
+ * rows that write one table row. The count is the number of rows the call
1060
+ * would otherwise have returned: inserted plus updated, never a row
1061
+ * `skipDuplicates` or `ignore` skipped. Cannot be combined with `select` or
1062
+ * `omit` (E003). On MySQL, `skipDuplicates` with `false` is E017: a skipped
1063
+ * row there counts as written.
1064
+ *
1065
+ * Typed by `C`, which defaults to the rows form, so args typed ahead of the
1066
+ * call (`const args: CreateManyArgs<User> = ...`) still resolve to rows.
1067
+ * Pass `false` as `C` to type count-only args ahead of the call, or
1068
+ * `boolean` for a flag known only at run time (the result is then either).
1069
+ */
1070
+ returning?: C;
1055
1071
  /**
1056
1072
  * Opt out of configured {@link GlobalFilters} on the conflict-UPDATE of
1057
1073
  * `onConflict` (inserts are never filtered). See {@link SkipGlobalFilters}.
@@ -1060,6 +1076,16 @@ export interface CreateManyArgs<T, D = OptionalInput<T>, S extends Record<string
1060
1076
  /** Query timeout in milliseconds. Rejects with an error if exceeded. */
1061
1077
  timeout?: number | undefined;
1062
1078
  }
1079
+ /** What `createMany({ returning: false })` resolves to. */
1080
+ export interface CreateManyCount {
1081
+ /** Rows written: inserted plus updated. */
1082
+ count: number;
1083
+ }
1084
+ /**
1085
+ * `createMany`'s result for a `returning` flag `C`: the rows for `true` or no
1086
+ * flag, `{ count }` for `false`, either for a `boolean` known only at run time.
1087
+ */
1088
+ export type CreateManyResult<Row, C> = [C] extends [false] ? CreateManyCount : [C] extends [true | undefined] ? Row[] : Row[] | CreateManyCount;
1063
1089
  /**
1064
1090
  * The columns of the primary key, or of one non-partial unique constraint or
1065
1091
  * index, in any order. A `createMany` conflict target.
@@ -1098,7 +1124,7 @@ export type ConflictUpdateData<T> = {
1098
1124
  [K in keyof T]?: T[K] | UpdateOperatorInput<T[K]> | SqlExpr<T[K]> | ColumnRef | ExcludedRef | undefined;
1099
1125
  };
1100
1126
  /** The node kinds an update expression is built from (see {@link SqlExpr}). */
1101
- export type SqlExprKind = 'add' | 'sub' | 'mul' | 'div' | 'coalesce' | 'greatest' | 'least' | 'case' | 'now' | 'excluded';
1127
+ export type SqlExprKind = 'add' | 'sub' | 'mul' | 'div' | 'coalesce' | 'greatest' | 'least' | 'case' | 'now' | 'excluded' | 'secondsBetween';
1102
1128
  /** Carries the phantom result type of a {@link SqlExpr}. Type-level only. */
1103
1129
  declare const SQL_EXPR_RESULT: unique symbol;
1104
1130
  /**
@@ -1622,8 +1648,11 @@ export interface GroupByArgs<T, R extends object = {}> {
1622
1648
  _min?: GroupByAggregateSpec<T> | undefined;
1623
1649
  /** Maximum value of fields (or JSON paths) in each group */
1624
1650
  _max?: GroupByAggregateSpec<T> | undefined;
1625
- /** Percentiles of fields in each group (PostgreSQL only). See {@link PercentileSpec}. */
1626
- _percentile?: Partial<Record<keyof T & string, PercentileSpec>> | undefined;
1651
+ /**
1652
+ * Percentiles of fields, or of expressions, in each group (PostgreSQL only).
1653
+ * See {@link PercentileSpec} and {@link PercentileExprSpec}.
1654
+ */
1655
+ _percentile?: PercentileArgs<T> | undefined;
1627
1656
  /** Filter whole groups by their aggregate values (SQL HAVING). JSON-path aggregates key by their alias. */
1628
1657
  having?: HavingClause<T> | undefined;
1629
1658
  /**
@@ -1769,9 +1798,19 @@ export interface AggregateArgs<T, R extends object = {}> {
1769
1798
  * s._percentile.statusCode; // number | null
1770
1799
  * ```
1771
1800
  *
1772
- * See {@link PercentileSpec}.
1801
+ * A key that names no field computes the percentile of an expression,
1802
+ * given as `of`:
1803
+ *
1804
+ * ```ts
1805
+ * const s = await db.jobs.aggregate({
1806
+ * _percentile: { wait: { of: expr.secondsBetween(col('requestedAt'), col('readyAt')), p: 0.95 } },
1807
+ * });
1808
+ * s._percentile.wait; // number | null, in seconds
1809
+ * ```
1810
+ *
1811
+ * See {@link PercentileSpec} and {@link PercentileExprSpec}.
1773
1812
  */
1774
- _percentile?: Partial<Record<keyof T & string, PercentileSpec>> | undefined;
1813
+ _percentile?: PercentileArgs<T> | undefined;
1775
1814
  /**
1776
1815
  * Opt in to aggregating PII-tagged (`defineSchema` `pii: true`) columns.
1777
1816
  * `_min` / `_max` / `_percentile` return a STORED VALUE (or one that equals a
@@ -1836,11 +1875,72 @@ export type AggregateResultOf<T, A> = AggregateCountPart<A> & GroupBySumAvgPart<
1836
1875
  export interface PercentileSpec {
1837
1876
  p: number | readonly number[];
1838
1877
  method?: PercentileMethod | undefined;
1878
+ /** Only on a key that names no field: see {@link PercentileExprSpec}. */
1879
+ of?: undefined;
1880
+ }
1881
+ /**
1882
+ * One `_percentile` entry over an expression, under a key that names no field
1883
+ * (the key is only the result's name and never reaches the SQL).
1884
+ *
1885
+ * - `of`: a numeric expression over the row's own columns, built with
1886
+ * {@link expr}: arithmetic (`expr.add` / `sub` / `mul` / `div`),
1887
+ * `expr.coalesce` / `greatest` / `least` / `case`, and
1888
+ * `expr.secondsBetween(col('a'), col('b'))` for the seconds from one
1889
+ * timestamp column to another. Bound values must be numbers or bigints.
1890
+ * `col('x')` alone is refused: use the field key `x` instead.
1891
+ * - `p` and `method` as in {@link PercentileSpec}. The expression is ordered as
1892
+ * double precision, so the result is a `number` for both methods (an integer
1893
+ * beyond 2^53 loses exactness).
1894
+ *
1895
+ * Every column the expression reads as a value needs `includePii: UNSAFE`
1896
+ * when it is PII-tagged; columns read only by an `expr.case` arm's `when` do
1897
+ * not. Over zero rows the result is `null`.
1898
+ */
1899
+ export interface PercentileExprSpec {
1900
+ of: SqlExpr<number | bigint | string>;
1901
+ p: number | readonly number[];
1902
+ method?: PercentileMethod | undefined;
1839
1903
  }
1904
+ /**
1905
+ * The `_percentile` argument: field keys take a {@link PercentileSpec}; any
1906
+ * other key names an expression's result and takes a {@link PercentileExprSpec}.
1907
+ * On an untyped table (`keyof T` is `string`) any key takes either: the
1908
+ * `string` key is dropped from the field half, which would otherwise narrow
1909
+ * every key to a field spec.
1910
+ */
1911
+ export type PercentileArgs<T> = {
1912
+ [K in keyof T & string as string extends K ? never : K]?: PercentileSpec | undefined;
1913
+ } & {
1914
+ [name: string]: PercentileSpec | PercentileExprSpec | undefined;
1915
+ };
1916
+ /**
1917
+ * `_percentile` naming fields only. `aggregate` / `groupBy` take args of this
1918
+ * shape when their type is generic, where {@link PercentileKeysChecked} cannot
1919
+ * read the keys, so a wrapper generic over its args still compiles.
1920
+ */
1921
+ export type FieldPercentiles<T> = {
1922
+ _percentile?: {
1923
+ [K in keyof T & string]?: PercentileSpec | undefined;
1924
+ } | undefined;
1925
+ };
1926
+ /**
1927
+ * Applied to `aggregate` / `groupBy` arguments: every `_percentile` key that
1928
+ * names no field must carry `of`, so a misspelled field name is a compile
1929
+ * error rather than an expression percentile with no expression.
1930
+ */
1931
+ export type PercentileKeysChecked<T, A> = A extends {
1932
+ _percentile: infer S;
1933
+ } ? [S] extends [object] ? {
1934
+ _percentile: {
1935
+ [K in keyof S]: K extends keyof T ? S[K] : PercentileExprSpec | undefined;
1936
+ };
1937
+ } : unknown : unknown;
1840
1938
  /** `'cont'` interpolates (`percentile_cont`); `'disc'` returns a stored value (`percentile_disc`). */
1841
1939
  export type PercentileMethod = 'cont' | 'disc';
1842
1940
  /** One percentile value: the column's own type for `disc`; for `cont` a number, or an interval as the column reads. */
1843
- type PercentileScalar<T, K, Spec> = K extends keyof T ? Spec extends {
1941
+ type PercentileScalar<T, K, Spec> = Spec extends {
1942
+ of: object;
1943
+ } ? number : K extends keyof T ? Spec extends {
1844
1944
  method: 'disc';
1845
1945
  } ? NonNullable<T[K]> : [NonNullable<T[K]>] extends [number] ? number : number | NonNullable<T[K]> : unknown;
1846
1946
  /** A scalar `p` yields one value, an array `p` yields an array, `null` over zero rows. */
@@ -91,7 +91,7 @@ export declare function buildCreateMany<T extends object>(qi: BuilderCtx, args:
91
91
  * builder (`skipGlobalFilters`), which a concurrent call could change across
92
92
  * an await.
93
93
  */
94
- export declare function planCreateMany<T extends object>(qi: BuilderCtx, args: CreateManyArgs<T>, projection?: WriteProjection): {
94
+ export declare function planCreateMany<T extends object>(qi: BuilderCtx, args: CreateManyArgs<T>, projection?: WriteProjection, canSplit?: boolean): {
95
95
  chunks: DeferredQuery<T[]>[];
96
96
  rowsPerChunk: number;
97
97
  };
@@ -192,9 +192,21 @@ export declare function piiFields(_qi: BuilderCtx, meta: TableMetadata): string[
192
192
  * 34,500/s with `RETURNING *`.
193
193
  */
194
194
  export interface WriteProjection {
195
- /** Unquoted column names, in table order. Never empty. */
195
+ /**
196
+ * Unquoted column names, in table order. Empty only for {@link COUNT_ONLY}:
197
+ * a caller's projection always names at least one column.
198
+ */
196
199
  readonly columns: readonly string[];
197
200
  }
201
+ /**
202
+ * The projection of `createMany({ returning: false })`: no column. A plain
203
+ * insert then sends no RETURNING / OUTPUT and reports its row count; an
204
+ * onConflict call returns only its identity columns (see
205
+ * {@link conflictIdentity}), which the count is taken from.
206
+ */
207
+ export declare const COUNT_ONLY: WriteProjection;
208
+ /** True for {@link COUNT_ONLY} (or any projection naming no column). */
209
+ export declare function isCountOnly(projection: WriteProjection | undefined): boolean;
198
210
  /**
199
211
  * The write-SQL cache segment for a projection. EMPTY for the default shape, so
200
212
  * every existing key is byte-identical; a projected statement differs only in