prisma-sql 1.58.0 → 1.59.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/generator.js CHANGED
@@ -54,7 +54,7 @@ var require_package = __commonJS({
54
54
  "package.json"(exports$1, module) {
55
55
  module.exports = {
56
56
  name: "prisma-sql",
57
- version: "1.58.0",
57
+ version: "1.59.0",
58
58
  description: "Convert Prisma queries to optimized SQL with type safety. 2-7x faster than Prisma Client.",
59
59
  main: "dist/index.cjs",
60
60
  module: "dist/index.js",
@@ -295,7 +295,8 @@ var REGEX_CACHE = {
295
295
  var LIMITS = Object.freeze({
296
296
  MAX_QUERY_DEPTH: 50,
297
297
  MAX_ARRAY_SIZE: 1e4,
298
- MAX_STRING_LENGTH: 1e4
298
+ MAX_STRING_LENGTH: 1e4,
299
+ MAX_HAVING_DEPTH: 50
299
300
  });
300
301
 
301
302
  // src/utils/normalize-value.ts
@@ -592,73 +593,6 @@ function createError(message, ctx, code = "VALIDATION_ERROR") {
592
593
  return new SqlBuilderError(parts.join("\n"), code, ctx);
593
594
  }
594
595
 
595
- // src/builder/shared/model-field-cache.ts
596
- var MODEL_CACHE = /* @__PURE__ */ new WeakMap();
597
- function quote(id) {
598
- const needsQuoting2 = !/^[a-z_][a-z0-9_]*$/.test(id) || /^(select|from|where|having|order|group|limit|offset|join|inner|left|right|outer|cross|full|and|or|not|by|as|on|union|intersect|except|case|when|then|else|end|user|users|table|column|index|values|in|like|between|is|exists|null|true|false|all|any|some|update|insert|delete|create|drop|alter|truncate|grant|revoke|exec|execute)$/i.test(
599
- id
600
- );
601
- if (needsQuoting2) {
602
- return `"${id.replace(/"/g, '""')}"`;
603
- }
604
- return id;
605
- }
606
- function ensureFullCache(model) {
607
- let cache = MODEL_CACHE.get(model);
608
- if (!cache) {
609
- const fieldInfo = /* @__PURE__ */ new Map();
610
- const scalarFields = /* @__PURE__ */ new Set();
611
- const relationFields = /* @__PURE__ */ new Set();
612
- const columnMap = /* @__PURE__ */ new Map();
613
- const fieldByName = /* @__PURE__ */ new Map();
614
- const quotedColumns = /* @__PURE__ */ new Map();
615
- for (const f of model.fields) {
616
- const info = {
617
- name: f.name,
618
- dbName: f.dbName || f.name,
619
- type: f.type,
620
- isRelation: !!f.isRelation,
621
- isRequired: !!f.isRequired
622
- };
623
- fieldInfo.set(f.name, info);
624
- fieldByName.set(f.name, f);
625
- if (info.isRelation) {
626
- relationFields.add(f.name);
627
- } else {
628
- scalarFields.add(f.name);
629
- const dbName = info.dbName;
630
- columnMap.set(f.name, dbName);
631
- quotedColumns.set(f.name, quote(dbName));
632
- }
633
- }
634
- cache = {
635
- fieldInfo,
636
- scalarFields,
637
- relationFields,
638
- columnMap,
639
- fieldByName,
640
- quotedColumns
641
- };
642
- MODEL_CACHE.set(model, cache);
643
- }
644
- return cache;
645
- }
646
- function getFieldInfo(model, fieldName) {
647
- return ensureFullCache(model).fieldInfo.get(fieldName);
648
- }
649
- function getScalarFieldSet(model) {
650
- return ensureFullCache(model).scalarFields;
651
- }
652
- function getRelationFieldSet(model) {
653
- return ensureFullCache(model).relationFields;
654
- }
655
- function getColumnMap(model) {
656
- return ensureFullCache(model).columnMap;
657
- }
658
- function getQuotedColumn(model, fieldName) {
659
- return ensureFullCache(model).quotedColumns.get(fieldName);
660
- }
661
-
662
596
  // src/builder/shared/validators/sql-validators.ts
663
597
  function isValidWhereClause(clause) {
664
598
  return isNotNullish(clause) && clause.trim().length > 0 && clause !== DEFAULT_WHERE_CLAUSE;
@@ -802,6 +736,78 @@ function needsQuoting(identifier) {
802
736
  return false;
803
737
  }
804
738
 
739
+ // src/builder/shared/model-field-cache.ts
740
+ var MODEL_CACHE = /* @__PURE__ */ new WeakMap();
741
+ function quoteIdent(id) {
742
+ if (typeof id !== "string" || id.trim().length === 0) {
743
+ throw new Error("quoteIdent: identifier is required and cannot be empty");
744
+ }
745
+ if (/[\u0000-\u001F\u007F]/.test(id)) {
746
+ throw new Error(
747
+ `quoteIdent: identifier contains invalid characters: ${JSON.stringify(id)}`
748
+ );
749
+ }
750
+ if (needsQuoting(id)) {
751
+ return `"${id.replace(/"/g, '""')}"`;
752
+ }
753
+ return id;
754
+ }
755
+ function ensureFullCache(model) {
756
+ let cache = MODEL_CACHE.get(model);
757
+ if (!cache) {
758
+ const fieldInfo = /* @__PURE__ */ new Map();
759
+ const scalarFields = /* @__PURE__ */ new Set();
760
+ const relationFields = /* @__PURE__ */ new Set();
761
+ const columnMap = /* @__PURE__ */ new Map();
762
+ const fieldByName = /* @__PURE__ */ new Map();
763
+ const quotedColumns = /* @__PURE__ */ new Map();
764
+ for (const f of model.fields) {
765
+ const info = {
766
+ name: f.name,
767
+ dbName: f.dbName || f.name,
768
+ type: f.type,
769
+ isRelation: !!f.isRelation,
770
+ isRequired: !!f.isRequired
771
+ };
772
+ fieldInfo.set(f.name, info);
773
+ fieldByName.set(f.name, f);
774
+ if (info.isRelation) {
775
+ relationFields.add(f.name);
776
+ } else {
777
+ scalarFields.add(f.name);
778
+ const dbName = info.dbName;
779
+ columnMap.set(f.name, dbName);
780
+ quotedColumns.set(f.name, quoteIdent(dbName));
781
+ }
782
+ }
783
+ cache = {
784
+ fieldInfo,
785
+ scalarFields,
786
+ relationFields,
787
+ columnMap,
788
+ fieldByName,
789
+ quotedColumns
790
+ };
791
+ MODEL_CACHE.set(model, cache);
792
+ }
793
+ return cache;
794
+ }
795
+ function getFieldInfo(model, fieldName) {
796
+ return ensureFullCache(model).fieldInfo.get(fieldName);
797
+ }
798
+ function getScalarFieldSet(model) {
799
+ return ensureFullCache(model).scalarFields;
800
+ }
801
+ function getRelationFieldSet(model) {
802
+ return ensureFullCache(model).relationFields;
803
+ }
804
+ function getColumnMap(model) {
805
+ return ensureFullCache(model).columnMap;
806
+ }
807
+ function getQuotedColumn(model, fieldName) {
808
+ return ensureFullCache(model).quotedColumns.get(fieldName);
809
+ }
810
+
805
811
  // src/builder/shared/sql-utils.ts
806
812
  function containsControlChars(s) {
807
813
  return /[\u0000-\u001F\u007F]/.test(s);
@@ -944,7 +950,7 @@ function assertSafeQualifiedName(input) {
944
950
  }
945
951
  }
946
952
  }
947
- function quote2(id) {
953
+ function quote(id) {
948
954
  if (isEmptyString(id)) {
949
955
  throw new Error("quote: identifier is required and cannot be empty");
950
956
  }
@@ -964,13 +970,13 @@ function resolveColumnName(model, fieldName) {
964
970
  return columnMap.get(fieldName) || fieldName;
965
971
  }
966
972
  function quoteColumn(model, fieldName) {
967
- if (!model) return quote2(fieldName);
973
+ if (!model) return quote(fieldName);
968
974
  const cached = getQuotedColumn(model, fieldName);
969
- return cached || quote2(fieldName);
975
+ return cached || quote(fieldName);
970
976
  }
971
977
  function col(alias, field, model) {
972
978
  const columnName = model ? getColumnMap(model).get(field) || field : field;
973
- const quoted = model ? getQuotedColumn(model, field) || quote2(columnName) : quote2(columnName);
979
+ const quoted = model ? getQuotedColumn(model, field) || quote(columnName) : quote(columnName);
974
980
  return alias + "." + quoted;
975
981
  }
976
982
  function colWithAlias(alias, field, model) {
@@ -981,9 +987,9 @@ function colWithAlias(alias, field, model) {
981
987
  throw new Error("colWithAlias: field is required and cannot be empty");
982
988
  }
983
989
  const columnName = resolveColumnName(model, field);
984
- const columnRef = alias + "." + (model ? getQuotedColumn(model, field) || quote2(columnName) : quote2(columnName));
990
+ const columnRef = alias + "." + (model ? getQuotedColumn(model, field) || quote(columnName) : quote(columnName));
985
991
  if (columnName !== field) {
986
- return columnRef + " AS " + quote2(field);
992
+ return columnRef + " AS " + quote(field);
987
993
  }
988
994
  return columnRef;
989
995
  }
@@ -1006,7 +1012,7 @@ function buildTableReference(schemaName, tableName, dialect) {
1006
1012
  }
1007
1013
  const d = dialect != null ? dialect : "postgres";
1008
1014
  if (d === "sqlite") {
1009
- return quote2(tableName);
1015
+ return quote(tableName);
1010
1016
  }
1011
1017
  if (isEmptyString(schemaName)) {
1012
1018
  throw new Error(
@@ -1671,6 +1677,7 @@ function buildOrderEntries(orderBy) {
1671
1677
  }
1672
1678
  return entries;
1673
1679
  }
1680
+ var MAX_NOT_DEPTH = 50;
1674
1681
  function buildNotComposite(expr, val, params, dialect, buildOp, separator) {
1675
1682
  const entries = Object.entries(val).filter(
1676
1683
  ([k, v]) => k !== "mode" && v !== void 0
@@ -1685,13 +1692,18 @@ function buildNotComposite(expr, val, params, dialect, buildOp, separator) {
1685
1692
  if (clauses.length === 1) return `${SQL_TEMPLATES.NOT} ${clauses[0]}`;
1686
1693
  return `${SQL_TEMPLATES.NOT} (${clauses.join(separator)})`;
1687
1694
  }
1688
- function buildScalarOperator(expr, op, val, params, mode, fieldType, dialect) {
1695
+ function buildScalarOperator(expr, op, val, params, mode, fieldType, dialect, depth = 0) {
1689
1696
  if (val === void 0) return "";
1697
+ if (depth > MAX_NOT_DEPTH) {
1698
+ throw new Error(
1699
+ `NOT operator nesting too deep (max ${MAX_NOT_DEPTH} levels). This usually indicates a circular reference or adversarial input.`
1700
+ );
1701
+ }
1690
1702
  if (val === null) {
1691
1703
  return handleNullValue(expr, op);
1692
1704
  }
1693
1705
  if (op === Ops.NOT && isPlainObject(val)) {
1694
- return handleNotOperator(expr, val, params, mode, fieldType, dialect);
1706
+ return handleNotOperator(expr, val, params, mode, fieldType, dialect, depth);
1695
1707
  }
1696
1708
  if (op === Ops.NOT) {
1697
1709
  const placeholder = params.addAuto(val);
@@ -1737,7 +1749,7 @@ function normalizeMode(v) {
1737
1749
  if (v === Modes.DEFAULT) return Modes.DEFAULT;
1738
1750
  return void 0;
1739
1751
  }
1740
- function handleNotOperator(expr, val, params, outerMode, fieldType, dialect) {
1752
+ function handleNotOperator(expr, val, params, outerMode, fieldType, dialect, depth = 0) {
1741
1753
  const innerMode = normalizeMode(val.mode);
1742
1754
  const effectiveMode = innerMode != null ? innerMode : outerMode;
1743
1755
  const entries = Object.entries(val).filter(
@@ -1754,7 +1766,8 @@ function handleNotOperator(expr, val, params, outerMode, fieldType, dialect) {
1754
1766
  params,
1755
1767
  effectiveMode,
1756
1768
  fieldType,
1757
- void 0
1769
+ void 0,
1770
+ depth + 1
1758
1771
  );
1759
1772
  if (sub && sub.trim().length > 0) clauses.push(`(${sub})`);
1760
1773
  }
@@ -1767,23 +1780,20 @@ function handleNotOperator(expr, val, params, outerMode, fieldType, dialect) {
1767
1780
  val,
1768
1781
  params,
1769
1782
  dialect,
1770
- (e, subOp, subVal, p, d) => buildScalarOperator(e, subOp, subVal, p, effectiveMode, fieldType, d),
1783
+ (e, subOp, subVal, p, d) => buildScalarOperator(
1784
+ e,
1785
+ subOp,
1786
+ subVal,
1787
+ p,
1788
+ effectiveMode,
1789
+ fieldType,
1790
+ d,
1791
+ depth + 1
1792
+ ),
1771
1793
  ` ${SQL_TEMPLATES.AND} `
1772
1794
  );
1773
1795
  }
1774
- function buildDynamicLikePattern(op, placeholder, dialect) {
1775
- if (dialect === "postgres") {
1776
- switch (op) {
1777
- case Ops.CONTAINS:
1778
- return `('%' || ${placeholder} || '%')`;
1779
- case Ops.STARTS_WITH:
1780
- return `(${placeholder} || '%')`;
1781
- case Ops.ENDS_WITH:
1782
- return `('%' || ${placeholder})`;
1783
- default:
1784
- return placeholder;
1785
- }
1786
- }
1796
+ function buildDynamicLikePattern(op, placeholder) {
1787
1797
  switch (op) {
1788
1798
  case Ops.CONTAINS:
1789
1799
  return `('%' || ${placeholder} || '%')`;
@@ -1799,7 +1809,7 @@ function handleLikeOperator(expr, op, val, params, mode, dialect) {
1799
1809
  if (val === void 0) return "";
1800
1810
  if (isDynamicParameter(val)) {
1801
1811
  const placeholder2 = params.addAuto(val);
1802
- const patternExpr = buildDynamicLikePattern(op, placeholder2, dialect);
1812
+ const patternExpr = buildDynamicLikePattern(op, placeholder2);
1803
1813
  if (mode === Modes.INSENSITIVE) {
1804
1814
  return caseInsensitiveLike(expr, patternExpr, dialect);
1805
1815
  }
@@ -2085,7 +2095,7 @@ function freezeJoins(items) {
2085
2095
  function isListRelation(fieldType) {
2086
2096
  return typeof fieldType === "string" && fieldType.endsWith("[]");
2087
2097
  }
2088
- function buildToOneNullCheck(field, parentAlias, relTable, relAlias, join3, wantNull) {
2098
+ function buildToOneNullCheck(field, parentModel, parentAlias, relTable, relAlias, join3, wantNull) {
2089
2099
  const isLocal = field.isForeignKeyLocal === true;
2090
2100
  const fkFields = normalizeKeyList(field.foreignKey);
2091
2101
  if (isLocal) {
@@ -2095,8 +2105,7 @@ function buildToOneNullCheck(field, parentAlias, relTable, relAlias, join3, want
2095
2105
  });
2096
2106
  }
2097
2107
  const parts = fkFields.map((fk) => {
2098
- const safe = fk.replace(/"/g, '""');
2099
- const expr = `${parentAlias}."${safe}"`;
2108
+ const expr = `${parentAlias}.${quoteColumn(parentModel, fk)}`;
2100
2109
  return wantNull ? `${expr} ${SQL_TEMPLATES.IS_NULL}` : `${expr} ${SQL_TEMPLATES.IS_NOT_NULL}`;
2101
2110
  });
2102
2111
  if (parts.length === 1) return parts[0];
@@ -2141,7 +2150,7 @@ function buildListRelationFilters(args) {
2141
2150
  ) || relModel.fields.find((f) => !f.isRelation && f.name === "id");
2142
2151
  if (checkField) {
2143
2152
  const leftJoinSql = `LEFT JOIN ${relTable} ${relAlias} ON ${join3}`;
2144
- const whereClause = `${relAlias}.${quote2(checkField.name)} IS NULL`;
2153
+ const whereClause = `${relAlias}.${quote(checkField.name)} IS NULL`;
2145
2154
  return Object.freeze({
2146
2155
  clause: whereClause,
2147
2156
  joins: freezeJoins([leftJoinSql])
@@ -2234,6 +2243,7 @@ function buildToOneRelationFilters(args) {
2234
2243
  const wantNull = filterKey === "is";
2235
2244
  const clause2 = buildToOneNullCheck(
2236
2245
  field,
2246
+ ctx.model,
2237
2247
  ctx.alias,
2238
2248
  relTable,
2239
2249
  relAlias,
@@ -2463,7 +2473,8 @@ function buildWhereInternal(where, ctx, builder) {
2463
2473
  }
2464
2474
  const allJoins = [];
2465
2475
  const clauses = [];
2466
- for (const [key, value] of Object.entries(where)) {
2476
+ for (const key in where) {
2477
+ const value = where[key];
2467
2478
  if (value === void 0) continue;
2468
2479
  const result = buildWhereEntry(key, value, ctx, builder);
2469
2480
  appendResult(result, clauses, allJoins);
@@ -2713,8 +2724,6 @@ function createStoreInternal(startIndex, initialParams = [], initialMappings = [
2713
2724
  }
2714
2725
  let dirty = true;
2715
2726
  let cachedSnapshot = null;
2716
- let frozenParams = null;
2717
- let frozenMappings = null;
2718
2727
  function assertCanAdd() {
2719
2728
  if (index > MAX_PARAM_INDEX) {
2720
2729
  throw new Error(
@@ -2766,17 +2775,13 @@ function createStoreInternal(startIndex, initialParams = [], initialMappings = [
2766
2775
  }
2767
2776
  function snapshot() {
2768
2777
  if (!dirty && cachedSnapshot) return cachedSnapshot;
2769
- if (!frozenParams) frozenParams = Object.freeze(params.slice());
2770
- if (!frozenMappings) frozenMappings = Object.freeze(mappings.slice());
2771
2778
  const snap = {
2772
2779
  index,
2773
- params: frozenParams,
2774
- mappings: frozenMappings
2780
+ params: params.slice(),
2781
+ mappings: mappings.slice()
2775
2782
  };
2776
2783
  cachedSnapshot = snap;
2777
2784
  dirty = false;
2778
- frozenParams = null;
2779
- frozenMappings = null;
2780
2785
  return snap;
2781
2786
  }
2782
2787
  return {
@@ -3706,7 +3711,7 @@ function buildDistinctColumns(distinct, fromAlias, model) {
3706
3711
  }
3707
3712
  function buildOutputColumns(scalarNames, includeNames, hasCount) {
3708
3713
  const outputCols = hasCount ? [...scalarNames, ...includeNames, "_count"] : [...scalarNames, ...includeNames];
3709
- const formatted = outputCols.map((n) => quote2(n)).join(SQL_SEPARATORS.FIELD_LIST);
3714
+ const formatted = outputCols.map((n) => quote(n)).join(SQL_SEPARATORS.FIELD_LIST);
3710
3715
  if (!isNonEmptyString(formatted)) {
3711
3716
  throw new Error("distinct emulation requires at least one output column");
3712
3717
  }
@@ -3802,7 +3807,7 @@ function buildIncludeColumns(spec) {
3802
3807
  dialect
3803
3808
  );
3804
3809
  if (countBuild.jsonPairs) {
3805
- countCols = jsonBuildObject(countBuild.jsonPairs, dialect) + " " + SQL_TEMPLATES.AS + " " + quote2("_count");
3810
+ countCols = jsonBuildObject(countBuild.jsonPairs, dialect) + " " + SQL_TEMPLATES.AS + " " + quote("_count");
3806
3811
  }
3807
3812
  countJoins = countBuild.joins;
3808
3813
  }
@@ -3815,7 +3820,7 @@ function buildIncludeColumns(spec) {
3815
3820
  const emptyJson = dialect === "postgres" ? `'[]'::json` : `json('[]')`;
3816
3821
  const includeCols = hasIncludes ? includes.map((inc) => {
3817
3822
  const expr = inc.isOneToOne ? "(" + inc.sql + ")" : "COALESCE((" + inc.sql + "), " + emptyJson + ")";
3818
- return expr + " " + SQL_TEMPLATES.AS + " " + quote2(inc.name);
3823
+ return expr + " " + SQL_TEMPLATES.AS + " " + quote(inc.name);
3819
3824
  }).join(SQL_SEPARATORS.FIELD_LIST) : "";
3820
3825
  const allCols = joinNonEmpty(
3821
3826
  [includeCols, countCols],
@@ -4180,6 +4185,7 @@ function buildComparisons(expr, filter, params, dialect, builder, excludeKeys =
4180
4185
  }
4181
4186
 
4182
4187
  // src/builder/aggregates.ts
4188
+ var MAX_NOT_DEPTH2 = 50;
4183
4189
  var AGGREGATES = [
4184
4190
  ["_sum", "SUM"],
4185
4191
  ["_avg", "AVG"],
@@ -4294,8 +4300,13 @@ function buildBinaryComparison(expr, op, val, params) {
4294
4300
  const placeholder = addHavingParam(params, op, val);
4295
4301
  return expr + " " + sqlOp + " " + placeholder;
4296
4302
  }
4297
- function buildSimpleComparison(expr, op, val, params, dialect) {
4303
+ function buildSimpleComparison(expr, op, val, params, dialect, depth = 0) {
4298
4304
  assertHavingOp(op);
4305
+ if (depth > MAX_NOT_DEPTH2) {
4306
+ throw new Error(
4307
+ `NOT operator nesting too deep in HAVING (max ${MAX_NOT_DEPTH2} levels).`
4308
+ );
4309
+ }
4299
4310
  if (val === null) return buildNullComparison(expr, op);
4300
4311
  if (op === Ops.NOT && isPlainObject(val)) {
4301
4312
  return buildNotComposite(
@@ -4303,7 +4314,7 @@ function buildSimpleComparison(expr, op, val, params, dialect) {
4303
4314
  val,
4304
4315
  params,
4305
4316
  dialect,
4306
- buildSimpleComparison,
4317
+ (e, subOp, subVal, p, d) => buildSimpleComparison(e, subOp, subVal, p, d, depth + 1),
4307
4318
  SQL_SEPARATORS.CONDITION_AND
4308
4319
  );
4309
4320
  }
@@ -4320,23 +4331,36 @@ function combineLogical(key, subClauses) {
4320
4331
  if (key === LogicalOps.NOT) return negateClauses(subClauses);
4321
4332
  return subClauses.join(" " + key + " ");
4322
4333
  }
4323
- function buildHavingNode(node, alias, params, dialect, model) {
4334
+ function buildHavingNode(node, alias, params, dialect, model, depth = 0) {
4335
+ if (depth > LIMITS.MAX_HAVING_DEPTH) {
4336
+ throw new Error(
4337
+ `HAVING clause nesting too deep (max ${LIMITS.MAX_HAVING_DEPTH} levels). This usually indicates a circular reference.`
4338
+ );
4339
+ }
4324
4340
  const clauses = [];
4325
4341
  for (const key in node) {
4326
4342
  if (!Object.prototype.hasOwnProperty.call(node, key)) continue;
4327
4343
  const value = node[key];
4328
- const built = buildHavingEntry(key, value, alias, params, dialect, model);
4344
+ const built = buildHavingEntry(
4345
+ key,
4346
+ value,
4347
+ alias,
4348
+ params,
4349
+ dialect,
4350
+ model,
4351
+ depth
4352
+ );
4329
4353
  for (const c of built) {
4330
4354
  if (c && c.length > 0) clauses.push(c);
4331
4355
  }
4332
4356
  }
4333
4357
  return clauses.join(SQL_SEPARATORS.CONDITION_AND);
4334
4358
  }
4335
- function buildLogicalClause2(key, value, alias, params, dialect, model) {
4359
+ function buildLogicalClause2(key, value, alias, params, dialect, model, depth = 0) {
4336
4360
  const items = normalizeLogicalValue2(key, value);
4337
4361
  const subClauses = [];
4338
4362
  for (const it of items) {
4339
- const c = buildHavingNode(it, alias, params, dialect, model);
4363
+ const c = buildHavingNode(it, alias, params, dialect, model, depth + 1);
4340
4364
  if (c && c.length > 0) subClauses.push("(" + c + ")");
4341
4365
  }
4342
4366
  if (subClauses.length === 0) return "";
@@ -4395,7 +4419,7 @@ function buildHavingForFieldFirstShape(fieldName, target, alias, params, dialect
4395
4419
  }
4396
4420
  return out;
4397
4421
  }
4398
- function buildHavingEntry(key, value, alias, params, dialect, model) {
4422
+ function buildHavingEntry(key, value, alias, params, dialect, model, depth = 0) {
4399
4423
  if (isLogicalKey(key)) {
4400
4424
  const logical = buildLogicalClause2(
4401
4425
  key,
@@ -4403,7 +4427,8 @@ function buildHavingEntry(key, value, alias, params, dialect, model) {
4403
4427
  alias,
4404
4428
  params,
4405
4429
  dialect,
4406
- model
4430
+ model,
4431
+ depth
4407
4432
  );
4408
4433
  return logical ? [logical] : [];
4409
4434
  }
@@ -4430,7 +4455,7 @@ function buildHavingClause(having, alias, params, model, dialect) {
4430
4455
  if (!isNotNullish(having)) return "";
4431
4456
  const d = dialect != null ? dialect : getGlobalDialect();
4432
4457
  if (!isPlainObject(having)) throw new Error("having must be an object");
4433
- return buildHavingNode(having, alias, params, d, model);
4458
+ return buildHavingNode(having, alias, params, d, model, 0);
4434
4459
  }
4435
4460
  function normalizeCountArg(v) {
4436
4461
  if (!isNotNullish(v)) return void 0;
@@ -4440,13 +4465,13 @@ function normalizeCountArg(v) {
4440
4465
  }
4441
4466
  function pushCountAllField(fields) {
4442
4467
  fields.push(
4443
- SQL_TEMPLATES.COUNT_ALL + " " + SQL_TEMPLATES.AS + " " + quote2("_count._all")
4468
+ SQL_TEMPLATES.COUNT_ALL + " " + SQL_TEMPLATES.AS + " " + quote("_count._all")
4444
4469
  );
4445
4470
  }
4446
4471
  function pushCountField(fields, alias, fieldName, model) {
4447
4472
  const outAlias = "_count." + fieldName;
4448
4473
  fields.push(
4449
- "COUNT(" + col(alias, fieldName, model) + ") " + SQL_TEMPLATES.AS + " " + quote2(outAlias)
4474
+ "COUNT(" + col(alias, fieldName, model) + ") " + SQL_TEMPLATES.AS + " " + quote(outAlias)
4450
4475
  );
4451
4476
  }
4452
4477
  function addCountFields(fields, countArg, alias, model) {
@@ -4483,7 +4508,7 @@ function assertAggregatableScalarField(model, agg, fieldName) {
4483
4508
  function pushAggregateFieldSql(fields, aggFn, alias, agg, fieldName, model) {
4484
4509
  const outAlias = agg + "." + fieldName;
4485
4510
  fields.push(
4486
- aggFn + "(" + col(alias, fieldName, model) + ") " + SQL_TEMPLATES.AS + " " + quote2(outAlias)
4511
+ aggFn + "(" + col(alias, fieldName, model) + ") " + SQL_TEMPLATES.AS + " " + quote(outAlias)
4487
4512
  );
4488
4513
  }
4489
4514
  function addAggregateFields(fields, args, alias, model) {
@@ -4567,7 +4592,7 @@ function buildGroupBySql(args, whereResult, tableName, alias, model, dialect) {
4567
4592
  assertSafeAlias(alias);
4568
4593
  assertSafeTableRef(tableName);
4569
4594
  const byFields = assertGroupByBy(args, model);
4570
- const d = getGlobalDialect();
4595
+ const d = dialect != null ? dialect : getGlobalDialect();
4571
4596
  const params = createParamStore(whereResult.nextParamIndex);
4572
4597
  const { groupFields, selectFields } = buildGroupBySelectParts(
4573
4598
  args,
@@ -4630,7 +4655,7 @@ function buildCountSql(whereResult, tableName, alias, skip, _dialect) {
4630
4655
  SQL_TEMPLATES.SELECT,
4631
4656
  SQL_TEMPLATES.COUNT_ALL,
4632
4657
  SQL_TEMPLATES.AS,
4633
- quote2("_count._all"),
4658
+ quote("_count._all"),
4634
4659
  SQL_TEMPLATES.FROM,
4635
4660
  tableName,
4636
4661
  alias
@@ -4671,15 +4696,21 @@ function buildSqlResult(args) {
4671
4696
  return buildAggregateSql(processed, whereResult, tableName, alias, modelDef);
4672
4697
  }
4673
4698
  if (method === "groupBy") {
4674
- return buildGroupBySql(processed, whereResult, tableName, alias, modelDef);
4699
+ return buildGroupBySql(
4700
+ processed,
4701
+ whereResult,
4702
+ tableName,
4703
+ alias,
4704
+ modelDef,
4705
+ dialect
4706
+ );
4675
4707
  }
4676
4708
  if (method === "count") {
4677
4709
  return buildCountSql(
4678
4710
  whereResult,
4679
4711
  tableName,
4680
4712
  alias,
4681
- processed.skip
4682
- );
4713
+ processed.skip);
4683
4714
  }
4684
4715
  return buildSelectSql({
4685
4716
  method,
@@ -4719,22 +4750,23 @@ function normalizeSqlAndMappingsForDialect(sql, paramMappings, dialect) {
4719
4750
  }
4720
4751
  function buildParamsFromMappings(mappings) {
4721
4752
  const sorted = [...mappings].sort((a, b) => a.index - b.index);
4722
- return sorted.reduce(
4723
- (acc, m) => {
4724
- if (m.dynamicName !== void 0) {
4725
- acc.dynamicKeys.push(m.dynamicName);
4726
- return acc;
4727
- }
4728
- if (m.value !== void 0) {
4729
- acc.staticParams.push(m.value);
4730
- return acc;
4731
- }
4753
+ const staticParams = [];
4754
+ const dynamicKeys = [];
4755
+ let paramOrder = "";
4756
+ for (const m of sorted) {
4757
+ if (m.dynamicName !== void 0) {
4758
+ dynamicKeys.push(m.dynamicName);
4759
+ paramOrder += "d";
4760
+ } else if (m.value !== void 0) {
4761
+ staticParams.push(m.value);
4762
+ paramOrder += "s";
4763
+ } else {
4732
4764
  throw new Error(
4733
4765
  `CRITICAL: ParamMap ${m.index} has neither dynamicName nor value`
4734
4766
  );
4735
- },
4736
- { staticParams: [], dynamicKeys: [] }
4737
- );
4767
+ }
4768
+ }
4769
+ return { staticParams, dynamicKeys, paramOrder };
4738
4770
  }
4739
4771
  function resolveModelContext(directive) {
4740
4772
  const { model, datamodel } = directive.context;
@@ -4800,12 +4832,13 @@ function finalizeDirective(args) {
4800
4832
  return (_a = m.value) != null ? _a : void 0;
4801
4833
  });
4802
4834
  validateParamConsistencyByDialect(normalizedSql, params, dialect);
4803
- const { staticParams, dynamicKeys } = buildParamsFromMappings(normalizedMappings);
4835
+ const { staticParams, dynamicKeys, paramOrder } = buildParamsFromMappings(normalizedMappings);
4804
4836
  return {
4805
4837
  method: directive.method,
4806
4838
  sql: normalizedSql,
4807
4839
  staticParams,
4808
4840
  dynamicKeys,
4841
+ paramOrder,
4809
4842
  paramMappings: normalizedMappings,
4810
4843
  originalDirective: directive
4811
4844
  };
@@ -4944,6 +4977,7 @@ function generateCode(models, queries, dialect, datamodel) {
4944
4977
  const { mappings, fieldTypes } = extractEnumMappings(datamodel);
4945
4978
  return `// Generated by @prisma-sql/generator - DO NOT EDIT
4946
4979
  import { buildSQL, buildBatchSql, parseBatchResults, buildBatchCountSql, parseBatchCountResults, createTransactionExecutor, transformQueryResults, type PrismaMethod, type Model, type BatchQuery, type BatchCountQuery, type TransactionQuery, type TransactionOptions } from 'prisma-sql'
4980
+ import { normalizeValue } from 'prisma-sql/utils/normalize-value'
4947
4981
 
4948
4982
  class DeferredQuery {
4949
4983
  constructor(
@@ -4985,14 +5019,12 @@ function createBatchProxy(): BatchProxy {
4985
5019
  {
4986
5020
  get(_target, modelName: string): any {
4987
5021
  if (typeof modelName === 'symbol') return undefined
4988
-
4989
5022
  const model = MODEL_MAP.get(modelName)
4990
5023
  if (!model) {
4991
5024
  throw new Error(
4992
5025
  \`Model '\${modelName}' not found. Available: \${[...MODEL_MAP.keys()].join(', ')}\`,
4993
5026
  )
4994
5027
  }
4995
-
4996
5028
  return new Proxy(
4997
5029
  {},
4998
5030
  {
@@ -5002,7 +5034,6 @@ function createBatchProxy(): BatchProxy {
5002
5034
  \`Method '\${method}' not supported in batch. Supported: \${[...ACCELERATED_METHODS].join(', ')}\`,
5003
5035
  )
5004
5036
  }
5005
-
5006
5037
  return (args?: any): DeferredQuery => {
5007
5038
  return new DeferredQuery(
5008
5039
  modelName,
@@ -5018,52 +5049,6 @@ function createBatchProxy(): BatchProxy {
5018
5049
  ) as BatchProxy
5019
5050
  }
5020
5051
 
5021
- function normalizeValue(value: unknown, seen = new WeakSet<object>(), depth = 0): unknown {
5022
- const MAX_DEPTH = 20
5023
- if (depth > MAX_DEPTH) {
5024
- throw new Error(\`Max normalization depth exceeded (\${MAX_DEPTH} levels)\`)
5025
- }
5026
- if (value instanceof Date) {
5027
- const t = value.getTime()
5028
- if (!Number.isFinite(t)) {
5029
- throw new Error('Invalid Date value in SQL params')
5030
- }
5031
- return value.toISOString()
5032
- }
5033
- if (typeof value === 'bigint') {
5034
- return value.toString()
5035
- }
5036
- if (Array.isArray(value)) {
5037
- const arrRef = value as unknown as object
5038
- if (seen.has(arrRef)) {
5039
- throw new Error('Circular reference in SQL params')
5040
- }
5041
- seen.add(arrRef)
5042
- const out = value.map((v) => normalizeValue(v, seen, depth + 1))
5043
- seen.delete(arrRef)
5044
- return out
5045
- }
5046
- if (value && typeof value === 'object') {
5047
- if (value instanceof Uint8Array) return value
5048
- if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) return value
5049
- const proto = Object.getPrototypeOf(value)
5050
- const isPlain = proto === Object.prototype || proto === null
5051
- if (!isPlain) return value
5052
- const obj = value as Record<string, unknown>
5053
- if (seen.has(obj)) {
5054
- throw new Error('Circular reference in SQL params')
5055
- }
5056
- seen.add(obj)
5057
- const out: Record<string, unknown> = {}
5058
- for (const [k, v] of Object.entries(obj)) {
5059
- out[k] = normalizeValue(v, seen, depth + 1)
5060
- }
5061
- seen.delete(obj)
5062
- return out
5063
- }
5064
- return value
5065
- }
5066
-
5067
5052
  function normalizeParams(params: unknown[]): unknown[] {
5068
5053
  return params.map(p => normalizeValue(p))
5069
5054
  }
@@ -5153,19 +5138,16 @@ function transformEnumValuesByModel(modelName: string, obj: any, path: string[]
5153
5138
  if (typeof obj === 'object') {
5154
5139
  const transformed: any = {}
5155
5140
  const modelFields = ENUM_FIELDS[modelName] || {}
5156
-
5157
5141
  for (const [key, value] of Object.entries(obj)) {
5158
5142
  const nextPath = [...path, key]
5159
5143
 
5160
5144
  const relModel = getRelatedModelName(modelName, key)
5161
-
5162
5145
  if (relModel && value && typeof value === 'object') {
5163
5146
  transformed[key] = transformEnumValuesByModel(relModel, value, nextPath)
5164
5147
  continue
5165
5148
  }
5166
5149
 
5167
5150
  const enumType = modelFields[key]
5168
-
5169
5151
  if (enumType) {
5170
5152
  if (value && typeof value === 'object' && !Array.isArray(value) && !(value instanceof Date)) {
5171
5153
  const transformedOperators: any = {}
@@ -5184,25 +5166,27 @@ function transformEnumValuesByModel(modelName: string, obj: any, path: string[]
5184
5166
  transformed[key] = value
5185
5167
  }
5186
5168
  }
5187
-
5188
5169
  return transformed
5189
5170
  }
5190
5171
 
5191
5172
  return obj
5192
5173
  }
5193
5174
 
5175
+ function bigIntSafeReplacer(_key: string, value: unknown): unknown {
5176
+ if (typeof value === 'bigint') return '__bigint__' + value.toString()
5177
+ return value
5178
+ }
5179
+
5194
5180
  function normalizeQuery(args: any): string {
5195
5181
  if (!args) return '{}'
5196
-
5197
- const normalized = JSON.parse(JSON.stringify(args))
5182
+ const jsonStr = JSON.stringify(args, bigIntSafeReplacer)
5183
+ const normalized = JSON.parse(jsonStr)
5198
5184
 
5199
5185
  function replaceDynamicParams(obj: any, path: string[] = []): any {
5200
5186
  if (!obj || typeof obj !== 'object') return obj
5201
-
5202
5187
  if (Array.isArray(obj)) {
5203
5188
  return obj.map((v) => replaceDynamicParams(v, path))
5204
5189
  }
5205
-
5206
5190
  const result: any = {}
5207
5191
  for (const [key, value] of Object.entries(obj)) {
5208
5192
  if (isDynamicKeyForQueryKey(path, key)) {
@@ -5218,7 +5202,6 @@ function normalizeQuery(args: any): string {
5218
5202
 
5219
5203
  function removeEmptyObjects(obj: any): any {
5220
5204
  if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return obj
5221
-
5222
5205
  const result: any = {}
5223
5206
  for (const [key, value] of Object.entries(obj)) {
5224
5207
  if (value && typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length === 0) {
@@ -5232,6 +5215,7 @@ function normalizeQuery(args: any): string {
5232
5215
  const cleaned = removeEmptyObjects(withMarkers)
5233
5216
 
5234
5217
  return JSON.stringify(cleaned, (key, value) => {
5218
+ if (typeof value === 'bigint') return '__bigint__' + value.toString()
5235
5219
  if (value && typeof value === 'object' && !Array.isArray(value)) {
5236
5220
  const sorted: Record<string, unknown> = {}
5237
5221
  for (const k of Object.keys(value).sort()) {
@@ -5245,37 +5229,28 @@ function normalizeQuery(args: any): string {
5245
5229
 
5246
5230
  function extractDynamicParams(args: any, dynamicKeys: string[]): unknown[] {
5247
5231
  const params: unknown[] = []
5248
-
5249
5232
  for (const key of dynamicKeys) {
5250
5233
  const parts = key.split(':')
5251
5234
  const lookupKey = parts.length === 2 ? parts[1] : key
5252
-
5253
5235
  const value =
5254
5236
  lookupKey.includes('.') ? getByPath(args, lookupKey) : args?.[lookupKey]
5255
-
5256
5237
  if (value === undefined) {
5257
5238
  throw new Error(\`Missing required parameter: \${key}\`)
5258
5239
  }
5259
-
5260
5240
  params.push(normalizeValue(value))
5261
5241
  }
5262
-
5263
5242
  return params
5264
5243
  }
5265
5244
 
5266
5245
  async function executeQuery(client: any, sql: string, params: unknown[]): Promise<unknown[]> {
5267
5246
  const normalizedParams = normalizeParams(params)
5268
-
5269
5247
  if (DIALECT === 'postgres') {
5270
5248
  return await client.unsafe(sql, normalizedParams)
5271
5249
  }
5272
-
5273
5250
  const stmt = client.prepare(sql)
5274
-
5275
5251
  if (sql.toUpperCase().includes('COUNT(*) AS')) {
5276
5252
  return [stmt.get(...normalizedParams)]
5277
5253
  }
5278
-
5279
5254
  return stmt.all(...normalizedParams)
5280
5255
  }
5281
5256
 
@@ -5300,14 +5275,11 @@ export function speedExtension(config: {
5300
5275
  }) => void
5301
5276
  }) {
5302
5277
  const { postgres, sqlite, debug, onQuery } = config
5303
-
5304
5278
  if (!postgres && !sqlite) {
5305
5279
  throw new Error('speedExtension requires postgres or sqlite client')
5306
5280
  }
5307
-
5308
5281
  const client = postgres || sqlite
5309
5282
  const actualDialect = postgres ? 'postgres' : 'sqlite'
5310
-
5311
5283
  if (actualDialect !== DIALECT) {
5312
5284
  throw new Error(\`Generated code is for \${DIALECT}, but you provided \${actualDialect}\`)
5313
5285
  }
@@ -5324,12 +5296,9 @@ export function speedExtension(config: {
5324
5296
  const handleMethod = async function(this: any, method: PrismaMethod, args: any) {
5325
5297
  const modelName = this?.name || this?.$name
5326
5298
  const startTime = Date.now()
5327
-
5328
5299
  const transformedArgs = transformEnumValuesByModel(modelName, args || {})
5329
-
5330
5300
  const queryKey = normalizeQuery(transformedArgs)
5331
5301
  const prebakedQuery = QUERIES[modelName]?.[method]?.[queryKey]
5332
-
5333
5302
  let sql: string
5334
5303
  let params: unknown[]
5335
5304
  let prebaked = false
@@ -5343,11 +5312,9 @@ export function speedExtension(config: {
5343
5312
  prebaked = true
5344
5313
  } else {
5345
5314
  const model = MODELS.find((m) => m.name === modelName)
5346
-
5347
5315
  if (!model) {
5348
5316
  return this.$parent[modelName][method](args)
5349
5317
  }
5350
-
5351
5318
  const result = buildSQL(model, MODELS, method, transformedArgs, DIALECT)
5352
5319
  sql = result.sql
5353
5320
  params = normalizeParams(result.params as unknown[])
@@ -5379,16 +5346,13 @@ export function speedExtension(config: {
5379
5346
  ): Promise<{ [K in keyof T]: any }> {
5380
5347
  const batchProxy = createBatchProxy()
5381
5348
  const queries = await callback(batchProxy)
5382
-
5383
5349
  const batchQueries: Record<string, BatchQuery> = {}
5384
-
5385
5350
  for (const [key, deferred] of Object.entries(queries)) {
5386
5351
  if (!(deferred instanceof DeferredQuery)) {
5387
5352
  throw new Error(
5388
5353
  \`Batch query '\${key}' must be a deferred query. Did you await it?\`,
5389
5354
  )
5390
5355
  }
5391
-
5392
5356
  batchQueries[key] = {
5393
5357
  model: deferred.model,
5394
5358
  method: deferred.method,
@@ -5414,8 +5378,8 @@ export function speedExtension(config: {
5414
5378
  const rows = await client.unsafe(sql, normalizedParams as any[])
5415
5379
  const row = rows[0] as Record<string, unknown>
5416
5380
  const results = parseBatchResults(row, keys, batchQueries, aliases)
5417
- const duration = Date.now() - startTime
5418
5381
 
5382
+ const duration = Date.now() - startTime
5419
5383
  onQuery?.({
5420
5384
  model: '_batch',
5421
5385
  method: 'batch',
@@ -5430,7 +5394,6 @@ export function speedExtension(config: {
5430
5394
 
5431
5395
  async function batchCount(queries: BatchCountQuery[]): Promise<number[]> {
5432
5396
  if (queries.length === 0) return []
5433
-
5434
5397
  const startTime = Date.now()
5435
5398
  const { sql, params } = buildBatchCountSql(
5436
5399
  queries,
@@ -5449,8 +5412,8 @@ export function speedExtension(config: {
5449
5412
  const rows = await client.unsafe(sql, normalizedParams as any[])
5450
5413
  const row = rows[0] as Record<string, unknown>
5451
5414
  const results = parseBatchCountResults(row, queries.length)
5452
- const duration = Date.now() - startTime
5453
5415
 
5416
+ const duration = Date.now() - startTime
5454
5417
  onQuery?.({
5455
5418
  model: '_batch',
5456
5419
  method: 'count',
@@ -5468,14 +5431,11 @@ export function speedExtension(config: {
5468
5431
  options?: TransactionOptions,
5469
5432
  ): Promise<unknown[]> {
5470
5433
  const startTime = Date.now()
5471
-
5472
5434
  if (debug) {
5473
5435
  console.log(\`[\${DIALECT}] $transaction (\${queries.length} queries)\`)
5474
5436
  }
5475
-
5476
5437
  const results = await txExecutor.execute(queries, options)
5477
5438
  const duration = Date.now() - startTime
5478
-
5479
5439
  onQuery?.({
5480
5440
  model: '_transaction',
5481
5441
  method: 'transaction',
@@ -5484,13 +5444,11 @@ export function speedExtension(config: {
5484
5444
  duration,
5485
5445
  prebaked: false,
5486
5446
  })
5487
-
5488
5447
  return results
5489
5448
  }
5490
5449
 
5491
5450
  return prisma.$extends({
5492
5451
  name: 'prisma-sql-generated',
5493
-
5494
5452
  client: {
5495
5453
  $original: prisma,
5496
5454
  $batch: batch as <T extends Record<string, DeferredQuery>>(
@@ -5499,7 +5457,6 @@ export function speedExtension(config: {
5499
5457
  $batchCount: batchCount as (...args: any[]) => Promise<number[]>,
5500
5458
  $transaction: transaction as (...args: any[]) => Promise<unknown[]>,
5501
5459
  },
5502
-
5503
5460
  model: {
5504
5461
  $allModels: {
5505
5462
  async findMany(args: any) {