turbine-orm 0.90.2-next.91484a2 → 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
package/README.md CHANGED
@@ -272,7 +272,7 @@ Going deep on one database means the parts other ORMs push to raw SQL are typed
272
272
 
273
273
  ## Serverless and edge
274
274
 
275
- The core is driver-agnostic: hand any pg-compatible pool to `turbineHttp()` and Turbine runs on Vercel Edge, Cloudflare Workers, Deno Deploy, or anywhere else without TCP. The main entry's import graph is held under **146 kB brotli** (edge entry under **114 kB**) with `pg` external, enforced by `size-limit` in CI at those exact numbers; run `npm run size` for the current figure.
275
+ The core is driver-agnostic: hand any pg-compatible pool to `turbineHttp()` and Turbine runs on Vercel Edge, Cloudflare Workers, Deno Deploy, or anywhere else without TCP. The main entry's import graph is held under **148 kB brotli** (edge entry under **116 kB**) with `pg` external, enforced by `size-limit` in CI at those exact numbers; run `npm run size` for the current figure.
276
276
 
277
277
  ```typescript
278
278
  import { Pool } from '@neondatabase/serverless';
@@ -359,11 +359,11 @@ It is also built to be extended rather than wrapped. All SQL generation routes t
359
359
  |---|---|---|---|---|---|
360
360
  | **Engine / runtime** | No engine binary (`pg` only) | Client + TS/WASM query compiler | No engine | No engine | Kysely as query runner; SQL built by MikroORM (v7) |
361
361
  | **Runtime deps** | 1 (`pg`) | `@prisma/client` + required driver adapter | 0 | 0 | `@mikro-orm/core` (0 deps) + a driver package |
362
- | **Main bundle (brotli)** | under 146 kB import graph (CI-enforced), `pg` external | ~1.6 MB client (TS/WASM compiler) | ~7 KB core | small | |
362
+ | **Main bundle (brotli)** | under 148 kB import graph (CI-enforced), `pg` external | ~1.6 MB client (TS/WASM compiler) | ~7 KB core | small | |
363
363
  | **Studio** | Read-only by default | Full CRUD, cloud-hosted | Full CRUD; [Gateway](https://gateway.drizzle.team/) self-hosted, free | None | AdminJS integration guide (CRUD panel) |
364
364
  | **Error PII safety** | Keys only by default | Values in messages | Raw pg errors | Raw pg errors | |
365
365
  | **Migrations** | SQL-first, SHA-256 checksums | DSL-generated, shadow DB | SQL or Drizzle Kit | None | Generated by diffing entities against a snapshot or the database; TS/JS classes |
366
- | **Edge runtime** | One import swap, under 114 kB brotli (CI-enforced) | Driver adapter + WASM compiler | Native | Native | Cloudflare Workers and Deno (v7) |
366
+ | **Edge runtime** | One import swap, under 116 kB brotli (CI-enforced) | Driver adapter + WASM compiler | Native | Native | Cloudflare Workers and Deno (v7) |
367
367
  | **Pipeline batching** | Parse/Bind/Execute protocol | Sequential in txn | Sequential | Manual | `flush()` runs queued changes in one transaction |
368
368
  | **Typed errors** | `isRetryable` discriminant | Error codes only | None | None | `DriverException` subclasses (`UniqueConstraintViolationException`, ...) |
369
369
  | **Nested relations** | 1 query, deep type inference | 1 query per relation by default; single-query `relationJoins` is Preview | 1 query, `relations()` re-declaration | Manual (`jsonArrayFrom`) | `populate`; default `balanced`: joins to-one, one query per to-many relation |
@@ -501,6 +501,17 @@ export interface EngineCapabilities {
501
501
  * not bound by it.
502
502
  */
503
503
  readonly maxInsertRows: number;
504
+ /**
505
+ * The most bind parameters each statement of a `createMany` that runs as
506
+ * several statements carries: at most `maxBindParameters`, and lower where
507
+ * an engine slows faster than linearly with a statement's size. SQLite
508
+ * finds each named parameter by a linear search, at prepare and at bind, so
509
+ * 40,000 six-column rows took 141 s in statements of 32,766 parameters and
510
+ * 1 s in statements of 250. A call whose rows cannot be told apart
511
+ * across statements, or on a pool with no `connect()`, is not split below
512
+ * `maxBindParameters` (see `planCreateMany`).
513
+ */
514
+ readonly chunkBindParameters: number;
504
515
  /**
505
516
  * What a single-statement upsert conflicts on. `'explicit'`: the conflict
506
517
  * target it names. `'any-unique-key'`: whichever unique key collides first
@@ -531,6 +542,16 @@ export interface EngineCapabilities {
531
542
  * it afterwards, so the statement runs in a transaction it can roll back.
532
543
  */
533
544
  readonly bulkUpsertAppliesRepeatedKeys: boolean;
545
+ /**
546
+ * A `skipDuplicates` insert reports a skipped row as affected, so its row
547
+ * count cannot be the rows it inserted. MySQL: its skip is a no-op
548
+ * `ON DUPLICATE KEY UPDATE`, which under `CLIENT_FOUND_ROWS` (mysql2's
549
+ * default) counts a matched row, and a one-row statement's result carries no
550
+ * `info` to subtract the duplicates from. `createMany` refuses
551
+ * `skipDuplicates` with `returning: false` here (E017). False on an engine
552
+ * that never runs a skip.
553
+ */
554
+ readonly skipDuplicatesCountsSkipped: boolean;
534
555
  /** `$transaction({ readOnly: true })`: `READ ONLY` after the dialect's BEGIN. Else E017. */
535
556
  readonly readOnlyTransactions: boolean;
536
557
  /**
@@ -817,7 +838,11 @@ export interface Dialect {
817
838
  readonly explainQuery?: {
818
839
  prefix: string;
819
840
  };
820
- /** Build a dialect-specific RETURNING clause. Return an empty string when unsupported. */
841
+ /**
842
+ * Build a dialect-specific RETURNING clause. Return an empty string when
843
+ * unsupported, and for an empty column list (a write that returns no column,
844
+ * `createMany({ returning: false })`).
845
+ */
821
846
  buildReturningClause(selection?: ReturningSelection): string;
822
847
  /** Build a single-row INSERT statement. Inputs are SQL-ready quoted fragments. */
823
848
  buildInsertStatement(input: InsertStatementInput): string;
@@ -108,12 +108,14 @@ exports.postgresDialect = {
108
108
  // Counted in a 16-bit field.
109
109
  maxBindParameters: 65535,
110
110
  maxInsertRows: Number.POSITIVE_INFINITY,
111
+ chunkBindParameters: 65535,
111
112
  upsertConflictTarget: 'explicit',
112
113
  setClauseOrder: 'snapshot',
113
114
  reselectLocksRows: false,
114
115
  integerDivision: '/',
115
116
  updateExpressions: true,
116
117
  bulkUpsertAppliesRepeatedKeys: false,
118
+ skipDuplicatesCountsSkipped: false,
117
119
  readOnlyTransactions: true,
118
120
  connectionParameters: true,
119
121
  backendCancel: true,
@@ -191,6 +193,8 @@ exports.postgresDialect = {
191
193
  return `COALESCE((${subquery}), ${fallback})`;
192
194
  },
193
195
  buildReturningClause(selection = '*') {
196
+ if (selection !== '*' && selection.length === 0)
197
+ return '';
194
198
  return ` RETURNING ${selection === '*' ? '*' : selection.join(', ')}`;
195
199
  },
196
200
  buildInsertStatement(input) {
@@ -47,9 +47,9 @@ export { HttpJsonSink, type HttpJsonSinkOptions, type MetricsFlushBatch, type Me
47
47
  export { executePipeline, type PipelineOptions, type PipelineResults, pipelineSupported } from './pipeline.js';
48
48
  export { fingerprintPrismaSchema } from './prisma-schema-fingerprint.js';
49
49
  export { type CaseArm, type ExprOperand, expr, type NumericOperand } from './query/expr.js';
50
- export { type AggregateArgs, type AggregateResult, type ArrayFilter, AUTO_ASSUMED_ROUND_TRIP_MS, AUTO_COUNT_BATCH_MIN_PARENT_ROWS, AUTO_JOIN_PENALTY_MS_PER_ROW, AUTO_TO_ONE_JOIN_MAX_ROWS, AUTO_TO_ONE_JOIN_ROWS_MAX, AUTO_TO_ONE_JOIN_ROWS_MIN, type ColumnRef, type ConnectOrCreateOp, type CountArgs, type CreateArgs, type CreateDataInput, type CreateManyArgs, col, type DeferredQuery, type DeleteArgs, type DeleteManyArgs, type FieldResult, type FindManyArgs, type FindManyStreamArgs, type FindUniqueArgs, type GlobalFilters, type GroupByAggregateSpec, type GroupByArgs, type GroupByDistinctOn, type GroupByResult, type HavingClause, type JsonEncoding, type JsonFilter, type JsonPathAggregateTarget, type JsonPathGroupKey, type JsonPathOrderBy, type JsonValue, jsonValue, type MiddlewareFn, type NestedCreateOp, type NestedUpdateOp, type NestedUpdateOpItem, type NestedUpsertOpItem, type OmitResult, type OrderByClause, type OrderByObject, type OrderBySpec, type OrderDirection, type PrivilegeOption, type QueryEvent, type QueryEventListener, QueryInterface, type QueryResult, type RelationDescriptor, type RelationFilter, type RelationLoadStrategy, type RelationOrderBy, type RelationOrderByChain, type RelationPickBy, type RelationPickOrderBy, type SelectResult, type SkipGlobalFilters, type TemporalInfinityReading, type TextSearchFilter, type TypedWithClause, UNSAFE, type Unsafe, type UpdateArgs, type UpdateDataInput, type UpdateInput, type UpdateManyArgs, type UpdateOperatorInput, type UpsertArgs, type VectorDistanceFilter, type VectorFilter, type VectorMetric, type VectorOrderBy, type VectorOrderByDistance, type WhereClause, type WhereOperator, type WhereValue, type WithClause, type WithOptions, type WithOrderByObject, type WithResult, } from './query/index.js';
50
+ export { type AggregateArgs, type AggregateResult, type ArrayFilter, AUTO_ASSUMED_ROUND_TRIP_MS, AUTO_COUNT_BATCH_MIN_PARENT_ROWS, AUTO_JOIN_PENALTY_MS_PER_ROW, AUTO_TO_ONE_JOIN_MAX_ROWS, AUTO_TO_ONE_JOIN_ROWS_MAX, AUTO_TO_ONE_JOIN_ROWS_MIN, type ColumnRef, type ConnectOrCreateOp, type CountArgs, type CreateArgs, type CreateDataInput, type CreateManyArgs, type CreateManyCount, type CreateManyResult, col, type DeferredQuery, type DeleteArgs, type DeleteManyArgs, type FieldResult, type FindManyArgs, type FindManyStreamArgs, type FindUniqueArgs, type GlobalFilters, type GroupByAggregateSpec, type GroupByArgs, type GroupByDistinctOn, type GroupByResult, type HavingClause, type JsonEncoding, type JsonFilter, type JsonPathAggregateTarget, type JsonPathGroupKey, type JsonPathOrderBy, type JsonValue, jsonValue, type MiddlewareFn, type NestedCreateOp, type NestedUpdateOp, type NestedUpdateOpItem, type NestedUpsertOpItem, type OmitResult, type OrderByClause, type OrderByObject, type OrderBySpec, type OrderDirection, type PrivilegeOption, type QueryEvent, type QueryEventListener, QueryInterface, type QueryResult, type RelationDescriptor, type RelationFilter, type RelationLoadStrategy, type RelationOrderBy, type RelationOrderByChain, type RelationPickBy, type RelationPickOrderBy, type SelectResult, type SkipGlobalFilters, type TemporalInfinityReading, type TextSearchFilter, type TypedWithClause, UNSAFE, type Unsafe, type UpdateArgs, type UpdateDataInput, type UpdateInput, type UpdateManyArgs, type UpdateOperatorInput, type UpsertArgs, type VectorDistanceFilter, type VectorFilter, type VectorMetric, type VectorOrderBy, type VectorOrderByDistance, type WhereClause, type WhereOperator, type WhereValue, type WithClause, type WithOptions, type WithOrderByObject, type WithResult, } from './query/index.js';
51
51
  export type { SqlCommentOptions } from './query/sql-comment.js';
52
- export type { AggregateResultOf, ConflictTarget, ConflictUpdateData, CreateManyOnConflict, DefaultInputTypes, ExcludedRef, GlobalFilter, NestedToOneCreateOp, NestedToOneUpdateOp, OptionalInput, PercentileMethod, PercentileSpec, RelationKind, RowLock, RowLockMode, SqlExpr, SqlExprKind, TableInputTypes, } from './query/types.js';
52
+ export type { AggregateResultOf, ConflictTarget, ConflictUpdateData, CreateManyOnConflict, DefaultInputTypes, ExcludedRef, FieldPercentiles, GlobalFilter, NestedToOneCreateOp, NestedToOneUpdateOp, OptionalInput, PercentileArgs, PercentileExprSpec, PercentileKeysChecked, PercentileMethod, PercentileSpec, RelationKind, RowLock, RowLockMode, SqlExpr, SqlExprKind, TableInputTypes, } from './query/types.js';
53
53
  export { MAX_QUERY_TAG_LENGTH, RAW_QUERY_MODEL } from './query-events.js';
54
54
  export { type ActiveSubscription, type ListenOptions, type ListenReconnectOptions, type NotificationHandler, type Subscription, validateChannel, } from './realtime.js';
55
55
  export type { CheckMetadata, ColumnMetadata, IndexMetadata, PrismaCompatMap, PrismaModelMap, PrismaRelationMap, PrismaSchemaSource, ReferentialAction, RelationDef, SchemaMetadata, TableMetadata, } from './schema.js';
package/dist/cjs/mssql.js CHANGED
@@ -644,6 +644,7 @@ exports.mssqlDialect = {
644
644
  // The limit the driver can actually send (see MSSQL_MAX_PARAMS), not 2100.
645
645
  maxBindParameters: MSSQL_MAX_PARAMS,
646
646
  maxInsertRows: MSSQL_MAX_INSERT_ROWS,
647
+ chunkBindParameters: MSSQL_MAX_PARAMS,
647
648
  upsertConflictTarget: 'explicit',
648
649
  setClauseOrder: 'snapshot',
649
650
  reselectLocksRows: false,
@@ -651,6 +652,7 @@ exports.mssqlDialect = {
651
652
  updateExpressions: true,
652
653
  // MERGE refuses a target row matched twice (error 8672).
653
654
  bulkUpsertAppliesRepeatedKeys: false,
655
+ skipDuplicatesCountsSkipped: false,
654
656
  readOnlyTransactions: false,
655
657
  connectionParameters: false,
656
658
  backendCancel: false,
package/dist/cjs/mysql.js CHANGED
@@ -709,6 +709,7 @@ exports.mysqlDialect = {
709
709
  // Counted in a 16-bit field.
710
710
  maxBindParameters: 65535,
711
711
  maxInsertRows: Number.POSITIVE_INFINITY,
712
+ chunkBindParameters: 65535,
712
713
  upsertConflictTarget: 'any-unique-key',
713
714
  setClauseOrder: 'left-to-right',
714
715
  reselectLocksRows: true,
@@ -716,6 +717,8 @@ exports.mysqlDialect = {
716
717
  updateExpressions: true,
717
718
  // Caught by the onConflict re-read, which runs in the reselect transaction.
718
719
  bulkUpsertAppliesRepeatedKeys: true,
720
+ // A skipped row is a no-op ON DUPLICATE KEY UPDATE, counted under FOUND_ROWS.
721
+ skipDuplicatesCountsSkipped: true,
719
722
  readOnlyTransactions: true,
720
723
  connectionParameters: false,
721
724
  backendCancel: false,
package/dist/cjs/powdb.js CHANGED
@@ -147,12 +147,14 @@ exports.powdbDialect = {
147
147
  structuredValueColumns: 'scalars',
148
148
  maxBindParameters: Number.POSITIVE_INFINITY,
149
149
  maxInsertRows: Number.POSITIVE_INFINITY,
150
+ chunkBindParameters: Number.POSITIVE_INFINITY,
150
151
  upsertConflictTarget: 'explicit',
151
152
  setClauseOrder: 'snapshot',
152
153
  reselectLocksRows: false,
153
154
  integerDivision: '/',
154
155
  updateExpressions: false,
155
156
  bulkUpsertAppliesRepeatedKeys: false,
157
+ skipDuplicatesCountsSkipped: false,
156
158
  readOnlyTransactions: false,
157
159
  connectionParameters: false,
158
160
  backendCancel: false,
@@ -37,7 +37,7 @@
37
37
  import type { PowdbPool } from './powdb.js';
38
38
  import type { MiddlewareFn, QueryInterfaceOptions } from './query/index.js';
39
39
  import type { TableClient } from './query/table-client.js';
40
- import type { AggregateArgs, AggregateResultOf, CountArgs, CreateArgs, CreateManyArgs, DeleteArgs, DeleteManyArgs, FieldResult, FindManyArgs, FindUniqueArgs, GroupByArgs, GroupByResult, OptionalInput, QueryResult, TypedWithClause, UpdateArgs, UpdateManyArgs, UpsertArgs } from './query/types.js';
40
+ import type { AggregateArgs, AggregateResultOf, CountArgs, CreateArgs, CreateManyArgs, CreateManyResult, DeleteArgs, DeleteManyArgs, FieldResult, FindManyArgs, FindUniqueArgs, GroupByArgs, GroupByResult, OptionalInput, QueryResult, TypedWithClause, UpdateArgs, UpdateManyArgs, UpsertArgs } from './query/types.js';
41
41
  import { type SchemaMetadata } from './schema.js';
42
42
  /**
43
43
  * Max values one `in` / `notIn` on a PowDB-native `datetime` column may carry.
@@ -1146,7 +1146,10 @@ export declare class PowqlInterface<T extends object = Record<string, unknown>>
1146
1146
  * byte-identical PowQL.
1147
1147
  */
1148
1148
  private assertUniformCreateManyRows;
1149
- createMany<S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined>(args: CreateManyArgs<T, OptionalInput<T>, S, O>): Promise<FieldResult<T, S, O>[]>;
1149
+ createMany<S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined, C extends boolean | undefined = undefined>(args: CreateManyArgs<T, OptionalInput<T>, S, O, C>): Promise<CreateManyResult<FieldResult<T, S, O>, C>>;
1150
+ /** `returning: false` validated: a boolean, and not combined with a projection. */
1151
+ private createManyCountOnly;
1152
+ private insertMany;
1150
1153
  update<S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined>(args: UpdateArgs<T, NoRelations, S, O>): Promise<FieldResult<T, S, O>>;
1151
1154
  updateMany(args: UpdateManyArgs<T>): Promise<{
1152
1155
  count: number;
package/dist/cjs/powql.js CHANGED
@@ -3860,35 +3860,56 @@ class PowqlInterface {
3860
3860
  async createMany(args) {
3861
3861
  args = this.normalizeArgs(args);
3862
3862
  return this.withMiddleware('createMany', args, async () => {
3863
- if (args.onConflict !== undefined) {
3864
- throw new errors_js_1.UnsupportedFeatureError('createMany({ onConflict })', 'powdb', "PowQL's upsert statement takes one row. Call upsert() per row inside $transaction.");
3865
- }
3866
- // Validated as every engine validates it; it acts only on onConflict's
3867
- // update, which is refused above.
3868
- (0, types_js_1.resolveSkipGlobalFilters)(args.skipGlobalFilters);
3869
- if (args.skipDuplicates) {
3870
- // PowQL's `insert … returning` has no conflict clause, so there is no
3871
- // faithful skip-duplicates form; refuse rather than silently insert.
3872
- throw new errors_js_1.UnsupportedFeatureError('createMany({ skipDuplicates: true })', 'powdb', 'PowQL insert has no ON CONFLICT DO NOTHING equivalent, filter duplicates before inserting.');
3873
- }
3874
- // Resolved before the write, so a bad projection writes nothing.
3875
- const plan = this.writeReturnPlan(args);
3876
- const supplied = args.data.map((d) => this.applyPkDefault(d));
3877
- if (!supplied.length)
3878
- return [];
3879
- // Uniformity is judged on what the caller supplied, as the SQL engines
3880
- // judge it; defaults are then filled per row.
3881
- this.assertUniformCreateManyRows(supplied);
3882
- const inputs = supplied.map((d) => this.applyColumnDefaults(d));
3883
- const params = [];
3884
- const tuples = inputs.map((d) => {
3885
- const assigns = this.scalarData(d);
3886
- return `{ ${assigns.map((a) => `${(0, powdb_shared_js_1.quotePowqlIdent)(a.col.name)} := ${this.writeRef(a.value, a.col, params)}`).join(', ')} }`;
3887
- });
3888
- // Multi-row insert with `returning` hands back every inserted row in one round-trip.
3889
- const { rows, native } = await this.exec(`insert ${this.qt} ${tuples.join(', ')} returning`, params, args.timeout, 'createMany');
3890
- return this.shape(rows, native).map((r) => this.shapeWriteRow(r, plan));
3863
+ // `returning: false` is checked as the SQL engines check it. PowQL's
3864
+ // insert still returns the rows, which are counted and dropped.
3865
+ const countOnly = this.createManyCountOnly(args);
3866
+ const rows = await this.insertMany(args);
3867
+ return countOnly ? { count: rows.length } : rows;
3868
+ });
3869
+ }
3870
+ /** `returning: false` validated: a boolean, and not combined with a projection. */
3871
+ createManyCountOnly(args) {
3872
+ const { returning } = args;
3873
+ if (returning === undefined || returning === true)
3874
+ return false;
3875
+ if (returning !== false) {
3876
+ throw new errors_js_1.ValidationError(`createMany on "${this.table}": returning must be true or false, received ${typeof returning}.`);
3877
+ }
3878
+ if (args.select !== undefined || args.omit !== undefined) {
3879
+ throw new errors_js_1.ValidationError(`createMany on "${this.table}": returning: false returns { count } and no rows, so it takes no ` +
3880
+ `\`${args.select !== undefined ? 'select' : 'omit'}\`. Drop one of them.`);
3881
+ }
3882
+ return true;
3883
+ }
3884
+ async insertMany(args) {
3885
+ if (args.onConflict !== undefined) {
3886
+ throw new errors_js_1.UnsupportedFeatureError('createMany({ onConflict })', 'powdb', "PowQL's upsert statement takes one row. Call upsert() per row inside $transaction.");
3887
+ }
3888
+ // Validated as every engine validates it; it acts only on onConflict's
3889
+ // update, which is refused above.
3890
+ (0, types_js_1.resolveSkipGlobalFilters)(args.skipGlobalFilters);
3891
+ if (args.skipDuplicates) {
3892
+ // PowQL's `insert … returning` has no conflict clause, so there is no
3893
+ // faithful skip-duplicates form; refuse rather than silently insert.
3894
+ throw new errors_js_1.UnsupportedFeatureError('createMany({ skipDuplicates: true })', 'powdb', 'PowQL insert has no ON CONFLICT DO NOTHING equivalent, filter duplicates before inserting.');
3895
+ }
3896
+ // Resolved before the write, so a bad projection writes nothing.
3897
+ const plan = this.writeReturnPlan(args);
3898
+ const supplied = args.data.map((d) => this.applyPkDefault(d));
3899
+ if (!supplied.length)
3900
+ return [];
3901
+ // Uniformity is judged on what the caller supplied, as the SQL engines
3902
+ // judge it; defaults are then filled per row.
3903
+ this.assertUniformCreateManyRows(supplied);
3904
+ const inputs = supplied.map((d) => this.applyColumnDefaults(d));
3905
+ const params = [];
3906
+ const tuples = inputs.map((d) => {
3907
+ const assigns = this.scalarData(d);
3908
+ return `{ ${assigns.map((a) => `${(0, powdb_shared_js_1.quotePowqlIdent)(a.col.name)} := ${this.writeRef(a.value, a.col, params)}`).join(', ')} }`;
3891
3909
  });
3910
+ // Multi-row insert with `returning` hands back every inserted row in one round-trip.
3911
+ const { rows, native } = await this.exec(`insert ${this.qt} ${tuples.join(', ')} returning`, params, args.timeout, 'createMany');
3912
+ return this.shape(rows, native).map((r) => this.shapeWriteRow(r, plan));
3892
3913
  }
3893
3914
  async update(args) {
3894
3915
  args = this.normalizeArgs(args);
@@ -137,7 +137,7 @@ export interface CompatQueryInterface {
137
137
  findFirstOrThrow(args?: Record<string, unknown>): Promise<unknown>;
138
138
  findUniqueOrThrow(args: Record<string, unknown>): Promise<unknown>;
139
139
  create(args: Record<string, unknown>): Promise<unknown>;
140
- createMany(args: Record<string, unknown>): Promise<unknown[]>;
140
+ createMany(args: Record<string, unknown>): Promise<unknown>;
141
141
  update(args: Record<string, unknown>): Promise<unknown>;
142
142
  updateMany(args: Record<string, unknown>): Promise<{
143
143
  count: number;
@@ -156,7 +156,7 @@ export interface CompatQueryInterface {
156
156
  buildFindFirstOrThrow(args?: Record<string, unknown>): DeferredQuery<unknown>;
157
157
  buildFindUniqueOrThrow(args: Record<string, unknown>): DeferredQuery<unknown>;
158
158
  buildCreate(args: Record<string, unknown>): DeferredQuery<unknown>;
159
- buildCreateMany(args: Record<string, unknown>): DeferredQuery<unknown[]>;
159
+ buildCreateMany(args: Record<string, unknown>): DeferredQuery<unknown>;
160
160
  buildUpdate(args: Record<string, unknown>): DeferredQuery<unknown>;
161
161
  buildUpdateMany(args: Record<string, unknown>): DeferredQuery<{
162
162
  count: number;
@@ -1901,12 +1901,12 @@ function createManyRunsOf(t) {
1901
1901
  async function createManyByRun(qi, t, runs) {
1902
1902
  let count = 0;
1903
1903
  for (const run of runs) {
1904
- const args = { data: run };
1904
+ const args = { data: run, returning: false };
1905
1905
  if (t.skipDuplicates)
1906
1906
  args.skipDuplicates = true;
1907
1907
  if (t.timeout !== undefined)
1908
1908
  args.timeout = t.timeout;
1909
- count += (await qi.createMany(args)).length;
1909
+ count += (await qi.createMany(args)).count;
1910
1910
  }
1911
1911
  return { count };
1912
1912
  }
@@ -2002,7 +2002,10 @@ function makeDelegate(ctx, mm, getQI, runInTx) {
2002
2002
  `array of them, received ${describeValue(data)}.`);
2003
2003
  }
2004
2004
  const rows = list.map((d) => translateWriteData(ctx, mm, applyCreateDefaults(mm, d)));
2005
- const t = { data: rows };
2005
+ // Prisma's createMany resolves to { count }, which core counts
2006
+ // without returning the rows (a plain MySQL insert returns none, so
2007
+ // counting rows reported 0 there).
2008
+ const t = { data: rows, returning: false };
2006
2009
  (0, index_js_1.applyNativeOptions)(index_js_1.CREATE_MANY_OPTIONS, args, t);
2007
2010
  if (args.skipDuplicates)
2008
2011
  t.skipDuplicates = true;
@@ -2014,11 +2017,11 @@ function makeDelegate(ctx, mm, getQI, runInTx) {
2014
2017
  // stays all-or-nothing and the count is the total actually inserted.
2015
2018
  const runs = createManyRunsOf(t);
2016
2019
  if (runs.length <= 1)
2017
- return qi.createMany(t).then((r) => ({ count: r.length }));
2020
+ return qi.createMany(t);
2018
2021
  return runInTx((table) => createManyByRun(table(mm.table), t, runs));
2019
2022
  }, {
2020
2023
  build: (qi, t) => qi.buildCreateMany(t),
2021
- reshape: (raw) => ({ count: raw.length }),
2024
+ reshape: (raw) => raw,
2022
2025
  // A mixed batch is more than one statement, so the array
2023
2026
  // $transaction([...]) form runs the whole array sequentially in a tx.
2024
2027
  nested: (t) => createManyRunsOf(t).length > 1,
@@ -54,6 +54,7 @@ exports.buildHavingNumericClauses = buildHavingNumericClauses;
54
54
  exports.buildAggregate = buildAggregate;
55
55
  const errors_js_1 = require("../errors.js");
56
56
  const schema_js_1 = require("../schema.js");
57
+ const expr_js_1 = require("./expr.js");
57
58
  const filters_js_1 = require("./filters.js");
58
59
  const types_js_1 = require("./types.js");
59
60
  const utils_js_1 = require("./utils.js");
@@ -325,7 +326,7 @@ function buildGroupBy(qi, args) {
325
326
  buildAggregates('_avg', 'AVG', args._avg);
326
327
  buildAggregates('_min', 'MIN', args._min);
327
328
  buildAggregates('_max', 'MAX', args._max);
328
- const percentiles = planPercentiles(qi, args._percentile, params, includePii, 'groupBy', claimResultKey);
329
+ const percentiles = planPercentiles(qi, args._percentile, params, includePii, 'groupBy', claimResultKey, (key) => key === '_count' || usedResultKeys.has(key));
329
330
  selectExprs.push(...percentiles.map((plan) => plan.expr));
330
331
  let sql = `SELECT ${selectExprs.join(', ')} FROM ${fromSql} GROUP BY ${groupExprs.join(', ')}`;
331
332
  // HAVING, filter whole groups by their aggregate values.
@@ -1070,7 +1071,7 @@ const PERCENTILE_HINT = {
1070
1071
  mssql: 'Percentile aggregates are not available on SQL Server in this release: PERCENTILE_CONT is a window ' +
1071
1072
  'function there, not an aggregate.',
1072
1073
  };
1073
- const PERCENTILE_SPEC_KEYS = ['p', 'method'];
1074
+ const PERCENTILE_SPEC_KEYS = ['p', 'method', 'of'];
1074
1075
  const PERCENTILE_METHODS = ['cont', 'disc'];
1075
1076
  const PERCENTILE_NOT_ORDERABLE = 'ordering or filtering groups by _percentile is not supported yet.';
1076
1077
  /**
@@ -1085,8 +1086,14 @@ const PERCENTILE_NOT_ORDERABLE = 'ordering or filtering groups by _percentile is
1085
1086
  * form), a JSON-path target, and two keys naming one column. Both methods need
1086
1087
  * `includePii: UNSAFE` on a PII column: `disc` returns a stored cell, and so
1087
1088
  * does `cont` at p = 0 or 1 or over a one-row group.
1089
+ *
1090
+ * A key with `of` names an expression's result (see `PercentileExprSpec`): it
1091
+ * must not name a field, the expression compiles through `compileReadExpr`,
1092
+ * it is ordered as `float8`, and its SQL alias is `_percentile_of_<n>`, so the
1093
+ * caller's name never reaches the SQL. Every PII column it reads as a value
1094
+ * needs `includePii: UNSAFE`, as a column percentile does.
1088
1095
  */
1089
- function planPercentiles(qi, spec, params, includePii, operation, claimResultKey) {
1096
+ function planPercentiles(qi, spec, params, includePii, operation, claimResultKey, resultKeyTaken) {
1090
1097
  if (spec === undefined)
1091
1098
  return [];
1092
1099
  const where = `${operation} _percentile on table "${qi.table}"`;
@@ -1102,6 +1109,16 @@ function planPercentiles(qi, spec, params, includePii, operation, claimResultKey
1102
1109
  const meta = qi.schema.tables[qi.table];
1103
1110
  const plans = [];
1104
1111
  const aliases = new Set();
1112
+ // Column aliases are `_percentile_<column>`; an expression's is the first
1113
+ // free `_percentile_of_<n>`, chosen after every column alias is known so a
1114
+ // column named `of_0` keeps its own.
1115
+ const columnAliases = new Set();
1116
+ for (const [key] of entries) {
1117
+ const column = (0, utils_js_1.resolveColumnName)(qi.tableMeta, key);
1118
+ if (column !== undefined)
1119
+ columnAliases.add(`_percentile_${column}`);
1120
+ }
1121
+ let exprCount = 0;
1105
1122
  for (const [key, entry] of entries) {
1106
1123
  const label = `${where}, field "${key}"`;
1107
1124
  if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) {
@@ -1116,7 +1133,7 @@ function planPercentiles(qi, spec, params, includePii, operation, claimResultKey
1116
1133
  throw new errors_js_1.ValidationError(`${label}: unknown key "${k}".${hint ? ` Did you mean "${hint}"?` : ''} Expected { p, method? }.`);
1117
1134
  }
1118
1135
  }
1119
- const { p, method = 'cont' } = entry;
1136
+ const { p, method = 'cont', of } = entry;
1120
1137
  if (!PERCENTILE_METHODS.includes(method)) {
1121
1138
  throw new errors_js_1.ValidationError(`${label}: method must be 'cont' or 'disc', received ${describeValue(method)}.`);
1122
1139
  }
@@ -1129,6 +1146,10 @@ function planPercentiles(qi, spec, params, includePii, operation, claimResultKey
1129
1146
  else {
1130
1147
  assertFraction(p, label);
1131
1148
  }
1149
+ if (of !== undefined) {
1150
+ plans.push(planExpressionPercentile(qi, key, of, p, method, params, includePii, operation, label, aliases, columnAliases, exprCount++, claimResultKey, resultKeyTaken));
1151
+ continue;
1152
+ }
1132
1153
  const col = qi.toColumn(key);
1133
1154
  if (method === 'cont' && qi.tableMeta.dateColumns.has(col)) {
1134
1155
  throw new errors_js_1.ValidationError(`${label}: method 'cont' interpolates and needs a numeric or interval column, and "${col}" is a ` +
@@ -1158,6 +1179,51 @@ function planPercentiles(qi, spec, params, includePii, operation, claimResultKey
1158
1179
  }
1159
1180
  return plans;
1160
1181
  }
1182
+ /** One `_percentile` entry over an expression (`of`); see {@link planPercentiles}. */
1183
+ function planExpressionPercentile(qi, key, of, p, method, params, includePii, operation, label, aliases, columnAliases, index, claimResultKey, resultKeyTaken) {
1184
+ const column = (0, utils_js_1.resolveColumnName)(qi.tableMeta, key);
1185
+ if (column !== undefined) {
1186
+ throw new errors_js_1.ValidationError(`${label}: "${key}" is a field of the table, and \`of\` names an expression. Give the expression a name ` +
1187
+ `that is not a field (the name is only its result's key), or drop \`of\` for the percentile of "${key}".`);
1188
+ }
1189
+ const compiled = (0, expr_js_1.compileReadExpr)({
1190
+ qi,
1191
+ name: key,
1192
+ operation: `${operation} _percentile`,
1193
+ bind: (value, cls) => {
1194
+ params.push(value);
1195
+ return `${qi.p(params.length)}::${cls === 'int' ? 'int8' : 'numeric'}`;
1196
+ },
1197
+ predicate: (when) => {
1198
+ const sql = whereMod.buildWhereClause(qi, when, params);
1199
+ if (sql === null)
1200
+ throw new errors_js_1.ValidationError(`${label}: an expr.case() arm's when compiles to no predicate.`);
1201
+ return sql;
1202
+ },
1203
+ }, of);
1204
+ const meta = qi.schema.tables[qi.table];
1205
+ for (const col of compiled.values) {
1206
+ const field = qi.tableMeta.reverseColumnMap[col] ?? (0, schema_js_1.snakeToCamel)(col);
1207
+ assertAggregatePiiOptIn(qi.table, meta, field, col, `${operation} _percentile "${key}"`, includePii);
1208
+ }
1209
+ // The caller's key never names the alias, so it skips every name already
1210
+ // taken, a groupBy key included, rather than collide with one.
1211
+ let n = index;
1212
+ const taken = (alias) => aliases.has(alias) || columnAliases.has(alias) || resultKeyTaken?.(alias) === true;
1213
+ while (taken(`_percentile_of_${n}`))
1214
+ n++;
1215
+ const alias = `_percentile_of_${n}`;
1216
+ aliases.add(alias);
1217
+ claimResultKey?.(alias, `_percentile "${key}"`);
1218
+ params.push(Array.isArray(p) ? [...p] : p);
1219
+ const fractions = `${qi.p(params.length)}::float8${Array.isArray(p) ? '[]' : ''}`;
1220
+ return {
1221
+ expr: `percentile_${method}(${fractions}) WITHIN GROUP (ORDER BY (${compiled.sql})::float8) AS ${qi.q(alias)}`,
1222
+ alias,
1223
+ field: key,
1224
+ col: null,
1225
+ };
1226
+ }
1161
1227
  function assertFraction(value, label) {
1162
1228
  if (typeof value === 'number' && Number.isFinite(value) && value >= 0 && value <= 1)
1163
1229
  return;
@@ -1199,6 +1265,10 @@ function readPercentiles(qi, plans, row) {
1199
1265
  function percentileElement(qi, col, value) {
1200
1266
  if (value === null || value === undefined)
1201
1267
  return null;
1268
+ // An expression is ordered as float8, which the driver reads as a number
1269
+ // (a supplied pool's own parser may hand back text).
1270
+ if (col === null)
1271
+ return typeof value === 'string' ? Number(value) : value;
1202
1272
  if (typeof value === 'string' && isInt8Type(whereMod.getColumnPgType(qi, col))) {
1203
1273
  const n = Number(value);
1204
1274
  return Number.isSafeInteger(n) ? n : value;
@@ -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, AggregateResultOf, CountArgs, CreateArgs, CreateManyArgs, DefaultInputTypes, DeleteArgs, DeleteManyArgs, FieldResult, FindManyArgs, FindManyStreamArgs, FindUniqueArgs, GroupByArgs, GroupByResult, QueryResult, TableInputTypes, TypedWithClause, UpdateArgs, UpdateManyArgs, UpsertArgs, WithClause } from './types.js';
15
+ import type { AggregateArgs, AggregateResultOf, CountArgs, CreateArgs, CreateManyArgs, CreateManyResult, DefaultInputTypes, DeleteArgs, DeleteManyArgs, FieldPercentiles, FieldResult, FindManyArgs, FindManyStreamArgs, FindUniqueArgs, GroupByArgs, GroupByResult, PercentileKeysChecked, QueryResult, TableInputTypes, TypedWithClause, UpdateArgs, UpdateManyArgs, UpsertArgs, WithClause } from './types.js';
16
16
  /**
17
17
  * Marginal cost of keeping a to-one relation on the JOIN plan, per parent row.
18
18
  *
@@ -1180,7 +1180,12 @@ export declare class QueryInterface<T extends object, R extends object = {}, M e
1180
1180
  * writes.ts because relations.ts imports writes.ts.
1181
1181
  */
1182
1182
  private resolveWriteProjection;
1183
- buildCreateMany<S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined>(args: CreateManyArgs<T, M['createMany'], S, O>): DeferredQuery<FieldResult<T, S, O>[]>;
1183
+ buildCreateMany<S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined, C extends boolean | undefined = undefined>(args: CreateManyArgs<T, M['createMany'], S, O, C>): DeferredQuery<CreateManyResult<FieldResult<T, S, O>, C>>;
1184
+ /**
1185
+ * `createMany`'s projection: the caller's `select` / `omit`, or
1186
+ * {@link writesMod.COUNT_ONLY} for `returning: false`, which takes neither.
1187
+ */
1188
+ private createManyProjection;
1184
1189
  buildUpdate<S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined>(args: UpdateArgs<T, R, S, O, M['update']>): DeferredQuery<FieldResult<T, S, O>>;
1185
1190
  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>>;
1186
1191
  buildUpsert<S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined>(args: UpsertArgs<T, R, S, O, M['whereUnique'], M['createMany'], M['updateMany']>): DeferredQuery<FieldResult<T, S, O>>;
@@ -1416,7 +1421,7 @@ export declare class QueryInterface<T extends object, R extends object = {}, M e
1416
1421
  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, M['whereUnique']>): Promise<QueryResult<T, R, W, S, O>>;
1417
1422
  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, M['whereUnique']>): DeferredQuery<QueryResult<T, R, W, S, O>>;
1418
1423
  create<S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined>(args: CreateArgs<T, R, S, O, M['create']>): Promise<FieldResult<T, S, O>>;
1419
- createMany<S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined>(args: CreateManyArgs<T, M['createMany'], S, O>): Promise<FieldResult<T, S, O>[]>;
1424
+ createMany<S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined, C extends boolean | undefined = undefined>(args: CreateManyArgs<T, M['createMany'], S, O, C>): Promise<CreateManyResult<FieldResult<T, S, O>, C>>;
1420
1425
  update<S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined>(args: UpdateArgs<T, R, S, O, M['update']>): Promise<FieldResult<T, S, O>>;
1421
1426
  private nestedCreate;
1422
1427
  private nestedUpdate;
@@ -1477,10 +1482,18 @@ export declare class QueryInterface<T extends object, R extends object = {}, M e
1477
1482
  * {@link GroupByResult}). Grouping by a JSON-path key yields a runtime alias
1478
1483
  * that cannot be typed, so those columns are not projected onto the row type.
1479
1484
  */
1480
- groupBy<A extends GroupByArgs<T, R>>(args: A): Promise<GroupByResult<T, A>[]>;
1481
- buildGroupBy<A extends GroupByArgs<T, R>>(args: A): DeferredQuery<GroupByResult<T, A>[]>;
1482
- buildAggregate<A extends AggregateArgs<T, R>>(args: A): DeferredQuery<AggregateResultOf<T, A>>;
1483
- aggregate<A extends AggregateArgs<T, R>>(args: A): Promise<AggregateResultOf<T, A>>;
1485
+ groupBy<A extends GroupByArgs<T, R>>(args: A & PercentileKeysChecked<T, A>): Promise<GroupByResult<T, A>[]>;
1486
+ /** Args of a generic type: field percentiles only (see {@link FieldPercentiles}). */
1487
+ groupBy<A extends Omit<GroupByArgs<T, R>, '_percentile'> & FieldPercentiles<T>>(args: A): Promise<GroupByResult<T, A>[]>;
1488
+ buildGroupBy<A extends GroupByArgs<T, R>>(args: A & PercentileKeysChecked<T, A>): DeferredQuery<GroupByResult<T, A>[]>;
1489
+ /** Args of a generic type: field percentiles only (see {@link FieldPercentiles}). */
1490
+ buildGroupBy<A extends Omit<GroupByArgs<T, R>, '_percentile'> & FieldPercentiles<T>>(args: A): DeferredQuery<GroupByResult<T, A>[]>;
1491
+ buildAggregate<A extends AggregateArgs<T, R>>(args: A & PercentileKeysChecked<T, A>): DeferredQuery<AggregateResultOf<T, A>>;
1492
+ /** Args of a generic type: field percentiles only (see {@link FieldPercentiles}). */
1493
+ buildAggregate<A extends Omit<AggregateArgs<T, R>, '_percentile'> & FieldPercentiles<T>>(args: A): DeferredQuery<AggregateResultOf<T, A>>;
1494
+ aggregate<A extends AggregateArgs<T, R>>(args: A & PercentileKeysChecked<T, A>): Promise<AggregateResultOf<T, A>>;
1495
+ /** Args of a generic type: field percentiles only (see {@link FieldPercentiles}). */
1496
+ aggregate<A extends Omit<AggregateArgs<T, R>, '_percentile'> & FieldPercentiles<T>>(args: A): Promise<AggregateResultOf<T, A>>;
1484
1497
  private resolveColumns;
1485
1498
  withFingerprint(withClause: WithClause | undefined, table?: string, depth?: number): string;
1486
1499
  private collectWithParams;