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