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
@@ -45,6 +45,7 @@ var __importStar = (this && this.__importStar) || (function () {
45
45
  };
46
46
  })();
47
47
  Object.defineProperty(exports, "__esModule", { value: true });
48
+ exports.COUNT_ONLY = void 0;
48
49
  exports.coerceWriteValue = coerceWriteValue;
49
50
  exports.buildReselectByWhere = buildReselectByWhere;
50
51
  exports.buildCreate = buildCreate;
@@ -62,6 +63,7 @@ exports.buildUpdateMany = buildUpdateMany;
62
63
  exports.buildDeleteMany = buildDeleteMany;
63
64
  exports.piiColumns = piiColumns;
64
65
  exports.piiFields = piiFields;
66
+ exports.isCountOnly = isCountOnly;
65
67
  exports.writeProjectionCacheSegment = writeProjectionCacheSegment;
66
68
  exports.applyUpdatedAtColumns = applyUpdatedAtColumns;
67
69
  exports.writeReturningColumns = writeReturningColumns;
@@ -891,12 +893,17 @@ function buildCreateMany(qi, args, overBindLimit = 'refuse', projection) {
891
893
  * builder (`skipGlobalFilters`), which a concurrent call could change across
892
894
  * an await.
893
895
  */
894
- function planCreateMany(qi, args, projection) {
896
+ function planCreateMany(qi, args, projection, canSplit = true) {
895
897
  const prepared = prepareCreateMany(qi, args, projection);
896
898
  if ('deferred' in prepared)
897
899
  return { chunks: [prepared.deferred], rowsPerChunk: args.data.length };
898
900
  const rows = prepared.rowValues;
899
- const size = rowsPerStatement(qi, prepared);
901
+ const { most, chunk } = statementSizes(qi, prepared);
902
+ // Rows that cannot be told apart across statements (refused below past one),
903
+ // and a call whose executor cannot run several statements in one
904
+ // transaction (`canSplit`), take the fewest statements the engine allows;
905
+ // the rest are split at the size the engine runs fastest.
906
+ const size = !canSplit || (prepared.conflict && !prepared.identity) ? most : chunk;
900
907
  if (rows.length <= size) {
901
908
  return { chunks: [emitCreateMany(qi, prepared, rows, 'allow', true)], rowsPerChunk: size };
902
909
  }
@@ -924,12 +931,19 @@ function prepareCreateMany(qi, args, projection) {
924
931
  // The data-independent half of onConflict is checked before the empty-data
925
932
  // shortcut, so a call is never valid with no rows and invalid with some.
926
933
  const conflict = args.onConflict === undefined ? null : resolveConflictPlan(qi, args);
934
+ const countOnly = isCountOnly(projection);
935
+ // Before the empty-data shortcut too, so the refusal never depends on the rows.
936
+ if (countOnly && args.skipDuplicates && qi.dialect.capabilities.skipDuplicatesCountsSkipped) {
937
+ throw new errors_js_1.UnsupportedFeatureError('createMany({ skipDuplicates: true, returning: false })', qi.dialect.name, 'It counts a skipped row as written, so the count would include the rows skipped. Without ' +
938
+ 'returning: false the call writes the same rows and reports no count.');
939
+ }
927
940
  if (args.data.length === 0) {
928
941
  return {
929
942
  deferred: {
930
- sql: `SELECT * FROM ${qt} WHERE false`,
943
+ // `1 = 0`, not `false`: T-SQL has no boolean literal in a condition.
944
+ sql: `SELECT * FROM ${qt} WHERE 1 = 0`,
931
945
  params: [],
932
- transform: () => [],
946
+ transform: () => (countOnly ? { count: 0 } : []),
933
947
  tag: `${qi.table}.createMany`,
934
948
  },
935
949
  };
@@ -958,7 +972,9 @@ function prepareCreateMany(qi, args, projection) {
958
972
  deferred: {
959
973
  sql: buildDefaultValuesInsert(qi, args.data.length, args.skipDuplicates, projection),
960
974
  params: [],
961
- transform: (result) => result.rows.map((row) => parseWriteRow(qi, row, projection)),
975
+ transform: (result) => countOnly
976
+ ? countOf(result)
977
+ : result.rows.map((row) => parseWriteRow(qi, row, projection)),
962
978
  tag: `${qi.table}.createMany`,
963
979
  },
964
980
  };
@@ -1036,32 +1052,40 @@ function emitCreateMany(qi, prepared, rowValues, overBindLimit, first) {
1036
1052
  return {
1037
1053
  sql: built.sql,
1038
1054
  params: built.params,
1039
- transform: (result) => result.rows.map((row) => parseWriteRow(qi, row, projection)),
1055
+ transform: (result) => isCountOnly(projection)
1056
+ ? countOf(result)
1057
+ : result.rows.map((row) => parseWriteRow(qi, row, projection)),
1040
1058
  tag: `${qi.table}.createMany`,
1041
1059
  };
1042
1060
  }
1043
1061
  /**
1044
- * Rows one `createMany` statement may carry: the most that keep its bind
1045
- * parameters within the engine's limit and, on SQL Server, its rows within
1046
- * the 1000 a plain `INSERT ... VALUES` accepts. The parameter cost is measured
1047
- * from one-row and two-row builds, which separates the per-row parameters from
1048
- * the ones bound once per statement (an onConflict update's values); the
1049
- * PostgreSQL UNNEST form binds one array per column, so its rows never run out.
1062
+ * Rows one `createMany` statement may carry. `most`: the most that keep its
1063
+ * bind parameters within the engine's limit and, on SQL Server, its rows
1064
+ * within the 1000 a plain `INSERT ... VALUES` accepts. `chunk`: the same under
1065
+ * the engine's `chunkBindParameters`, the size a call that runs as several
1066
+ * statements is split at. The parameter cost is measured from one-row and
1067
+ * two-row builds, which separates the per-row parameters from the ones bound
1068
+ * once per statement (an onConflict update's values); the PostgreSQL UNNEST
1069
+ * form binds one array per column, so its rows never run out.
1050
1070
  */
1051
- function rowsPerStatement(qi, prepared) {
1071
+ function statementSizes(qi, prepared) {
1052
1072
  // An onConflict statement (SQL Server's MERGE) reads its rows as a derived
1053
1073
  // table, which the plain INSERT ... VALUES row cap does not bind.
1054
1074
  const cap = prepared.conflict ? Infinity : qi.dialect.capabilities.maxInsertRows;
1055
- const limit = (0, dialect_js_1.maxBindParameters)(qi.dialect);
1075
+ const limit = (0, dialect_js_1.maxBindParameters)(qi.dialect) ?? Infinity;
1076
+ // A dialect written before the key existed has none, and splits at its limit.
1077
+ const preferred = qi.dialect.capabilities.chunkBindParameters;
1078
+ const chunkLimit = typeof preferred === 'number' && preferred < limit ? preferred : limit;
1056
1079
  const rows = prepared.rowValues;
1057
- if (limit === undefined || rows.length < 2)
1058
- return cap;
1080
+ if (!Number.isFinite(chunkLimit) || rows.length < 2)
1081
+ return { most: cap, chunk: cap };
1059
1082
  const one = emitCreateMany(qi, prepared, rows.slice(0, 1), 'allow', true).params.length;
1060
1083
  const perRow = emitCreateMany(qi, prepared, rows.slice(0, 2), 'allow', true).params.length - one;
1061
1084
  if (perRow <= 0)
1062
- return cap;
1085
+ return { most: cap, chunk: cap };
1063
1086
  const fixed = Math.max(0, one - perRow);
1064
- return Math.max(1, Math.min(cap, Math.floor((limit - fixed) / perRow)));
1087
+ const fit = (bound) => Number.isFinite(bound) ? Math.max(1, Math.min(cap, Math.floor((bound - fixed) / perRow))) : cap;
1088
+ return { most: fit(limit), chunk: fit(chunkLimit) };
1065
1089
  }
1066
1090
  const ON_CONFLICT_KEYS = new Set(['target', 'update', 'where', 'ignore']);
1067
1091
  /**
@@ -1422,6 +1446,11 @@ function buildBulkUpsert(qi, conflict, insert, columns, overBindLimit, projectio
1422
1446
  transform: (result) => {
1423
1447
  if (identity)
1424
1448
  assertEachRowWrittenOnce(qi, conflict, identity, result.rows, first);
1449
+ // COUNT_ONLY: the rows back are the identity columns (or none at all when
1450
+ // the written rows cannot be identified), counted and never returned.
1451
+ if (isCountOnly(projection)) {
1452
+ return (identity ? { count: result.rows.length } : countOf(result));
1453
+ }
1425
1454
  const rows = result.rows.map((row) => parseWriteRow(qi, row, projection));
1426
1455
  if (identity && identity.added.length > 0) {
1427
1456
  for (const row of rows)
@@ -2052,6 +2081,21 @@ function piiFields(_qi, meta) {
2052
2081
  }
2053
2082
  return out;
2054
2083
  }
2084
+ /**
2085
+ * The projection of `createMany({ returning: false })`: no column. A plain
2086
+ * insert then sends no RETURNING / OUTPUT and reports its row count; an
2087
+ * onConflict call returns only its identity columns (see
2088
+ * {@link conflictIdentity}), which the count is taken from.
2089
+ */
2090
+ exports.COUNT_ONLY = Object.freeze({ columns: Object.freeze([]) });
2091
+ /** True for {@link COUNT_ONLY} (or any projection naming no column). */
2092
+ function isCountOnly(projection) {
2093
+ return projection !== undefined && projection.columns.length === 0;
2094
+ }
2095
+ /** `{ count }` from a write that returned no rows to count: the engine's row count. */
2096
+ function countOf(result) {
2097
+ return { count: typeof result.rowCount === 'number' ? result.rowCount : result.rows.length };
2098
+ }
2055
2099
  /**
2056
2100
  * The write-SQL cache segment for a projection. EMPTY for the default shape, so
2057
2101
  * every existing key is byte-identical; a projected statement differs only in
@@ -2147,6 +2191,9 @@ function writeReturningColumns(qi, projection) {
2147
2191
  */
2148
2192
  function writeReselectSelection(qi, projection) {
2149
2193
  const cols = writeReturningColumns(qi, projection);
2194
+ // COUNT_ONLY: the re-read is counted, not returned, so it reads no column.
2195
+ if (cols !== '*' && cols.length === 0)
2196
+ return '1';
2150
2197
  return cols === '*' ? '*' : cols.join(', ');
2151
2198
  }
2152
2199
  /**
@@ -913,12 +913,16 @@ exports.sqliteDialect = {
913
913
  // The default SQLITE_MAX_VARIABLE_NUMBER.
914
914
  maxBindParameters: 32766,
915
915
  maxInsertRows: Number.POSITIVE_INFINITY,
916
+ // Measured: 40,000 rows insert 70 to 140 times faster in statements of 250
917
+ // parameters than of 32,766 (named-parameter lookup is a linear search).
918
+ chunkBindParameters: 250,
916
919
  upsertConflictTarget: 'explicit',
917
920
  setClauseOrder: 'snapshot',
918
921
  reselectLocksRows: false,
919
922
  integerDivision: '/',
920
923
  updateExpressions: true,
921
924
  bulkUpsertAppliesRepeatedKeys: true,
925
+ skipDuplicatesCountsSkipped: false,
922
926
  readOnlyTransactions: false,
923
927
  connectionParameters: false,
924
928
  backendCancel: false,
@@ -1129,6 +1133,8 @@ exports.sqliteDialect = {
1129
1133
  (boolean ? 'Write true or false.' : 'Write a number, or a string holding one, such as "12.5".'));
1130
1134
  },
1131
1135
  buildReturningClause(selection = '*') {
1136
+ if (selection !== '*' && selection.length === 0)
1137
+ return '';
1132
1138
  return ` RETURNING ${selection === '*' ? '*' : selection.join(', ')}`;
1133
1139
  },
1134
1140
  buildInsertStatement(input) {
package/dist/dialect.d.ts CHANGED
@@ -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;
package/dist/dialect.js CHANGED
@@ -102,12 +102,14 @@ export const postgresDialect = {
102
102
  // Counted in a 16-bit field.
103
103
  maxBindParameters: 65535,
104
104
  maxInsertRows: Number.POSITIVE_INFINITY,
105
+ chunkBindParameters: 65535,
105
106
  upsertConflictTarget: 'explicit',
106
107
  setClauseOrder: 'snapshot',
107
108
  reselectLocksRows: false,
108
109
  integerDivision: '/',
109
110
  updateExpressions: true,
110
111
  bulkUpsertAppliesRepeatedKeys: false,
112
+ skipDuplicatesCountsSkipped: false,
111
113
  readOnlyTransactions: true,
112
114
  connectionParameters: true,
113
115
  backendCancel: true,
@@ -185,6 +187,8 @@ export const postgresDialect = {
185
187
  return `COALESCE((${subquery}), ${fallback})`;
186
188
  },
187
189
  buildReturningClause(selection = '*') {
190
+ if (selection !== '*' && selection.length === 0)
191
+ return '';
188
192
  return ` RETURNING ${selection === '*' ? '*' : selection.join(', ')}`;
189
193
  },
190
194
  buildInsertStatement(input) {
package/dist/index.d.ts CHANGED
@@ -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/mssql.js CHANGED
@@ -633,6 +633,7 @@ export const mssqlDialect = {
633
633
  // The limit the driver can actually send (see MSSQL_MAX_PARAMS), not 2100.
634
634
  maxBindParameters: MSSQL_MAX_PARAMS,
635
635
  maxInsertRows: MSSQL_MAX_INSERT_ROWS,
636
+ chunkBindParameters: MSSQL_MAX_PARAMS,
636
637
  upsertConflictTarget: 'explicit',
637
638
  setClauseOrder: 'snapshot',
638
639
  reselectLocksRows: false,
@@ -640,6 +641,7 @@ export const mssqlDialect = {
640
641
  updateExpressions: true,
641
642
  // MERGE refuses a target row matched twice (error 8672).
642
643
  bulkUpsertAppliesRepeatedKeys: false,
644
+ skipDuplicatesCountsSkipped: false,
643
645
  readOnlyTransactions: false,
644
646
  connectionParameters: false,
645
647
  backendCancel: false,
package/dist/mysql.js CHANGED
@@ -698,6 +698,7 @@ export const mysqlDialect = {
698
698
  // Counted in a 16-bit field.
699
699
  maxBindParameters: 65535,
700
700
  maxInsertRows: Number.POSITIVE_INFINITY,
701
+ chunkBindParameters: 65535,
701
702
  upsertConflictTarget: 'any-unique-key',
702
703
  setClauseOrder: 'left-to-right',
703
704
  reselectLocksRows: true,
@@ -705,6 +706,8 @@ export const mysqlDialect = {
705
706
  updateExpressions: true,
706
707
  // Caught by the onConflict re-read, which runs in the reselect transaction.
707
708
  bulkUpsertAppliesRepeatedKeys: true,
709
+ // A skipped row is a no-op ON DUPLICATE KEY UPDATE, counted under FOUND_ROWS.
710
+ skipDuplicatesCountsSkipped: true,
708
711
  readOnlyTransactions: true,
709
712
  connectionParameters: false,
710
713
  backendCancel: false,
package/dist/powdb.js CHANGED
@@ -116,12 +116,14 @@ export const powdbDialect = {
116
116
  structuredValueColumns: 'scalars',
117
117
  maxBindParameters: Number.POSITIVE_INFINITY,
118
118
  maxInsertRows: Number.POSITIVE_INFINITY,
119
+ chunkBindParameters: Number.POSITIVE_INFINITY,
119
120
  upsertConflictTarget: 'explicit',
120
121
  setClauseOrder: 'snapshot',
121
122
  reselectLocksRows: false,
122
123
  integerDivision: '/',
123
124
  updateExpressions: false,
124
125
  bulkUpsertAppliesRepeatedKeys: false,
126
+ skipDuplicatesCountsSkipped: false,
125
127
  readOnlyTransactions: false,
126
128
  connectionParameters: false,
127
129
  backendCancel: false,
package/dist/powql.d.ts CHANGED
@@ -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/powql.js CHANGED
@@ -3857,35 +3857,56 @@ export class PowqlInterface {
3857
3857
  async createMany(args) {
3858
3858
  args = this.normalizeArgs(args);
3859
3859
  return this.withMiddleware('createMany', args, async () => {
3860
- if (args.onConflict !== undefined) {
3861
- throw new UnsupportedFeatureError('createMany({ onConflict })', 'powdb', "PowQL's upsert statement takes one row. Call upsert() per row inside $transaction.");
3862
- }
3863
- // Validated as every engine validates it; it acts only on onConflict's
3864
- // update, which is refused above.
3865
- resolveSkipGlobalFilters(args.skipGlobalFilters);
3866
- if (args.skipDuplicates) {
3867
- // PowQL's `insert … returning` has no conflict clause, so there is no
3868
- // faithful skip-duplicates form; refuse rather than silently insert.
3869
- throw new UnsupportedFeatureError('createMany({ skipDuplicates: true })', 'powdb', 'PowQL insert has no ON CONFLICT DO NOTHING equivalent, filter duplicates before inserting.');
3870
- }
3871
- // Resolved before the write, so a bad projection writes nothing.
3872
- const plan = this.writeReturnPlan(args);
3873
- const supplied = args.data.map((d) => this.applyPkDefault(d));
3874
- if (!supplied.length)
3875
- return [];
3876
- // Uniformity is judged on what the caller supplied, as the SQL engines
3877
- // judge it; defaults are then filled per row.
3878
- this.assertUniformCreateManyRows(supplied);
3879
- const inputs = supplied.map((d) => this.applyColumnDefaults(d));
3880
- const params = [];
3881
- const tuples = inputs.map((d) => {
3882
- const assigns = this.scalarData(d);
3883
- return `{ ${assigns.map((a) => `${quotePowqlIdent(a.col.name)} := ${this.writeRef(a.value, a.col, params)}`).join(', ')} }`;
3884
- });
3885
- // Multi-row insert with `returning` hands back every inserted row in one round-trip.
3886
- const { rows, native } = await this.exec(`insert ${this.qt} ${tuples.join(', ')} returning`, params, args.timeout, 'createMany');
3887
- return this.shape(rows, native).map((r) => this.shapeWriteRow(r, plan));
3860
+ // `returning: false` is checked as the SQL engines check it. PowQL's
3861
+ // insert still returns the rows, which are counted and dropped.
3862
+ const countOnly = this.createManyCountOnly(args);
3863
+ const rows = await this.insertMany(args);
3864
+ return countOnly ? { count: rows.length } : rows;
3865
+ });
3866
+ }
3867
+ /** `returning: false` validated: a boolean, and not combined with a projection. */
3868
+ createManyCountOnly(args) {
3869
+ const { returning } = args;
3870
+ if (returning === undefined || returning === true)
3871
+ return false;
3872
+ if (returning !== false) {
3873
+ throw new ValidationError(`createMany on "${this.table}": returning must be true or false, received ${typeof returning}.`);
3874
+ }
3875
+ if (args.select !== undefined || args.omit !== undefined) {
3876
+ throw new ValidationError(`createMany on "${this.table}": returning: false returns { count } and no rows, so it takes no ` +
3877
+ `\`${args.select !== undefined ? 'select' : 'omit'}\`. Drop one of them.`);
3878
+ }
3879
+ return true;
3880
+ }
3881
+ async insertMany(args) {
3882
+ if (args.onConflict !== undefined) {
3883
+ throw new UnsupportedFeatureError('createMany({ onConflict })', 'powdb', "PowQL's upsert statement takes one row. Call upsert() per row inside $transaction.");
3884
+ }
3885
+ // Validated as every engine validates it; it acts only on onConflict's
3886
+ // update, which is refused above.
3887
+ resolveSkipGlobalFilters(args.skipGlobalFilters);
3888
+ if (args.skipDuplicates) {
3889
+ // PowQL's `insert … returning` has no conflict clause, so there is no
3890
+ // faithful skip-duplicates form; refuse rather than silently insert.
3891
+ throw new UnsupportedFeatureError('createMany({ skipDuplicates: true })', 'powdb', 'PowQL insert has no ON CONFLICT DO NOTHING equivalent, filter duplicates before inserting.');
3892
+ }
3893
+ // Resolved before the write, so a bad projection writes nothing.
3894
+ const plan = this.writeReturnPlan(args);
3895
+ const supplied = args.data.map((d) => this.applyPkDefault(d));
3896
+ if (!supplied.length)
3897
+ return [];
3898
+ // Uniformity is judged on what the caller supplied, as the SQL engines
3899
+ // judge it; defaults are then filled per row.
3900
+ this.assertUniformCreateManyRows(supplied);
3901
+ const inputs = supplied.map((d) => this.applyColumnDefaults(d));
3902
+ const params = [];
3903
+ const tuples = inputs.map((d) => {
3904
+ const assigns = this.scalarData(d);
3905
+ return `{ ${assigns.map((a) => `${quotePowqlIdent(a.col.name)} := ${this.writeRef(a.value, a.col, params)}`).join(', ')} }`;
3888
3906
  });
3907
+ // Multi-row insert with `returning` hands back every inserted row in one round-trip.
3908
+ const { rows, native } = await this.exec(`insert ${this.qt} ${tuples.join(', ')} returning`, params, args.timeout, 'createMany');
3909
+ return this.shape(rows, native).map((r) => this.shapeWriteRow(r, plan));
3889
3910
  }
3890
3911
  async update(args) {
3891
3912
  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;
@@ -1864,12 +1864,12 @@ function createManyRunsOf(t) {
1864
1864
  async function createManyByRun(qi, t, runs) {
1865
1865
  let count = 0;
1866
1866
  for (const run of runs) {
1867
- const args = { data: run };
1867
+ const args = { data: run, returning: false };
1868
1868
  if (t.skipDuplicates)
1869
1869
  args.skipDuplicates = true;
1870
1870
  if (t.timeout !== undefined)
1871
1871
  args.timeout = t.timeout;
1872
- count += (await qi.createMany(args)).length;
1872
+ count += (await qi.createMany(args)).count;
1873
1873
  }
1874
1874
  return { count };
1875
1875
  }
@@ -1965,7 +1965,10 @@ function makeDelegate(ctx, mm, getQI, runInTx) {
1965
1965
  `array of them, received ${describeValue(data)}.`);
1966
1966
  }
1967
1967
  const rows = list.map((d) => translateWriteData(ctx, mm, applyCreateDefaults(mm, d)));
1968
- const t = { data: rows };
1968
+ // Prisma's createMany resolves to { count }, which core counts
1969
+ // without returning the rows (a plain MySQL insert returns none, so
1970
+ // counting rows reported 0 there).
1971
+ const t = { data: rows, returning: false };
1969
1972
  applyNativeOptions(CREATE_MANY_OPTIONS, args, t);
1970
1973
  if (args.skipDuplicates)
1971
1974
  t.skipDuplicates = true;
@@ -1977,11 +1980,11 @@ function makeDelegate(ctx, mm, getQI, runInTx) {
1977
1980
  // stays all-or-nothing and the count is the total actually inserted.
1978
1981
  const runs = createManyRunsOf(t);
1979
1982
  if (runs.length <= 1)
1980
- return qi.createMany(t).then((r) => ({ count: r.length }));
1983
+ return qi.createMany(t);
1981
1984
  return runInTx((table) => createManyByRun(table(mm.table), t, runs));
1982
1985
  }, {
1983
1986
  build: (qi, t) => qi.buildCreateMany(t),
1984
- reshape: (raw) => ({ count: raw.length }),
1987
+ reshape: (raw) => raw,
1985
1988
  // A mixed batch is more than one statement, so the array
1986
1989
  // $transaction([...]) form runs the whole array sequentially in a tx.
1987
1990
  nested: (t) => createManyRunsOf(t).length > 1,