@prisma/client-engine-runtime 6.13.0 → 6.14.0-dev.10

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.
@@ -78,6 +78,7 @@ export type QueryPlanDbQuery = {
78
78
  fragments: Fragment[];
79
79
  placeholderFormat: PlaceholderFormat;
80
80
  params: PrismaValue[];
81
+ chunkable: boolean;
81
82
  };
82
83
  export type Fragment = {
83
84
  type: 'stringChunk';
package/dist/index.d.mts CHANGED
@@ -1,3 +1,4 @@
1
+ import { ConnectionInfo } from '@prisma/driver-adapter-utils';
1
2
  import { Context } from '@opentelemetry/api';
2
3
  import type { IsolationLevel } from '@prisma/driver-adapter-utils';
3
4
  import { Span } from '@opentelemetry/api';
@@ -218,13 +219,14 @@ export declare type QueryEvent = {
218
219
 
219
220
  export declare class QueryInterpreter {
220
221
  #private;
221
- constructor({ transactionManager, placeholderValues, onQuery, tracingHelper, serializer, rawSerializer, provider, }: QueryInterpreterOptions);
222
+ constructor({ transactionManager, placeholderValues, onQuery, tracingHelper, serializer, rawSerializer, provider, connectionInfo, }: QueryInterpreterOptions);
222
223
  static forSql(options: {
223
224
  transactionManager: QueryInterpreterTransactionManager;
224
225
  placeholderValues: Record<string, unknown>;
225
226
  onQuery?: (event: QueryEvent) => void;
226
227
  tracingHelper: TracingHelper;
227
228
  provider?: SchemaProvider;
229
+ connectionInfo?: ConnectionInfo;
228
230
  }): QueryInterpreter;
229
231
  run(queryPlan: QueryPlanNode, queryable: SqlQueryable): Promise<unknown>;
230
232
  private interpretNode;
@@ -238,6 +240,7 @@ export declare type QueryInterpreterOptions = {
238
240
  serializer: (results: SqlResultSet) => Value;
239
241
  rawSerializer?: (results: SqlResultSet) => Value;
240
242
  provider?: SchemaProvider;
243
+ connectionInfo?: ConnectionInfo;
241
244
  };
242
245
 
243
246
  export declare type QueryInterpreterTransactionManager = {
@@ -261,6 +264,7 @@ export declare type QueryPlanDbQuery = {
261
264
  fragments: Fragment[];
262
265
  placeholderFormat: PlaceholderFormat;
263
266
  params: PrismaValue[];
267
+ chunkable: boolean;
264
268
  };
265
269
 
266
270
  export declare type QueryPlanNode = {
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { ConnectionInfo } from '@prisma/driver-adapter-utils';
1
2
  import { Context } from '@opentelemetry/api';
2
3
  import type { IsolationLevel } from '@prisma/driver-adapter-utils';
3
4
  import { Span } from '@opentelemetry/api';
@@ -218,13 +219,14 @@ export declare type QueryEvent = {
218
219
 
219
220
  export declare class QueryInterpreter {
220
221
  #private;
221
- constructor({ transactionManager, placeholderValues, onQuery, tracingHelper, serializer, rawSerializer, provider, }: QueryInterpreterOptions);
222
+ constructor({ transactionManager, placeholderValues, onQuery, tracingHelper, serializer, rawSerializer, provider, connectionInfo, }: QueryInterpreterOptions);
222
223
  static forSql(options: {
223
224
  transactionManager: QueryInterpreterTransactionManager;
224
225
  placeholderValues: Record<string, unknown>;
225
226
  onQuery?: (event: QueryEvent) => void;
226
227
  tracingHelper: TracingHelper;
227
228
  provider?: SchemaProvider;
229
+ connectionInfo?: ConnectionInfo;
228
230
  }): QueryInterpreter;
229
231
  run(queryPlan: QueryPlanNode, queryable: SqlQueryable): Promise<unknown>;
230
232
  private interpretNode;
@@ -238,6 +240,7 @@ export declare type QueryInterpreterOptions = {
238
240
  serializer: (results: SqlResultSet) => Value;
239
241
  rawSerializer?: (results: SqlResultSet) => Value;
240
242
  provider?: SchemaProvider;
243
+ connectionInfo?: ConnectionInfo;
241
244
  };
242
245
 
243
246
  export declare type QueryInterpreterTransactionManager = {
@@ -261,6 +264,7 @@ export declare type QueryPlanDbQuery = {
261
264
  fragments: Fragment[];
262
265
  placeholderFormat: PlaceholderFormat;
263
266
  params: PrismaValue[];
267
+ chunkable: boolean;
264
268
  };
265
269
 
266
270
  export declare type QueryPlanNode = {
package/dist/index.js CHANGED
@@ -708,17 +708,21 @@ function isPrismaValueBigInt(value) {
708
708
  }
709
709
 
710
710
  // src/interpreter/renderQuery.ts
711
- function renderQuery(dbQuery, scope, generators) {
711
+ function renderQuery(dbQuery, scope, generators, maxChunkSize) {
712
712
  const queryType = dbQuery.type;
713
+ const params = evaluateParams(dbQuery.params, scope, generators);
713
714
  switch (queryType) {
714
715
  case "rawSql":
715
- return renderRawSql(dbQuery.sql, evaluateParams(dbQuery.params, scope, generators));
716
- case "templateSql":
717
- return renderTemplateSql(
718
- dbQuery.fragments,
719
- dbQuery.placeholderFormat,
720
- evaluateParams(dbQuery.params, scope, generators)
721
- );
716
+ return [renderRawSql(dbQuery.sql, evaluateParams(dbQuery.params, scope, generators))];
717
+ case "templateSql": {
718
+ const chunks = dbQuery.chunkable ? chunkParams(dbQuery.fragments, params, maxChunkSize) : [params];
719
+ return chunks.map((params2) => {
720
+ if (maxChunkSize !== void 0 && params2.length > maxChunkSize) {
721
+ throw new UserFacingError("The query parameter limit supported by your database is exceeded.", "P2029");
722
+ }
723
+ return renderTemplateSql(dbQuery.fragments, dbQuery.placeholderFormat, params2);
724
+ });
725
+ }
722
726
  default:
723
727
  assertNever(queryType, `Invalid query type`);
724
728
  }
@@ -756,61 +760,36 @@ function evaluateParam(param, scope, generators) {
756
760
  return value;
757
761
  }
758
762
  function renderTemplateSql(fragments, placeholderFormat, params) {
759
- let paramIndex = 0;
760
- let placeholderNumber = 1;
763
+ let sql = "";
764
+ const ctx = { placeholderNumber: 1 };
761
765
  const flattenedParams = [];
762
- const sql = fragments.map((fragment) => {
763
- const fragmentType = fragment.type;
764
- switch (fragmentType) {
765
- case "parameter":
766
- if (paramIndex >= params.length) {
767
- throw new Error(`Malformed query template. Fragments attempt to read over ${params.length} parameters.`);
768
- }
769
- flattenedParams.push(params[paramIndex++]);
770
- return formatPlaceholder(placeholderFormat, placeholderNumber++);
771
- case "stringChunk":
772
- return fragment.chunk;
773
- case "parameterTuple": {
774
- if (paramIndex >= params.length) {
775
- throw new Error(`Malformed query template. Fragments attempt to read over ${params.length} parameters.`);
776
- }
777
- const paramValue = params[paramIndex++];
778
- const paramArray = Array.isArray(paramValue) ? paramValue : [paramValue];
779
- const placeholders = paramArray.length == 0 ? "NULL" : paramArray.map((value) => {
780
- flattenedParams.push(value);
781
- return formatPlaceholder(placeholderFormat, placeholderNumber++);
782
- }).join(",");
783
- return `(${placeholders})`;
784
- }
785
- case "parameterTupleList": {
786
- if (paramIndex >= params.length) {
787
- throw new Error(`Malformed query template. Fragments attempt to read over ${params.length} parameters.`);
788
- }
789
- const paramValue = params[paramIndex++];
790
- if (!Array.isArray(paramValue)) {
791
- throw new Error(`Malformed query template. Tuple list expected.`);
792
- }
793
- if (paramValue.length === 0) {
794
- throw new Error(`Malformed query template. Tuple list cannot be empty.`);
795
- }
796
- const tupleList = paramValue.map((tuple) => {
797
- if (!Array.isArray(tuple)) {
798
- throw new Error(`Malformed query template. Tuple expected.`);
799
- }
800
- const elements = tuple.map((value) => {
801
- flattenedParams.push(value);
802
- return formatPlaceholder(placeholderFormat, placeholderNumber++);
803
- }).join(fragment.itemSeparator);
804
- return `${fragment.itemPrefix}${elements}${fragment.itemSuffix}`;
805
- }).join(fragment.groupSeparator);
806
- return tupleList;
807
- }
808
- default:
809
- assertNever(fragmentType, "Invalid fragment type");
810
- }
811
- }).join("");
766
+ for (const fragment of pairFragmentsWithParams(fragments, params)) {
767
+ flattenedParams.push(...flattenedFragmentParams(fragment));
768
+ sql += renderFragment(fragment, placeholderFormat, ctx);
769
+ }
812
770
  return renderRawSql(sql, flattenedParams);
813
771
  }
772
+ function renderFragment(fragment, placeholderFormat, ctx) {
773
+ const fragmentType = fragment.type;
774
+ switch (fragmentType) {
775
+ case "parameter":
776
+ return formatPlaceholder(placeholderFormat, ctx.placeholderNumber++);
777
+ case "stringChunk":
778
+ return fragment.chunk;
779
+ case "parameterTuple": {
780
+ const placeholders = fragment.value.length == 0 ? "NULL" : fragment.value.map(() => formatPlaceholder(placeholderFormat, ctx.placeholderNumber++)).join(",");
781
+ return `(${placeholders})`;
782
+ }
783
+ case "parameterTupleList": {
784
+ return fragment.value.map((tuple) => {
785
+ const elements = tuple.map(() => formatPlaceholder(placeholderFormat, ctx.placeholderNumber++)).join(fragment.itemSeparator);
786
+ return `${fragment.itemPrefix}${elements}${fragment.itemSuffix}`;
787
+ }).join(fragment.groupSeparator);
788
+ }
789
+ default:
790
+ assertNever(fragmentType, "Invalid fragment type");
791
+ }
792
+ }
814
793
  function formatPlaceholder(placeholderFormat, placeholderNumber) {
815
794
  return placeholderFormat.hasNumbering ? `${placeholderFormat.prefix}${placeholderNumber}` : placeholderFormat.prefix;
816
795
  }
@@ -843,6 +822,143 @@ function toArgType(value) {
843
822
  function doesRequireEvaluation(param) {
844
823
  return isPrismaValuePlaceholder(param) || isPrismaValueGenerator(param);
845
824
  }
825
+ function* pairFragmentsWithParams(fragments, params) {
826
+ let index = 0;
827
+ for (const fragment of fragments) {
828
+ switch (fragment.type) {
829
+ case "parameter": {
830
+ if (index >= params.length) {
831
+ throw new Error(`Malformed query template. Fragments attempt to read over ${params.length} parameters.`);
832
+ }
833
+ yield { ...fragment, value: params[index++] };
834
+ break;
835
+ }
836
+ case "stringChunk": {
837
+ yield fragment;
838
+ break;
839
+ }
840
+ case "parameterTuple": {
841
+ if (index >= params.length) {
842
+ throw new Error(`Malformed query template. Fragments attempt to read over ${params.length} parameters.`);
843
+ }
844
+ const value = params[index++];
845
+ yield { ...fragment, value: Array.isArray(value) ? value : [value] };
846
+ break;
847
+ }
848
+ case "parameterTupleList": {
849
+ if (index >= params.length) {
850
+ throw new Error(`Malformed query template. Fragments attempt to read over ${params.length} parameters.`);
851
+ }
852
+ const value = params[index++];
853
+ if (!Array.isArray(value)) {
854
+ throw new Error(`Malformed query template. Tuple list expected.`);
855
+ }
856
+ if (value.length === 0) {
857
+ throw new Error(`Malformed query template. Tuple list cannot be empty.`);
858
+ }
859
+ for (const tuple of value) {
860
+ if (!Array.isArray(tuple)) {
861
+ throw new Error(`Malformed query template. Tuple expected.`);
862
+ }
863
+ }
864
+ yield { ...fragment, value };
865
+ break;
866
+ }
867
+ }
868
+ }
869
+ }
870
+ function* flattenedFragmentParams(fragment) {
871
+ switch (fragment.type) {
872
+ case "parameter":
873
+ yield fragment.value;
874
+ break;
875
+ case "stringChunk":
876
+ break;
877
+ case "parameterTuple":
878
+ yield* fragment.value;
879
+ break;
880
+ case "parameterTupleList":
881
+ for (const tuple of fragment.value) {
882
+ yield* tuple;
883
+ }
884
+ break;
885
+ }
886
+ }
887
+ function chunkParams(fragments, params, maxChunkSize) {
888
+ let totalParamCount = 0;
889
+ let maxParamsPerFragment = 0;
890
+ for (const fragment of pairFragmentsWithParams(fragments, params)) {
891
+ let paramSize = 0;
892
+ for (const _ of flattenedFragmentParams(fragment)) {
893
+ void _;
894
+ paramSize++;
895
+ }
896
+ maxParamsPerFragment = Math.max(maxParamsPerFragment, paramSize);
897
+ totalParamCount += paramSize;
898
+ }
899
+ let chunkedParams = [[]];
900
+ for (const fragment of pairFragmentsWithParams(fragments, params)) {
901
+ switch (fragment.type) {
902
+ case "parameter": {
903
+ for (const params2 of chunkedParams) {
904
+ params2.push(fragment.value);
905
+ }
906
+ break;
907
+ }
908
+ case "stringChunk": {
909
+ break;
910
+ }
911
+ case "parameterTuple": {
912
+ const thisParamCount = fragment.value.length;
913
+ let chunks = [];
914
+ if (maxChunkSize && // Have we split the parameters into chunks already?
915
+ chunkedParams.length === 1 && // Is this the fragment that has the most parameters?
916
+ thisParamCount === maxParamsPerFragment && // Do we need chunking to fit the parameters?
917
+ totalParamCount > maxChunkSize && // Would chunking enable us to fit the parameters?
918
+ totalParamCount - thisParamCount < maxChunkSize) {
919
+ const availableSize = maxChunkSize - (totalParamCount - thisParamCount);
920
+ chunks = chunkArray(fragment.value, availableSize);
921
+ } else {
922
+ chunks = [fragment.value];
923
+ }
924
+ chunkedParams = chunkedParams.flatMap((params2) => chunks.map((chunk) => [...params2, chunk]));
925
+ break;
926
+ }
927
+ case "parameterTupleList": {
928
+ const thisParamCount = fragment.value.reduce((acc, tuple) => acc + tuple.length, 0);
929
+ const completeChunks = [];
930
+ let currentChunk = [];
931
+ let currentChunkParamCount = 0;
932
+ for (const tuple of fragment.value) {
933
+ if (maxChunkSize && // Have we split the parameters into chunks already?
934
+ chunkedParams.length === 1 && // Is this the fragment that has the most parameters?
935
+ thisParamCount === maxParamsPerFragment && // Is there anything in the current chunk?
936
+ currentChunk.length > 0 && // Will adding this tuple exceed the max chunk size?
937
+ totalParamCount - thisParamCount + currentChunkParamCount + tuple.length > maxChunkSize) {
938
+ completeChunks.push(currentChunk);
939
+ currentChunk = [];
940
+ currentChunkParamCount = 0;
941
+ }
942
+ currentChunk.push(tuple);
943
+ currentChunkParamCount += tuple.length;
944
+ }
945
+ if (currentChunk.length > 0) {
946
+ completeChunks.push(currentChunk);
947
+ }
948
+ chunkedParams = chunkedParams.flatMap((params2) => completeChunks.map((chunk) => [...params2, chunk]));
949
+ break;
950
+ }
951
+ }
952
+ }
953
+ return chunkedParams;
954
+ }
955
+ function chunkArray(array, chunkSize) {
956
+ const result = [];
957
+ for (let i = 0; i < array.length; i += chunkSize) {
958
+ result.push(array.slice(i, i + chunkSize));
959
+ }
960
+ return result;
961
+ }
846
962
 
847
963
  // src/interpreter/serializeSql.ts
848
964
  var import_driver_adapter_utils2 = require("@prisma/driver-adapter-utils");
@@ -1052,6 +1168,7 @@ var QueryInterpreter = class _QueryInterpreter {
1052
1168
  #serializer;
1053
1169
  #rawSerializer;
1054
1170
  #provider;
1171
+ #connectioInfo;
1055
1172
  constructor({
1056
1173
  transactionManager,
1057
1174
  placeholderValues,
@@ -1059,7 +1176,8 @@ var QueryInterpreter = class _QueryInterpreter {
1059
1176
  tracingHelper,
1060
1177
  serializer,
1061
1178
  rawSerializer,
1062
- provider
1179
+ provider,
1180
+ connectionInfo
1063
1181
  }) {
1064
1182
  this.#transactionManager = transactionManager;
1065
1183
  this.#placeholderValues = placeholderValues;
@@ -1068,6 +1186,7 @@ var QueryInterpreter = class _QueryInterpreter {
1068
1186
  this.#serializer = serializer;
1069
1187
  this.#rawSerializer = rawSerializer ?? serializer;
1070
1188
  this.#provider = provider;
1189
+ this.#connectioInfo = connectionInfo;
1071
1190
  }
1072
1191
  static forSql(options) {
1073
1192
  return new _QueryInterpreter({
@@ -1077,7 +1196,8 @@ var QueryInterpreter = class _QueryInterpreter {
1077
1196
  tracingHelper: options.tracingHelper,
1078
1197
  serializer: serializeSql,
1079
1198
  rawSerializer: serializeRawSql,
1080
- provider: options.provider
1199
+ provider: options.provider,
1200
+ connectionInfo: options.connectionInfo
1081
1201
  });
1082
1202
  }
1083
1203
  async run(queryPlan, queryable) {
@@ -1138,21 +1258,29 @@ var QueryInterpreter = class _QueryInterpreter {
1138
1258
  };
1139
1259
  }
1140
1260
  case "execute": {
1141
- const query = renderQuery(node.args, scope, generators);
1142
- return this.#withQuerySpanAndEvent(query, queryable, async () => {
1143
- return { value: await queryable.executeRaw(query) };
1144
- });
1261
+ const queries = renderQuery(node.args, scope, generators, this.#maxChunkSize());
1262
+ let sum = 0;
1263
+ for (const query of queries) {
1264
+ sum += await this.#withQuerySpanAndEvent(query, queryable, () => queryable.executeRaw(query));
1265
+ }
1266
+ return { value: sum };
1145
1267
  }
1146
1268
  case "query": {
1147
- const query = renderQuery(node.args, scope, generators);
1148
- return this.#withQuerySpanAndEvent(query, queryable, async () => {
1149
- const result = await queryable.queryRaw(query);
1150
- if (node.args.type === "rawSql") {
1151
- return { value: this.#rawSerializer(result), lastInsertId: result.lastInsertId };
1269
+ const queries = renderQuery(node.args, scope, generators, this.#maxChunkSize());
1270
+ let results;
1271
+ for (const query of queries) {
1272
+ const result = await this.#withQuerySpanAndEvent(query, queryable, () => queryable.queryRaw(query));
1273
+ if (results === void 0) {
1274
+ results = result;
1152
1275
  } else {
1153
- return { value: this.#serializer(result), lastInsertId: result.lastInsertId };
1276
+ results.rows.push(...result.rows);
1277
+ results.lastInsertId = result.lastInsertId;
1154
1278
  }
1155
- });
1279
+ }
1280
+ return {
1281
+ value: node.args.type === "rawSql" ? this.#rawSerializer(results) : this.#serializer(results),
1282
+ lastInsertId: results?.lastInsertId
1283
+ };
1156
1284
  }
1157
1285
  case "reverse": {
1158
1286
  const { value, lastInsertId } = await this.interpretNode(node.args, queryable, scope, generators);
@@ -1289,6 +1417,34 @@ var QueryInterpreter = class _QueryInterpreter {
1289
1417
  assertNever(node, `Unexpected node type: ${node.type}`);
1290
1418
  }
1291
1419
  }
1420
+ #maxChunkSize() {
1421
+ if (this.#connectioInfo?.maxBindValues !== void 0) {
1422
+ return this.#connectioInfo.maxBindValues;
1423
+ }
1424
+ return this.#providerMaxChunkSize();
1425
+ }
1426
+ #providerMaxChunkSize() {
1427
+ if (this.#provider === void 0) {
1428
+ return void 0;
1429
+ }
1430
+ switch (this.#provider) {
1431
+ case "cockroachdb":
1432
+ case "postgres":
1433
+ case "postgresql":
1434
+ case "prisma+postgres":
1435
+ return 32766;
1436
+ case "mysql":
1437
+ return 65535;
1438
+ case "sqlite":
1439
+ return 999;
1440
+ case "sqlserver":
1441
+ return 2098;
1442
+ case "mongodb":
1443
+ return void 0;
1444
+ default:
1445
+ assertNever(this.#provider, `Unexpected provider: ${this.#provider}`);
1446
+ }
1447
+ }
1292
1448
  #withQuerySpanAndEvent(query, queryable, execute) {
1293
1449
  return withQuerySpanAndEvent({
1294
1450
  query,
package/dist/index.mjs CHANGED
@@ -659,17 +659,21 @@ function isPrismaValueBigInt(value) {
659
659
  }
660
660
 
661
661
  // src/interpreter/renderQuery.ts
662
- function renderQuery(dbQuery, scope, generators) {
662
+ function renderQuery(dbQuery, scope, generators, maxChunkSize) {
663
663
  const queryType = dbQuery.type;
664
+ const params = evaluateParams(dbQuery.params, scope, generators);
664
665
  switch (queryType) {
665
666
  case "rawSql":
666
- return renderRawSql(dbQuery.sql, evaluateParams(dbQuery.params, scope, generators));
667
- case "templateSql":
668
- return renderTemplateSql(
669
- dbQuery.fragments,
670
- dbQuery.placeholderFormat,
671
- evaluateParams(dbQuery.params, scope, generators)
672
- );
667
+ return [renderRawSql(dbQuery.sql, evaluateParams(dbQuery.params, scope, generators))];
668
+ case "templateSql": {
669
+ const chunks = dbQuery.chunkable ? chunkParams(dbQuery.fragments, params, maxChunkSize) : [params];
670
+ return chunks.map((params2) => {
671
+ if (maxChunkSize !== void 0 && params2.length > maxChunkSize) {
672
+ throw new UserFacingError("The query parameter limit supported by your database is exceeded.", "P2029");
673
+ }
674
+ return renderTemplateSql(dbQuery.fragments, dbQuery.placeholderFormat, params2);
675
+ });
676
+ }
673
677
  default:
674
678
  assertNever(queryType, `Invalid query type`);
675
679
  }
@@ -707,61 +711,36 @@ function evaluateParam(param, scope, generators) {
707
711
  return value;
708
712
  }
709
713
  function renderTemplateSql(fragments, placeholderFormat, params) {
710
- let paramIndex = 0;
711
- let placeholderNumber = 1;
714
+ let sql = "";
715
+ const ctx = { placeholderNumber: 1 };
712
716
  const flattenedParams = [];
713
- const sql = fragments.map((fragment) => {
714
- const fragmentType = fragment.type;
715
- switch (fragmentType) {
716
- case "parameter":
717
- if (paramIndex >= params.length) {
718
- throw new Error(`Malformed query template. Fragments attempt to read over ${params.length} parameters.`);
719
- }
720
- flattenedParams.push(params[paramIndex++]);
721
- return formatPlaceholder(placeholderFormat, placeholderNumber++);
722
- case "stringChunk":
723
- return fragment.chunk;
724
- case "parameterTuple": {
725
- if (paramIndex >= params.length) {
726
- throw new Error(`Malformed query template. Fragments attempt to read over ${params.length} parameters.`);
727
- }
728
- const paramValue = params[paramIndex++];
729
- const paramArray = Array.isArray(paramValue) ? paramValue : [paramValue];
730
- const placeholders = paramArray.length == 0 ? "NULL" : paramArray.map((value) => {
731
- flattenedParams.push(value);
732
- return formatPlaceholder(placeholderFormat, placeholderNumber++);
733
- }).join(",");
734
- return `(${placeholders})`;
735
- }
736
- case "parameterTupleList": {
737
- if (paramIndex >= params.length) {
738
- throw new Error(`Malformed query template. Fragments attempt to read over ${params.length} parameters.`);
739
- }
740
- const paramValue = params[paramIndex++];
741
- if (!Array.isArray(paramValue)) {
742
- throw new Error(`Malformed query template. Tuple list expected.`);
743
- }
744
- if (paramValue.length === 0) {
745
- throw new Error(`Malformed query template. Tuple list cannot be empty.`);
746
- }
747
- const tupleList = paramValue.map((tuple) => {
748
- if (!Array.isArray(tuple)) {
749
- throw new Error(`Malformed query template. Tuple expected.`);
750
- }
751
- const elements = tuple.map((value) => {
752
- flattenedParams.push(value);
753
- return formatPlaceholder(placeholderFormat, placeholderNumber++);
754
- }).join(fragment.itemSeparator);
755
- return `${fragment.itemPrefix}${elements}${fragment.itemSuffix}`;
756
- }).join(fragment.groupSeparator);
757
- return tupleList;
758
- }
759
- default:
760
- assertNever(fragmentType, "Invalid fragment type");
761
- }
762
- }).join("");
717
+ for (const fragment of pairFragmentsWithParams(fragments, params)) {
718
+ flattenedParams.push(...flattenedFragmentParams(fragment));
719
+ sql += renderFragment(fragment, placeholderFormat, ctx);
720
+ }
763
721
  return renderRawSql(sql, flattenedParams);
764
722
  }
723
+ function renderFragment(fragment, placeholderFormat, ctx) {
724
+ const fragmentType = fragment.type;
725
+ switch (fragmentType) {
726
+ case "parameter":
727
+ return formatPlaceholder(placeholderFormat, ctx.placeholderNumber++);
728
+ case "stringChunk":
729
+ return fragment.chunk;
730
+ case "parameterTuple": {
731
+ const placeholders = fragment.value.length == 0 ? "NULL" : fragment.value.map(() => formatPlaceholder(placeholderFormat, ctx.placeholderNumber++)).join(",");
732
+ return `(${placeholders})`;
733
+ }
734
+ case "parameterTupleList": {
735
+ return fragment.value.map((tuple) => {
736
+ const elements = tuple.map(() => formatPlaceholder(placeholderFormat, ctx.placeholderNumber++)).join(fragment.itemSeparator);
737
+ return `${fragment.itemPrefix}${elements}${fragment.itemSuffix}`;
738
+ }).join(fragment.groupSeparator);
739
+ }
740
+ default:
741
+ assertNever(fragmentType, "Invalid fragment type");
742
+ }
743
+ }
765
744
  function formatPlaceholder(placeholderFormat, placeholderNumber) {
766
745
  return placeholderFormat.hasNumbering ? `${placeholderFormat.prefix}${placeholderNumber}` : placeholderFormat.prefix;
767
746
  }
@@ -794,6 +773,143 @@ function toArgType(value) {
794
773
  function doesRequireEvaluation(param) {
795
774
  return isPrismaValuePlaceholder(param) || isPrismaValueGenerator(param);
796
775
  }
776
+ function* pairFragmentsWithParams(fragments, params) {
777
+ let index = 0;
778
+ for (const fragment of fragments) {
779
+ switch (fragment.type) {
780
+ case "parameter": {
781
+ if (index >= params.length) {
782
+ throw new Error(`Malformed query template. Fragments attempt to read over ${params.length} parameters.`);
783
+ }
784
+ yield { ...fragment, value: params[index++] };
785
+ break;
786
+ }
787
+ case "stringChunk": {
788
+ yield fragment;
789
+ break;
790
+ }
791
+ case "parameterTuple": {
792
+ if (index >= params.length) {
793
+ throw new Error(`Malformed query template. Fragments attempt to read over ${params.length} parameters.`);
794
+ }
795
+ const value = params[index++];
796
+ yield { ...fragment, value: Array.isArray(value) ? value : [value] };
797
+ break;
798
+ }
799
+ case "parameterTupleList": {
800
+ if (index >= params.length) {
801
+ throw new Error(`Malformed query template. Fragments attempt to read over ${params.length} parameters.`);
802
+ }
803
+ const value = params[index++];
804
+ if (!Array.isArray(value)) {
805
+ throw new Error(`Malformed query template. Tuple list expected.`);
806
+ }
807
+ if (value.length === 0) {
808
+ throw new Error(`Malformed query template. Tuple list cannot be empty.`);
809
+ }
810
+ for (const tuple of value) {
811
+ if (!Array.isArray(tuple)) {
812
+ throw new Error(`Malformed query template. Tuple expected.`);
813
+ }
814
+ }
815
+ yield { ...fragment, value };
816
+ break;
817
+ }
818
+ }
819
+ }
820
+ }
821
+ function* flattenedFragmentParams(fragment) {
822
+ switch (fragment.type) {
823
+ case "parameter":
824
+ yield fragment.value;
825
+ break;
826
+ case "stringChunk":
827
+ break;
828
+ case "parameterTuple":
829
+ yield* fragment.value;
830
+ break;
831
+ case "parameterTupleList":
832
+ for (const tuple of fragment.value) {
833
+ yield* tuple;
834
+ }
835
+ break;
836
+ }
837
+ }
838
+ function chunkParams(fragments, params, maxChunkSize) {
839
+ let totalParamCount = 0;
840
+ let maxParamsPerFragment = 0;
841
+ for (const fragment of pairFragmentsWithParams(fragments, params)) {
842
+ let paramSize = 0;
843
+ for (const _ of flattenedFragmentParams(fragment)) {
844
+ void _;
845
+ paramSize++;
846
+ }
847
+ maxParamsPerFragment = Math.max(maxParamsPerFragment, paramSize);
848
+ totalParamCount += paramSize;
849
+ }
850
+ let chunkedParams = [[]];
851
+ for (const fragment of pairFragmentsWithParams(fragments, params)) {
852
+ switch (fragment.type) {
853
+ case "parameter": {
854
+ for (const params2 of chunkedParams) {
855
+ params2.push(fragment.value);
856
+ }
857
+ break;
858
+ }
859
+ case "stringChunk": {
860
+ break;
861
+ }
862
+ case "parameterTuple": {
863
+ const thisParamCount = fragment.value.length;
864
+ let chunks = [];
865
+ if (maxChunkSize && // Have we split the parameters into chunks already?
866
+ chunkedParams.length === 1 && // Is this the fragment that has the most parameters?
867
+ thisParamCount === maxParamsPerFragment && // Do we need chunking to fit the parameters?
868
+ totalParamCount > maxChunkSize && // Would chunking enable us to fit the parameters?
869
+ totalParamCount - thisParamCount < maxChunkSize) {
870
+ const availableSize = maxChunkSize - (totalParamCount - thisParamCount);
871
+ chunks = chunkArray(fragment.value, availableSize);
872
+ } else {
873
+ chunks = [fragment.value];
874
+ }
875
+ chunkedParams = chunkedParams.flatMap((params2) => chunks.map((chunk) => [...params2, chunk]));
876
+ break;
877
+ }
878
+ case "parameterTupleList": {
879
+ const thisParamCount = fragment.value.reduce((acc, tuple) => acc + tuple.length, 0);
880
+ const completeChunks = [];
881
+ let currentChunk = [];
882
+ let currentChunkParamCount = 0;
883
+ for (const tuple of fragment.value) {
884
+ if (maxChunkSize && // Have we split the parameters into chunks already?
885
+ chunkedParams.length === 1 && // Is this the fragment that has the most parameters?
886
+ thisParamCount === maxParamsPerFragment && // Is there anything in the current chunk?
887
+ currentChunk.length > 0 && // Will adding this tuple exceed the max chunk size?
888
+ totalParamCount - thisParamCount + currentChunkParamCount + tuple.length > maxChunkSize) {
889
+ completeChunks.push(currentChunk);
890
+ currentChunk = [];
891
+ currentChunkParamCount = 0;
892
+ }
893
+ currentChunk.push(tuple);
894
+ currentChunkParamCount += tuple.length;
895
+ }
896
+ if (currentChunk.length > 0) {
897
+ completeChunks.push(currentChunk);
898
+ }
899
+ chunkedParams = chunkedParams.flatMap((params2) => completeChunks.map((chunk) => [...params2, chunk]));
900
+ break;
901
+ }
902
+ }
903
+ }
904
+ return chunkedParams;
905
+ }
906
+ function chunkArray(array, chunkSize) {
907
+ const result = [];
908
+ for (let i = 0; i < array.length; i += chunkSize) {
909
+ result.push(array.slice(i, i + chunkSize));
910
+ }
911
+ return result;
912
+ }
797
913
 
798
914
  // src/interpreter/serializeSql.ts
799
915
  import { ColumnTypeEnum } from "@prisma/driver-adapter-utils";
@@ -1003,6 +1119,7 @@ var QueryInterpreter = class _QueryInterpreter {
1003
1119
  #serializer;
1004
1120
  #rawSerializer;
1005
1121
  #provider;
1122
+ #connectioInfo;
1006
1123
  constructor({
1007
1124
  transactionManager,
1008
1125
  placeholderValues,
@@ -1010,7 +1127,8 @@ var QueryInterpreter = class _QueryInterpreter {
1010
1127
  tracingHelper,
1011
1128
  serializer,
1012
1129
  rawSerializer,
1013
- provider
1130
+ provider,
1131
+ connectionInfo
1014
1132
  }) {
1015
1133
  this.#transactionManager = transactionManager;
1016
1134
  this.#placeholderValues = placeholderValues;
@@ -1019,6 +1137,7 @@ var QueryInterpreter = class _QueryInterpreter {
1019
1137
  this.#serializer = serializer;
1020
1138
  this.#rawSerializer = rawSerializer ?? serializer;
1021
1139
  this.#provider = provider;
1140
+ this.#connectioInfo = connectionInfo;
1022
1141
  }
1023
1142
  static forSql(options) {
1024
1143
  return new _QueryInterpreter({
@@ -1028,7 +1147,8 @@ var QueryInterpreter = class _QueryInterpreter {
1028
1147
  tracingHelper: options.tracingHelper,
1029
1148
  serializer: serializeSql,
1030
1149
  rawSerializer: serializeRawSql,
1031
- provider: options.provider
1150
+ provider: options.provider,
1151
+ connectionInfo: options.connectionInfo
1032
1152
  });
1033
1153
  }
1034
1154
  async run(queryPlan, queryable) {
@@ -1089,21 +1209,29 @@ var QueryInterpreter = class _QueryInterpreter {
1089
1209
  };
1090
1210
  }
1091
1211
  case "execute": {
1092
- const query = renderQuery(node.args, scope, generators);
1093
- return this.#withQuerySpanAndEvent(query, queryable, async () => {
1094
- return { value: await queryable.executeRaw(query) };
1095
- });
1212
+ const queries = renderQuery(node.args, scope, generators, this.#maxChunkSize());
1213
+ let sum = 0;
1214
+ for (const query of queries) {
1215
+ sum += await this.#withQuerySpanAndEvent(query, queryable, () => queryable.executeRaw(query));
1216
+ }
1217
+ return { value: sum };
1096
1218
  }
1097
1219
  case "query": {
1098
- const query = renderQuery(node.args, scope, generators);
1099
- return this.#withQuerySpanAndEvent(query, queryable, async () => {
1100
- const result = await queryable.queryRaw(query);
1101
- if (node.args.type === "rawSql") {
1102
- return { value: this.#rawSerializer(result), lastInsertId: result.lastInsertId };
1220
+ const queries = renderQuery(node.args, scope, generators, this.#maxChunkSize());
1221
+ let results;
1222
+ for (const query of queries) {
1223
+ const result = await this.#withQuerySpanAndEvent(query, queryable, () => queryable.queryRaw(query));
1224
+ if (results === void 0) {
1225
+ results = result;
1103
1226
  } else {
1104
- return { value: this.#serializer(result), lastInsertId: result.lastInsertId };
1227
+ results.rows.push(...result.rows);
1228
+ results.lastInsertId = result.lastInsertId;
1105
1229
  }
1106
- });
1230
+ }
1231
+ return {
1232
+ value: node.args.type === "rawSql" ? this.#rawSerializer(results) : this.#serializer(results),
1233
+ lastInsertId: results?.lastInsertId
1234
+ };
1107
1235
  }
1108
1236
  case "reverse": {
1109
1237
  const { value, lastInsertId } = await this.interpretNode(node.args, queryable, scope, generators);
@@ -1240,6 +1368,34 @@ var QueryInterpreter = class _QueryInterpreter {
1240
1368
  assertNever(node, `Unexpected node type: ${node.type}`);
1241
1369
  }
1242
1370
  }
1371
+ #maxChunkSize() {
1372
+ if (this.#connectioInfo?.maxBindValues !== void 0) {
1373
+ return this.#connectioInfo.maxBindValues;
1374
+ }
1375
+ return this.#providerMaxChunkSize();
1376
+ }
1377
+ #providerMaxChunkSize() {
1378
+ if (this.#provider === void 0) {
1379
+ return void 0;
1380
+ }
1381
+ switch (this.#provider) {
1382
+ case "cockroachdb":
1383
+ case "postgres":
1384
+ case "postgresql":
1385
+ case "prisma+postgres":
1386
+ return 32766;
1387
+ case "mysql":
1388
+ return 65535;
1389
+ case "sqlite":
1390
+ return 999;
1391
+ case "sqlserver":
1392
+ return 2098;
1393
+ case "mongodb":
1394
+ return void 0;
1395
+ default:
1396
+ assertNever(this.#provider, `Unexpected provider: ${this.#provider}`);
1397
+ }
1398
+ }
1243
1399
  #withQuerySpanAndEvent(query, queryable, execute) {
1244
1400
  return withQuerySpanAndEvent({
1245
1401
  query,
@@ -1,4 +1,4 @@
1
- import { SqlQueryable, SqlResultSet } from '@prisma/driver-adapter-utils';
1
+ import { ConnectionInfo, SqlQueryable, SqlResultSet } from '@prisma/driver-adapter-utils';
2
2
  import { QueryEvent } from '../events';
3
3
  import { QueryPlanNode } from '../QueryPlan';
4
4
  import { type SchemaProvider } from '../schema';
@@ -19,16 +19,18 @@ export type QueryInterpreterOptions = {
19
19
  serializer: (results: SqlResultSet) => Value;
20
20
  rawSerializer?: (results: SqlResultSet) => Value;
21
21
  provider?: SchemaProvider;
22
+ connectionInfo?: ConnectionInfo;
22
23
  };
23
24
  export declare class QueryInterpreter {
24
25
  #private;
25
- constructor({ transactionManager, placeholderValues, onQuery, tracingHelper, serializer, rawSerializer, provider, }: QueryInterpreterOptions);
26
+ constructor({ transactionManager, placeholderValues, onQuery, tracingHelper, serializer, rawSerializer, provider, connectionInfo, }: QueryInterpreterOptions);
26
27
  static forSql(options: {
27
28
  transactionManager: QueryInterpreterTransactionManager;
28
29
  placeholderValues: Record<string, unknown>;
29
30
  onQuery?: (event: QueryEvent) => void;
30
31
  tracingHelper: TracingHelper;
31
32
  provider?: SchemaProvider;
33
+ connectionInfo?: ConnectionInfo;
32
34
  }): QueryInterpreter;
33
35
  run(queryPlan: QueryPlanNode, queryable: SqlQueryable): Promise<unknown>;
34
36
  private interpretNode;
@@ -2,5 +2,5 @@ import { SqlQuery } from '@prisma/driver-adapter-utils';
2
2
  import type { PrismaValue, QueryPlanDbQuery } from '../QueryPlan';
3
3
  import { GeneratorRegistrySnapshot } from './generators';
4
4
  import { ScopeBindings } from './scope';
5
- export declare function renderQuery(dbQuery: QueryPlanDbQuery, scope: ScopeBindings, generators: GeneratorRegistrySnapshot): SqlQuery;
5
+ export declare function renderQuery(dbQuery: QueryPlanDbQuery, scope: ScopeBindings, generators: GeneratorRegistrySnapshot, maxChunkSize?: number): SqlQuery[];
6
6
  export declare function evaluateParam(param: PrismaValue, scope: ScopeBindings, generators: GeneratorRegistrySnapshot): unknown;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prisma/client-engine-runtime",
3
- "version": "6.13.0",
3
+ "version": "6.14.0-dev.10",
4
4
  "description": "This package is intended for Prisma's internal use",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -31,8 +31,8 @@
31
31
  "nanoid": "5.1.5",
32
32
  "ulid": "3.0.0",
33
33
  "uuid": "11.1.0",
34
- "@prisma/debug": "6.13.0",
35
- "@prisma/driver-adapter-utils": "6.13.0"
34
+ "@prisma/debug": "6.14.0-dev.10",
35
+ "@prisma/driver-adapter-utils": "6.14.0-dev.10"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/jest": "29.5.14",