prisma-sql 1.44.0 → 1.46.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/index.cjs CHANGED
@@ -49,20 +49,13 @@ var SQL_SEPARATORS = Object.freeze({
49
49
  CONDITION_OR: " OR ",
50
50
  ORDER_BY: ", "
51
51
  });
52
- var SQL_RESERVED_WORDS = /* @__PURE__ */ new Set([
52
+ var ALIAS_FORBIDDEN_KEYWORDS = /* @__PURE__ */ new Set([
53
53
  "select",
54
54
  "from",
55
55
  "where",
56
- "and",
57
- "or",
58
- "not",
59
- "in",
60
- "like",
61
- "between",
56
+ "having",
62
57
  "order",
63
- "by",
64
58
  "group",
65
- "having",
66
59
  "limit",
67
60
  "offset",
68
61
  "join",
@@ -70,14 +63,42 @@ var SQL_RESERVED_WORDS = /* @__PURE__ */ new Set([
70
63
  "left",
71
64
  "right",
72
65
  "outer",
73
- "on",
66
+ "cross",
67
+ "full",
68
+ "and",
69
+ "or",
70
+ "not",
71
+ "by",
74
72
  "as",
73
+ "on",
74
+ "union",
75
+ "intersect",
76
+ "except",
77
+ "case",
78
+ "when",
79
+ "then",
80
+ "else",
81
+ "end"
82
+ ]);
83
+ var SQL_KEYWORDS = /* @__PURE__ */ new Set([
84
+ ...ALIAS_FORBIDDEN_KEYWORDS,
85
+ "user",
86
+ "users",
75
87
  "table",
76
88
  "column",
77
89
  "index",
78
- "user",
79
- "users",
80
90
  "values",
91
+ "in",
92
+ "like",
93
+ "between",
94
+ "is",
95
+ "exists",
96
+ "null",
97
+ "true",
98
+ "false",
99
+ "all",
100
+ "any",
101
+ "some",
81
102
  "update",
82
103
  "insert",
83
104
  "delete",
@@ -88,25 +109,9 @@ var SQL_RESERVED_WORDS = /* @__PURE__ */ new Set([
88
109
  "grant",
89
110
  "revoke",
90
111
  "exec",
91
- "execute",
92
- "union",
93
- "intersect",
94
- "except",
95
- "case",
96
- "when",
97
- "then",
98
- "else",
99
- "end",
100
- "null",
101
- "true",
102
- "false",
103
- "is",
104
- "exists",
105
- "all",
106
- "any",
107
- "some"
112
+ "execute"
108
113
  ]);
109
- var SQL_KEYWORDS = SQL_RESERVED_WORDS;
114
+ var SQL_RESERVED_WORDS = SQL_KEYWORDS;
110
115
  var DEFAULT_WHERE_CLAUSE = "1=1";
111
116
  var SPECIAL_FIELDS = Object.freeze({
112
117
  ID: "id"
@@ -185,12 +190,48 @@ var LIMITS = Object.freeze({
185
190
  });
186
191
 
187
192
  // src/utils/normalize-value.ts
188
- function normalizeValue(value) {
193
+ var MAX_DEPTH = 20;
194
+ function normalizeValue(value, seen = /* @__PURE__ */ new WeakSet(), depth = 0) {
195
+ if (depth > MAX_DEPTH) {
196
+ throw new Error(`Max normalization depth exceeded (${MAX_DEPTH} levels)`);
197
+ }
189
198
  if (value instanceof Date) {
199
+ const t = value.getTime();
200
+ if (!Number.isFinite(t)) {
201
+ throw new Error("Invalid Date value in SQL params");
202
+ }
190
203
  return value.toISOString();
191
204
  }
205
+ if (typeof value === "bigint") {
206
+ return value.toString();
207
+ }
192
208
  if (Array.isArray(value)) {
193
- return value.map(normalizeValue);
209
+ const arrRef = value;
210
+ if (seen.has(arrRef)) {
211
+ throw new Error("Circular reference in SQL params");
212
+ }
213
+ seen.add(arrRef);
214
+ const out = value.map((v) => normalizeValue(v, seen, depth + 1));
215
+ seen.delete(arrRef);
216
+ return out;
217
+ }
218
+ if (value && typeof value === "object") {
219
+ if (value instanceof Uint8Array) return value;
220
+ if (typeof Buffer !== "undefined" && Buffer.isBuffer(value)) return value;
221
+ const proto = Object.getPrototypeOf(value);
222
+ const isPlain = proto === Object.prototype || proto === null;
223
+ if (!isPlain) return value;
224
+ const obj = value;
225
+ if (seen.has(obj)) {
226
+ throw new Error("Circular reference in SQL params");
227
+ }
228
+ seen.add(obj);
229
+ const out = {};
230
+ for (const [k, v] of Object.entries(obj)) {
231
+ out[k] = normalizeValue(v, seen, depth + 1);
232
+ }
233
+ seen.delete(obj);
234
+ return out;
194
235
  }
195
236
  return value;
196
237
  }
@@ -371,7 +412,7 @@ function prepareArrayParam(value, dialect) {
371
412
  throw new Error("prepareArrayParam requires array value");
372
413
  }
373
414
  if (dialect === "postgres") {
374
- return value.map(normalizeValue);
415
+ return value.map((v) => normalizeValue(v));
375
416
  }
376
417
  return JSON.stringify(value);
377
418
  }
@@ -443,36 +484,46 @@ function createError(message, ctx, code = "VALIDATION_ERROR") {
443
484
  }
444
485
 
445
486
  // src/builder/shared/model-field-cache.ts
446
- var SCALAR_SET_CACHE = /* @__PURE__ */ new WeakMap();
447
- var RELATION_SET_CACHE = /* @__PURE__ */ new WeakMap();
487
+ var MODEL_CACHE = /* @__PURE__ */ new WeakMap();
488
+ function ensureFullCache(model) {
489
+ let cache = MODEL_CACHE.get(model);
490
+ if (!cache) {
491
+ const fieldInfo = /* @__PURE__ */ new Map();
492
+ const scalarFields = /* @__PURE__ */ new Set();
493
+ const relationFields = /* @__PURE__ */ new Set();
494
+ const columnMap = /* @__PURE__ */ new Map();
495
+ for (const f of model.fields) {
496
+ const info = {
497
+ name: f.name,
498
+ dbName: f.dbName || f.name,
499
+ type: f.type,
500
+ isRelation: !!f.isRelation,
501
+ isRequired: !!f.isRequired
502
+ };
503
+ fieldInfo.set(f.name, info);
504
+ if (info.isRelation) {
505
+ relationFields.add(f.name);
506
+ } else {
507
+ scalarFields.add(f.name);
508
+ columnMap.set(f.name, info.dbName);
509
+ }
510
+ }
511
+ cache = { fieldInfo, scalarFields, relationFields, columnMap };
512
+ MODEL_CACHE.set(model, cache);
513
+ }
514
+ return cache;
515
+ }
516
+ function getFieldInfo(model, fieldName) {
517
+ return ensureFullCache(model).fieldInfo.get(fieldName);
518
+ }
448
519
  function getScalarFieldSet(model) {
449
- const cached = SCALAR_SET_CACHE.get(model);
450
- if (cached) return cached;
451
- const s = /* @__PURE__ */ new Set();
452
- for (const f of model.fields) if (!f.isRelation) s.add(f.name);
453
- SCALAR_SET_CACHE.set(model, s);
454
- return s;
520
+ return ensureFullCache(model).scalarFields;
455
521
  }
456
522
  function getRelationFieldSet(model) {
457
- const cached = RELATION_SET_CACHE.get(model);
458
- if (cached) return cached;
459
- const s = /* @__PURE__ */ new Set();
460
- for (const f of model.fields) if (f.isRelation) s.add(f.name);
461
- RELATION_SET_CACHE.set(model, s);
462
- return s;
463
- }
464
- var COLUMN_MAP_CACHE = /* @__PURE__ */ new WeakMap();
523
+ return ensureFullCache(model).relationFields;
524
+ }
465
525
  function getColumnMap(model) {
466
- const cached = COLUMN_MAP_CACHE.get(model);
467
- if (cached) return cached;
468
- const map = /* @__PURE__ */ new Map();
469
- for (const f of model.fields) {
470
- if (!f.isRelation) {
471
- map.set(f.name, f.dbName || f.name);
472
- }
473
- }
474
- COLUMN_MAP_CACHE.set(model, map);
475
- return map;
526
+ return ensureFullCache(model).columnMap;
476
527
  }
477
528
 
478
529
  // src/builder/shared/validators/sql-validators.ts
@@ -483,20 +534,21 @@ function isEmptyWhere(where) {
483
534
  if (!isNotNullish(where)) return true;
484
535
  return Object.keys(where).length === 0;
485
536
  }
537
+ function sqlPreview(sql) {
538
+ const s = String(sql);
539
+ if (s.length <= 160) return s;
540
+ return `${s.slice(0, 160)}...`;
541
+ }
486
542
  function validateSelectQuery(sql) {
487
543
  if (!hasValidContent(sql)) {
488
544
  throw new Error("CRITICAL: Generated empty SQL query");
489
545
  }
490
546
  if (!hasRequiredKeywords(sql)) {
491
- throw new Error(
492
- `CRITICAL: Invalid SQL structure. SQL: ${sql.substring(0, 100)}...`
493
- );
547
+ throw new Error(`CRITICAL: Invalid SQL structure. SQL: ${sqlPreview(sql)}`);
494
548
  }
495
549
  }
496
- function sqlPreview(sql) {
497
- return `${sql.substring(0, 100)}...`;
498
- }
499
- function parseDollarNumber(sql, start, n) {
550
+ function parseDollarNumber(sql, start) {
551
+ const n = sql.length;
500
552
  let i = start;
501
553
  let num = 0;
502
554
  let hasDigit = false;
@@ -507,14 +559,14 @@ function parseDollarNumber(sql, start, n) {
507
559
  num = num * 10 + (c - 48);
508
560
  i++;
509
561
  }
510
- if (!hasDigit || num <= 0) return { next: i, num: 0, ok: false };
511
- return { next: i, num, ok: true };
562
+ if (!hasDigit || num <= 0) return { next: i, num: 0 };
563
+ return { next: i, num };
512
564
  }
513
565
  function scanDollarPlaceholders(sql, markUpTo) {
514
566
  const seen = new Uint8Array(markUpTo + 1);
515
- let count = 0;
516
567
  let min = Number.POSITIVE_INFINITY;
517
568
  let max = 0;
569
+ let sawAny = false;
518
570
  const n = sql.length;
519
571
  let i = 0;
520
572
  while (i < n) {
@@ -522,17 +574,21 @@ function scanDollarPlaceholders(sql, markUpTo) {
522
574
  i++;
523
575
  continue;
524
576
  }
525
- const { next, num, ok } = parseDollarNumber(sql, i + 1, n);
526
- i = next;
527
- if (!ok) continue;
528
- count++;
577
+ const parsed = parseDollarNumber(sql, i + 1);
578
+ i = parsed.next;
579
+ const num = parsed.num;
580
+ if (num === 0) continue;
581
+ sawAny = true;
529
582
  if (num < min) min = num;
530
583
  if (num > max) max = num;
531
584
  if (num <= markUpTo) seen[num] = 1;
532
585
  }
533
- return { count, min, max, seen };
586
+ if (!sawAny) {
587
+ return { min: 0, max: 0, seen, sawAny: false };
588
+ }
589
+ return { min, max, seen, sawAny: true };
534
590
  }
535
- function assertNoGaps(scan, rangeMin, rangeMax, sql) {
591
+ function assertNoGapsDollar(scan, rangeMin, rangeMax, sql) {
536
592
  for (let k = rangeMin; k <= rangeMax; k++) {
537
593
  if (scan.seen[k] !== 1) {
538
594
  throw new Error(
@@ -543,174 +599,75 @@ function assertNoGaps(scan, rangeMin, rangeMax, sql) {
543
599
  }
544
600
  function validateParamConsistency(sql, params) {
545
601
  const paramLen = params.length;
546
- if (paramLen === 0) {
547
- if (sql.indexOf("$") === -1) return;
548
- }
549
602
  const scan = scanDollarPlaceholders(sql, paramLen);
550
- if (scan.count === 0) {
551
- if (paramLen !== 0) {
603
+ if (paramLen === 0) {
604
+ if (scan.sawAny) {
552
605
  throw new Error(
553
- `CRITICAL: Parameter mismatch - SQL has no placeholders but ${paramLen} params provided.`
606
+ `CRITICAL: SQL contains placeholders but params is empty. SQL: ${sqlPreview(sql)}`
554
607
  );
555
608
  }
556
609
  return;
557
610
  }
558
- if (scan.max !== paramLen) {
611
+ if (!scan.sawAny) {
559
612
  throw new Error(
560
- `CRITICAL: Parameter mismatch - SQL max placeholder is $${scan.max} but ${paramLen} params provided. This will cause SQL execution to fail. SQL: ${sqlPreview(sql)}`
613
+ `CRITICAL: SQL is missing placeholders ($1..$${paramLen}) but params has length ${paramLen}. SQL: ${sqlPreview(sql)}`
561
614
  );
562
615
  }
563
- assertNoGaps(scan, 1, scan.max, sql);
564
- }
565
- function needsQuoting(id) {
566
- if (!isNonEmptyString(id)) return true;
567
- const isKeyword = SQL_KEYWORDS.has(id.toLowerCase());
568
- if (isKeyword) return true;
569
- const isValidIdentifier = REGEX_CACHE.VALID_IDENTIFIER.test(id);
570
- return !isValidIdentifier;
571
- }
572
- function validateParamConsistencyFragment(sql, params) {
573
- const paramLen = params.length;
574
- const scan = scanDollarPlaceholders(sql, paramLen);
575
- if (scan.max === 0) return;
576
- if (scan.max > paramLen) {
616
+ if (scan.min !== 1) {
577
617
  throw new Error(
578
- `CRITICAL: Parameter mismatch - SQL references $${scan.max} but only ${paramLen} params provided. SQL: ${sqlPreview(sql)}`
618
+ `CRITICAL: Placeholder range must start at $1, got min=$${scan.min}. SQL: ${sqlPreview(sql)}`
579
619
  );
580
620
  }
581
- assertNoGaps(scan, scan.min, scan.max, sql);
582
- }
583
- function assertOrThrow(condition, message) {
584
- if (!condition) throw new Error(message);
585
- }
586
- function dialectPlaceholderPrefix(dialect) {
587
- return dialect === "sqlite" ? "?" : "$";
588
- }
589
- function parseSqlitePlaceholderIndices(sql) {
590
- const re = /\?(?:(\d+))?/g;
591
- const indices = [];
592
- let anonCount = 0;
593
- let sawNumbered = false;
594
- let sawAnonymous = false;
595
- for (const m of sql.matchAll(re)) {
596
- const n = m[1];
597
- if (n) {
598
- sawNumbered = true;
599
- indices.push(parseInt(n, 10));
600
- } else {
601
- sawAnonymous = true;
602
- anonCount += 1;
603
- indices.push(anonCount);
604
- }
605
- }
606
- return { indices, sawNumbered, sawAnonymous };
607
- }
608
- function parseDollarPlaceholderIndices(sql) {
609
- const re = /\$(\d+)/g;
610
- const indices = [];
611
- for (const m of sql.matchAll(re)) indices.push(parseInt(m[1], 10));
612
- return indices;
613
- }
614
- function getPlaceholderIndices(sql, dialect) {
615
- if (dialect === "sqlite") return parseSqlitePlaceholderIndices(sql);
616
- return {
617
- indices: parseDollarPlaceholderIndices(sql),
618
- sawNumbered: false,
619
- sawAnonymous: false
620
- };
621
- }
622
- function maxIndex(indices) {
623
- return indices.length > 0 ? Math.max(...indices) : 0;
624
- }
625
- function ensureNoMixedSqlitePlaceholders(sawNumbered, sawAnonymous) {
626
- assertOrThrow(
627
- !(sawNumbered && sawAnonymous),
628
- `CRITICAL: Mixed sqlite placeholders ('?' and '?NNN') are not supported.`
629
- );
630
- }
631
- function ensurePlaceholderMaxMatchesMappingsLength(max, mappingsLength, dialect) {
632
- assertOrThrow(
633
- max === mappingsLength,
634
- `CRITICAL: SQL placeholder max mismatch - max is ${dialectPlaceholderPrefix(dialect)}${max}, but mappings length is ${mappingsLength}.`
635
- );
636
- }
637
- function ensureSequentialPlaceholders(placeholders, max, dialect) {
638
- const prefix = dialectPlaceholderPrefix(dialect);
639
- for (let i = 1; i <= max; i++) {
640
- assertOrThrow(
641
- placeholders.has(i),
642
- `CRITICAL: Missing SQL placeholder ${prefix}${i} - placeholders must be sequential 1..${max}.`
621
+ if (scan.max !== paramLen) {
622
+ throw new Error(
623
+ `CRITICAL: Placeholder max must match params length. max=$${scan.max}, params=${paramLen}. SQL: ${sqlPreview(sql)}`
643
624
  );
644
625
  }
626
+ assertNoGapsDollar(scan, 1, paramLen, sql);
645
627
  }
646
- function validateMappingIndex(mapping, max) {
647
- assertOrThrow(
648
- Number.isInteger(mapping.index) && mapping.index >= 1 && mapping.index <= max,
649
- `CRITICAL: ParamMapping index ${mapping.index} out of range 1..${max}.`
650
- );
651
- }
652
- function ensureUniqueMappingIndex(mappingIndices, index, dialect) {
653
- assertOrThrow(
654
- !mappingIndices.has(index),
655
- `CRITICAL: Duplicate ParamMapping index ${index} - each placeholder index must map to exactly one ParamMap.`
656
- );
657
- mappingIndices.add(index);
658
- }
659
- function ensureMappingIndexExistsInSql(placeholders, index) {
660
- assertOrThrow(
661
- placeholders.has(index),
662
- `CRITICAL: ParamMapping index ${index} not found in SQL placeholders.`
663
- );
664
- }
665
- function validateMappingValueShape(mapping) {
666
- assertOrThrow(
667
- !(mapping.dynamicName !== void 0 && mapping.value !== void 0),
668
- `CRITICAL: ParamMap ${mapping.index} has both dynamicName and value`
669
- );
670
- assertOrThrow(
671
- !(mapping.dynamicName === void 0 && mapping.value === void 0),
672
- `CRITICAL: ParamMap ${mapping.index} has neither dynamicName nor value`
673
- );
628
+ function countQuestionMarkPlaceholders(sql) {
629
+ const s = String(sql);
630
+ let count = 0;
631
+ for (let i = 0; i < s.length; i++) {
632
+ if (s.charCodeAt(i) === 63) count++;
633
+ }
634
+ return count;
674
635
  }
675
- function ensureMappingsCoverAllIndices(mappingIndices, max, dialect) {
676
- const prefix = dialectPlaceholderPrefix(dialect);
677
- for (let i = 1; i <= max; i++) {
678
- assertOrThrow(
679
- mappingIndices.has(i),
680
- `CRITICAL: Missing ParamMap for placeholder ${prefix}${i} - mappings must cover 1..${max} with no gaps.`
636
+ function validateQuestionMarkConsistency(sql, params) {
637
+ const expected = params.length;
638
+ const found = countQuestionMarkPlaceholders(sql);
639
+ if (expected !== found) {
640
+ throw new Error(
641
+ `CRITICAL: Parameter mismatch - expected ${expected} '?' placeholders, found ${found}. SQL: ${sqlPreview(sql)}`
681
642
  );
682
643
  }
683
644
  }
684
- function validateMappingsAgainstPlaceholders(mappings, placeholders, max, dialect) {
685
- const mappingIndices = /* @__PURE__ */ new Set();
686
- for (const mapping of mappings) {
687
- validateMappingIndex(mapping, max);
688
- ensureUniqueMappingIndex(mappingIndices, mapping.index);
689
- ensureMappingIndexExistsInSql(placeholders, mapping.index);
690
- validateMappingValueShape(mapping);
645
+ function validateParamConsistencyByDialect(sql, params, dialect) {
646
+ if (dialect === "postgres") {
647
+ validateParamConsistency(sql, params);
648
+ return;
691
649
  }
692
- ensureMappingsCoverAllIndices(mappingIndices, max, dialect);
693
- }
694
- function validateSqlPositions(sql, mappings, dialect) {
695
- const { indices, sawNumbered, sawAnonymous } = getPlaceholderIndices(
696
- sql,
697
- dialect
698
- );
699
650
  if (dialect === "sqlite") {
700
- ensureNoMixedSqlitePlaceholders(sawNumbered, sawAnonymous);
651
+ validateQuestionMarkConsistency(sql, params);
652
+ return;
653
+ }
654
+ if (dialect === "mysql" || dialect === "mariadb") {
655
+ validateQuestionMarkConsistency(sql, params);
656
+ return;
701
657
  }
702
- const placeholders = new Set(indices);
703
- if (placeholders.size === 0 && mappings.length === 0) return;
704
- const max = maxIndex(indices);
705
- ensurePlaceholderMaxMatchesMappingsLength(max, mappings.length, dialect);
706
- ensureSequentialPlaceholders(placeholders, max, dialect);
707
- validateMappingsAgainstPlaceholders(mappings, placeholders, max, dialect);
658
+ validateParamConsistency(sql, params);
659
+ }
660
+ function needsQuoting(identifier) {
661
+ const s = String(identifier);
662
+ if (!REGEX_CACHE.VALID_IDENTIFIER.test(s)) return true;
663
+ const lowered = s.toLowerCase();
664
+ if (SQL_KEYWORDS.has(lowered)) return true;
665
+ return false;
708
666
  }
709
667
 
710
668
  // src/builder/shared/sql-utils.ts
711
- var NUL = String.fromCharCode(0);
712
669
  function containsControlChars(s) {
713
- return s.includes(NUL) || s.includes("\n") || s.includes("\r");
670
+ return /[\u0000-\u001F\u007F]/.test(s);
714
671
  }
715
672
  function assertNoControlChars(label, s) {
716
673
  if (containsControlChars(s)) {
@@ -929,16 +886,46 @@ function buildTableReference(schemaName, tableName, dialect) {
929
886
  return `"${safeSchema}"."${safeTable}"`;
930
887
  }
931
888
  function assertSafeAlias(alias) {
932
- const a = String(alias);
933
- if (a.trim().length === 0) {
934
- throw new Error("alias is required and cannot be empty");
889
+ if (typeof alias !== "string") {
890
+ throw new Error(`Invalid alias: expected string, got ${typeof alias}`);
891
+ }
892
+ const a = alias.trim();
893
+ if (a.length === 0) {
894
+ throw new Error("Invalid alias: required and cannot be empty");
895
+ }
896
+ if (a !== alias) {
897
+ throw new Error("Invalid alias: leading/trailing whitespace");
935
898
  }
936
- if (containsControlChars(a) || a.includes(";")) {
937
- throw new Error(`alias contains unsafe characters: ${JSON.stringify(a)}`);
899
+ if (/[\u0000-\u001F\u007F]/.test(a)) {
900
+ throw new Error(
901
+ "Invalid alias: contains unsafe characters (control characters)"
902
+ );
903
+ }
904
+ if (a.includes('"') || a.includes("'") || a.includes("`")) {
905
+ throw new Error("Invalid alias: contains unsafe characters (quotes)");
906
+ }
907
+ if (a.includes(";")) {
908
+ throw new Error("Invalid alias: contains unsafe characters (semicolon)");
909
+ }
910
+ if (a.includes("--") || a.includes("/*") || a.includes("*/")) {
911
+ throw new Error(
912
+ "Invalid alias: contains unsafe characters (SQL comment tokens)"
913
+ );
914
+ }
915
+ if (/\s/.test(a)) {
916
+ throw new Error(
917
+ "Invalid alias: must be a simple identifier without whitespace"
918
+ );
919
+ }
920
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(a)) {
921
+ throw new Error(
922
+ `Invalid alias: must be a simple identifier (alphanumeric with underscores): "${alias}"`
923
+ );
938
924
  }
939
- if (!/^[A-Za-z_]\w*$/.test(a)) {
925
+ const lowered = a.toLowerCase();
926
+ if (ALIAS_FORBIDDEN_KEYWORDS.has(lowered)) {
940
927
  throw new Error(
941
- `alias must be a simple identifier, got: ${JSON.stringify(a)}`
928
+ `Invalid alias: '${alias}' is a SQL keyword that would break query parsing. Forbidden aliases: ${[...ALIAS_FORBIDDEN_KEYWORDS].join(", ")}`
942
929
  );
943
930
  }
944
931
  }
@@ -980,7 +967,9 @@ function isValidRelationField(field) {
980
967
  if (fk.length === 0) return false;
981
968
  const refsRaw = field.references;
982
969
  const refs = normalizeKeyList(refsRaw);
983
- if (refs.length === 0) return false;
970
+ if (refs.length === 0) {
971
+ return fk.length === 1;
972
+ }
984
973
  if (refs.length !== fk.length) return false;
985
974
  return true;
986
975
  }
@@ -995,6 +984,8 @@ function getReferenceFieldNames(field, foreignKeyCount) {
995
984
  return refs;
996
985
  }
997
986
  function joinCondition(field, parentModel, childModel, parentAlias, childAlias) {
987
+ assertSafeAlias(parentAlias);
988
+ assertSafeAlias(childAlias);
998
989
  const fkFields = normalizeKeyList(field.foreignKey);
999
990
  if (fkFields.length === 0) {
1000
991
  throw createError(
@@ -1144,6 +1135,66 @@ function normalizeOrderByInput(orderBy, parseValue) {
1144
1135
  throw new Error("orderBy must be an object or array of objects");
1145
1136
  }
1146
1137
 
1138
+ // src/builder/shared/order-by-determinism.ts
1139
+ function modelHasScalarId(model) {
1140
+ if (!model) return false;
1141
+ return getScalarFieldSet(model).has("id");
1142
+ }
1143
+ function hasIdTiebreaker(orderBy, parse) {
1144
+ if (!isNotNullish(orderBy)) return false;
1145
+ const normalized = normalizeOrderByInput(orderBy, parse);
1146
+ return normalized.some(
1147
+ (obj) => Object.prototype.hasOwnProperty.call(obj, "id")
1148
+ );
1149
+ }
1150
+ function addIdTiebreaker(orderBy) {
1151
+ if (Array.isArray(orderBy)) return [...orderBy, { id: "asc" }];
1152
+ return [orderBy, { id: "asc" }];
1153
+ }
1154
+ function ensureDeterministicOrderByInput(args) {
1155
+ const { orderBy, model, parseValue } = args;
1156
+ if (!modelHasScalarId(model)) return orderBy;
1157
+ if (!isNotNullish(orderBy)) {
1158
+ return { id: "asc" };
1159
+ }
1160
+ if (hasIdTiebreaker(orderBy, parseValue)) return orderBy;
1161
+ return addIdTiebreaker(orderBy);
1162
+ }
1163
+
1164
+ // src/builder/shared/validators/field-assertions.ts
1165
+ var NUMERIC_TYPES = /* @__PURE__ */ new Set(["Int", "Float", "Decimal", "BigInt"]);
1166
+ function assertScalarField(model, fieldName, context) {
1167
+ const field = getFieldInfo(model, fieldName);
1168
+ if (!field) {
1169
+ throw createError(
1170
+ `${context} references unknown field '${fieldName}' on model ${model.name}`,
1171
+ {
1172
+ field: fieldName,
1173
+ modelName: model.name,
1174
+ availableFields: model.fields.map((f) => f.name)
1175
+ }
1176
+ );
1177
+ }
1178
+ if (field.isRelation) {
1179
+ throw createError(
1180
+ `${context} does not support relation field '${fieldName}'`,
1181
+ { field: fieldName, modelName: model.name }
1182
+ );
1183
+ }
1184
+ return field;
1185
+ }
1186
+ function assertNumericField(model, fieldName, context) {
1187
+ const field = assertScalarField(model, fieldName, context);
1188
+ const baseType = field.type.replace(/\[\]|\?/g, "");
1189
+ if (!NUMERIC_TYPES.has(baseType)) {
1190
+ throw createError(
1191
+ `${context} requires numeric field, got '${field.type}'`,
1192
+ { field: fieldName, modelName: model.name }
1193
+ );
1194
+ }
1195
+ return field;
1196
+ }
1197
+
1147
1198
  // src/builder/pagination.ts
1148
1199
  var MAX_LIMIT_OFFSET = 2147483647;
1149
1200
  function parseDirectionRaw(raw, errorLabel) {
@@ -1184,30 +1235,31 @@ function parseOrderByValue(v, fieldName) {
1184
1235
  assertAllowedOrderByKeys(obj, fieldName);
1185
1236
  return { direction, nulls };
1186
1237
  }
1187
- function normalizeFiniteInteger(name, v) {
1188
- if (typeof v !== "number" || !Number.isFinite(v) || !Number.isInteger(v)) {
1189
- throw new Error(`${name} must be an integer`);
1190
- }
1191
- return v;
1192
- }
1193
1238
  function normalizeNonNegativeInt(name, v) {
1194
1239
  if (schemaParser.isDynamicParameter(v)) return v;
1195
- const n = normalizeFiniteInteger(name, v);
1196
- if (n < 0) {
1197
- throw new Error(`${name} must be >= 0`);
1198
- }
1199
- if (n > MAX_LIMIT_OFFSET) {
1200
- throw new Error(`${name} must be <= ${MAX_LIMIT_OFFSET}`);
1201
- }
1202
- return n;
1240
+ const result = normalizeIntLike(name, v, {
1241
+ min: 0,
1242
+ max: MAX_LIMIT_OFFSET,
1243
+ allowZero: true
1244
+ });
1245
+ if (result === void 0)
1246
+ throw new Error(`${name} normalization returned undefined`);
1247
+ return result;
1248
+ }
1249
+ function normalizeIntAllowNegative(name, v) {
1250
+ if (schemaParser.isDynamicParameter(v)) return v;
1251
+ const result = normalizeIntLike(name, v, {
1252
+ min: Number.MIN_SAFE_INTEGER,
1253
+ max: MAX_LIMIT_OFFSET,
1254
+ allowZero: true
1255
+ });
1256
+ if (result === void 0)
1257
+ throw new Error(`${name} normalization returned undefined`);
1258
+ return result;
1203
1259
  }
1204
1260
  function hasNonNullishProp(v, key) {
1205
1261
  return isPlainObject(v) && key in v && isNotNullish(v[key]);
1206
1262
  }
1207
- function normalizeIntegerOrDynamic(name, v) {
1208
- if (schemaParser.isDynamicParameter(v)) return v;
1209
- return normalizeFiniteInteger(name, v);
1210
- }
1211
1263
  function readSkipTake(relArgs) {
1212
1264
  const hasSkip = hasNonNullishProp(relArgs, "skip");
1213
1265
  const hasTake = hasNonNullishProp(relArgs, "take");
@@ -1221,7 +1273,7 @@ function readSkipTake(relArgs) {
1221
1273
  }
1222
1274
  const obj = relArgs;
1223
1275
  const skipVal = hasSkip ? normalizeNonNegativeInt("skip", obj.skip) : void 0;
1224
- const takeVal = hasTake ? normalizeIntegerOrDynamic("take", obj.take) : void 0;
1276
+ const takeVal = hasTake ? normalizeIntAllowNegative("take", obj.take) : void 0;
1225
1277
  return { hasSkip, hasTake, skipVal, takeVal };
1226
1278
  }
1227
1279
  function buildOrderByFragment(entries, alias, dialect, model) {
@@ -1247,9 +1299,7 @@ function buildOrderByFragment(entries, alias, dialect, model) {
1247
1299
  return out.join(SQL_SEPARATORS.ORDER_BY);
1248
1300
  }
1249
1301
  function defaultNullsFor(dialect, direction) {
1250
- if (dialect === "postgres") {
1251
- return direction === "asc" ? "last" : "first";
1252
- }
1302
+ if (dialect === "postgres") return direction === "asc" ? "last" : "first";
1253
1303
  return direction === "asc" ? "first" : "last";
1254
1304
  }
1255
1305
  function ensureCursorFieldsInOrder(orderEntries, cursorEntries) {
@@ -1266,13 +1316,12 @@ function ensureCursorFieldsInOrder(orderEntries, cursorEntries) {
1266
1316
  }
1267
1317
  function buildCursorFilterParts(cursor, cursorAlias, params, model) {
1268
1318
  const entries = Object.entries(cursor);
1269
- if (entries.length === 0) {
1319
+ if (entries.length === 0)
1270
1320
  throw new Error("cursor must have at least one field");
1271
- }
1272
1321
  const placeholdersByField = /* @__PURE__ */ new Map();
1273
1322
  const parts = [];
1274
1323
  for (const [field, value] of entries) {
1275
- const c = `${cursorAlias}.${quote(field)}`;
1324
+ const c = `${cursorAlias}.${quoteColumn(model, field)}`;
1276
1325
  if (value === null) {
1277
1326
  parts.push(`${c} IS NULL`);
1278
1327
  continue;
@@ -1286,13 +1335,6 @@ function buildCursorFilterParts(cursor, cursorAlias, params, model) {
1286
1335
  placeholdersByField
1287
1336
  };
1288
1337
  }
1289
- function cursorValueExpr(tableName, cursorAlias, cursorWhereSql, field, model) {
1290
- const colName = quote(field);
1291
- return `(SELECT ${cursorAlias}.${colName} ${SQL_TEMPLATES.FROM} ${tableName} ${cursorAlias} ${SQL_TEMPLATES.WHERE} ${cursorWhereSql} ${SQL_TEMPLATES.LIMIT} 1)`;
1292
- }
1293
- function buildCursorRowExistsExpr(tableName, cursorAlias, cursorWhereSql) {
1294
- return `EXISTS (${SQL_TEMPLATES.SELECT} 1 ${SQL_TEMPLATES.FROM} ${tableName} ${cursorAlias} ${SQL_TEMPLATES.WHERE} ${cursorWhereSql} ${SQL_TEMPLATES.LIMIT} 1)`;
1295
- }
1296
1338
  function buildCursorEqualityExpr(columnExpr, valueExpr) {
1297
1339
  return `((${valueExpr} IS NULL AND ${columnExpr} IS NULL) OR (${valueExpr} IS NOT NULL AND ${columnExpr} = ${valueExpr}))`;
1298
1340
  }
@@ -1329,26 +1371,70 @@ function buildOrderEntries(orderBy) {
1329
1371
  if (typeof value === "string") {
1330
1372
  entries.push({ field, direction: value });
1331
1373
  } else {
1332
- entries.push({
1333
- field,
1334
- direction: value.sort,
1335
- nulls: value.nulls
1336
- });
1374
+ entries.push({ field, direction: value.direction, nulls: value.nulls });
1337
1375
  }
1338
1376
  }
1339
1377
  }
1340
1378
  return entries;
1341
1379
  }
1380
+ function buildCursorCteSelectList(cursorEntries, orderEntries, model) {
1381
+ const seen = /* @__PURE__ */ new Set();
1382
+ const ordered = [];
1383
+ for (const [f] of cursorEntries) {
1384
+ if (!seen.has(f)) {
1385
+ seen.add(f);
1386
+ ordered.push(f);
1387
+ }
1388
+ }
1389
+ for (const e of orderEntries) {
1390
+ if (!seen.has(e.field)) {
1391
+ seen.add(e.field);
1392
+ ordered.push(e.field);
1393
+ }
1394
+ }
1395
+ if (ordered.length === 0) throw new Error("cursor cte select list is empty");
1396
+ return ordered.map((f) => quoteColumn(model, f)).join(SQL_SEPARATORS.FIELD_LIST);
1397
+ }
1398
+ function truncateIdent(name, maxLen) {
1399
+ const s = String(name);
1400
+ if (s.length <= maxLen) return s;
1401
+ return s.slice(0, maxLen);
1402
+ }
1403
+ function buildCursorNames(outerAlias) {
1404
+ const maxLen = 63;
1405
+ const base = outerAlias.toLowerCase();
1406
+ const cteName = truncateIdent(`__tp_cursor_${base}`, maxLen);
1407
+ const srcAlias = truncateIdent(`__tp_cursor_src_${base}`, maxLen);
1408
+ if (cteName === outerAlias || srcAlias === outerAlias) {
1409
+ return {
1410
+ cteName: truncateIdent(`__tp_cursor_${base}_x`, maxLen),
1411
+ srcAlias: truncateIdent(`__tp_cursor_src_${base}_x`, maxLen)
1412
+ };
1413
+ }
1414
+ return { cteName, srcAlias };
1415
+ }
1416
+ function assertCursorAndOrderFieldsScalar(model, cursor, orderEntries) {
1417
+ if (!model) return;
1418
+ for (const k of Object.keys(cursor)) assertScalarField(model, k, "cursor");
1419
+ for (const e of orderEntries) assertScalarField(model, e.field, "orderBy");
1420
+ }
1342
1421
  function buildCursorCondition(cursor, orderBy, tableName, alias, params, dialect, model) {
1343
1422
  var _a;
1423
+ assertSafeTableRef(tableName);
1424
+ assertSafeAlias(alias);
1344
1425
  const d = dialect != null ? dialect : getGlobalDialect();
1345
1426
  const cursorEntries = Object.entries(cursor);
1346
- if (cursorEntries.length === 0) {
1427
+ if (cursorEntries.length === 0)
1347
1428
  throw new Error("cursor must have at least one field");
1348
- }
1349
- const cursorAlias = "__tp_cursor_src";
1350
- const { whereSql: cursorWhereSql, placeholdersByField } = buildCursorFilterParts(cursor, cursorAlias, params);
1351
- let orderEntries = buildOrderEntries(orderBy);
1429
+ const { cteName, srcAlias } = buildCursorNames(alias);
1430
+ assertSafeAlias(cteName);
1431
+ assertSafeAlias(srcAlias);
1432
+ const deterministicOrderBy = ensureDeterministicOrderByInput({
1433
+ orderBy,
1434
+ model,
1435
+ parseValue: parseOrderByValue
1436
+ });
1437
+ let orderEntries = buildOrderEntries(deterministicOrderBy);
1352
1438
  if (orderEntries.length === 0) {
1353
1439
  orderEntries = cursorEntries.map(([field]) => ({
1354
1440
  field,
@@ -1357,11 +1443,23 @@ function buildCursorCondition(cursor, orderBy, tableName, alias, params, dialect
1357
1443
  } else {
1358
1444
  orderEntries = ensureCursorFieldsInOrder(orderEntries, cursorEntries);
1359
1445
  }
1360
- const existsExpr = buildCursorRowExistsExpr(
1361
- tableName,
1362
- cursorAlias,
1363
- cursorWhereSql
1446
+ assertCursorAndOrderFieldsScalar(model, cursor, orderEntries);
1447
+ const { whereSql: cursorWhereSql, placeholdersByField } = buildCursorFilterParts(cursor, srcAlias, params, model);
1448
+ const cursorOrderBy = orderEntries.map(
1449
+ (e) => `${srcAlias}.${quoteColumn(model, e.field)} ${e.direction.toUpperCase()}`
1450
+ ).join(", ");
1451
+ const selectList = buildCursorCteSelectList(
1452
+ cursorEntries,
1453
+ orderEntries,
1454
+ model
1364
1455
  );
1456
+ const cte = `${cteName} AS (
1457
+ SELECT ${selectList} FROM ${tableName} ${srcAlias}
1458
+ WHERE ${cursorWhereSql}
1459
+ ORDER BY ${cursorOrderBy}
1460
+ LIMIT 1
1461
+ )`;
1462
+ const existsExpr = `EXISTS (SELECT 1 FROM ${cteName})`;
1365
1463
  const outerCursorMatch = buildOuterCursorMatch(
1366
1464
  cursor,
1367
1465
  alias,
@@ -1369,34 +1467,29 @@ function buildCursorCondition(cursor, orderBy, tableName, alias, params, dialect
1369
1467
  params,
1370
1468
  model
1371
1469
  );
1470
+ const getValueExpr = (field) => `(SELECT ${quoteColumn(model, field)} FROM ${cteName})`;
1372
1471
  const orClauses = [];
1373
1472
  for (let level = 0; level < orderEntries.length; level++) {
1374
1473
  const andParts = [];
1375
1474
  for (let i = 0; i < level; i++) {
1376
1475
  const e2 = orderEntries[i];
1377
1476
  const c2 = col(alias, e2.field, model);
1378
- const v2 = cursorValueExpr(
1379
- tableName,
1380
- cursorAlias,
1381
- cursorWhereSql,
1382
- e2.field);
1477
+ const v2 = getValueExpr(e2.field);
1383
1478
  andParts.push(buildCursorEqualityExpr(c2, v2));
1384
1479
  }
1385
1480
  const e = orderEntries[level];
1386
1481
  const c = col(alias, e.field, model);
1387
- const v = cursorValueExpr(
1388
- tableName,
1389
- cursorAlias,
1390
- cursorWhereSql,
1391
- e.field);
1482
+ const v = getValueExpr(e.field);
1392
1483
  const nulls = (_a = e.nulls) != null ? _a : defaultNullsFor(d, e.direction);
1393
1484
  andParts.push(buildCursorInequalityExpr(c, e.direction, nulls, v));
1394
1485
  orClauses.push(`(${andParts.join(SQL_SEPARATORS.CONDITION_AND)})`);
1395
1486
  }
1396
1487
  const exclusive = orClauses.join(SQL_SEPARATORS.CONDITION_OR);
1397
- return `(${existsExpr} ${SQL_SEPARATORS.CONDITION_AND} ((${exclusive})${SQL_SEPARATORS.CONDITION_OR}(${outerCursorMatch})))`;
1488
+ const condition = `(${existsExpr} ${SQL_SEPARATORS.CONDITION_AND} ((${exclusive})${SQL_SEPARATORS.CONDITION_OR}(${outerCursorMatch})))`;
1489
+ return { cte, condition };
1398
1490
  }
1399
1491
  function buildOrderBy(orderBy, alias, dialect, model) {
1492
+ assertSafeAlias(alias);
1400
1493
  const entries = buildOrderEntries(orderBy);
1401
1494
  if (entries.length === 0) return "";
1402
1495
  const d = dialect != null ? dialect : getGlobalDialect();
@@ -1418,9 +1511,7 @@ function normalizeTakeLike(v) {
1418
1511
  max: MAX_LIMIT_OFFSET,
1419
1512
  allowZero: true
1420
1513
  });
1421
- if (typeof n === "number") {
1422
- if (n === 0) return 0;
1423
- }
1514
+ if (typeof n === "number" && n === 0) return 0;
1424
1515
  return n;
1425
1516
  }
1426
1517
  function normalizeSkipLike(v) {
@@ -1496,6 +1587,11 @@ function buildScalarOperator(expr, op, val, params, mode, fieldType, dialect) {
1496
1587
  }
1497
1588
  return handleInOperator(expr, op, val, params, dialect);
1498
1589
  }
1590
+ if (op === Ops.EQUALS && mode === Modes.INSENSITIVE && !isNotNullish(dialect)) {
1591
+ throw createError(`Insensitive equals requires a SQL dialect`, {
1592
+ operator: op
1593
+ });
1594
+ }
1499
1595
  return handleComparisonOperator(expr, op, val, params);
1500
1596
  }
1501
1597
  function handleNullValue(expr, op) {
@@ -1511,6 +1607,28 @@ function normalizeMode(v) {
1511
1607
  function handleNotOperator(expr, val, params, outerMode, fieldType, dialect) {
1512
1608
  const innerMode = normalizeMode(val.mode);
1513
1609
  const effectiveMode = innerMode != null ? innerMode : outerMode;
1610
+ const entries = Object.entries(val).filter(
1611
+ ([k, v]) => k !== "mode" && v !== void 0
1612
+ );
1613
+ if (entries.length === 0) return "";
1614
+ if (!isNotNullish(dialect)) {
1615
+ const clauses = [];
1616
+ for (const [subOp, subVal] of entries) {
1617
+ const sub = buildScalarOperator(
1618
+ expr,
1619
+ subOp,
1620
+ subVal,
1621
+ params,
1622
+ effectiveMode,
1623
+ fieldType,
1624
+ void 0
1625
+ );
1626
+ if (sub && sub.trim().length > 0) clauses.push(`(${sub})`);
1627
+ }
1628
+ if (clauses.length === 0) return "";
1629
+ if (clauses.length === 1) return `${SQL_TEMPLATES.NOT} ${clauses[0]}`;
1630
+ return `${SQL_TEMPLATES.NOT} (${clauses.join(` ${SQL_TEMPLATES.AND} `)})`;
1631
+ }
1514
1632
  return buildNotComposite(
1515
1633
  expr,
1516
1634
  val,
@@ -1725,6 +1843,7 @@ function handleArrayIsEmpty(expr, val, dialect) {
1725
1843
 
1726
1844
  // src/builder/where/operators-json.ts
1727
1845
  var SAFE_JSON_PATH_SEGMENT = /^[a-zA-Z_]\w*$/;
1846
+ var MAX_PATH_SEGMENT_LENGTH = 255;
1728
1847
  function validateJsonPathSegments(segments) {
1729
1848
  for (const segment of segments) {
1730
1849
  if (typeof segment !== "string") {
@@ -1733,6 +1852,12 @@ function validateJsonPathSegments(segments) {
1733
1852
  value: segment
1734
1853
  });
1735
1854
  }
1855
+ if (segment.length > MAX_PATH_SEGMENT_LENGTH) {
1856
+ throw createError(
1857
+ `JSON path segment too long: max ${MAX_PATH_SEGMENT_LENGTH} characters`,
1858
+ { operator: Ops.PATH, value: `[${segment.length} chars]` }
1859
+ );
1860
+ }
1736
1861
  if (!SAFE_JSON_PATH_SEGMENT.test(segment)) {
1737
1862
  throw createError(
1738
1863
  `Invalid JSON path segment: '${segment}'. Must be alphanumeric with underscores, starting with letter or underscore.`,
@@ -1821,6 +1946,9 @@ function handleJsonWildcard(expr, op, val, params, wildcards, dialect) {
1821
1946
 
1822
1947
  // src/builder/where/relations.ts
1823
1948
  var NO_JOINS = Object.freeze([]);
1949
+ function freezeJoins(items) {
1950
+ return Object.freeze([...items]);
1951
+ }
1824
1952
  function isListRelation(fieldType) {
1825
1953
  return typeof fieldType === "string" && fieldType.endsWith("[]");
1826
1954
  }
@@ -1883,7 +2011,7 @@ function buildListRelationFilters(args) {
1883
2011
  const whereClause = `${relAlias}.${quote(checkField.name)} IS NULL`;
1884
2012
  return Object.freeze({
1885
2013
  clause: whereClause,
1886
- joins: [leftJoinSql]
2014
+ joins: freezeJoins([leftJoinSql])
1887
2015
  });
1888
2016
  }
1889
2017
  }
@@ -2088,7 +2216,7 @@ function assertValidOperator(fieldName, op, fieldType, path, modelName) {
2088
2216
  Ops.HAS_EVERY,
2089
2217
  Ops.IS_EMPTY
2090
2218
  ]);
2091
- const JSON_OPS = /* @__PURE__ */ new Set([
2219
+ const JSON_OPS2 = /* @__PURE__ */ new Set([
2092
2220
  Ops.PATH,
2093
2221
  Ops.STRING_CONTAINS,
2094
2222
  Ops.STRING_STARTS_WITH,
@@ -2105,7 +2233,7 @@ function assertValidOperator(fieldName, op, fieldType, path, modelName) {
2105
2233
  modelName
2106
2234
  });
2107
2235
  }
2108
- const isJsonOp = JSON_OPS.has(op);
2236
+ const isJsonOp = JSON_OPS2.has(op);
2109
2237
  const isFieldJson = isJsonType(fieldType);
2110
2238
  const jsonOpMismatch = isJsonOp && !isFieldJson;
2111
2239
  if (jsonOpMismatch) {
@@ -2119,6 +2247,14 @@ function assertValidOperator(fieldName, op, fieldType, path, modelName) {
2119
2247
  }
2120
2248
 
2121
2249
  // src/builder/where/builder.ts
2250
+ var MAX_QUERY_DEPTH = 50;
2251
+ var EMPTY_JOINS = Object.freeze([]);
2252
+ var JSON_OPS = /* @__PURE__ */ new Set([
2253
+ Ops.PATH,
2254
+ Ops.STRING_CONTAINS,
2255
+ Ops.STRING_STARTS_WITH,
2256
+ Ops.STRING_ENDS_WITH
2257
+ ]);
2122
2258
  var WhereBuilder = class {
2123
2259
  build(where, ctx) {
2124
2260
  if (!isPlainObject(where)) {
@@ -2130,8 +2266,6 @@ var WhereBuilder = class {
2130
2266
  return buildWhereInternal(where, ctx, this);
2131
2267
  }
2132
2268
  };
2133
- var MAX_QUERY_DEPTH = 50;
2134
- var EMPTY_JOINS = Object.freeze([]);
2135
2269
  var whereBuilderInstance = new WhereBuilder();
2136
2270
  function freezeResult(clause, joins = EMPTY_JOINS) {
2137
2271
  return Object.freeze({ clause, joins });
@@ -2308,16 +2442,8 @@ function buildOperator(expr, op, val, ctx, mode, fieldType) {
2308
2442
  if (fieldType && isArrayType(fieldType)) {
2309
2443
  return buildArrayOperator(expr, op, val, ctx.params, fieldType, ctx.dialect);
2310
2444
  }
2311
- if (fieldType && isJsonType(fieldType)) {
2312
- const JSON_OPS = /* @__PURE__ */ new Set([
2313
- Ops.PATH,
2314
- Ops.STRING_CONTAINS,
2315
- Ops.STRING_STARTS_WITH,
2316
- Ops.STRING_ENDS_WITH
2317
- ]);
2318
- if (JSON_OPS.has(op)) {
2319
- return buildJsonOperator(expr, op, val, ctx.params, ctx.dialect);
2320
- }
2445
+ if (fieldType && isJsonType(fieldType) && JSON_OPS.has(op)) {
2446
+ return buildJsonOperator(expr, op, val, ctx.params, ctx.dialect);
2321
2447
  }
2322
2448
  return buildScalarOperator(
2323
2449
  expr,
@@ -2338,7 +2464,7 @@ function toSafeSqlIdentifier(input) {
2338
2464
  const base = startsOk ? cleaned : `_${cleaned}`;
2339
2465
  const fallback = base.length > 0 ? base : "_t";
2340
2466
  const lowered = fallback.toLowerCase();
2341
- return SQL_KEYWORDS.has(lowered) ? `_${lowered}` : lowered;
2467
+ return ALIAS_FORBIDDEN_KEYWORDS.has(lowered) ? `_${lowered}` : lowered;
2342
2468
  }
2343
2469
  function createAliasGenerator(maxAliases = 1e4) {
2344
2470
  let counter = 0;
@@ -2548,6 +2674,7 @@ function toPublicResult(clause, joins, params) {
2548
2674
  // src/builder/where.ts
2549
2675
  function buildWhereClause(where, options) {
2550
2676
  var _a, _b, _c, _d, _e;
2677
+ assertSafeAlias(options.alias);
2551
2678
  const dialect = options.dialect || getGlobalDialect();
2552
2679
  const params = (_a = options.params) != null ? _a : createParamStore();
2553
2680
  const ctx = {
@@ -2563,22 +2690,6 @@ function buildWhereClause(where, options) {
2563
2690
  };
2564
2691
  const result = whereBuilderInstance.build(where, ctx);
2565
2692
  const publicResult = toPublicResult(result.clause, result.joins, params);
2566
- if (!options.isSubquery) {
2567
- const nums = [...publicResult.clause.matchAll(/\$(\d+)/g)].map(
2568
- (m) => parseInt(m[1], 10)
2569
- );
2570
- if (nums.length > 0) {
2571
- const min = Math.min(...nums);
2572
- if (min === 1) {
2573
- validateParamConsistency(publicResult.clause, publicResult.params);
2574
- } else {
2575
- validateParamConsistencyFragment(
2576
- publicResult.clause,
2577
- publicResult.params
2578
- );
2579
- }
2580
- }
2581
- }
2582
2693
  return publicResult;
2583
2694
  }
2584
2695
 
@@ -2716,6 +2827,9 @@ function buildRelationSelect(relArgs, relModel, relAlias) {
2716
2827
  }
2717
2828
 
2718
2829
  // src/builder/select/includes.ts
2830
+ var MAX_INCLUDE_DEPTH = 10;
2831
+ var MAX_TOTAL_SUBQUERIES = 100;
2832
+ var MAX_TOTAL_INCLUDES = 50;
2719
2833
  function getRelationTableReference(relModel, dialect) {
2720
2834
  return buildTableReference(
2721
2835
  SQL_TEMPLATES.PUBLIC_SCHEMA,
@@ -2761,107 +2875,23 @@ function relationEntriesFromArgs(args, model) {
2761
2875
  pushFrom(args.select);
2762
2876
  return out;
2763
2877
  }
2764
- function assertScalarField(model, fieldName) {
2765
- const f = model.fields.find((x) => x.name === fieldName);
2766
- if (!f) {
2767
- throw new Error(
2768
- `orderBy references unknown field '${fieldName}' on model ${model.name}`
2769
- );
2770
- }
2771
- if (f.isRelation) {
2772
- throw new Error(
2773
- `orderBy does not support relation field '${fieldName}' on model ${model.name}`
2774
- );
2775
- }
2776
- }
2777
- function validateOrderByDirection(fieldName, v) {
2778
- const s = String(v).toLowerCase();
2779
- if (s !== "asc" && s !== "desc") {
2780
- throw new Error(
2781
- `Invalid orderBy direction for '${fieldName}': ${String(v)}`
2782
- );
2783
- }
2784
- }
2785
- function validateOrderByObject(fieldName, v) {
2786
- if (!("sort" in v)) {
2787
- throw new Error(
2788
- `orderBy for '${fieldName}' must be 'asc' | 'desc' or { sort, nulls? }`
2789
- );
2790
- }
2791
- validateOrderByDirection(fieldName, v.sort);
2792
- if ("nulls" in v && isNotNullish(v.nulls)) {
2793
- const n = String(v.nulls).toLowerCase();
2794
- if (n !== "first" && n !== "last") {
2795
- throw new Error(
2796
- `Invalid orderBy.nulls for '${fieldName}': ${String(v.nulls)}`
2797
- );
2798
- }
2799
- }
2800
- const allowed = /* @__PURE__ */ new Set(["sort", "nulls"]);
2801
- for (const k of Object.keys(v)) {
2802
- if (!allowed.has(k)) {
2803
- throw new Error(`Unsupported orderBy key '${k}' for field '${fieldName}'`);
2804
- }
2805
- }
2806
- }
2807
- function normalizeOrderByFieldName(name) {
2808
- const fieldName = String(name).trim();
2809
- if (fieldName.length === 0) {
2810
- throw new Error("orderBy field name cannot be empty");
2811
- }
2812
- return fieldName;
2813
- }
2814
- function requirePlainObjectForOrderByEntry(v) {
2815
- if (typeof v !== "object" || v === null || Array.isArray(v)) {
2816
- throw new Error("orderBy array entries must be objects");
2817
- }
2818
- return v;
2819
- }
2820
- function parseSingleFieldOrderByObject(obj) {
2821
- const entries = Object.entries(obj);
2822
- if (entries.length !== 1) {
2823
- throw new Error("orderBy array entries must have exactly one field");
2824
- }
2825
- const fieldName = normalizeOrderByFieldName(entries[0][0]);
2826
- return [fieldName, entries[0][1]];
2827
- }
2828
- function parseOrderByArray(orderBy) {
2829
- return orderBy.map(
2830
- (item) => parseSingleFieldOrderByObject(requirePlainObjectForOrderByEntry(item))
2831
- );
2832
- }
2833
- function parseOrderByObject(orderBy) {
2834
- const out = [];
2835
- for (const [k, v] of Object.entries(orderBy)) {
2836
- out.push([normalizeOrderByFieldName(k), v]);
2837
- }
2838
- return out;
2839
- }
2840
- function getOrderByEntries(orderBy) {
2841
- if (!isNotNullish(orderBy)) return [];
2842
- if (Array.isArray(orderBy)) {
2843
- return parseOrderByArray(orderBy);
2844
- }
2845
- if (typeof orderBy === "object" && orderBy !== null) {
2846
- return parseOrderByObject(orderBy);
2847
- }
2848
- throw new Error("orderBy must be an object or array of objects");
2849
- }
2850
2878
  function validateOrderByForModel(model, orderBy) {
2851
- const entries = getOrderByEntries(orderBy);
2852
- for (const [fieldName, v] of entries) {
2853
- assertScalarField(model, fieldName);
2854
- if (typeof v === "string") {
2855
- validateOrderByDirection(fieldName, v);
2856
- continue;
2879
+ if (!isNotNullish(orderBy)) return;
2880
+ const scalarSet = getScalarFieldSet(model);
2881
+ const normalized = normalizeOrderByInput(orderBy, parseOrderByValue);
2882
+ for (const item of normalized) {
2883
+ const entries = Object.entries(item);
2884
+ if (entries.length !== 1) {
2885
+ throw new Error("orderBy array entries must have exactly one field");
2857
2886
  }
2858
- if (isPlainObject(v)) {
2859
- validateOrderByObject(fieldName, v);
2860
- continue;
2887
+ const fieldName = String(entries[0][0]).trim();
2888
+ if (fieldName.length === 0)
2889
+ throw new Error("orderBy field name cannot be empty");
2890
+ if (!scalarSet.has(fieldName)) {
2891
+ throw new Error(
2892
+ `orderBy references unknown or non-scalar field '${fieldName}' on model ${model.name}`
2893
+ );
2861
2894
  }
2862
- throw new Error(
2863
- `orderBy for '${fieldName}' must be 'asc' | 'desc' or { sort, nulls? }`
2864
- );
2865
2895
  }
2866
2896
  }
2867
2897
  function appendLimitOffset(sql, dialect, params, takeVal, skipVal, scope) {
@@ -2890,7 +2920,10 @@ function readWhereInput(relArgs) {
2890
2920
  function readOrderByInput(relArgs) {
2891
2921
  if (!isPlainObject(relArgs)) return { hasOrderBy: false, orderBy: void 0 };
2892
2922
  if (!("orderBy" in relArgs)) return { hasOrderBy: false, orderBy: void 0 };
2893
- return { hasOrderBy: true, orderBy: relArgs.orderBy };
2923
+ return {
2924
+ hasOrderBy: true,
2925
+ orderBy: relArgs.orderBy
2926
+ };
2894
2927
  }
2895
2928
  function extractRelationPaginationConfig(relArgs) {
2896
2929
  const { hasOrderBy, orderBy: rawOrderByInput } = readOrderByInput(relArgs);
@@ -2912,44 +2945,25 @@ function extractRelationPaginationConfig(relArgs) {
2912
2945
  function maybeReverseNegativeTake(takeVal, hasOrderBy, orderByInput) {
2913
2946
  if (typeof takeVal !== "number") return { takeVal, orderByInput };
2914
2947
  if (takeVal >= 0) return { takeVal, orderByInput };
2915
- if (!hasOrderBy) {
2948
+ if (!hasOrderBy)
2916
2949
  throw new Error("Negative take requires orderBy for deterministic results");
2917
- }
2918
2950
  return {
2919
2951
  takeVal: Math.abs(takeVal),
2920
2952
  orderByInput: reverseOrderByInput(orderByInput)
2921
2953
  };
2922
2954
  }
2923
- function hasIdTiebreaker(orderByInput) {
2924
- const entries = Array.isArray(orderByInput) ? orderByInput : [orderByInput];
2925
- return entries.some(
2926
- (entry) => isPlainObject(entry) ? Object.prototype.hasOwnProperty.call(entry, "id") : false
2927
- );
2928
- }
2929
- function modelHasScalarId(relModel) {
2930
- const idField = relModel.fields.find((f) => f.name === "id");
2931
- return Boolean(idField && !idField.isRelation);
2932
- }
2933
- function addIdTiebreaker(orderByInput) {
2934
- if (Array.isArray(orderByInput)) return [...orderByInput, { id: "asc" }];
2935
- return [orderByInput, { id: "asc" }];
2936
- }
2937
- function ensureDeterministicOrderBy(relModel, hasOrderBy, orderByInput, hasPagination) {
2938
- if (!hasPagination) {
2939
- if (hasOrderBy && isNotNullish(orderByInput)) {
2940
- validateOrderByForModel(relModel, orderByInput);
2941
- }
2942
- return orderByInput;
2955
+ function finalizeOrderByForInclude(args) {
2956
+ if (args.hasOrderBy && isNotNullish(args.orderByInput)) {
2957
+ validateOrderByForModel(args.relModel, args.orderByInput);
2943
2958
  }
2944
- if (!hasOrderBy) {
2945
- return modelHasScalarId(relModel) ? { id: "asc" } : orderByInput;
2959
+ if (!args.hasPagination) {
2960
+ return args.orderByInput;
2946
2961
  }
2947
- if (isNotNullish(orderByInput)) {
2948
- validateOrderByForModel(relModel, orderByInput);
2949
- }
2950
- if (!modelHasScalarId(relModel)) return orderByInput;
2951
- if (hasIdTiebreaker(orderByInput)) return orderByInput;
2952
- return addIdTiebreaker(orderByInput);
2962
+ return ensureDeterministicOrderByInput({
2963
+ orderBy: args.hasOrderBy ? args.orderByInput : void 0,
2964
+ model: args.relModel,
2965
+ parseValue: parseOrderByValue
2966
+ });
2953
2967
  }
2954
2968
  function buildSelectWithNestedIncludes(relArgs, relModel, relAlias, ctx) {
2955
2969
  let relSelect = buildRelationSelect(relArgs, relModel, relAlias);
@@ -2960,7 +2974,10 @@ function buildSelectWithNestedIncludes(relArgs, relModel, relAlias, ctx) {
2960
2974
  relAlias,
2961
2975
  ctx.aliasGen,
2962
2976
  ctx.params,
2963
- ctx.dialect
2977
+ ctx.dialect,
2978
+ ctx.visitPath || [],
2979
+ (ctx.depth || 0) + 1,
2980
+ ctx.stats
2964
2981
  ) : [];
2965
2982
  if (isNonEmptyArray(nestedIncludes)) {
2966
2983
  const emptyJson = ctx.dialect === "postgres" ? `'[]'::json` : `json('[]')`;
@@ -3047,11 +3064,7 @@ function buildListIncludeSpec(args) {
3047
3064
  joinPredicate: args.joinPredicate,
3048
3065
  whereClause: args.whereClause
3049
3066
  });
3050
- return Object.freeze({
3051
- name: args.relName,
3052
- sql: sql2,
3053
- isOneToOne: false
3054
- });
3067
+ return Object.freeze({ name: args.relName, sql: sql2, isOneToOne: false });
3055
3068
  }
3056
3069
  const rowAlias = args.ctx.aliasGen.next(`${args.relName}_row`);
3057
3070
  let base = buildBaseSql({
@@ -3062,9 +3075,7 @@ function buildListIncludeSpec(args) {
3062
3075
  joinPredicate: args.joinPredicate,
3063
3076
  whereClause: args.whereClause
3064
3077
  });
3065
- if (args.orderBySql) {
3066
- base += ` ${SQL_TEMPLATES.ORDER_BY} ${args.orderBySql}`;
3067
- }
3078
+ if (args.orderBySql) base += ` ${SQL_TEMPLATES.ORDER_BY} ${args.orderBySql}`;
3068
3079
  base = appendLimitOffset(
3069
3080
  base,
3070
3081
  args.ctx.dialect,
@@ -3075,11 +3086,7 @@ function buildListIncludeSpec(args) {
3075
3086
  );
3076
3087
  const selectExpr = jsonAgg("row", args.ctx.dialect);
3077
3088
  const sql = `${SQL_TEMPLATES.SELECT} ${selectExpr} ${SQL_TEMPLATES.FROM} (${base}) ${rowAlias}`;
3078
- return Object.freeze({
3079
- name: args.relName,
3080
- sql,
3081
- isOneToOne: false
3082
- });
3089
+ return Object.freeze({ name: args.relName, sql, isOneToOne: false });
3083
3090
  }
3084
3091
  function buildSingleInclude(relName, relArgs, field, relModel, ctx) {
3085
3092
  const relTable = getRelationTableReference(relModel, ctx.dialect);
@@ -3110,12 +3117,12 @@ function buildSingleInclude(relName, relArgs, field, relModel, ctx) {
3110
3117
  paginationConfig.orderBy
3111
3118
  );
3112
3119
  const hasPagination = paginationConfig.hasSkip || paginationConfig.hasTake;
3113
- const finalOrderByInput = ensureDeterministicOrderBy(
3120
+ const finalOrderByInput = finalizeOrderByForInclude({
3114
3121
  relModel,
3115
- paginationConfig.hasOrderBy,
3116
- adjusted.orderByInput,
3122
+ hasOrderBy: paginationConfig.hasOrderBy,
3123
+ orderByInput: adjusted.orderByInput,
3117
3124
  hasPagination
3118
- );
3125
+ });
3119
3126
  const orderBySql = buildOrderBySql(
3120
3127
  finalOrderByInput,
3121
3128
  relAlias,
@@ -3136,11 +3143,7 @@ function buildSingleInclude(relName, relArgs, field, relModel, ctx) {
3136
3143
  skipVal: paginationConfig.skipVal,
3137
3144
  ctx
3138
3145
  });
3139
- return Object.freeze({
3140
- name: relName,
3141
- sql,
3142
- isOneToOne: true
3143
- });
3146
+ return Object.freeze({ name: relName, sql, isOneToOne: true });
3144
3147
  }
3145
3148
  return buildListIncludeSpec({
3146
3149
  relName,
@@ -3156,32 +3159,69 @@ function buildSingleInclude(relName, relArgs, field, relModel, ctx) {
3156
3159
  ctx
3157
3160
  });
3158
3161
  }
3159
- function buildIncludeSqlInternal(args, model, schemas, parentAlias, aliasGen, params, dialect) {
3162
+ function buildIncludeSqlInternal(args, model, schemas, parentAlias, aliasGen, params, dialect, visitPath = [], depth = 0, stats) {
3163
+ if (!stats) stats = { totalIncludes: 0, totalSubqueries: 0, maxDepth: 0 };
3164
+ if (depth > MAX_INCLUDE_DEPTH) {
3165
+ throw new Error(
3166
+ `Maximum include depth of ${MAX_INCLUDE_DEPTH} exceeded. Path: ${visitPath.join(" -> ")}. Deep includes cause exponential SQL complexity and performance issues.`
3167
+ );
3168
+ }
3169
+ stats.maxDepth = Math.max(stats.maxDepth, depth);
3160
3170
  const includes = [];
3161
3171
  const entries = relationEntriesFromArgs(args, model);
3162
3172
  for (const [relName, relArgs] of entries) {
3163
3173
  if (relArgs === false) continue;
3174
+ stats.totalIncludes++;
3175
+ if (stats.totalIncludes > MAX_TOTAL_INCLUDES) {
3176
+ throw new Error(
3177
+ `Maximum total includes (${MAX_TOTAL_INCLUDES}) exceeded. Current: ${stats.totalIncludes} includes. Query has ${stats.maxDepth} levels deep. Simplify your query structure or use multiple queries.`
3178
+ );
3179
+ }
3180
+ stats.totalSubqueries++;
3181
+ if (stats.totalSubqueries > MAX_TOTAL_SUBQUERIES) {
3182
+ throw new Error(
3183
+ `Query complexity limit exceeded: ${stats.totalSubqueries} subqueries generated. Maximum allowed: ${MAX_TOTAL_SUBQUERIES}. This indicates exponential include nesting. Stats: depth=${stats.maxDepth}, includes=${stats.totalIncludes}. Path: ${visitPath.join(" -> ")}. Simplify your include structure or split into multiple queries.`
3184
+ );
3185
+ }
3164
3186
  const resolved = resolveRelationOrThrow(model, schemas, relName);
3165
- const include = buildSingleInclude(
3166
- relName,
3167
- relArgs,
3168
- resolved.field,
3169
- resolved.relModel,
3170
- {
3187
+ const relationPath = `${model.name}.${relName}`;
3188
+ const currentPath = [...visitPath, relationPath];
3189
+ if (visitPath.includes(relationPath)) {
3190
+ throw new Error(
3191
+ `Circular include detected: ${currentPath.join(" -> ")}. Relation '${relationPath}' creates an infinite loop.`
3192
+ );
3193
+ }
3194
+ const modelOccurrences = currentPath.filter(
3195
+ (p) => p.startsWith(`${resolved.relModel.name}.`)
3196
+ ).length;
3197
+ if (modelOccurrences > 2) {
3198
+ throw new Error(
3199
+ `Include too deeply nested: model '${resolved.relModel.name}' appears ${modelOccurrences} times in path: ${currentPath.join(" -> ")}`
3200
+ );
3201
+ }
3202
+ includes.push(
3203
+ buildSingleInclude(relName, relArgs, resolved.field, resolved.relModel, {
3171
3204
  model,
3172
3205
  schemas,
3173
3206
  parentAlias,
3174
3207
  aliasGen,
3175
3208
  dialect,
3176
- params
3177
- }
3209
+ params,
3210
+ visitPath: currentPath,
3211
+ depth: depth + 1,
3212
+ stats
3213
+ })
3178
3214
  );
3179
- includes.push(include);
3180
3215
  }
3181
3216
  return includes;
3182
3217
  }
3183
3218
  function buildIncludeSql(args, model, schemas, parentAlias, params, dialect) {
3184
3219
  const aliasGen = createAliasGenerator();
3220
+ const stats = {
3221
+ totalIncludes: 0,
3222
+ totalSubqueries: 0,
3223
+ maxDepth: 0
3224
+ };
3185
3225
  return buildIncludeSqlInternal(
3186
3226
  args,
3187
3227
  model,
@@ -3189,7 +3229,10 @@ function buildIncludeSql(args, model, schemas, parentAlias, params, dialect) {
3189
3229
  parentAlias,
3190
3230
  aliasGen,
3191
3231
  params,
3192
- dialect
3232
+ dialect,
3233
+ [],
3234
+ 0,
3235
+ stats
3193
3236
  );
3194
3237
  }
3195
3238
  function resolveCountRelationOrThrow(relName, model, schemas) {
@@ -3200,10 +3243,14 @@ function resolveCountRelationOrThrow(relName, model, schemas) {
3200
3243
  );
3201
3244
  }
3202
3245
  const field = model.fields.find((f) => f.name === relName);
3203
- if (!field) {
3246
+ if (!field)
3204
3247
  throw new Error(
3205
3248
  `_count.${relName} references unknown relation on model ${model.name}`
3206
3249
  );
3250
+ if (!isValidRelationField(field)) {
3251
+ throw new Error(
3252
+ `_count.${relName} has invalid relation metadata on model ${model.name}`
3253
+ );
3207
3254
  }
3208
3255
  const relModel = schemas.find((m) => m.name === field.relatedModel);
3209
3256
  if (!relModel) {
@@ -3213,31 +3260,78 @@ function resolveCountRelationOrThrow(relName, model, schemas) {
3213
3260
  }
3214
3261
  return { field, relModel };
3215
3262
  }
3216
- function groupByColForCount(field, countAlias) {
3217
- const fkFields = normalizeKeyList(field.foreignKey);
3218
- const refFields = normalizeKeyList(field.references);
3219
- return field.isForeignKeyLocal ? `${countAlias}.${quote(refFields[0] || "id")}` : `${countAlias}.${quote(fkFields[0])}`;
3263
+ function defaultReferencesForCount(fkCount) {
3264
+ if (fkCount === 1) return ["id"];
3265
+ throw new Error(
3266
+ "Relation count for composite keys requires explicit references matching foreignKey length"
3267
+ );
3220
3268
  }
3221
- function leftJoinOnForCount(field, parentAlias, joinAlias) {
3269
+ function resolveCountKeyPairs(field) {
3222
3270
  const fkFields = normalizeKeyList(field.foreignKey);
3223
- const refFields = normalizeKeyList(field.references);
3224
- return field.isForeignKeyLocal ? `${joinAlias}.__fk = ${parentAlias}.${quote(fkFields[0])}` : `${joinAlias}.__fk = ${parentAlias}.${quote(refFields[0] || "id")}`;
3271
+ if (fkFields.length === 0)
3272
+ throw new Error("Relation count requires foreignKey");
3273
+ const refsRaw = field.references;
3274
+ const refs = normalizeKeyList(refsRaw);
3275
+ const refFields = refs.length > 0 ? refs : defaultReferencesForCount(fkFields.length);
3276
+ if (refFields.length !== fkFields.length) {
3277
+ throw new Error(
3278
+ "Relation count requires references count to match foreignKey count"
3279
+ );
3280
+ }
3281
+ const relKeyFields = field.isForeignKeyLocal ? refFields : fkFields;
3282
+ const parentKeyFields = field.isForeignKeyLocal ? fkFields : refFields;
3283
+ return { relKeyFields, parentKeyFields };
3284
+ }
3285
+ function aliasQualifiedColumn(alias, model, field) {
3286
+ return `${alias}.${quoteColumn(model, field)}`;
3287
+ }
3288
+ function subqueryForCount(args) {
3289
+ const selectKeys = args.relKeyFields.map(
3290
+ (f, i) => `${aliasQualifiedColumn(args.countAlias, args.relModel, f)} AS "__fk${i}"`
3291
+ ).join(SQL_SEPARATORS.FIELD_LIST);
3292
+ const groupByKeys = args.relKeyFields.map((f) => aliasQualifiedColumn(args.countAlias, args.relModel, f)).join(SQL_SEPARATORS.FIELD_LIST);
3293
+ const cntExpr = args.dialect === "postgres" ? "COUNT(*)::int AS __cnt" : "COUNT(*) AS __cnt";
3294
+ return `(SELECT ${selectKeys}${SQL_SEPARATORS.FIELD_LIST}${cntExpr} FROM ${args.relTable} ${args.countAlias} GROUP BY ${groupByKeys})`;
3225
3295
  }
3226
- function subqueryForCount(dialect, relTable, countAlias, groupByCol) {
3227
- return dialect === "postgres" ? `(SELECT ${groupByCol} AS __fk, COUNT(*)::int AS __cnt FROM ${relTable} ${countAlias} GROUP BY ${groupByCol})` : `(SELECT ${groupByCol} AS __fk, COUNT(*) AS __cnt FROM ${relTable} ${countAlias} GROUP BY ${groupByCol})`;
3296
+ function leftJoinOnForCount(args) {
3297
+ const parts = args.parentKeyFields.map(
3298
+ (f, i) => `${args.joinAlias}."__fk${i}" = ${aliasQualifiedColumn(args.parentAlias, args.parentModel, f)}`
3299
+ );
3300
+ return parts.length === 1 ? parts[0] : `(${parts.join(" AND ")})`;
3301
+ }
3302
+ function nextAliasAvoiding(aliasGen, base, forbidden) {
3303
+ let a = aliasGen.next(base);
3304
+ while (forbidden.has(a)) a = aliasGen.next(base);
3305
+ return a;
3228
3306
  }
3229
3307
  function buildCountJoinAndPair(args) {
3230
3308
  const relTable = getRelationTableReference(args.relModel, args.dialect);
3231
- const countAlias = `__tp_cnt_${args.relName}`;
3232
- const groupByCol = groupByColForCount(args.field, countAlias);
3233
- const subquery = subqueryForCount(
3234
- args.dialect,
3309
+ const { relKeyFields, parentKeyFields } = resolveCountKeyPairs(args.field);
3310
+ const forbidden = /* @__PURE__ */ new Set([args.parentAlias]);
3311
+ const countAlias = nextAliasAvoiding(
3312
+ args.aliasGen,
3313
+ `__tp_cnt_${args.relName}`,
3314
+ forbidden
3315
+ );
3316
+ forbidden.add(countAlias);
3317
+ const subquery = subqueryForCount({
3318
+ dialect: args.dialect,
3235
3319
  relTable,
3236
3320
  countAlias,
3237
- groupByCol
3321
+ relModel: args.relModel,
3322
+ relKeyFields
3323
+ });
3324
+ const joinAlias = nextAliasAvoiding(
3325
+ args.aliasGen,
3326
+ `__tp_cnt_j_${args.relName}`,
3327
+ forbidden
3238
3328
  );
3239
- const joinAlias = `__tp_cnt_j_${args.relName}`;
3240
- const leftJoinOn = leftJoinOnForCount(args.field, args.parentAlias, joinAlias);
3329
+ const leftJoinOn = leftJoinOnForCount({
3330
+ joinAlias,
3331
+ parentAlias: args.parentAlias,
3332
+ parentModel: args.parentModel,
3333
+ parentKeyFields
3334
+ });
3241
3335
  return {
3242
3336
  joinSql: `LEFT JOIN ${subquery} ${joinAlias} ON ${leftJoinOn}`,
3243
3337
  pairSql: `${sqlStringLiteral(args.relName)}, COALESCE(${joinAlias}.__cnt, 0)`
@@ -3246,6 +3340,7 @@ function buildCountJoinAndPair(args) {
3246
3340
  function buildRelationCountSql(countSelect, model, schemas, parentAlias, _params, dialect) {
3247
3341
  const joins = [];
3248
3342
  const pairs = [];
3343
+ const aliasGen = createAliasGenerator();
3249
3344
  for (const [relName, shouldCount] of Object.entries(countSelect)) {
3250
3345
  if (!shouldCount) continue;
3251
3346
  const resolved = resolveCountRelationOrThrow(relName, model, schemas);
@@ -3253,29 +3348,33 @@ function buildRelationCountSql(countSelect, model, schemas, parentAlias, _params
3253
3348
  relName,
3254
3349
  field: resolved.field,
3255
3350
  relModel: resolved.relModel,
3351
+ parentModel: model,
3256
3352
  parentAlias,
3257
- dialect
3353
+ dialect,
3354
+ aliasGen
3258
3355
  });
3259
3356
  joins.push(built.joinSql);
3260
3357
  pairs.push(built.pairSql);
3261
3358
  }
3262
- return {
3263
- joins,
3264
- jsonPairs: pairs.join(SQL_SEPARATORS.FIELD_LIST)
3265
- };
3359
+ return { joins, jsonPairs: pairs.join(SQL_SEPARATORS.FIELD_LIST) };
3266
3360
  }
3267
3361
 
3268
3362
  // src/builder/select/assembly.ts
3269
- var SIMPLE_SELECT_RE_CACHE = /* @__PURE__ */ new Map();
3270
- function normalizeFinalParams(params) {
3271
- return params.map(normalizeValue);
3272
- }
3363
+ var ALIAS_CAPTURE = "([A-Za-z_][A-Za-z0-9_]*)";
3364
+ var COLUMN_PART = '(?:"([^"]+)"|([a-z_][a-z0-9_]*))';
3365
+ var AS_PART = `(?:\\s+AS\\s+${COLUMN_PART})?`;
3366
+ var SIMPLE_COLUMN_PATTERN = `^${ALIAS_CAPTURE}\\.${COLUMN_PART}${AS_PART}$`;
3367
+ var SIMPLE_COLUMN_RE = new RegExp(SIMPLE_COLUMN_PATTERN, "i");
3273
3368
  function joinNonEmpty(parts, sep) {
3274
3369
  return parts.filter((s) => s.trim().length > 0).join(sep);
3275
3370
  }
3276
3371
  function buildWhereSql(conditions) {
3277
3372
  if (!isNonEmptyArray(conditions)) return "";
3278
- return ` ${SQL_TEMPLATES.WHERE} ${conditions.join(SQL_SEPARATORS.CONDITION_AND)}`;
3373
+ const parts = [
3374
+ SQL_TEMPLATES.WHERE,
3375
+ conditions.join(SQL_SEPARATORS.CONDITION_AND)
3376
+ ];
3377
+ return ` ${parts.join(" ")}`;
3279
3378
  }
3280
3379
  function buildJoinsSql(...joinGroups) {
3281
3380
  const all = [];
@@ -3290,37 +3389,43 @@ function buildSelectList(baseSelect, extraCols) {
3290
3389
  if (base && extra) return `${base}${SQL_SEPARATORS.FIELD_LIST}${extra}`;
3291
3390
  return base || extra;
3292
3391
  }
3293
- function finalizeSql(sql, params) {
3392
+ function finalizeSql(sql, params, dialect) {
3294
3393
  const snapshot = params.snapshot();
3295
3394
  validateSelectQuery(sql);
3296
- validateParamConsistency(sql, snapshot.params);
3395
+ validateParamConsistencyByDialect(
3396
+ sql,
3397
+ snapshot.params,
3398
+ dialect === "sqlite" ? "postgres" : dialect
3399
+ );
3297
3400
  return Object.freeze({
3298
3401
  sql,
3299
- params: normalizeFinalParams(snapshot.params),
3402
+ params: snapshot.params,
3300
3403
  paramMappings: Object.freeze(snapshot.mappings)
3301
3404
  });
3302
3405
  }
3303
- function parseSimpleScalarSelect(select, alias) {
3304
- var _a, _b;
3406
+ function parseSimpleScalarSelect(select, fromAlias) {
3407
+ var _a, _b, _c, _d;
3305
3408
  const raw = select.trim();
3306
3409
  if (raw.length === 0) return [];
3307
- let re = SIMPLE_SELECT_RE_CACHE.get(alias);
3308
- if (!re) {
3309
- const safeAlias2 = alias.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3310
- re = new RegExp(`^${safeAlias2}\\.(?:"([^"]+)"|([a-z_][a-z0-9_]*))$`, "i");
3311
- SIMPLE_SELECT_RE_CACHE.set(alias, re);
3312
- }
3313
3410
  const parts = raw.split(SQL_SEPARATORS.FIELD_LIST);
3314
3411
  const names = [];
3315
3412
  for (const part of parts) {
3316
3413
  const p = part.trim();
3317
- const m = p.match(re);
3414
+ const m = p.match(SIMPLE_COLUMN_RE);
3318
3415
  if (!m) {
3319
3416
  throw new Error(
3320
- `sqlite distinct emulation requires scalar select fields to be simple columns. Got: ${p}`
3417
+ `sqlite distinct emulation requires scalar select fields to be simple columns (optionally with AS). Got: ${p}`
3321
3418
  );
3322
3419
  }
3323
- const name = ((_b = (_a = m[1]) != null ? _a : m[2]) != null ? _b : "").trim();
3420
+ const actualAlias = m[1];
3421
+ if (actualAlias.toLowerCase() !== fromAlias.toLowerCase()) {
3422
+ throw new Error(
3423
+ `Expected alias '${fromAlias}', got '${actualAlias}' in: ${p}`
3424
+ );
3425
+ }
3426
+ const columnName = ((_b = (_a = m[2]) != null ? _a : m[3]) != null ? _b : "").trim();
3427
+ const outAlias = ((_d = (_c = m[4]) != null ? _c : m[5]) != null ? _d : "").trim();
3428
+ const name = outAlias.length > 0 ? outAlias : columnName;
3324
3429
  if (name.length === 0) {
3325
3430
  throw new Error(`Failed to parse selected column name from: ${p}`);
3326
3431
  }
@@ -3329,18 +3434,18 @@ function parseSimpleScalarSelect(select, alias) {
3329
3434
  return names;
3330
3435
  }
3331
3436
  function replaceOrderByAlias(orderBy, fromAlias, outerAlias) {
3332
- const needle = `${fromAlias}.`;
3333
- const replacement = `${outerAlias}.`;
3334
- return orderBy.split(needle).join(replacement);
3437
+ const src = String(fromAlias);
3438
+ if (src.length === 0) return orderBy;
3439
+ const escaped = src.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3440
+ const re = new RegExp(`\\b${escaped}\\.`, "gi");
3441
+ return orderBy.replace(re, `${outerAlias}.`);
3335
3442
  }
3336
3443
  function buildDistinctColumns(distinct, fromAlias, model) {
3337
3444
  return distinct.map((f) => col(fromAlias, f, model)).join(SQL_SEPARATORS.FIELD_LIST);
3338
3445
  }
3339
3446
  function buildOutputColumns(scalarNames, includeNames, hasCount) {
3340
3447
  const outputCols = [...scalarNames, ...includeNames];
3341
- if (hasCount) {
3342
- outputCols.push("_count");
3343
- }
3448
+ if (hasCount) outputCols.push("_count");
3344
3449
  const formatted = outputCols.map((n) => quote(n)).join(SQL_SEPARATORS.FIELD_LIST);
3345
3450
  if (!isNonEmptyString(formatted)) {
3346
3451
  throw new Error("distinct emulation requires at least one output column");
@@ -3349,9 +3454,10 @@ function buildOutputColumns(scalarNames, includeNames, hasCount) {
3349
3454
  }
3350
3455
  function buildWindowOrder(args) {
3351
3456
  const { baseOrder, idField, fromAlias, model } = args;
3457
+ const fromLower = String(fromAlias).toLowerCase();
3352
3458
  const orderFields = baseOrder.split(SQL_SEPARATORS.ORDER_BY).map((s) => s.trim().toLowerCase());
3353
3459
  const hasIdInOrder = orderFields.some(
3354
- (f) => f.startsWith(`${fromAlias}.id `) || f.startsWith(`${fromAlias}."id" `)
3460
+ (f) => f.startsWith(`${fromLower}.id `) || f.startsWith(`${fromLower}."id" `)
3355
3461
  );
3356
3462
  if (hasIdInOrder) return baseOrder;
3357
3463
  const idTiebreaker = idField ? `, ${col(fromAlias, "id", model)} ASC` : "";
@@ -3386,15 +3492,37 @@ function buildSqliteDistinctQuery(spec, selectWithIncludes, countJoins) {
3386
3492
  const outerOrder = isNonEmptyString(orderBy) ? replaceOrderByAlias(orderBy, from.alias, `"__tp_distinct"`) : replaceOrderByAlias(fallbackOrder, from.alias, `"__tp_distinct"`);
3387
3493
  const joins = buildJoinsSql(whereJoins, countJoins);
3388
3494
  const conditions = [];
3389
- if (whereClause && whereClause !== "1=1") {
3390
- conditions.push(whereClause);
3391
- }
3495
+ if (whereClause && whereClause !== "1=1") conditions.push(whereClause);
3392
3496
  const whereSql = buildWhereSql(conditions);
3393
3497
  const innerSelectList = selectWithIncludes.trim();
3394
3498
  const innerComma = innerSelectList.length > 0 ? SQL_SEPARATORS.FIELD_LIST : "";
3395
- const inner = `${SQL_TEMPLATES.SELECT} ${innerSelectList}${innerComma}ROW_NUMBER() OVER (PARTITION BY ${distinctCols} ORDER BY ${windowOrder}) ${SQL_TEMPLATES.AS} "__tp_rn" ${SQL_TEMPLATES.FROM} ${from.table} ${from.alias}${joins}${whereSql}`;
3396
- const outer = `${SQL_TEMPLATES.SELECT} ${outerSelectCols} ${SQL_TEMPLATES.FROM} (${inner}) ${SQL_TEMPLATES.AS} "__tp_distinct" ${SQL_TEMPLATES.WHERE} "__tp_rn" = 1` + (isNonEmptyString(outerOrder) ? ` ${SQL_TEMPLATES.ORDER_BY} ${outerOrder}` : "");
3397
- return outer;
3499
+ const innerParts = [
3500
+ SQL_TEMPLATES.SELECT,
3501
+ innerSelectList + innerComma,
3502
+ `ROW_NUMBER() OVER (PARTITION BY ${distinctCols} ORDER BY ${windowOrder})`,
3503
+ SQL_TEMPLATES.AS,
3504
+ '"__tp_rn"',
3505
+ SQL_TEMPLATES.FROM,
3506
+ from.table,
3507
+ from.alias
3508
+ ];
3509
+ if (joins) innerParts.push(joins);
3510
+ if (whereSql) innerParts.push(whereSql);
3511
+ const inner = innerParts.filter(Boolean).join(" ");
3512
+ const outerParts = [
3513
+ SQL_TEMPLATES.SELECT,
3514
+ outerSelectCols,
3515
+ SQL_TEMPLATES.FROM,
3516
+ `(${inner})`,
3517
+ SQL_TEMPLATES.AS,
3518
+ '"__tp_distinct"',
3519
+ SQL_TEMPLATES.WHERE,
3520
+ '"__tp_rn" = 1'
3521
+ ];
3522
+ if (isNonEmptyString(outerOrder)) {
3523
+ outerParts.push(SQL_TEMPLATES.ORDER_BY, outerOrder);
3524
+ }
3525
+ return outerParts.filter(Boolean).join(" ");
3398
3526
  }
3399
3527
  function buildIncludeColumns(spec) {
3400
3528
  var _a, _b;
@@ -3522,6 +3650,7 @@ function constructFinalSql(spec) {
3522
3650
  orderBy,
3523
3651
  distinct,
3524
3652
  method,
3653
+ cursorCte,
3525
3654
  cursorClause,
3526
3655
  params,
3527
3656
  dialect,
@@ -3536,9 +3665,13 @@ function constructFinalSql(spec) {
3536
3665
  const spec2 = withCountJoins(spec, countJoins, whereJoins);
3537
3666
  let sql2 = buildSqliteDistinctQuery(spec2, selectWithIncludes).trim();
3538
3667
  sql2 = appendPagination(sql2, spec);
3539
- return finalizeSql(sql2, params);
3668
+ return finalizeSql(sql2, params, dialect);
3540
3669
  }
3541
- const parts = [SQL_TEMPLATES.SELECT];
3670
+ const parts = [];
3671
+ if (cursorCte) {
3672
+ parts.push(`WITH ${cursorCte}`);
3673
+ }
3674
+ parts.push(SQL_TEMPLATES.SELECT);
3542
3675
  const distinctOn = dialect === "postgres" ? buildPostgresDistinctOnClause(from.alias, distinct, model) : null;
3543
3676
  if (distinctOn) parts.push(distinctOn);
3544
3677
  const baseSelect = (select != null ? select : "").trim();
@@ -3554,7 +3687,7 @@ function constructFinalSql(spec) {
3554
3687
  if (isNonEmptyString(orderBy)) parts.push(SQL_TEMPLATES.ORDER_BY, orderBy);
3555
3688
  let sql = parts.join(" ").trim();
3556
3689
  sql = appendPagination(sql, spec);
3557
- return finalizeSql(sql, params);
3690
+ return finalizeSql(sql, params, dialect);
3558
3691
  }
3559
3692
 
3560
3693
  // src/builder/select.ts
@@ -3587,7 +3720,7 @@ function buildPostgresDistinctOrderBy(distinctFields, existing) {
3587
3720
  }
3588
3721
  return next;
3589
3722
  }
3590
- function applyPostgresDistinctOrderBy(args, _model) {
3723
+ function applyPostgresDistinctOrderBy(args) {
3591
3724
  const distinctFields = normalizeDistinctFields(args.distinct);
3592
3725
  if (distinctFields.length === 0) return args;
3593
3726
  if (!isNotNullish(args.orderBy)) return args;
@@ -3597,19 +3730,6 @@ function applyPostgresDistinctOrderBy(args, _model) {
3597
3730
  orderBy: buildPostgresDistinctOrderBy(distinctFields, existing)
3598
3731
  });
3599
3732
  }
3600
- function assertScalarFieldOnModel(model, fieldName, ctx) {
3601
- const f = model.fields.find((x) => x.name === fieldName);
3602
- if (!f) {
3603
- throw new Error(
3604
- `${ctx} references unknown field '${fieldName}' on model ${model.name}`
3605
- );
3606
- }
3607
- if (f.isRelation) {
3608
- throw new Error(
3609
- `${ctx} does not support relation field '${fieldName}' on model ${model.name}`
3610
- );
3611
- }
3612
- }
3613
3733
  function validateDistinct(model, distinct) {
3614
3734
  if (!isNotNullish(distinct) || !isNonEmptyArray(distinct)) return;
3615
3735
  const seen = /* @__PURE__ */ new Set();
@@ -3620,24 +3740,24 @@ function validateDistinct(model, distinct) {
3620
3740
  throw new Error(`distinct must not contain duplicates (field: '${f}')`);
3621
3741
  }
3622
3742
  seen.add(f);
3623
- assertScalarFieldOnModel(model, f, "distinct");
3743
+ assertScalarField(model, f, "distinct");
3624
3744
  }
3625
3745
  }
3626
- function validateOrderByValue(fieldName, v) {
3627
- parseOrderByValue(v, fieldName);
3628
- }
3629
3746
  function validateOrderBy(model, orderBy) {
3630
3747
  if (!isNotNullish(orderBy)) return;
3631
3748
  const items = normalizeOrderByInput2(orderBy);
3632
3749
  if (items.length === 0) return;
3633
3750
  for (const it of items) {
3634
3751
  const entries = Object.entries(it);
3752
+ if (entries.length !== 1) {
3753
+ throw new Error("orderBy array entries must have exactly one field");
3754
+ }
3635
3755
  const fieldName = String(entries[0][0]).trim();
3636
3756
  if (fieldName.length === 0) {
3637
3757
  throw new Error("orderBy field name cannot be empty");
3638
3758
  }
3639
- assertScalarFieldOnModel(model, fieldName, "orderBy");
3640
- validateOrderByValue(fieldName, entries[0][1]);
3759
+ assertScalarField(model, fieldName, "orderBy");
3760
+ parseOrderByValue(entries[0][1], fieldName);
3641
3761
  }
3642
3762
  }
3643
3763
  function validateCursor(model, cursor) {
@@ -3654,7 +3774,7 @@ function validateCursor(model, cursor) {
3654
3774
  if (f.length === 0) {
3655
3775
  throw new Error("cursor field name cannot be empty");
3656
3776
  }
3657
- assertScalarFieldOnModel(model, f, "cursor");
3777
+ assertScalarField(model, f, "cursor");
3658
3778
  }
3659
3779
  }
3660
3780
  function resolveDialect(dialect) {
@@ -3673,20 +3793,21 @@ function normalizeArgsForNegativeTake(method, args) {
3673
3793
  orderBy: reverseOrderByInput(args.orderBy)
3674
3794
  });
3675
3795
  }
3676
- function normalizeArgsForDialect(dialect, args, model) {
3796
+ function normalizeArgsForDialect(dialect, args) {
3677
3797
  if (dialect !== "postgres") return args;
3678
3798
  return applyPostgresDistinctOrderBy(args);
3679
3799
  }
3680
3800
  function buildCursorClauseIfAny(input) {
3681
- const { cursor, orderBy, tableName, alias, params, dialect } = input;
3682
- if (!isNotNullish(cursor)) return void 0;
3801
+ const { cursor, orderBy, tableName, alias, params, dialect, model } = input;
3802
+ if (!isNotNullish(cursor)) return {};
3683
3803
  return buildCursorCondition(
3684
3804
  cursor,
3685
3805
  orderBy,
3686
3806
  tableName,
3687
3807
  alias,
3688
3808
  params,
3689
- dialect
3809
+ dialect,
3810
+ model
3690
3811
  );
3691
3812
  }
3692
3813
  function buildSelectSpec(input) {
@@ -3725,14 +3846,20 @@ function buildSelectSpec(input) {
3725
3846
  params,
3726
3847
  dialect
3727
3848
  );
3728
- const cursorClause = buildCursorClauseIfAny({
3849
+ const cursorResult = buildCursorClauseIfAny({
3729
3850
  cursor,
3730
3851
  orderBy: normalizedArgs.orderBy,
3731
3852
  tableName,
3732
3853
  alias,
3733
3854
  params,
3734
- dialect
3855
+ dialect,
3856
+ model
3735
3857
  });
3858
+ if (dialect === "sqlite" && isNonEmptyArray(normalizedArgs.distinct) && cursorResult.condition) {
3859
+ throw new Error(
3860
+ "Cursor pagination with distinct is not supported in SQLite due to window function limitations. Use findMany with skip/take instead, or remove distinct."
3861
+ );
3862
+ }
3736
3863
  return {
3737
3864
  select: selectFields,
3738
3865
  includes,
@@ -3743,7 +3870,8 @@ function buildSelectSpec(input) {
3743
3870
  pagination: { take, skip },
3744
3871
  distinct: normalizedArgs.distinct,
3745
3872
  method,
3746
- cursorClause,
3873
+ cursorCte: cursorResult.cte,
3874
+ cursorClause: cursorResult.condition,
3747
3875
  params,
3748
3876
  dialect,
3749
3877
  model,
@@ -3757,9 +3885,7 @@ function buildSelectSql(input) {
3757
3885
  assertSafeTableRef(from.tableName);
3758
3886
  const dialectToUse = resolveDialect(dialect);
3759
3887
  const argsForSql = normalizeArgsForNegativeTake(method, args);
3760
- const normalizedArgs = normalizeArgsForDialect(
3761
- dialectToUse,
3762
- argsForSql);
3888
+ const normalizedArgs = normalizeArgsForDialect(dialectToUse, argsForSql);
3763
3889
  validateDistinct(model, normalizedArgs.distinct);
3764
3890
  validateOrderBy(model, normalizedArgs.orderBy);
3765
3891
  validateCursor(model, normalizedArgs.cursor);
@@ -3775,8 +3901,21 @@ function buildSelectSql(input) {
3775
3901
  });
3776
3902
  return constructFinalSql(spec);
3777
3903
  }
3778
- var MODEL_FIELD_CACHE = /* @__PURE__ */ new WeakMap();
3779
- var NUMERIC_TYPES = /* @__PURE__ */ new Set(["Int", "Float", "Decimal", "BigInt"]);
3904
+
3905
+ // src/builder/shared/comparison-builder.ts
3906
+ function buildComparisons(expr, filter, params, dialect, builder, excludeKeys = /* @__PURE__ */ new Set(["mode"])) {
3907
+ const out = [];
3908
+ for (const [op, val] of Object.entries(filter)) {
3909
+ if (excludeKeys.has(op) || val === void 0) continue;
3910
+ const built = builder(expr, op, val, params, dialect);
3911
+ if (built && built.trim().length > 0) {
3912
+ out.push(built);
3913
+ }
3914
+ }
3915
+ return out;
3916
+ }
3917
+
3918
+ // src/builder/aggregates.ts
3780
3919
  var AGGREGATES = [
3781
3920
  ["_sum", "SUM"],
3782
3921
  ["_avg", "AVG"],
@@ -3791,22 +3930,32 @@ var COMPARISON_OPS = {
3791
3930
  [Ops.LT]: "<",
3792
3931
  [Ops.LTE]: "<="
3793
3932
  };
3794
- function normalizeFinalParams2(params) {
3795
- return params.map(normalizeValue);
3796
- }
3797
- function getModelFieldMap(model) {
3798
- const cached = MODEL_FIELD_CACHE.get(model);
3799
- if (cached) return cached;
3800
- const m = /* @__PURE__ */ new Map();
3801
- for (const f of model.fields) {
3802
- m.set(f.name, { name: f.name, type: f.type, isRelation: !!f.isRelation });
3803
- }
3804
- MODEL_FIELD_CACHE.set(model, m);
3805
- return m;
3806
- }
3933
+ var HAVING_ALLOWED_OPS = /* @__PURE__ */ new Set([
3934
+ Ops.EQUALS,
3935
+ Ops.NOT,
3936
+ Ops.GT,
3937
+ Ops.GTE,
3938
+ Ops.LT,
3939
+ Ops.LTE,
3940
+ Ops.IN,
3941
+ Ops.NOT_IN
3942
+ ]);
3807
3943
  function isTruthySelection(v) {
3808
3944
  return v === true;
3809
3945
  }
3946
+ function isLogicalKey(key) {
3947
+ return key === LogicalOps.AND || key === LogicalOps.OR || key === LogicalOps.NOT;
3948
+ }
3949
+ function isAggregateKey(key) {
3950
+ return key === "_count" || key === "_sum" || key === "_avg" || key === "_min" || key === "_max";
3951
+ }
3952
+ function assertHavingOp(op) {
3953
+ if (!HAVING_ALLOWED_OPS.has(op)) {
3954
+ throw new Error(
3955
+ `Unsupported HAVING operator '${op}'. Allowed: ${[...HAVING_ALLOWED_OPS].join(", ")}`
3956
+ );
3957
+ }
3958
+ }
3810
3959
  function aggExprForField(aggKey, field, alias, model) {
3811
3960
  if (aggKey === "_count") {
3812
3961
  return field === "_all" ? `COUNT(*)` : `COUNT(${col(alias, field, model)})`;
@@ -3840,32 +3989,9 @@ function normalizeLogicalValue2(operator, value) {
3840
3989
  }
3841
3990
  return out;
3842
3991
  }
3843
- if (isPlainObject(value)) {
3844
- return [value];
3845
- }
3992
+ if (isPlainObject(value)) return [value];
3846
3993
  throw new Error(`${operator} must be an object or array of objects in HAVING`);
3847
3994
  }
3848
- function assertScalarField2(model, fieldName, ctx) {
3849
- const m = getModelFieldMap(model);
3850
- const field = m.get(fieldName);
3851
- if (!field) {
3852
- throw new Error(
3853
- `${ctx} references unknown field '${fieldName}' on model ${model.name}. Available fields: ${model.fields.map((f) => f.name).join(", ")}`
3854
- );
3855
- }
3856
- if (field.isRelation) {
3857
- throw new Error(`${ctx} does not support relation field '${fieldName}'`);
3858
- }
3859
- return { name: field.name, type: field.type };
3860
- }
3861
- function assertAggregateFieldType(aggKey, fieldType, fieldName, modelName) {
3862
- const baseType = fieldType.replace(/\[\]|\?/g, "");
3863
- if ((aggKey === "_sum" || aggKey === "_avg") && !NUMERIC_TYPES.has(baseType)) {
3864
- throw new Error(
3865
- `Cannot use ${aggKey} on non-numeric field '${fieldName}' (type: ${fieldType}) on model ${modelName}`
3866
- );
3867
- }
3868
- }
3869
3995
  function buildNullComparison(expr, op) {
3870
3996
  if (op === Ops.EQUALS) return `${expr} ${SQL_TEMPLATES.IS_NULL}`;
3871
3997
  if (op === Ops.NOT) return `${expr} ${SQL_TEMPLATES.IS_NOT_NULL}`;
@@ -3892,6 +4018,7 @@ function buildBinaryComparison(expr, op, val, params) {
3892
4018
  return `${expr} ${sqlOp} ${placeholder}`;
3893
4019
  }
3894
4020
  function buildSimpleComparison(expr, op, val, params, dialect) {
4021
+ assertHavingOp(op);
3895
4022
  if (val === null) return buildNullComparison(expr, op);
3896
4023
  if (op === Ops.NOT && isPlainObject(val)) {
3897
4024
  return buildNotComposite(
@@ -3908,12 +4035,6 @@ function buildSimpleComparison(expr, op, val, params, dialect) {
3908
4035
  }
3909
4036
  return buildBinaryComparison(expr, op, val, params);
3910
4037
  }
3911
- function isLogicalKey(key) {
3912
- return key === LogicalOps.AND || key === LogicalOps.OR || key === LogicalOps.NOT;
3913
- }
3914
- function isAggregateKey(key) {
3915
- return key === "_count" || key === "_sum" || key === "_avg" || key === "_min" || key === "_max";
3916
- }
3917
4038
  function negateClauses(subClauses) {
3918
4039
  if (subClauses.length === 1) return `${SQL_TEMPLATES.NOT} ${subClauses[0]}`;
3919
4040
  return `${SQL_TEMPLATES.NOT} (${subClauses.join(SQL_SEPARATORS.CONDITION_AND)})`;
@@ -3922,16 +4043,75 @@ function combineLogical(key, subClauses) {
3922
4043
  if (key === LogicalOps.NOT) return negateClauses(subClauses);
3923
4044
  return subClauses.join(` ${key} `);
3924
4045
  }
4046
+ function buildHavingNode(node, alias, params, dialect, model) {
4047
+ const clauses = [];
4048
+ const entries = Object.entries(node);
4049
+ for (const [key, value] of entries) {
4050
+ const built = buildHavingEntry(key, value, alias, params, dialect, model);
4051
+ for (const c of built) {
4052
+ if (c && c.trim().length > 0) clauses.push(c);
4053
+ }
4054
+ }
4055
+ return clauses.join(SQL_SEPARATORS.CONDITION_AND);
4056
+ }
3925
4057
  function buildLogicalClause2(key, value, alias, params, dialect, model) {
3926
4058
  const items = normalizeLogicalValue2(key, value);
3927
4059
  const subClauses = [];
3928
4060
  for (const it of items) {
3929
4061
  const c = buildHavingNode(it, alias, params, dialect, model);
3930
- if (c && c !== "") subClauses.push(`(${c})`);
4062
+ if (c && c.trim().length > 0) subClauses.push(`(${c})`);
3931
4063
  }
3932
4064
  if (subClauses.length === 0) return "";
3933
4065
  return combineLogical(key, subClauses);
3934
4066
  }
4067
+ function assertHavingAggTarget(aggKey, field, model) {
4068
+ if (field === "_all") {
4069
+ if (aggKey !== "_count")
4070
+ throw new Error(`HAVING '${aggKey}' does not support '_all'`);
4071
+ return;
4072
+ }
4073
+ if (aggKey === "_sum" || aggKey === "_avg") {
4074
+ assertNumericField(model, field, "HAVING");
4075
+ } else {
4076
+ assertScalarField(model, field, "HAVING");
4077
+ }
4078
+ }
4079
+ function buildHavingOpsForExpr(expr, filter, params, dialect) {
4080
+ return buildComparisons(expr, filter, params, dialect, buildSimpleComparison);
4081
+ }
4082
+ function buildHavingForAggregateFirstShape(aggKey, target, alias, params, dialect, model) {
4083
+ if (!isPlainObject(target)) {
4084
+ throw new Error(`HAVING '${aggKey}' must be an object`);
4085
+ }
4086
+ const out = [];
4087
+ for (const [field, filter] of Object.entries(target)) {
4088
+ assertHavingAggTarget(aggKey, field, model);
4089
+ if (!isPlainObject(filter) || Object.keys(filter).length === 0) continue;
4090
+ const expr = aggExprForField(aggKey, field, alias, model);
4091
+ out.push(...buildHavingOpsForExpr(expr, filter, params, dialect));
4092
+ }
4093
+ return out;
4094
+ }
4095
+ function buildHavingForFieldFirstShape(fieldName, target, alias, params, dialect, model) {
4096
+ if (!isPlainObject(target)) {
4097
+ throw new Error(`HAVING '${fieldName}' must be an object`);
4098
+ }
4099
+ assertScalarField(model, fieldName, "HAVING");
4100
+ const out = [];
4101
+ const obj = target;
4102
+ const keys = ["_count", "_sum", "_avg", "_min", "_max"];
4103
+ for (const aggKey of keys) {
4104
+ const aggFilter = obj[aggKey];
4105
+ if (!isPlainObject(aggFilter)) continue;
4106
+ if (Object.keys(aggFilter).length === 0) continue;
4107
+ if (aggKey === "_sum" || aggKey === "_avg") {
4108
+ assertNumericField(model, fieldName, "HAVING");
4109
+ }
4110
+ const expr = aggExprForField(aggKey, fieldName, alias, model);
4111
+ out.push(...buildHavingOpsForExpr(expr, aggFilter, params, dialect));
4112
+ }
4113
+ return out;
4114
+ }
3935
4115
  function buildHavingEntry(key, value, alias, params, dialect, model) {
3936
4116
  if (isLogicalKey(key)) {
3937
4117
  const logical = buildLogicalClause2(
@@ -3963,71 +4143,10 @@ function buildHavingEntry(key, value, alias, params, dialect, model) {
3963
4143
  model
3964
4144
  );
3965
4145
  }
3966
- function buildHavingNode(node, alias, params, dialect, model) {
3967
- const clauses = [];
3968
- for (const [key, value] of Object.entries(node)) {
3969
- const built = buildHavingEntry(key, value, alias, params, dialect, model);
3970
- for (const c of built) {
3971
- if (c && c.trim().length > 0) clauses.push(c);
3972
- }
3973
- }
3974
- return clauses.join(SQL_SEPARATORS.CONDITION_AND);
3975
- }
3976
- function assertHavingAggTarget(aggKey, field, model) {
3977
- if (field === "_all") {
3978
- if (aggKey !== "_count") {
3979
- throw new Error(`HAVING '${aggKey}' does not support '_all'`);
3980
- }
3981
- return;
3982
- }
3983
- const f = assertScalarField2(model, field, "HAVING");
3984
- assertAggregateFieldType(aggKey, f.type, f.name, model.name);
3985
- }
3986
- function buildHavingOpsForExpr(expr, filter, params, dialect) {
3987
- const out = [];
3988
- for (const [op, val] of Object.entries(filter)) {
3989
- if (op === "mode") continue;
3990
- const built = buildSimpleComparison(expr, op, val, params, dialect);
3991
- if (built && built.trim().length > 0) out.push(built);
3992
- }
3993
- return out;
3994
- }
3995
- function buildHavingForAggregateFirstShape(aggKey, target, alias, params, dialect, model) {
3996
- if (!isPlainObject(target)) return [];
3997
- const out = [];
3998
- for (const [field, filter] of Object.entries(target)) {
3999
- assertHavingAggTarget(aggKey, field, model);
4000
- if (!isPlainObject(filter) || Object.keys(filter).length === 0) continue;
4001
- const expr = aggExprForField(aggKey, field, alias, model);
4002
- out.push(...buildHavingOpsForExpr(expr, filter, params, dialect));
4003
- }
4004
- return out;
4005
- }
4006
- function buildHavingForFieldFirstShape(fieldName, target, alias, params, dialect, model) {
4007
- if (!isPlainObject(target)) return [];
4008
- const field = assertScalarField2(model, fieldName, "HAVING");
4009
- const out = [];
4010
- const obj = target;
4011
- const keys = ["_count", "_sum", "_avg", "_min", "_max"];
4012
- for (const aggKey of keys) {
4013
- const aggFilter = obj[aggKey];
4014
- if (!isPlainObject(aggFilter)) continue;
4015
- assertAggregateFieldType(aggKey, field.type, field.name, model.name);
4016
- const entries = Object.entries(aggFilter);
4017
- if (entries.length === 0) continue;
4018
- const expr = aggExprForField(aggKey, fieldName, alias, model);
4019
- for (const [op, val] of entries) {
4020
- if (op === "mode") continue;
4021
- const built = buildSimpleComparison(expr, op, val, params, dialect);
4022
- if (built && built.trim().length > 0) out.push(built);
4023
- }
4024
- }
4025
- return out;
4026
- }
4027
4146
  function buildHavingClause(having, alias, params, model, dialect) {
4028
4147
  if (!isNotNullish(having)) return "";
4029
4148
  const d = dialect != null ? dialect : getGlobalDialect();
4030
- if (!isPlainObject(having)) return "";
4149
+ if (!isPlainObject(having)) throw new Error("having must be an object");
4031
4150
  return buildHavingNode(having, alias, params, d, model);
4032
4151
  }
4033
4152
  function normalizeCountArg(v) {
@@ -4041,26 +4160,13 @@ function pushCountAllField(fields) {
4041
4160
  `${SQL_TEMPLATES.COUNT_ALL} ${SQL_TEMPLATES.AS} ${quote("_count._all")}`
4042
4161
  );
4043
4162
  }
4044
- function assertCountableScalarField(fieldMap, model, fieldName) {
4045
- const field = fieldMap.get(fieldName);
4046
- if (!field) {
4047
- throw new Error(
4048
- `Field '${fieldName}' does not exist on model ${model.name}`
4049
- );
4050
- }
4051
- if (field.isRelation) {
4052
- throw new Error(
4053
- `Cannot use _count on relation field '${fieldName}' on model ${model.name}`
4054
- );
4055
- }
4056
- }
4057
4163
  function pushCountField(fields, alias, fieldName, model) {
4058
4164
  const outAlias = `_count.${fieldName}`;
4059
4165
  fields.push(
4060
4166
  `COUNT(${col(alias, fieldName, model)}) ${SQL_TEMPLATES.AS} ${quote(outAlias)}`
4061
4167
  );
4062
4168
  }
4063
- function addCountFields(fields, countArg, alias, model, fieldMap) {
4169
+ function addCountFields(fields, countArg, alias, model) {
4064
4170
  if (!isNotNullish(countArg)) return;
4065
4171
  if (countArg === true) {
4066
4172
  pushCountAllField(fields);
@@ -4074,7 +4180,7 @@ function addCountFields(fields, countArg, alias, model, fieldMap) {
4074
4180
  ([f, v]) => f !== "_all" && isTruthySelection(v)
4075
4181
  );
4076
4182
  for (const [f] of selected) {
4077
- assertCountableScalarField(fieldMap, model, f);
4183
+ assertScalarField(model, f, "_count");
4078
4184
  pushCountField(fields, alias, f, model);
4079
4185
  }
4080
4186
  }
@@ -4082,19 +4188,12 @@ function getAggregateSelectionObject(args, agg) {
4082
4188
  const obj = args[agg];
4083
4189
  return isPlainObject(obj) ? obj : void 0;
4084
4190
  }
4085
- function assertAggregatableScalarField(fieldMap, model, agg, fieldName) {
4086
- const field = fieldMap.get(fieldName);
4087
- if (!field) {
4088
- throw new Error(
4089
- `Field '${fieldName}' does not exist on model ${model.name}`
4090
- );
4091
- }
4092
- if (field.isRelation) {
4093
- throw new Error(
4094
- `Cannot use ${agg} on relation field '${fieldName}' on model ${model.name}`
4095
- );
4191
+ function assertAggregatableScalarField(model, agg, fieldName) {
4192
+ if (agg === "_sum" || agg === "_avg") {
4193
+ assertNumericField(model, fieldName, agg);
4194
+ } else {
4195
+ assertScalarField(model, fieldName, agg);
4096
4196
  }
4097
- return field;
4098
4197
  }
4099
4198
  function pushAggregateFieldSql(fields, aggFn, alias, agg, fieldName, model) {
4100
4199
  const outAlias = `${agg}.${fieldName}`;
@@ -4102,7 +4201,7 @@ function pushAggregateFieldSql(fields, aggFn, alias, agg, fieldName, model) {
4102
4201
  `${aggFn}(${col(alias, fieldName, model)}) ${SQL_TEMPLATES.AS} ${quote(outAlias)}`
4103
4202
  );
4104
4203
  }
4105
- function addAggregateFields(fields, args, alias, model, fieldMap) {
4204
+ function addAggregateFields(fields, args, alias, model) {
4106
4205
  for (const [agg, aggFn] of AGGREGATES) {
4107
4206
  const obj = getAggregateSelectionObject(args, agg);
4108
4207
  if (!obj) continue;
@@ -4110,23 +4209,16 @@ function addAggregateFields(fields, args, alias, model, fieldMap) {
4110
4209
  if (fieldName === "_all")
4111
4210
  throw new Error(`'${agg}' does not support '_all'`);
4112
4211
  if (!isTruthySelection(selection)) continue;
4113
- const field = assertAggregatableScalarField(
4114
- fieldMap,
4115
- model,
4116
- agg,
4117
- fieldName
4118
- );
4119
- assertAggregateFieldType(agg, field.type, fieldName, model.name);
4212
+ assertAggregatableScalarField(model, agg, fieldName);
4120
4213
  pushAggregateFieldSql(fields, aggFn, alias, agg, fieldName, model);
4121
4214
  }
4122
4215
  }
4123
4216
  }
4124
4217
  function buildAggregateFields(args, alias, model) {
4125
4218
  const fields = [];
4126
- const fieldMap = getModelFieldMap(model);
4127
4219
  const countArg = normalizeCountArg(args._count);
4128
- addCountFields(fields, countArg, alias, model, fieldMap);
4129
- addAggregateFields(fields, args, alias, model, fieldMap);
4220
+ addCountFields(fields, countArg, alias, model);
4221
+ addAggregateFields(fields, args, alias, model);
4130
4222
  return fields;
4131
4223
  }
4132
4224
  function buildAggregateSql(args, whereResult, tableName, alias, model) {
@@ -4150,7 +4242,7 @@ function buildAggregateSql(args, whereResult, tableName, alias, model) {
4150
4242
  validateParamConsistency(sql, whereResult.params);
4151
4243
  return Object.freeze({
4152
4244
  sql,
4153
- params: Object.freeze(normalizeFinalParams2([...whereResult.params])),
4245
+ params: Object.freeze([...whereResult.params]),
4154
4246
  paramMappings: Object.freeze([...whereResult.paramMappings])
4155
4247
  });
4156
4248
  }
@@ -4163,32 +4255,22 @@ function assertGroupByBy(args, model) {
4163
4255
  if (bySet.size !== byFields.length) {
4164
4256
  throw new Error("buildGroupBySql: by must not contain duplicates");
4165
4257
  }
4166
- const modelFieldMap = getModelFieldMap(model);
4167
4258
  for (const f of byFields) {
4168
- const field = modelFieldMap.get(f);
4169
- if (!field) {
4170
- throw new Error(
4171
- `groupBy.by references unknown field '${f}' on model ${model.name}`
4172
- );
4173
- }
4174
- if (field.isRelation) {
4175
- throw new Error(
4176
- `groupBy.by does not support relation field '${f}' on model ${model.name}`
4177
- );
4178
- }
4259
+ assertScalarField(model, f, "groupBy.by");
4179
4260
  }
4180
4261
  return byFields;
4181
4262
  }
4182
4263
  function buildGroupBySelectParts(args, alias, model, byFields) {
4183
4264
  const groupCols = byFields.map((f) => col(alias, f, model));
4265
+ const selectCols = byFields.map((f) => colWithAlias(alias, f, model));
4184
4266
  const groupFields = groupCols.join(SQL_SEPARATORS.FIELD_LIST);
4185
4267
  const aggFields = buildAggregateFields(args, alias, model);
4186
- const selectFields = isNonEmptyArray(aggFields) ? groupCols.concat(aggFields).join(SQL_SEPARATORS.FIELD_LIST) : groupCols.join(SQL_SEPARATORS.FIELD_LIST);
4268
+ const selectFields = isNonEmptyArray(aggFields) ? selectCols.concat(aggFields).join(SQL_SEPARATORS.FIELD_LIST) : selectCols.join(SQL_SEPARATORS.FIELD_LIST);
4187
4269
  return { groupCols, groupFields, selectFields };
4188
4270
  }
4189
4271
  function buildGroupByHaving(args, alias, params, model, dialect) {
4190
4272
  if (!isNotNullish(args.having)) return "";
4191
- if (!isPlainObject(args.having)) return "";
4273
+ if (!isPlainObject(args.having)) throw new Error("having must be an object");
4192
4274
  const h = buildHavingClause(args.having, alias, params, model, dialect);
4193
4275
  if (!h || h.trim().length === 0) return "";
4194
4276
  return `${SQL_TEMPLATES.HAVING} ${h}`;
@@ -4221,64 +4303,60 @@ function buildGroupBySql(args, whereResult, tableName, alias, model, dialect) {
4221
4303
  const snapshot = params.snapshot();
4222
4304
  validateSelectQuery(sql);
4223
4305
  validateParamConsistency(sql, [...whereResult.params, ...snapshot.params]);
4224
- const mergedParams = [...whereResult.params, ...snapshot.params];
4225
4306
  return Object.freeze({
4226
4307
  sql,
4227
- params: Object.freeze(normalizeFinalParams2(mergedParams)),
4308
+ params: Object.freeze([...whereResult.params, ...snapshot.params]),
4228
4309
  paramMappings: Object.freeze([
4229
4310
  ...whereResult.paramMappings,
4230
4311
  ...snapshot.mappings
4231
4312
  ])
4232
4313
  });
4233
4314
  }
4234
- function buildCountSql(whereResult, tableName, alias, skip, dialect) {
4315
+ function buildCountSql(whereResult, tableName, alias, skip, _dialect) {
4235
4316
  assertSafeAlias(alias);
4236
4317
  assertSafeTableRef(tableName);
4237
- const d = dialect != null ? dialect : getGlobalDialect();
4318
+ if (skip !== void 0 && skip !== null) {
4319
+ if (schemaParser.isDynamicParameter(skip)) {
4320
+ throw new Error(
4321
+ "count() with skip is not supported because it produces nondeterministic results. Dynamic skip cannot be validated at build time. Use findMany().length or add explicit orderBy + cursor/skip logic in a deterministic query."
4322
+ );
4323
+ }
4324
+ if (typeof skip === "string") {
4325
+ const s = skip.trim();
4326
+ if (s.length > 0) {
4327
+ const n = Number(s);
4328
+ if (Number.isFinite(n) && Number.isInteger(n) && n > 0) {
4329
+ throw new Error(
4330
+ "count() with skip is not supported because it produces nondeterministic results. Use findMany().length or add explicit orderBy to ensure deterministic behavior."
4331
+ );
4332
+ }
4333
+ }
4334
+ }
4335
+ if (typeof skip === "number" && Number.isFinite(skip) && Number.isInteger(skip) && skip > 0) {
4336
+ throw new Error(
4337
+ "count() with skip is not supported because it produces nondeterministic results. Use findMany().length or add explicit orderBy to ensure deterministic behavior."
4338
+ );
4339
+ }
4340
+ }
4238
4341
  const whereClause = isValidWhereClause(whereResult.clause) ? `${SQL_TEMPLATES.WHERE} ${whereResult.clause}` : "";
4239
- const params = createParamStore(whereResult.nextParamIndex);
4240
- const baseSubSelect = [
4241
- SQL_TEMPLATES.SELECT,
4242
- "1",
4243
- SQL_TEMPLATES.FROM,
4244
- tableName,
4245
- alias,
4246
- whereClause
4247
- ].filter((x) => x && String(x).trim().length > 0).join(" ").trim();
4248
- const normalizedSkip = normalizeSkipLike(skip);
4249
- const subSelect = applyCountSkip(baseSubSelect, normalizedSkip, params, d);
4250
4342
  const sql = [
4251
4343
  SQL_TEMPLATES.SELECT,
4252
4344
  SQL_TEMPLATES.COUNT_ALL,
4253
4345
  SQL_TEMPLATES.AS,
4254
4346
  quote("_count._all"),
4255
4347
  SQL_TEMPLATES.FROM,
4256
- `(${subSelect})`,
4257
- SQL_TEMPLATES.AS,
4258
- `"sub"`
4348
+ tableName,
4349
+ alias,
4350
+ whereClause
4259
4351
  ].filter((x) => x && String(x).trim().length > 0).join(" ").trim();
4260
4352
  validateSelectQuery(sql);
4261
- const snapshot = params.snapshot();
4262
- const mergedParams = [...whereResult.params, ...snapshot.params];
4263
- validateParamConsistency(sql, mergedParams);
4353
+ validateParamConsistency(sql, whereResult.params);
4264
4354
  return Object.freeze({
4265
4355
  sql,
4266
- params: Object.freeze(normalizeFinalParams2(mergedParams)),
4267
- paramMappings: Object.freeze([
4268
- ...whereResult.paramMappings,
4269
- ...snapshot.mappings
4270
- ])
4356
+ params: Object.freeze([...whereResult.params]),
4357
+ paramMappings: Object.freeze([...whereResult.paramMappings])
4271
4358
  });
4272
4359
  }
4273
- function applyCountSkip(subSelect, normalizedSkip, params, dialect) {
4274
- const shouldApply = schemaParser.isDynamicParameter(normalizedSkip) || typeof normalizedSkip === "number" && normalizedSkip > 0;
4275
- if (!shouldApply) return subSelect;
4276
- const placeholder = addAutoScoped(params, normalizedSkip, "count.skip");
4277
- if (dialect === "sqlite") {
4278
- return `${subSelect} ${SQL_TEMPLATES.LIMIT} -1 ${SQL_TEMPLATES.OFFSET} ${placeholder}`;
4279
- }
4280
- return `${subSelect} ${SQL_TEMPLATES.OFFSET} ${placeholder}`;
4281
- }
4282
4360
  function safeAlias(input) {
4283
4361
  const raw = String(input).toLowerCase();
4284
4362
  const cleaned = raw.replace(/[^a-z0-9_]/g, "_");
@@ -4428,8 +4506,12 @@ function buildAndNormalizeSql(args) {
4428
4506
  );
4429
4507
  }
4430
4508
  function finalizeDirective(args) {
4431
- const { directive, normalizedSql, normalizedMappings } = args;
4432
- validateSqlPositions(normalizedSql, normalizedMappings, getGlobalDialect());
4509
+ const { directive, normalizedSql, normalizedMappings, dialect } = args;
4510
+ const params = normalizedMappings.map((m) => {
4511
+ var _a;
4512
+ return (_a = m.value) != null ? _a : void 0;
4513
+ });
4514
+ validateParamConsistencyByDialect(normalizedSql, params, dialect);
4433
4515
  const { staticParams, dynamicKeys } = buildParamsFromMappings(normalizedMappings);
4434
4516
  return {
4435
4517
  method: directive.method,
@@ -4466,7 +4548,8 @@ function generateSQL(directive) {
4466
4548
  return finalizeDirective({
4467
4549
  directive,
4468
4550
  normalizedSql: normalized.sql,
4469
- normalizedMappings: normalized.paramMappings
4551
+ normalizedMappings: normalized.paramMappings,
4552
+ dialect
4470
4553
  });
4471
4554
  }
4472
4555
 
@@ -4843,9 +4926,7 @@ function buildSQLFull(model, models, method, args, dialect) {
4843
4926
  whereResult,
4844
4927
  tableName,
4845
4928
  alias,
4846
- args.skip,
4847
- dialect
4848
- );
4929
+ args.skip);
4849
4930
  break;
4850
4931
  default:
4851
4932
  result = buildSelectSql({