prisma-sql 1.43.0 → 1.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/generator.cjs +808 -555
- package/dist/generator.cjs.map +1 -1
- package/dist/generator.js +808 -555
- package/dist/generator.js.map +1 -1
- package/dist/index.cjs +765 -553
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +765 -553
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
52
|
+
var ALIAS_FORBIDDEN_KEYWORDS = /* @__PURE__ */ new Set([
|
|
53
53
|
"select",
|
|
54
54
|
"from",
|
|
55
55
|
"where",
|
|
56
|
-
"
|
|
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
|
-
"
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
447
|
-
|
|
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
|
-
|
|
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
|
-
|
|
458
|
-
|
|
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
|
-
|
|
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
|
|
@@ -532,7 +583,7 @@ function scanDollarPlaceholders(sql, markUpTo) {
|
|
|
532
583
|
}
|
|
533
584
|
return { count, min, max, seen };
|
|
534
585
|
}
|
|
535
|
-
function
|
|
586
|
+
function assertNoGapsDollar(scan, rangeMin, rangeMax, sql) {
|
|
536
587
|
for (let k = rangeMin; k <= rangeMax; k++) {
|
|
537
588
|
if (scan.seen[k] !== 1) {
|
|
538
589
|
throw new Error(
|
|
@@ -560,7 +611,7 @@ function validateParamConsistency(sql, params) {
|
|
|
560
611
|
`CRITICAL: Parameter mismatch - SQL max placeholder is $${scan.max} but ${paramLen} params provided. This will cause SQL execution to fail. SQL: ${sqlPreview(sql)}`
|
|
561
612
|
);
|
|
562
613
|
}
|
|
563
|
-
|
|
614
|
+
assertNoGapsDollar(scan, 1, scan.max, sql);
|
|
564
615
|
}
|
|
565
616
|
function needsQuoting(id) {
|
|
566
617
|
if (!isNonEmptyString(id)) return true;
|
|
@@ -578,14 +629,11 @@ function validateParamConsistencyFragment(sql, params) {
|
|
|
578
629
|
`CRITICAL: Parameter mismatch - SQL references $${scan.max} but only ${paramLen} params provided. SQL: ${sqlPreview(sql)}`
|
|
579
630
|
);
|
|
580
631
|
}
|
|
581
|
-
|
|
632
|
+
assertNoGapsDollar(scan, scan.min, scan.max, sql);
|
|
582
633
|
}
|
|
583
634
|
function assertOrThrow(condition, message) {
|
|
584
635
|
if (!condition) throw new Error(message);
|
|
585
636
|
}
|
|
586
|
-
function dialectPlaceholderPrefix(dialect) {
|
|
587
|
-
return dialect === "sqlite" ? "?" : "$";
|
|
588
|
-
}
|
|
589
637
|
function parseSqlitePlaceholderIndices(sql) {
|
|
590
638
|
const re = /\?(?:(\d+))?/g;
|
|
591
639
|
const indices = [];
|
|
@@ -605,112 +653,70 @@ function parseSqlitePlaceholderIndices(sql) {
|
|
|
605
653
|
}
|
|
606
654
|
return { indices, sawNumbered, sawAnonymous };
|
|
607
655
|
}
|
|
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
656
|
function maxIndex(indices) {
|
|
623
657
|
return indices.length > 0 ? Math.max(...indices) : 0;
|
|
624
658
|
}
|
|
625
|
-
function
|
|
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);
|
|
659
|
+
function ensureSequentialIndices(seen, max, prefix) {
|
|
639
660
|
for (let i = 1; i <= max; i++) {
|
|
640
661
|
assertOrThrow(
|
|
641
|
-
|
|
662
|
+
seen.has(i),
|
|
642
663
|
`CRITICAL: Missing SQL placeholder ${prefix}${i} - placeholders must be sequential 1..${max}.`
|
|
643
664
|
);
|
|
644
665
|
}
|
|
645
666
|
}
|
|
646
|
-
function
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
667
|
+
function validateSqlitePlaceholders(sql, params) {
|
|
668
|
+
const paramLen = params.length;
|
|
669
|
+
const { indices, sawNumbered, sawAnonymous } = parseSqlitePlaceholderIndices(sql);
|
|
670
|
+
if (indices.length === 0) {
|
|
671
|
+
if (paramLen !== 0) {
|
|
672
|
+
throw new Error(
|
|
673
|
+
`CRITICAL: Parameter mismatch - SQL has no sqlite placeholders but ${paramLen} params provided. SQL: ${sqlPreview(sql)}`
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
653
678
|
assertOrThrow(
|
|
654
|
-
!
|
|
655
|
-
`CRITICAL:
|
|
679
|
+
!(sawNumbered && sawAnonymous),
|
|
680
|
+
`CRITICAL: Mixed sqlite placeholders ('?' and '?NNN') are not supported.`
|
|
656
681
|
);
|
|
657
|
-
|
|
658
|
-
}
|
|
659
|
-
function ensureMappingIndexExistsInSql(placeholders, index) {
|
|
682
|
+
const max = maxIndex(indices);
|
|
660
683
|
assertOrThrow(
|
|
661
|
-
|
|
662
|
-
`CRITICAL:
|
|
684
|
+
max === paramLen,
|
|
685
|
+
`CRITICAL: SQL placeholder max mismatch - max is ?${max}, but params length is ${paramLen}. SQL: ${sqlPreview(sql)}`
|
|
663
686
|
);
|
|
687
|
+
const set = new Set(indices);
|
|
688
|
+
ensureSequentialIndices(set, max, "?");
|
|
664
689
|
}
|
|
665
|
-
function
|
|
666
|
-
|
|
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
|
-
);
|
|
690
|
+
function validateDollarPlaceholders(sql, params) {
|
|
691
|
+
validateParamConsistency(sql, params);
|
|
674
692
|
}
|
|
675
|
-
function
|
|
676
|
-
const
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
mappingIndices.has(i),
|
|
680
|
-
`CRITICAL: Missing ParamMap for placeholder ${prefix}${i} - mappings must cover 1..${max} with no gaps.`
|
|
681
|
-
);
|
|
682
|
-
}
|
|
693
|
+
function detectPlaceholderStyle(sql) {
|
|
694
|
+
const hasDollar = /\$\d+/.test(sql);
|
|
695
|
+
const hasSqliteQ = /\?(?:\d+)?/.test(sql);
|
|
696
|
+
return { hasDollar, hasSqliteQ };
|
|
683
697
|
}
|
|
684
|
-
function
|
|
685
|
-
const
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
698
|
+
function validateParamConsistencyByDialect(sql, params, dialect) {
|
|
699
|
+
const { hasDollar, hasSqliteQ } = detectPlaceholderStyle(sql);
|
|
700
|
+
if (dialect !== "sqlite") {
|
|
701
|
+
if (hasSqliteQ && !hasDollar) {
|
|
702
|
+
throw new Error(
|
|
703
|
+
`CRITICAL: Non-sqlite dialect query contains sqlite '?' placeholders. SQL: ${sqlPreview(sql)}`
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
return validateDollarPlaceholders(sql, params);
|
|
691
707
|
}
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
sql,
|
|
697
|
-
dialect
|
|
698
|
-
);
|
|
699
|
-
if (dialect === "sqlite") {
|
|
700
|
-
ensureNoMixedSqlitePlaceholders(sawNumbered, sawAnonymous);
|
|
708
|
+
if (hasDollar && hasSqliteQ) {
|
|
709
|
+
throw new Error(
|
|
710
|
+
`CRITICAL: Mixed placeholder styles ($N and ?/ ?NNN) are not supported. SQL: ${sqlPreview(sql)}`
|
|
711
|
+
);
|
|
701
712
|
}
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
const max = maxIndex(indices);
|
|
705
|
-
ensurePlaceholderMaxMatchesMappingsLength(max, mappings.length, dialect);
|
|
706
|
-
ensureSequentialPlaceholders(placeholders, max, dialect);
|
|
707
|
-
validateMappingsAgainstPlaceholders(mappings, placeholders, max, dialect);
|
|
713
|
+
if (hasSqliteQ) return validateSqlitePlaceholders(sql, params);
|
|
714
|
+
return validateDollarPlaceholders(sql, params);
|
|
708
715
|
}
|
|
709
716
|
|
|
710
717
|
// src/builder/shared/sql-utils.ts
|
|
711
|
-
var NUL = String.fromCharCode(0);
|
|
712
718
|
function containsControlChars(s) {
|
|
713
|
-
return
|
|
719
|
+
return /[\u0000-\u001F\u007F]/.test(s);
|
|
714
720
|
}
|
|
715
721
|
function assertNoControlChars(label, s) {
|
|
716
722
|
if (containsControlChars(s)) {
|
|
@@ -929,16 +935,46 @@ function buildTableReference(schemaName, tableName, dialect) {
|
|
|
929
935
|
return `"${safeSchema}"."${safeTable}"`;
|
|
930
936
|
}
|
|
931
937
|
function assertSafeAlias(alias) {
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
938
|
+
if (typeof alias !== "string") {
|
|
939
|
+
throw new Error(`Invalid alias: expected string, got ${typeof alias}`);
|
|
940
|
+
}
|
|
941
|
+
const a = alias.trim();
|
|
942
|
+
if (a.length === 0) {
|
|
943
|
+
throw new Error("Invalid alias: required and cannot be empty");
|
|
935
944
|
}
|
|
936
|
-
if (
|
|
937
|
-
throw new Error(
|
|
945
|
+
if (a !== alias) {
|
|
946
|
+
throw new Error("Invalid alias: leading/trailing whitespace");
|
|
938
947
|
}
|
|
939
|
-
if (
|
|
948
|
+
if (/[\u0000-\u001F\u007F]/.test(a)) {
|
|
940
949
|
throw new Error(
|
|
941
|
-
|
|
950
|
+
"Invalid alias: contains unsafe characters (control characters)"
|
|
951
|
+
);
|
|
952
|
+
}
|
|
953
|
+
if (a.includes('"') || a.includes("'") || a.includes("`")) {
|
|
954
|
+
throw new Error("Invalid alias: contains unsafe characters (quotes)");
|
|
955
|
+
}
|
|
956
|
+
if (a.includes(";")) {
|
|
957
|
+
throw new Error("Invalid alias: contains unsafe characters (semicolon)");
|
|
958
|
+
}
|
|
959
|
+
if (a.includes("--") || a.includes("/*") || a.includes("*/")) {
|
|
960
|
+
throw new Error(
|
|
961
|
+
"Invalid alias: contains unsafe characters (SQL comment tokens)"
|
|
962
|
+
);
|
|
963
|
+
}
|
|
964
|
+
if (/\s/.test(a)) {
|
|
965
|
+
throw new Error(
|
|
966
|
+
"Invalid alias: must be a simple identifier without whitespace"
|
|
967
|
+
);
|
|
968
|
+
}
|
|
969
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(a)) {
|
|
970
|
+
throw new Error(
|
|
971
|
+
`Invalid alias: must be a simple identifier (alphanumeric with underscores): "${alias}"`
|
|
972
|
+
);
|
|
973
|
+
}
|
|
974
|
+
const lowered = a.toLowerCase();
|
|
975
|
+
if (ALIAS_FORBIDDEN_KEYWORDS.has(lowered)) {
|
|
976
|
+
throw new Error(
|
|
977
|
+
`Invalid alias: '${alias}' is a SQL keyword that would break query parsing. Forbidden aliases: ${[...ALIAS_FORBIDDEN_KEYWORDS].join(", ")}`
|
|
942
978
|
);
|
|
943
979
|
}
|
|
944
980
|
}
|
|
@@ -980,7 +1016,9 @@ function isValidRelationField(field) {
|
|
|
980
1016
|
if (fk.length === 0) return false;
|
|
981
1017
|
const refsRaw = field.references;
|
|
982
1018
|
const refs = normalizeKeyList(refsRaw);
|
|
983
|
-
if (refs.length === 0)
|
|
1019
|
+
if (refs.length === 0) {
|
|
1020
|
+
return fk.length === 1;
|
|
1021
|
+
}
|
|
984
1022
|
if (refs.length !== fk.length) return false;
|
|
985
1023
|
return true;
|
|
986
1024
|
}
|
|
@@ -995,6 +1033,8 @@ function getReferenceFieldNames(field, foreignKeyCount) {
|
|
|
995
1033
|
return refs;
|
|
996
1034
|
}
|
|
997
1035
|
function joinCondition(field, parentModel, childModel, parentAlias, childAlias) {
|
|
1036
|
+
assertSafeAlias(parentAlias);
|
|
1037
|
+
assertSafeAlias(childAlias);
|
|
998
1038
|
const fkFields = normalizeKeyList(field.foreignKey);
|
|
999
1039
|
if (fkFields.length === 0) {
|
|
1000
1040
|
throw createError(
|
|
@@ -1144,6 +1184,32 @@ function normalizeOrderByInput(orderBy, parseValue) {
|
|
|
1144
1184
|
throw new Error("orderBy must be an object or array of objects");
|
|
1145
1185
|
}
|
|
1146
1186
|
|
|
1187
|
+
// src/builder/shared/order-by-determinism.ts
|
|
1188
|
+
function modelHasScalarId(model) {
|
|
1189
|
+
if (!model) return false;
|
|
1190
|
+
return getScalarFieldSet(model).has("id");
|
|
1191
|
+
}
|
|
1192
|
+
function hasIdTiebreaker(orderBy, parse) {
|
|
1193
|
+
if (!isNotNullish(orderBy)) return false;
|
|
1194
|
+
const normalized = normalizeOrderByInput(orderBy, parse);
|
|
1195
|
+
return normalized.some(
|
|
1196
|
+
(obj) => Object.prototype.hasOwnProperty.call(obj, "id")
|
|
1197
|
+
);
|
|
1198
|
+
}
|
|
1199
|
+
function addIdTiebreaker(orderBy) {
|
|
1200
|
+
if (Array.isArray(orderBy)) return [...orderBy, { id: "asc" }];
|
|
1201
|
+
return [orderBy, { id: "asc" }];
|
|
1202
|
+
}
|
|
1203
|
+
function ensureDeterministicOrderByInput(args) {
|
|
1204
|
+
const { orderBy, model, parseValue } = args;
|
|
1205
|
+
if (!modelHasScalarId(model)) return orderBy;
|
|
1206
|
+
if (!isNotNullish(orderBy)) {
|
|
1207
|
+
return { id: "asc" };
|
|
1208
|
+
}
|
|
1209
|
+
if (hasIdTiebreaker(orderBy, parseValue)) return orderBy;
|
|
1210
|
+
return addIdTiebreaker(orderBy);
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1147
1213
|
// src/builder/pagination.ts
|
|
1148
1214
|
var MAX_LIMIT_OFFSET = 2147483647;
|
|
1149
1215
|
function parseDirectionRaw(raw, errorLabel) {
|
|
@@ -1206,7 +1272,15 @@ function hasNonNullishProp(v, key) {
|
|
|
1206
1272
|
}
|
|
1207
1273
|
function normalizeIntegerOrDynamic(name, v) {
|
|
1208
1274
|
if (schemaParser.isDynamicParameter(v)) return v;
|
|
1209
|
-
|
|
1275
|
+
const result = normalizeIntLike(name, v, {
|
|
1276
|
+
min: Number.MIN_SAFE_INTEGER,
|
|
1277
|
+
max: MAX_LIMIT_OFFSET,
|
|
1278
|
+
allowZero: true
|
|
1279
|
+
});
|
|
1280
|
+
if (result === void 0) {
|
|
1281
|
+
throw new Error(`${name} normalization returned undefined`);
|
|
1282
|
+
}
|
|
1283
|
+
return result;
|
|
1210
1284
|
}
|
|
1211
1285
|
function readSkipTake(relArgs) {
|
|
1212
1286
|
const hasSkip = hasNonNullishProp(relArgs, "skip");
|
|
@@ -1272,7 +1346,7 @@ function buildCursorFilterParts(cursor, cursorAlias, params, model) {
|
|
|
1272
1346
|
const placeholdersByField = /* @__PURE__ */ new Map();
|
|
1273
1347
|
const parts = [];
|
|
1274
1348
|
for (const [field, value] of entries) {
|
|
1275
|
-
const c = `${cursorAlias}.${
|
|
1349
|
+
const c = `${cursorAlias}.${quoteColumn(model, field)}`;
|
|
1276
1350
|
if (value === null) {
|
|
1277
1351
|
parts.push(`${c} IS NULL`);
|
|
1278
1352
|
continue;
|
|
@@ -1286,13 +1360,6 @@ function buildCursorFilterParts(cursor, cursorAlias, params, model) {
|
|
|
1286
1360
|
placeholdersByField
|
|
1287
1361
|
};
|
|
1288
1362
|
}
|
|
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
1363
|
function buildCursorEqualityExpr(columnExpr, valueExpr) {
|
|
1297
1364
|
return `((${valueExpr} IS NULL AND ${columnExpr} IS NULL) OR (${valueExpr} IS NOT NULL AND ${columnExpr} = ${valueExpr}))`;
|
|
1298
1365
|
}
|
|
@@ -1331,7 +1398,7 @@ function buildOrderEntries(orderBy) {
|
|
|
1331
1398
|
} else {
|
|
1332
1399
|
entries.push({
|
|
1333
1400
|
field,
|
|
1334
|
-
direction: value.
|
|
1401
|
+
direction: value.direction,
|
|
1335
1402
|
nulls: value.nulls
|
|
1336
1403
|
});
|
|
1337
1404
|
}
|
|
@@ -1339,16 +1406,53 @@ function buildOrderEntries(orderBy) {
|
|
|
1339
1406
|
}
|
|
1340
1407
|
return entries;
|
|
1341
1408
|
}
|
|
1409
|
+
function buildCursorCteSelectList(cursorEntries, orderEntries, model) {
|
|
1410
|
+
const set = /* @__PURE__ */ new Set();
|
|
1411
|
+
for (const [f] of cursorEntries) set.add(f);
|
|
1412
|
+
for (const e of orderEntries) set.add(e.field);
|
|
1413
|
+
const cols = [...set].map((f) => quoteColumn(model, f));
|
|
1414
|
+
if (cols.length === 0) {
|
|
1415
|
+
throw new Error("cursor cte select list is empty");
|
|
1416
|
+
}
|
|
1417
|
+
return cols.join(SQL_SEPARATORS.FIELD_LIST);
|
|
1418
|
+
}
|
|
1419
|
+
function truncateIdent(name, maxLen) {
|
|
1420
|
+
const s = String(name);
|
|
1421
|
+
if (s.length <= maxLen) return s;
|
|
1422
|
+
return s.slice(0, maxLen);
|
|
1423
|
+
}
|
|
1424
|
+
function buildCursorNames(outerAlias) {
|
|
1425
|
+
const maxLen = 63;
|
|
1426
|
+
const base = outerAlias.toLowerCase();
|
|
1427
|
+
const cteName = truncateIdent(`__tp_cursor_${base}`, maxLen);
|
|
1428
|
+
const srcAlias = truncateIdent(`__tp_cursor_src_${base}`, maxLen);
|
|
1429
|
+
if (cteName === outerAlias || srcAlias === outerAlias) {
|
|
1430
|
+
return {
|
|
1431
|
+
cteName: truncateIdent(`__tp_cursor_${base}_x`, maxLen),
|
|
1432
|
+
srcAlias: truncateIdent(`__tp_cursor_src_${base}_x`, maxLen)
|
|
1433
|
+
};
|
|
1434
|
+
}
|
|
1435
|
+
return { cteName, srcAlias };
|
|
1436
|
+
}
|
|
1342
1437
|
function buildCursorCondition(cursor, orderBy, tableName, alias, params, dialect, model) {
|
|
1343
1438
|
var _a;
|
|
1439
|
+
assertSafeTableRef(tableName);
|
|
1440
|
+
assertSafeAlias(alias);
|
|
1344
1441
|
const d = dialect != null ? dialect : getGlobalDialect();
|
|
1345
1442
|
const cursorEntries = Object.entries(cursor);
|
|
1346
1443
|
if (cursorEntries.length === 0) {
|
|
1347
1444
|
throw new Error("cursor must have at least one field");
|
|
1348
1445
|
}
|
|
1349
|
-
const
|
|
1350
|
-
|
|
1351
|
-
|
|
1446
|
+
const { cteName, srcAlias } = buildCursorNames(alias);
|
|
1447
|
+
assertSafeAlias(cteName);
|
|
1448
|
+
assertSafeAlias(srcAlias);
|
|
1449
|
+
const { whereSql: cursorWhereSql, placeholdersByField } = buildCursorFilterParts(cursor, srcAlias, params, model);
|
|
1450
|
+
const deterministicOrderBy = ensureDeterministicOrderByInput({
|
|
1451
|
+
orderBy,
|
|
1452
|
+
model,
|
|
1453
|
+
parseValue: parseOrderByValue
|
|
1454
|
+
});
|
|
1455
|
+
let orderEntries = buildOrderEntries(deterministicOrderBy);
|
|
1352
1456
|
if (orderEntries.length === 0) {
|
|
1353
1457
|
orderEntries = cursorEntries.map(([field]) => ({
|
|
1354
1458
|
field,
|
|
@@ -1357,11 +1461,21 @@ function buildCursorCondition(cursor, orderBy, tableName, alias, params, dialect
|
|
|
1357
1461
|
} else {
|
|
1358
1462
|
orderEntries = ensureCursorFieldsInOrder(orderEntries, cursorEntries);
|
|
1359
1463
|
}
|
|
1360
|
-
const
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1464
|
+
const cursorOrderBy = orderEntries.map(
|
|
1465
|
+
(e) => `${srcAlias}.${quoteColumn(model, e.field)} ${e.direction.toUpperCase()}`
|
|
1466
|
+
).join(", ");
|
|
1467
|
+
const selectList = buildCursorCteSelectList(
|
|
1468
|
+
cursorEntries,
|
|
1469
|
+
orderEntries,
|
|
1470
|
+
model
|
|
1364
1471
|
);
|
|
1472
|
+
const cte = `${cteName} AS (
|
|
1473
|
+
SELECT ${selectList} FROM ${tableName} ${srcAlias}
|
|
1474
|
+
WHERE ${cursorWhereSql}
|
|
1475
|
+
ORDER BY ${cursorOrderBy}
|
|
1476
|
+
LIMIT 1
|
|
1477
|
+
)`;
|
|
1478
|
+
const existsExpr = `EXISTS (SELECT 1 FROM ${cteName})`;
|
|
1365
1479
|
const outerCursorMatch = buildOuterCursorMatch(
|
|
1366
1480
|
cursor,
|
|
1367
1481
|
alias,
|
|
@@ -1369,34 +1483,31 @@ function buildCursorCondition(cursor, orderBy, tableName, alias, params, dialect
|
|
|
1369
1483
|
params,
|
|
1370
1484
|
model
|
|
1371
1485
|
);
|
|
1486
|
+
const getValueExpr = (field) => {
|
|
1487
|
+
return `(SELECT ${quoteColumn(model, field)} FROM ${cteName})`;
|
|
1488
|
+
};
|
|
1372
1489
|
const orClauses = [];
|
|
1373
1490
|
for (let level = 0; level < orderEntries.length; level++) {
|
|
1374
1491
|
const andParts = [];
|
|
1375
1492
|
for (let i = 0; i < level; i++) {
|
|
1376
1493
|
const e2 = orderEntries[i];
|
|
1377
1494
|
const c2 = col(alias, e2.field, model);
|
|
1378
|
-
const v2 =
|
|
1379
|
-
tableName,
|
|
1380
|
-
cursorAlias,
|
|
1381
|
-
cursorWhereSql,
|
|
1382
|
-
e2.field);
|
|
1495
|
+
const v2 = getValueExpr(e2.field);
|
|
1383
1496
|
andParts.push(buildCursorEqualityExpr(c2, v2));
|
|
1384
1497
|
}
|
|
1385
1498
|
const e = orderEntries[level];
|
|
1386
1499
|
const c = col(alias, e.field, model);
|
|
1387
|
-
const v =
|
|
1388
|
-
tableName,
|
|
1389
|
-
cursorAlias,
|
|
1390
|
-
cursorWhereSql,
|
|
1391
|
-
e.field);
|
|
1500
|
+
const v = getValueExpr(e.field);
|
|
1392
1501
|
const nulls = (_a = e.nulls) != null ? _a : defaultNullsFor(d, e.direction);
|
|
1393
1502
|
andParts.push(buildCursorInequalityExpr(c, e.direction, nulls, v));
|
|
1394
1503
|
orClauses.push(`(${andParts.join(SQL_SEPARATORS.CONDITION_AND)})`);
|
|
1395
1504
|
}
|
|
1396
1505
|
const exclusive = orClauses.join(SQL_SEPARATORS.CONDITION_OR);
|
|
1397
|
-
|
|
1506
|
+
const condition = `(${existsExpr} ${SQL_SEPARATORS.CONDITION_AND} ((${exclusive})${SQL_SEPARATORS.CONDITION_OR}(${outerCursorMatch})))`;
|
|
1507
|
+
return { cte, condition };
|
|
1398
1508
|
}
|
|
1399
1509
|
function buildOrderBy(orderBy, alias, dialect, model) {
|
|
1510
|
+
assertSafeAlias(alias);
|
|
1400
1511
|
const entries = buildOrderEntries(orderBy);
|
|
1401
1512
|
if (entries.length === 0) return "";
|
|
1402
1513
|
const d = dialect != null ? dialect : getGlobalDialect();
|
|
@@ -1496,6 +1607,11 @@ function buildScalarOperator(expr, op, val, params, mode, fieldType, dialect) {
|
|
|
1496
1607
|
}
|
|
1497
1608
|
return handleInOperator(expr, op, val, params, dialect);
|
|
1498
1609
|
}
|
|
1610
|
+
if (op === Ops.EQUALS && mode === Modes.INSENSITIVE && !isNotNullish(dialect)) {
|
|
1611
|
+
throw createError(`Insensitive equals requires a SQL dialect`, {
|
|
1612
|
+
operator: op
|
|
1613
|
+
});
|
|
1614
|
+
}
|
|
1499
1615
|
return handleComparisonOperator(expr, op, val, params);
|
|
1500
1616
|
}
|
|
1501
1617
|
function handleNullValue(expr, op) {
|
|
@@ -1511,6 +1627,28 @@ function normalizeMode(v) {
|
|
|
1511
1627
|
function handleNotOperator(expr, val, params, outerMode, fieldType, dialect) {
|
|
1512
1628
|
const innerMode = normalizeMode(val.mode);
|
|
1513
1629
|
const effectiveMode = innerMode != null ? innerMode : outerMode;
|
|
1630
|
+
const entries = Object.entries(val).filter(
|
|
1631
|
+
([k, v]) => k !== "mode" && v !== void 0
|
|
1632
|
+
);
|
|
1633
|
+
if (entries.length === 0) return "";
|
|
1634
|
+
if (!isNotNullish(dialect)) {
|
|
1635
|
+
const clauses = [];
|
|
1636
|
+
for (const [subOp, subVal] of entries) {
|
|
1637
|
+
const sub = buildScalarOperator(
|
|
1638
|
+
expr,
|
|
1639
|
+
subOp,
|
|
1640
|
+
subVal,
|
|
1641
|
+
params,
|
|
1642
|
+
effectiveMode,
|
|
1643
|
+
fieldType,
|
|
1644
|
+
void 0
|
|
1645
|
+
);
|
|
1646
|
+
if (sub && sub.trim().length > 0) clauses.push(`(${sub})`);
|
|
1647
|
+
}
|
|
1648
|
+
if (clauses.length === 0) return "";
|
|
1649
|
+
if (clauses.length === 1) return `${SQL_TEMPLATES.NOT} ${clauses[0]}`;
|
|
1650
|
+
return `${SQL_TEMPLATES.NOT} (${clauses.join(` ${SQL_TEMPLATES.AND} `)})`;
|
|
1651
|
+
}
|
|
1514
1652
|
return buildNotComposite(
|
|
1515
1653
|
expr,
|
|
1516
1654
|
val,
|
|
@@ -1725,6 +1863,7 @@ function handleArrayIsEmpty(expr, val, dialect) {
|
|
|
1725
1863
|
|
|
1726
1864
|
// src/builder/where/operators-json.ts
|
|
1727
1865
|
var SAFE_JSON_PATH_SEGMENT = /^[a-zA-Z_]\w*$/;
|
|
1866
|
+
var MAX_PATH_SEGMENT_LENGTH = 255;
|
|
1728
1867
|
function validateJsonPathSegments(segments) {
|
|
1729
1868
|
for (const segment of segments) {
|
|
1730
1869
|
if (typeof segment !== "string") {
|
|
@@ -1733,6 +1872,12 @@ function validateJsonPathSegments(segments) {
|
|
|
1733
1872
|
value: segment
|
|
1734
1873
|
});
|
|
1735
1874
|
}
|
|
1875
|
+
if (segment.length > MAX_PATH_SEGMENT_LENGTH) {
|
|
1876
|
+
throw createError(
|
|
1877
|
+
`JSON path segment too long: max ${MAX_PATH_SEGMENT_LENGTH} characters`,
|
|
1878
|
+
{ operator: Ops.PATH, value: `[${segment.length} chars]` }
|
|
1879
|
+
);
|
|
1880
|
+
}
|
|
1736
1881
|
if (!SAFE_JSON_PATH_SEGMENT.test(segment)) {
|
|
1737
1882
|
throw createError(
|
|
1738
1883
|
`Invalid JSON path segment: '${segment}'. Must be alphanumeric with underscores, starting with letter or underscore.`,
|
|
@@ -1821,6 +1966,9 @@ function handleJsonWildcard(expr, op, val, params, wildcards, dialect) {
|
|
|
1821
1966
|
|
|
1822
1967
|
// src/builder/where/relations.ts
|
|
1823
1968
|
var NO_JOINS = Object.freeze([]);
|
|
1969
|
+
function freezeJoins(items) {
|
|
1970
|
+
return Object.freeze([...items]);
|
|
1971
|
+
}
|
|
1824
1972
|
function isListRelation(fieldType) {
|
|
1825
1973
|
return typeof fieldType === "string" && fieldType.endsWith("[]");
|
|
1826
1974
|
}
|
|
@@ -1883,7 +2031,7 @@ function buildListRelationFilters(args) {
|
|
|
1883
2031
|
const whereClause = `${relAlias}.${quote(checkField.name)} IS NULL`;
|
|
1884
2032
|
return Object.freeze({
|
|
1885
2033
|
clause: whereClause,
|
|
1886
|
-
joins: [leftJoinSql]
|
|
2034
|
+
joins: freezeJoins([leftJoinSql])
|
|
1887
2035
|
});
|
|
1888
2036
|
}
|
|
1889
2037
|
}
|
|
@@ -2088,7 +2236,7 @@ function assertValidOperator(fieldName, op, fieldType, path, modelName) {
|
|
|
2088
2236
|
Ops.HAS_EVERY,
|
|
2089
2237
|
Ops.IS_EMPTY
|
|
2090
2238
|
]);
|
|
2091
|
-
const
|
|
2239
|
+
const JSON_OPS2 = /* @__PURE__ */ new Set([
|
|
2092
2240
|
Ops.PATH,
|
|
2093
2241
|
Ops.STRING_CONTAINS,
|
|
2094
2242
|
Ops.STRING_STARTS_WITH,
|
|
@@ -2105,7 +2253,7 @@ function assertValidOperator(fieldName, op, fieldType, path, modelName) {
|
|
|
2105
2253
|
modelName
|
|
2106
2254
|
});
|
|
2107
2255
|
}
|
|
2108
|
-
const isJsonOp =
|
|
2256
|
+
const isJsonOp = JSON_OPS2.has(op);
|
|
2109
2257
|
const isFieldJson = isJsonType(fieldType);
|
|
2110
2258
|
const jsonOpMismatch = isJsonOp && !isFieldJson;
|
|
2111
2259
|
if (jsonOpMismatch) {
|
|
@@ -2119,6 +2267,14 @@ function assertValidOperator(fieldName, op, fieldType, path, modelName) {
|
|
|
2119
2267
|
}
|
|
2120
2268
|
|
|
2121
2269
|
// src/builder/where/builder.ts
|
|
2270
|
+
var MAX_QUERY_DEPTH = 50;
|
|
2271
|
+
var EMPTY_JOINS = Object.freeze([]);
|
|
2272
|
+
var JSON_OPS = /* @__PURE__ */ new Set([
|
|
2273
|
+
Ops.PATH,
|
|
2274
|
+
Ops.STRING_CONTAINS,
|
|
2275
|
+
Ops.STRING_STARTS_WITH,
|
|
2276
|
+
Ops.STRING_ENDS_WITH
|
|
2277
|
+
]);
|
|
2122
2278
|
var WhereBuilder = class {
|
|
2123
2279
|
build(where, ctx) {
|
|
2124
2280
|
if (!isPlainObject(where)) {
|
|
@@ -2130,8 +2286,6 @@ var WhereBuilder = class {
|
|
|
2130
2286
|
return buildWhereInternal(where, ctx, this);
|
|
2131
2287
|
}
|
|
2132
2288
|
};
|
|
2133
|
-
var MAX_QUERY_DEPTH = 50;
|
|
2134
|
-
var EMPTY_JOINS = Object.freeze([]);
|
|
2135
2289
|
var whereBuilderInstance = new WhereBuilder();
|
|
2136
2290
|
function freezeResult(clause, joins = EMPTY_JOINS) {
|
|
2137
2291
|
return Object.freeze({ clause, joins });
|
|
@@ -2308,16 +2462,8 @@ function buildOperator(expr, op, val, ctx, mode, fieldType) {
|
|
|
2308
2462
|
if (fieldType && isArrayType(fieldType)) {
|
|
2309
2463
|
return buildArrayOperator(expr, op, val, ctx.params, fieldType, ctx.dialect);
|
|
2310
2464
|
}
|
|
2311
|
-
if (fieldType && isJsonType(fieldType)) {
|
|
2312
|
-
|
|
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
|
-
}
|
|
2465
|
+
if (fieldType && isJsonType(fieldType) && JSON_OPS.has(op)) {
|
|
2466
|
+
return buildJsonOperator(expr, op, val, ctx.params, ctx.dialect);
|
|
2321
2467
|
}
|
|
2322
2468
|
return buildScalarOperator(
|
|
2323
2469
|
expr,
|
|
@@ -2338,7 +2484,7 @@ function toSafeSqlIdentifier(input) {
|
|
|
2338
2484
|
const base = startsOk ? cleaned : `_${cleaned}`;
|
|
2339
2485
|
const fallback = base.length > 0 ? base : "_t";
|
|
2340
2486
|
const lowered = fallback.toLowerCase();
|
|
2341
|
-
return
|
|
2487
|
+
return ALIAS_FORBIDDEN_KEYWORDS.has(lowered) ? `_${lowered}` : lowered;
|
|
2342
2488
|
}
|
|
2343
2489
|
function createAliasGenerator(maxAliases = 1e4) {
|
|
2344
2490
|
let counter = 0;
|
|
@@ -2548,6 +2694,7 @@ function toPublicResult(clause, joins, params) {
|
|
|
2548
2694
|
// src/builder/where.ts
|
|
2549
2695
|
function buildWhereClause(where, options) {
|
|
2550
2696
|
var _a, _b, _c, _d, _e;
|
|
2697
|
+
assertSafeAlias(options.alias);
|
|
2551
2698
|
const dialect = options.dialect || getGlobalDialect();
|
|
2552
2699
|
const params = (_a = options.params) != null ? _a : createParamStore();
|
|
2553
2700
|
const ctx = {
|
|
@@ -2716,6 +2863,9 @@ function buildRelationSelect(relArgs, relModel, relAlias) {
|
|
|
2716
2863
|
}
|
|
2717
2864
|
|
|
2718
2865
|
// src/builder/select/includes.ts
|
|
2866
|
+
var MAX_INCLUDE_DEPTH = 10;
|
|
2867
|
+
var MAX_TOTAL_SUBQUERIES = 100;
|
|
2868
|
+
var MAX_TOTAL_INCLUDES = 50;
|
|
2719
2869
|
function getRelationTableReference(relModel, dialect) {
|
|
2720
2870
|
return buildTableReference(
|
|
2721
2871
|
SQL_TEMPLATES.PUBLIC_SCHEMA,
|
|
@@ -2761,107 +2911,24 @@ function relationEntriesFromArgs(args, model) {
|
|
|
2761
2911
|
pushFrom(args.select);
|
|
2762
2912
|
return out;
|
|
2763
2913
|
}
|
|
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
2914
|
function validateOrderByForModel(model, orderBy) {
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2915
|
+
if (!isNotNullish(orderBy)) return;
|
|
2916
|
+
const scalarSet = getScalarFieldSet(model);
|
|
2917
|
+
const normalized = normalizeOrderByInput(orderBy, parseOrderByValue);
|
|
2918
|
+
for (const item of normalized) {
|
|
2919
|
+
const entries = Object.entries(item);
|
|
2920
|
+
if (entries.length !== 1) {
|
|
2921
|
+
throw new Error("orderBy array entries must have exactly one field");
|
|
2857
2922
|
}
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2923
|
+
const fieldName = String(entries[0][0]).trim();
|
|
2924
|
+
if (fieldName.length === 0) {
|
|
2925
|
+
throw new Error("orderBy field name cannot be empty");
|
|
2926
|
+
}
|
|
2927
|
+
if (!scalarSet.has(fieldName)) {
|
|
2928
|
+
throw new Error(
|
|
2929
|
+
`orderBy references unknown or non-scalar field '${fieldName}' on model ${model.name}`
|
|
2930
|
+
);
|
|
2861
2931
|
}
|
|
2862
|
-
throw new Error(
|
|
2863
|
-
`orderBy for '${fieldName}' must be 'asc' | 'desc' or { sort, nulls? }`
|
|
2864
|
-
);
|
|
2865
2932
|
}
|
|
2866
2933
|
}
|
|
2867
2934
|
function appendLimitOffset(sql, dialect, params, takeVal, skipVal, scope) {
|
|
@@ -2890,7 +2957,10 @@ function readWhereInput(relArgs) {
|
|
|
2890
2957
|
function readOrderByInput(relArgs) {
|
|
2891
2958
|
if (!isPlainObject(relArgs)) return { hasOrderBy: false, orderBy: void 0 };
|
|
2892
2959
|
if (!("orderBy" in relArgs)) return { hasOrderBy: false, orderBy: void 0 };
|
|
2893
|
-
return {
|
|
2960
|
+
return {
|
|
2961
|
+
hasOrderBy: true,
|
|
2962
|
+
orderBy: relArgs.orderBy
|
|
2963
|
+
};
|
|
2894
2964
|
}
|
|
2895
2965
|
function extractRelationPaginationConfig(relArgs) {
|
|
2896
2966
|
const { hasOrderBy, orderBy: rawOrderByInput } = readOrderByInput(relArgs);
|
|
@@ -2920,36 +2990,28 @@ function maybeReverseNegativeTake(takeVal, hasOrderBy, orderByInput) {
|
|
|
2920
2990
|
orderByInput: reverseOrderByInput(orderByInput)
|
|
2921
2991
|
};
|
|
2922
2992
|
}
|
|
2923
|
-
function
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
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);
|
|
2993
|
+
function ensureDeterministicOrderByForInclude(args) {
|
|
2994
|
+
if (!args.hasPagination) {
|
|
2995
|
+
if (args.hasOrderBy && isNotNullish(args.orderByInput)) {
|
|
2996
|
+
validateOrderByForModel(args.relModel, args.orderByInput);
|
|
2941
2997
|
}
|
|
2942
|
-
return orderByInput;
|
|
2998
|
+
return args.orderByInput;
|
|
2943
2999
|
}
|
|
2944
|
-
if (!hasOrderBy) {
|
|
2945
|
-
return
|
|
3000
|
+
if (!args.hasOrderBy) {
|
|
3001
|
+
return ensureDeterministicOrderByInput({
|
|
3002
|
+
orderBy: void 0,
|
|
3003
|
+
model: args.relModel,
|
|
3004
|
+
parseValue: parseOrderByValue
|
|
3005
|
+
});
|
|
2946
3006
|
}
|
|
2947
|
-
if (isNotNullish(orderByInput)) {
|
|
2948
|
-
validateOrderByForModel(relModel, orderByInput);
|
|
3007
|
+
if (isNotNullish(args.orderByInput)) {
|
|
3008
|
+
validateOrderByForModel(args.relModel, args.orderByInput);
|
|
2949
3009
|
}
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
3010
|
+
return ensureDeterministicOrderByInput({
|
|
3011
|
+
orderBy: args.orderByInput,
|
|
3012
|
+
model: args.relModel,
|
|
3013
|
+
parseValue: parseOrderByValue
|
|
3014
|
+
});
|
|
2953
3015
|
}
|
|
2954
3016
|
function buildSelectWithNestedIncludes(relArgs, relModel, relAlias, ctx) {
|
|
2955
3017
|
let relSelect = buildRelationSelect(relArgs, relModel, relAlias);
|
|
@@ -2960,7 +3022,10 @@ function buildSelectWithNestedIncludes(relArgs, relModel, relAlias, ctx) {
|
|
|
2960
3022
|
relAlias,
|
|
2961
3023
|
ctx.aliasGen,
|
|
2962
3024
|
ctx.params,
|
|
2963
|
-
ctx.dialect
|
|
3025
|
+
ctx.dialect,
|
|
3026
|
+
ctx.visitPath || [],
|
|
3027
|
+
(ctx.depth || 0) + 1,
|
|
3028
|
+
ctx.stats
|
|
2964
3029
|
) : [];
|
|
2965
3030
|
if (isNonEmptyArray(nestedIncludes)) {
|
|
2966
3031
|
const emptyJson = ctx.dialect === "postgres" ? `'[]'::json` : `json('[]')`;
|
|
@@ -3110,12 +3175,12 @@ function buildSingleInclude(relName, relArgs, field, relModel, ctx) {
|
|
|
3110
3175
|
paginationConfig.orderBy
|
|
3111
3176
|
);
|
|
3112
3177
|
const hasPagination = paginationConfig.hasSkip || paginationConfig.hasTake;
|
|
3113
|
-
const finalOrderByInput =
|
|
3178
|
+
const finalOrderByInput = ensureDeterministicOrderByForInclude({
|
|
3114
3179
|
relModel,
|
|
3115
|
-
paginationConfig.hasOrderBy,
|
|
3116
|
-
adjusted.orderByInput,
|
|
3180
|
+
hasOrderBy: paginationConfig.hasOrderBy,
|
|
3181
|
+
orderByInput: adjusted.orderByInput,
|
|
3117
3182
|
hasPagination
|
|
3118
|
-
);
|
|
3183
|
+
});
|
|
3119
3184
|
const orderBySql = buildOrderBySql(
|
|
3120
3185
|
finalOrderByInput,
|
|
3121
3186
|
relAlias,
|
|
@@ -3156,12 +3221,48 @@ function buildSingleInclude(relName, relArgs, field, relModel, ctx) {
|
|
|
3156
3221
|
ctx
|
|
3157
3222
|
});
|
|
3158
3223
|
}
|
|
3159
|
-
function buildIncludeSqlInternal(args, model, schemas, parentAlias, aliasGen, params, dialect) {
|
|
3224
|
+
function buildIncludeSqlInternal(args, model, schemas, parentAlias, aliasGen, params, dialect, visitPath = [], depth = 0, stats) {
|
|
3225
|
+
if (!stats) {
|
|
3226
|
+
stats = { totalIncludes: 0, totalSubqueries: 0, maxDepth: 0 };
|
|
3227
|
+
}
|
|
3228
|
+
if (depth > MAX_INCLUDE_DEPTH) {
|
|
3229
|
+
throw new Error(
|
|
3230
|
+
`Maximum include depth of ${MAX_INCLUDE_DEPTH} exceeded. Path: ${visitPath.join(" -> ")}. Deep includes cause exponential SQL complexity and performance issues.`
|
|
3231
|
+
);
|
|
3232
|
+
}
|
|
3233
|
+
stats.maxDepth = Math.max(stats.maxDepth, depth);
|
|
3160
3234
|
const includes = [];
|
|
3161
3235
|
const entries = relationEntriesFromArgs(args, model);
|
|
3162
3236
|
for (const [relName, relArgs] of entries) {
|
|
3163
3237
|
if (relArgs === false) continue;
|
|
3238
|
+
stats.totalIncludes++;
|
|
3239
|
+
if (stats.totalIncludes > MAX_TOTAL_INCLUDES) {
|
|
3240
|
+
throw new Error(
|
|
3241
|
+
`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.`
|
|
3242
|
+
);
|
|
3243
|
+
}
|
|
3244
|
+
stats.totalSubqueries++;
|
|
3245
|
+
if (stats.totalSubqueries > MAX_TOTAL_SUBQUERIES) {
|
|
3246
|
+
throw new Error(
|
|
3247
|
+
`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.`
|
|
3248
|
+
);
|
|
3249
|
+
}
|
|
3164
3250
|
const resolved = resolveRelationOrThrow(model, schemas, relName);
|
|
3251
|
+
const relationPath = `${model.name}.${relName}`;
|
|
3252
|
+
const currentPath = [...visitPath, relationPath];
|
|
3253
|
+
if (visitPath.includes(relationPath)) {
|
|
3254
|
+
throw new Error(
|
|
3255
|
+
`Circular include detected: ${currentPath.join(" -> ")}. Relation '${relationPath}' creates an infinite loop.`
|
|
3256
|
+
);
|
|
3257
|
+
}
|
|
3258
|
+
const modelOccurrences = currentPath.filter(
|
|
3259
|
+
(p) => p.startsWith(`${resolved.relModel.name}.`)
|
|
3260
|
+
).length;
|
|
3261
|
+
if (modelOccurrences > 2) {
|
|
3262
|
+
throw new Error(
|
|
3263
|
+
`Include too deeply nested: model '${resolved.relModel.name}' appears ${modelOccurrences} times in path: ${currentPath.join(" -> ")}`
|
|
3264
|
+
);
|
|
3265
|
+
}
|
|
3165
3266
|
const include = buildSingleInclude(
|
|
3166
3267
|
relName,
|
|
3167
3268
|
relArgs,
|
|
@@ -3173,7 +3274,10 @@ function buildIncludeSqlInternal(args, model, schemas, parentAlias, aliasGen, pa
|
|
|
3173
3274
|
parentAlias,
|
|
3174
3275
|
aliasGen,
|
|
3175
3276
|
dialect,
|
|
3176
|
-
params
|
|
3277
|
+
params,
|
|
3278
|
+
visitPath: currentPath,
|
|
3279
|
+
depth: depth + 1,
|
|
3280
|
+
stats
|
|
3177
3281
|
}
|
|
3178
3282
|
);
|
|
3179
3283
|
includes.push(include);
|
|
@@ -3182,6 +3286,11 @@ function buildIncludeSqlInternal(args, model, schemas, parentAlias, aliasGen, pa
|
|
|
3182
3286
|
}
|
|
3183
3287
|
function buildIncludeSql(args, model, schemas, parentAlias, params, dialect) {
|
|
3184
3288
|
const aliasGen = createAliasGenerator();
|
|
3289
|
+
const stats = {
|
|
3290
|
+
totalIncludes: 0,
|
|
3291
|
+
totalSubqueries: 0,
|
|
3292
|
+
maxDepth: 0
|
|
3293
|
+
};
|
|
3185
3294
|
return buildIncludeSqlInternal(
|
|
3186
3295
|
args,
|
|
3187
3296
|
model,
|
|
@@ -3189,7 +3298,10 @@ function buildIncludeSql(args, model, schemas, parentAlias, params, dialect) {
|
|
|
3189
3298
|
parentAlias,
|
|
3190
3299
|
aliasGen,
|
|
3191
3300
|
params,
|
|
3192
|
-
dialect
|
|
3301
|
+
dialect,
|
|
3302
|
+
[],
|
|
3303
|
+
0,
|
|
3304
|
+
stats
|
|
3193
3305
|
);
|
|
3194
3306
|
}
|
|
3195
3307
|
function resolveCountRelationOrThrow(relName, model, schemas) {
|
|
@@ -3205,6 +3317,11 @@ function resolveCountRelationOrThrow(relName, model, schemas) {
|
|
|
3205
3317
|
`_count.${relName} references unknown relation on model ${model.name}`
|
|
3206
3318
|
);
|
|
3207
3319
|
}
|
|
3320
|
+
if (!isValidRelationField(field)) {
|
|
3321
|
+
throw new Error(
|
|
3322
|
+
`_count.${relName} has invalid relation metadata on model ${model.name}`
|
|
3323
|
+
);
|
|
3324
|
+
}
|
|
3208
3325
|
const relModel = schemas.find((m) => m.name === field.relatedModel);
|
|
3209
3326
|
if (!relModel) {
|
|
3210
3327
|
throw new Error(
|
|
@@ -3213,31 +3330,81 @@ function resolveCountRelationOrThrow(relName, model, schemas) {
|
|
|
3213
3330
|
}
|
|
3214
3331
|
return { field, relModel };
|
|
3215
3332
|
}
|
|
3216
|
-
function
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3333
|
+
function defaultReferencesForCount(fkCount) {
|
|
3334
|
+
if (fkCount === 1) return ["id"];
|
|
3335
|
+
throw new Error(
|
|
3336
|
+
"Relation count for composite keys requires explicit references matching foreignKey length"
|
|
3337
|
+
);
|
|
3220
3338
|
}
|
|
3221
|
-
function
|
|
3339
|
+
function resolveCountKeyPairs(field) {
|
|
3222
3340
|
const fkFields = normalizeKeyList(field.foreignKey);
|
|
3223
|
-
|
|
3224
|
-
|
|
3341
|
+
if (fkFields.length === 0) {
|
|
3342
|
+
throw new Error("Relation count requires foreignKey");
|
|
3343
|
+
}
|
|
3344
|
+
const refsRaw = field.references;
|
|
3345
|
+
const refs = normalizeKeyList(refsRaw);
|
|
3346
|
+
const refFields = refs.length > 0 ? refs : defaultReferencesForCount(fkFields.length);
|
|
3347
|
+
if (refFields.length !== fkFields.length) {
|
|
3348
|
+
throw new Error(
|
|
3349
|
+
"Relation count requires references count to match foreignKey count"
|
|
3350
|
+
);
|
|
3351
|
+
}
|
|
3352
|
+
const relKeyFields = field.isForeignKeyLocal ? refFields : fkFields;
|
|
3353
|
+
const parentKeyFields = field.isForeignKeyLocal ? fkFields : refFields;
|
|
3354
|
+
return { relKeyFields, parentKeyFields };
|
|
3225
3355
|
}
|
|
3226
|
-
function
|
|
3227
|
-
return
|
|
3356
|
+
function aliasQualifiedColumn(alias, model, field) {
|
|
3357
|
+
return `${alias}.${quoteColumn(model, field)}`;
|
|
3358
|
+
}
|
|
3359
|
+
function subqueryForCount(args) {
|
|
3360
|
+
const selectKeys = args.relKeyFields.map(
|
|
3361
|
+
(f, i) => `${aliasQualifiedColumn(args.countAlias, args.relModel, f)} AS "__fk${i}"`
|
|
3362
|
+
).join(SQL_SEPARATORS.FIELD_LIST);
|
|
3363
|
+
const groupByKeys = args.relKeyFields.map((f) => aliasQualifiedColumn(args.countAlias, args.relModel, f)).join(SQL_SEPARATORS.FIELD_LIST);
|
|
3364
|
+
const cntExpr = args.dialect === "postgres" ? "COUNT(*)::int AS __cnt" : "COUNT(*) AS __cnt";
|
|
3365
|
+
return `(SELECT ${selectKeys}${SQL_SEPARATORS.FIELD_LIST}${cntExpr} FROM ${args.relTable} ${args.countAlias} GROUP BY ${groupByKeys})`;
|
|
3366
|
+
}
|
|
3367
|
+
function leftJoinOnForCount(args) {
|
|
3368
|
+
const parts = args.parentKeyFields.map(
|
|
3369
|
+
(f, i) => `${args.joinAlias}."__fk${i}" = ${aliasQualifiedColumn(args.parentAlias, args.parentModel, f)}`
|
|
3370
|
+
);
|
|
3371
|
+
return parts.length === 1 ? parts[0] : `(${parts.join(" AND ")})`;
|
|
3372
|
+
}
|
|
3373
|
+
function nextAliasAvoiding(aliasGen, base, forbidden) {
|
|
3374
|
+
let a = aliasGen.next(base);
|
|
3375
|
+
while (forbidden.has(a)) {
|
|
3376
|
+
a = aliasGen.next(base);
|
|
3377
|
+
}
|
|
3378
|
+
return a;
|
|
3228
3379
|
}
|
|
3229
3380
|
function buildCountJoinAndPair(args) {
|
|
3230
3381
|
const relTable = getRelationTableReference(args.relModel, args.dialect);
|
|
3231
|
-
const
|
|
3232
|
-
const
|
|
3233
|
-
const
|
|
3234
|
-
args.
|
|
3382
|
+
const { relKeyFields, parentKeyFields } = resolveCountKeyPairs(args.field);
|
|
3383
|
+
const forbidden = /* @__PURE__ */ new Set([args.parentAlias]);
|
|
3384
|
+
const countAlias = nextAliasAvoiding(
|
|
3385
|
+
args.aliasGen,
|
|
3386
|
+
`__tp_cnt_${args.relName}`,
|
|
3387
|
+
forbidden
|
|
3388
|
+
);
|
|
3389
|
+
forbidden.add(countAlias);
|
|
3390
|
+
const subquery = subqueryForCount({
|
|
3391
|
+
dialect: args.dialect,
|
|
3235
3392
|
relTable,
|
|
3236
3393
|
countAlias,
|
|
3237
|
-
|
|
3394
|
+
relModel: args.relModel,
|
|
3395
|
+
relKeyFields
|
|
3396
|
+
});
|
|
3397
|
+
const joinAlias = nextAliasAvoiding(
|
|
3398
|
+
args.aliasGen,
|
|
3399
|
+
`__tp_cnt_j_${args.relName}`,
|
|
3400
|
+
forbidden
|
|
3238
3401
|
);
|
|
3239
|
-
const
|
|
3240
|
-
|
|
3402
|
+
const leftJoinOn = leftJoinOnForCount({
|
|
3403
|
+
joinAlias,
|
|
3404
|
+
parentAlias: args.parentAlias,
|
|
3405
|
+
parentModel: args.parentModel,
|
|
3406
|
+
parentKeyFields
|
|
3407
|
+
});
|
|
3241
3408
|
return {
|
|
3242
3409
|
joinSql: `LEFT JOIN ${subquery} ${joinAlias} ON ${leftJoinOn}`,
|
|
3243
3410
|
pairSql: `${sqlStringLiteral(args.relName)}, COALESCE(${joinAlias}.__cnt, 0)`
|
|
@@ -3246,6 +3413,7 @@ function buildCountJoinAndPair(args) {
|
|
|
3246
3413
|
function buildRelationCountSql(countSelect, model, schemas, parentAlias, _params, dialect) {
|
|
3247
3414
|
const joins = [];
|
|
3248
3415
|
const pairs = [];
|
|
3416
|
+
const aliasGen = createAliasGenerator();
|
|
3249
3417
|
for (const [relName, shouldCount] of Object.entries(countSelect)) {
|
|
3250
3418
|
if (!shouldCount) continue;
|
|
3251
3419
|
const resolved = resolveCountRelationOrThrow(relName, model, schemas);
|
|
@@ -3253,8 +3421,10 @@ function buildRelationCountSql(countSelect, model, schemas, parentAlias, _params
|
|
|
3253
3421
|
relName,
|
|
3254
3422
|
field: resolved.field,
|
|
3255
3423
|
relModel: resolved.relModel,
|
|
3424
|
+
parentModel: model,
|
|
3256
3425
|
parentAlias,
|
|
3257
|
-
dialect
|
|
3426
|
+
dialect,
|
|
3427
|
+
aliasGen
|
|
3258
3428
|
});
|
|
3259
3429
|
joins.push(built.joinSql);
|
|
3260
3430
|
pairs.push(built.pairSql);
|
|
@@ -3266,13 +3436,21 @@ function buildRelationCountSql(countSelect, model, schemas, parentAlias, _params
|
|
|
3266
3436
|
}
|
|
3267
3437
|
|
|
3268
3438
|
// src/builder/select/assembly.ts
|
|
3269
|
-
var
|
|
3439
|
+
var ALIAS_CAPTURE = "([A-Za-z_][A-Za-z0-9_]*)";
|
|
3440
|
+
var COLUMN_PART = '(?:"([^"]+)"|([a-z_][a-z0-9_]*))';
|
|
3441
|
+
var AS_PART = `(?:\\s+AS\\s+${COLUMN_PART})?`;
|
|
3442
|
+
var SIMPLE_COLUMN_PATTERN = `^${ALIAS_CAPTURE}\\.${COLUMN_PART}${AS_PART}$`;
|
|
3443
|
+
var SIMPLE_COLUMN_RE = new RegExp(SIMPLE_COLUMN_PATTERN, "i");
|
|
3270
3444
|
function joinNonEmpty(parts, sep) {
|
|
3271
3445
|
return parts.filter((s) => s.trim().length > 0).join(sep);
|
|
3272
3446
|
}
|
|
3273
3447
|
function buildWhereSql(conditions) {
|
|
3274
3448
|
if (!isNonEmptyArray(conditions)) return "";
|
|
3275
|
-
|
|
3449
|
+
const parts = [
|
|
3450
|
+
SQL_TEMPLATES.WHERE,
|
|
3451
|
+
conditions.join(SQL_SEPARATORS.CONDITION_AND)
|
|
3452
|
+
];
|
|
3453
|
+
return ` ${parts.join(" ")}`;
|
|
3276
3454
|
}
|
|
3277
3455
|
function buildJoinsSql(...joinGroups) {
|
|
3278
3456
|
const all = [];
|
|
@@ -3287,37 +3465,39 @@ function buildSelectList(baseSelect, extraCols) {
|
|
|
3287
3465
|
if (base && extra) return `${base}${SQL_SEPARATORS.FIELD_LIST}${extra}`;
|
|
3288
3466
|
return base || extra;
|
|
3289
3467
|
}
|
|
3290
|
-
function finalizeSql(sql, params) {
|
|
3468
|
+
function finalizeSql(sql, params, dialect) {
|
|
3291
3469
|
const snapshot = params.snapshot();
|
|
3292
3470
|
validateSelectQuery(sql);
|
|
3293
|
-
|
|
3471
|
+
validateParamConsistencyByDialect(sql, snapshot.params, dialect);
|
|
3294
3472
|
return Object.freeze({
|
|
3295
3473
|
sql,
|
|
3296
3474
|
params: snapshot.params,
|
|
3297
|
-
paramMappings: snapshot.mappings
|
|
3475
|
+
paramMappings: Object.freeze(snapshot.mappings)
|
|
3298
3476
|
});
|
|
3299
3477
|
}
|
|
3300
|
-
function parseSimpleScalarSelect(select,
|
|
3301
|
-
var _a, _b;
|
|
3478
|
+
function parseSimpleScalarSelect(select, fromAlias) {
|
|
3479
|
+
var _a, _b, _c, _d;
|
|
3302
3480
|
const raw = select.trim();
|
|
3303
3481
|
if (raw.length === 0) return [];
|
|
3304
|
-
let re = SIMPLE_SELECT_RE_CACHE.get(alias);
|
|
3305
|
-
if (!re) {
|
|
3306
|
-
const safeAlias2 = alias.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3307
|
-
re = new RegExp(`^${safeAlias2}\\.(?:"([^"]+)"|([a-z_][a-z0-9_]*))$`, "i");
|
|
3308
|
-
SIMPLE_SELECT_RE_CACHE.set(alias, re);
|
|
3309
|
-
}
|
|
3310
3482
|
const parts = raw.split(SQL_SEPARATORS.FIELD_LIST);
|
|
3311
3483
|
const names = [];
|
|
3312
3484
|
for (const part of parts) {
|
|
3313
3485
|
const p = part.trim();
|
|
3314
|
-
const m = p.match(
|
|
3486
|
+
const m = p.match(SIMPLE_COLUMN_RE);
|
|
3315
3487
|
if (!m) {
|
|
3316
3488
|
throw new Error(
|
|
3317
|
-
`sqlite distinct emulation requires scalar select fields to be simple columns. Got: ${p}`
|
|
3489
|
+
`sqlite distinct emulation requires scalar select fields to be simple columns (optionally with AS). Got: ${p}`
|
|
3490
|
+
);
|
|
3491
|
+
}
|
|
3492
|
+
const actualAlias = m[1];
|
|
3493
|
+
if (actualAlias.toLowerCase() !== fromAlias.toLowerCase()) {
|
|
3494
|
+
throw new Error(
|
|
3495
|
+
`Expected alias '${fromAlias}', got '${actualAlias}' in: ${p}`
|
|
3318
3496
|
);
|
|
3319
3497
|
}
|
|
3320
|
-
const
|
|
3498
|
+
const columnName = ((_b = (_a = m[2]) != null ? _a : m[3]) != null ? _b : "").trim();
|
|
3499
|
+
const outAlias = ((_d = (_c = m[4]) != null ? _c : m[5]) != null ? _d : "").trim();
|
|
3500
|
+
const name = outAlias.length > 0 ? outAlias : columnName;
|
|
3321
3501
|
if (name.length === 0) {
|
|
3322
3502
|
throw new Error(`Failed to parse selected column name from: ${p}`);
|
|
3323
3503
|
}
|
|
@@ -3326,18 +3506,18 @@ function parseSimpleScalarSelect(select, alias) {
|
|
|
3326
3506
|
return names;
|
|
3327
3507
|
}
|
|
3328
3508
|
function replaceOrderByAlias(orderBy, fromAlias, outerAlias) {
|
|
3329
|
-
const
|
|
3330
|
-
|
|
3331
|
-
|
|
3509
|
+
const src = String(fromAlias);
|
|
3510
|
+
if (src.length === 0) return orderBy;
|
|
3511
|
+
const escaped = src.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3512
|
+
const re = new RegExp(`\\b${escaped}\\.`, "gi");
|
|
3513
|
+
return orderBy.replace(re, `${outerAlias}.`);
|
|
3332
3514
|
}
|
|
3333
3515
|
function buildDistinctColumns(distinct, fromAlias, model) {
|
|
3334
3516
|
return distinct.map((f) => col(fromAlias, f, model)).join(SQL_SEPARATORS.FIELD_LIST);
|
|
3335
3517
|
}
|
|
3336
3518
|
function buildOutputColumns(scalarNames, includeNames, hasCount) {
|
|
3337
3519
|
const outputCols = [...scalarNames, ...includeNames];
|
|
3338
|
-
if (hasCount)
|
|
3339
|
-
outputCols.push("_count");
|
|
3340
|
-
}
|
|
3520
|
+
if (hasCount) outputCols.push("_count");
|
|
3341
3521
|
const formatted = outputCols.map((n) => quote(n)).join(SQL_SEPARATORS.FIELD_LIST);
|
|
3342
3522
|
if (!isNonEmptyString(formatted)) {
|
|
3343
3523
|
throw new Error("distinct emulation requires at least one output column");
|
|
@@ -3346,9 +3526,10 @@ function buildOutputColumns(scalarNames, includeNames, hasCount) {
|
|
|
3346
3526
|
}
|
|
3347
3527
|
function buildWindowOrder(args) {
|
|
3348
3528
|
const { baseOrder, idField, fromAlias, model } = args;
|
|
3529
|
+
const fromLower = String(fromAlias).toLowerCase();
|
|
3349
3530
|
const orderFields = baseOrder.split(SQL_SEPARATORS.ORDER_BY).map((s) => s.trim().toLowerCase());
|
|
3350
3531
|
const hasIdInOrder = orderFields.some(
|
|
3351
|
-
(f) => f.startsWith(`${
|
|
3532
|
+
(f) => f.startsWith(`${fromLower}.id `) || f.startsWith(`${fromLower}."id" `)
|
|
3352
3533
|
);
|
|
3353
3534
|
if (hasIdInOrder) return baseOrder;
|
|
3354
3535
|
const idTiebreaker = idField ? `, ${col(fromAlias, "id", model)} ASC` : "";
|
|
@@ -3383,15 +3564,37 @@ function buildSqliteDistinctQuery(spec, selectWithIncludes, countJoins) {
|
|
|
3383
3564
|
const outerOrder = isNonEmptyString(orderBy) ? replaceOrderByAlias(orderBy, from.alias, `"__tp_distinct"`) : replaceOrderByAlias(fallbackOrder, from.alias, `"__tp_distinct"`);
|
|
3384
3565
|
const joins = buildJoinsSql(whereJoins, countJoins);
|
|
3385
3566
|
const conditions = [];
|
|
3386
|
-
if (whereClause && whereClause !== "1=1")
|
|
3387
|
-
conditions.push(whereClause);
|
|
3388
|
-
}
|
|
3567
|
+
if (whereClause && whereClause !== "1=1") conditions.push(whereClause);
|
|
3389
3568
|
const whereSql = buildWhereSql(conditions);
|
|
3390
3569
|
const innerSelectList = selectWithIncludes.trim();
|
|
3391
3570
|
const innerComma = innerSelectList.length > 0 ? SQL_SEPARATORS.FIELD_LIST : "";
|
|
3392
|
-
const
|
|
3393
|
-
|
|
3394
|
-
|
|
3571
|
+
const innerParts = [
|
|
3572
|
+
SQL_TEMPLATES.SELECT,
|
|
3573
|
+
innerSelectList + innerComma,
|
|
3574
|
+
`ROW_NUMBER() OVER (PARTITION BY ${distinctCols} ORDER BY ${windowOrder})`,
|
|
3575
|
+
SQL_TEMPLATES.AS,
|
|
3576
|
+
'"__tp_rn"',
|
|
3577
|
+
SQL_TEMPLATES.FROM,
|
|
3578
|
+
from.table,
|
|
3579
|
+
from.alias
|
|
3580
|
+
];
|
|
3581
|
+
if (joins) innerParts.push(joins);
|
|
3582
|
+
if (whereSql) innerParts.push(whereSql);
|
|
3583
|
+
const inner = innerParts.filter(Boolean).join(" ");
|
|
3584
|
+
const outerParts = [
|
|
3585
|
+
SQL_TEMPLATES.SELECT,
|
|
3586
|
+
outerSelectCols,
|
|
3587
|
+
SQL_TEMPLATES.FROM,
|
|
3588
|
+
`(${inner})`,
|
|
3589
|
+
SQL_TEMPLATES.AS,
|
|
3590
|
+
'"__tp_distinct"',
|
|
3591
|
+
SQL_TEMPLATES.WHERE,
|
|
3592
|
+
'"__tp_rn" = 1'
|
|
3593
|
+
];
|
|
3594
|
+
if (isNonEmptyString(outerOrder)) {
|
|
3595
|
+
outerParts.push(SQL_TEMPLATES.ORDER_BY, outerOrder);
|
|
3596
|
+
}
|
|
3597
|
+
return outerParts.filter(Boolean).join(" ");
|
|
3395
3598
|
}
|
|
3396
3599
|
function buildIncludeColumns(spec) {
|
|
3397
3600
|
var _a, _b;
|
|
@@ -3519,6 +3722,7 @@ function constructFinalSql(spec) {
|
|
|
3519
3722
|
orderBy,
|
|
3520
3723
|
distinct,
|
|
3521
3724
|
method,
|
|
3725
|
+
cursorCte,
|
|
3522
3726
|
cursorClause,
|
|
3523
3727
|
params,
|
|
3524
3728
|
dialect,
|
|
@@ -3533,9 +3737,13 @@ function constructFinalSql(spec) {
|
|
|
3533
3737
|
const spec2 = withCountJoins(spec, countJoins, whereJoins);
|
|
3534
3738
|
let sql2 = buildSqliteDistinctQuery(spec2, selectWithIncludes).trim();
|
|
3535
3739
|
sql2 = appendPagination(sql2, spec);
|
|
3536
|
-
return finalizeSql(sql2, params);
|
|
3740
|
+
return finalizeSql(sql2, params, dialect);
|
|
3741
|
+
}
|
|
3742
|
+
const parts = [];
|
|
3743
|
+
if (cursorCte) {
|
|
3744
|
+
parts.push(`WITH ${cursorCte}`);
|
|
3537
3745
|
}
|
|
3538
|
-
|
|
3746
|
+
parts.push(SQL_TEMPLATES.SELECT);
|
|
3539
3747
|
const distinctOn = dialect === "postgres" ? buildPostgresDistinctOnClause(from.alias, distinct, model) : null;
|
|
3540
3748
|
if (distinctOn) parts.push(distinctOn);
|
|
3541
3749
|
const baseSelect = (select != null ? select : "").trim();
|
|
@@ -3551,7 +3759,41 @@ function constructFinalSql(spec) {
|
|
|
3551
3759
|
if (isNonEmptyString(orderBy)) parts.push(SQL_TEMPLATES.ORDER_BY, orderBy);
|
|
3552
3760
|
let sql = parts.join(" ").trim();
|
|
3553
3761
|
sql = appendPagination(sql, spec);
|
|
3554
|
-
return finalizeSql(sql, params);
|
|
3762
|
+
return finalizeSql(sql, params, dialect);
|
|
3763
|
+
}
|
|
3764
|
+
|
|
3765
|
+
// src/builder/shared/validators/field-assertions.ts
|
|
3766
|
+
var NUMERIC_TYPES = /* @__PURE__ */ new Set(["Int", "Float", "Decimal", "BigInt"]);
|
|
3767
|
+
function assertScalarField(model, fieldName, context) {
|
|
3768
|
+
const field = getFieldInfo(model, fieldName);
|
|
3769
|
+
if (!field) {
|
|
3770
|
+
throw createError(
|
|
3771
|
+
`${context} references unknown field '${fieldName}' on model ${model.name}`,
|
|
3772
|
+
{
|
|
3773
|
+
field: fieldName,
|
|
3774
|
+
modelName: model.name,
|
|
3775
|
+
availableFields: model.fields.map((f) => f.name)
|
|
3776
|
+
}
|
|
3777
|
+
);
|
|
3778
|
+
}
|
|
3779
|
+
if (field.isRelation) {
|
|
3780
|
+
throw createError(
|
|
3781
|
+
`${context} does not support relation field '${fieldName}'`,
|
|
3782
|
+
{ field: fieldName, modelName: model.name }
|
|
3783
|
+
);
|
|
3784
|
+
}
|
|
3785
|
+
return field;
|
|
3786
|
+
}
|
|
3787
|
+
function assertNumericField(model, fieldName, context) {
|
|
3788
|
+
const field = assertScalarField(model, fieldName, context);
|
|
3789
|
+
const baseType = field.type.replace(/\[\]|\?/g, "");
|
|
3790
|
+
if (!NUMERIC_TYPES.has(baseType)) {
|
|
3791
|
+
throw createError(
|
|
3792
|
+
`${context} requires numeric field, got '${field.type}'`,
|
|
3793
|
+
{ field: fieldName, modelName: model.name }
|
|
3794
|
+
);
|
|
3795
|
+
}
|
|
3796
|
+
return field;
|
|
3555
3797
|
}
|
|
3556
3798
|
|
|
3557
3799
|
// src/builder/select.ts
|
|
@@ -3584,7 +3826,7 @@ function buildPostgresDistinctOrderBy(distinctFields, existing) {
|
|
|
3584
3826
|
}
|
|
3585
3827
|
return next;
|
|
3586
3828
|
}
|
|
3587
|
-
function applyPostgresDistinctOrderBy(args
|
|
3829
|
+
function applyPostgresDistinctOrderBy(args) {
|
|
3588
3830
|
const distinctFields = normalizeDistinctFields(args.distinct);
|
|
3589
3831
|
if (distinctFields.length === 0) return args;
|
|
3590
3832
|
if (!isNotNullish(args.orderBy)) return args;
|
|
@@ -3594,19 +3836,6 @@ function applyPostgresDistinctOrderBy(args, _model) {
|
|
|
3594
3836
|
orderBy: buildPostgresDistinctOrderBy(distinctFields, existing)
|
|
3595
3837
|
});
|
|
3596
3838
|
}
|
|
3597
|
-
function assertScalarFieldOnModel(model, fieldName, ctx) {
|
|
3598
|
-
const f = model.fields.find((x) => x.name === fieldName);
|
|
3599
|
-
if (!f) {
|
|
3600
|
-
throw new Error(
|
|
3601
|
-
`${ctx} references unknown field '${fieldName}' on model ${model.name}`
|
|
3602
|
-
);
|
|
3603
|
-
}
|
|
3604
|
-
if (f.isRelation) {
|
|
3605
|
-
throw new Error(
|
|
3606
|
-
`${ctx} does not support relation field '${fieldName}' on model ${model.name}`
|
|
3607
|
-
);
|
|
3608
|
-
}
|
|
3609
|
-
}
|
|
3610
3839
|
function validateDistinct(model, distinct) {
|
|
3611
3840
|
if (!isNotNullish(distinct) || !isNonEmptyArray(distinct)) return;
|
|
3612
3841
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -3617,24 +3846,24 @@ function validateDistinct(model, distinct) {
|
|
|
3617
3846
|
throw new Error(`distinct must not contain duplicates (field: '${f}')`);
|
|
3618
3847
|
}
|
|
3619
3848
|
seen.add(f);
|
|
3620
|
-
|
|
3849
|
+
assertScalarField(model, f, "distinct");
|
|
3621
3850
|
}
|
|
3622
3851
|
}
|
|
3623
|
-
function validateOrderByValue(fieldName, v) {
|
|
3624
|
-
parseOrderByValue(v, fieldName);
|
|
3625
|
-
}
|
|
3626
3852
|
function validateOrderBy(model, orderBy) {
|
|
3627
3853
|
if (!isNotNullish(orderBy)) return;
|
|
3628
3854
|
const items = normalizeOrderByInput2(orderBy);
|
|
3629
3855
|
if (items.length === 0) return;
|
|
3630
3856
|
for (const it of items) {
|
|
3631
3857
|
const entries = Object.entries(it);
|
|
3858
|
+
if (entries.length !== 1) {
|
|
3859
|
+
throw new Error("orderBy array entries must have exactly one field");
|
|
3860
|
+
}
|
|
3632
3861
|
const fieldName = String(entries[0][0]).trim();
|
|
3633
3862
|
if (fieldName.length === 0) {
|
|
3634
3863
|
throw new Error("orderBy field name cannot be empty");
|
|
3635
3864
|
}
|
|
3636
|
-
|
|
3637
|
-
|
|
3865
|
+
assertScalarField(model, fieldName, "orderBy");
|
|
3866
|
+
parseOrderByValue(entries[0][1], fieldName);
|
|
3638
3867
|
}
|
|
3639
3868
|
}
|
|
3640
3869
|
function validateCursor(model, cursor) {
|
|
@@ -3651,7 +3880,7 @@ function validateCursor(model, cursor) {
|
|
|
3651
3880
|
if (f.length === 0) {
|
|
3652
3881
|
throw new Error("cursor field name cannot be empty");
|
|
3653
3882
|
}
|
|
3654
|
-
|
|
3883
|
+
assertScalarField(model, f, "cursor");
|
|
3655
3884
|
}
|
|
3656
3885
|
}
|
|
3657
3886
|
function resolveDialect(dialect) {
|
|
@@ -3670,20 +3899,21 @@ function normalizeArgsForNegativeTake(method, args) {
|
|
|
3670
3899
|
orderBy: reverseOrderByInput(args.orderBy)
|
|
3671
3900
|
});
|
|
3672
3901
|
}
|
|
3673
|
-
function normalizeArgsForDialect(dialect, args
|
|
3902
|
+
function normalizeArgsForDialect(dialect, args) {
|
|
3674
3903
|
if (dialect !== "postgres") return args;
|
|
3675
3904
|
return applyPostgresDistinctOrderBy(args);
|
|
3676
3905
|
}
|
|
3677
3906
|
function buildCursorClauseIfAny(input) {
|
|
3678
|
-
const { cursor, orderBy, tableName, alias, params, dialect } = input;
|
|
3679
|
-
if (!isNotNullish(cursor)) return
|
|
3907
|
+
const { cursor, orderBy, tableName, alias, params, dialect, model } = input;
|
|
3908
|
+
if (!isNotNullish(cursor)) return {};
|
|
3680
3909
|
return buildCursorCondition(
|
|
3681
3910
|
cursor,
|
|
3682
3911
|
orderBy,
|
|
3683
3912
|
tableName,
|
|
3684
3913
|
alias,
|
|
3685
3914
|
params,
|
|
3686
|
-
dialect
|
|
3915
|
+
dialect,
|
|
3916
|
+
model
|
|
3687
3917
|
);
|
|
3688
3918
|
}
|
|
3689
3919
|
function buildSelectSpec(input) {
|
|
@@ -3722,14 +3952,20 @@ function buildSelectSpec(input) {
|
|
|
3722
3952
|
params,
|
|
3723
3953
|
dialect
|
|
3724
3954
|
);
|
|
3725
|
-
const
|
|
3955
|
+
const cursorResult = buildCursorClauseIfAny({
|
|
3726
3956
|
cursor,
|
|
3727
3957
|
orderBy: normalizedArgs.orderBy,
|
|
3728
3958
|
tableName,
|
|
3729
3959
|
alias,
|
|
3730
3960
|
params,
|
|
3731
|
-
dialect
|
|
3961
|
+
dialect,
|
|
3962
|
+
model
|
|
3732
3963
|
});
|
|
3964
|
+
if (dialect === "sqlite" && isNonEmptyArray(normalizedArgs.distinct) && cursorResult.condition) {
|
|
3965
|
+
throw new Error(
|
|
3966
|
+
"Cursor pagination with distinct is not supported in SQLite due to window function limitations. Use findMany with skip/take instead, or remove distinct."
|
|
3967
|
+
);
|
|
3968
|
+
}
|
|
3733
3969
|
return {
|
|
3734
3970
|
select: selectFields,
|
|
3735
3971
|
includes,
|
|
@@ -3740,7 +3976,8 @@ function buildSelectSpec(input) {
|
|
|
3740
3976
|
pagination: { take, skip },
|
|
3741
3977
|
distinct: normalizedArgs.distinct,
|
|
3742
3978
|
method,
|
|
3743
|
-
|
|
3979
|
+
cursorCte: cursorResult.cte,
|
|
3980
|
+
cursorClause: cursorResult.condition,
|
|
3744
3981
|
params,
|
|
3745
3982
|
dialect,
|
|
3746
3983
|
model,
|
|
@@ -3754,9 +3991,7 @@ function buildSelectSql(input) {
|
|
|
3754
3991
|
assertSafeTableRef(from.tableName);
|
|
3755
3992
|
const dialectToUse = resolveDialect(dialect);
|
|
3756
3993
|
const argsForSql = normalizeArgsForNegativeTake(method, args);
|
|
3757
|
-
const normalizedArgs = normalizeArgsForDialect(
|
|
3758
|
-
dialectToUse,
|
|
3759
|
-
argsForSql);
|
|
3994
|
+
const normalizedArgs = normalizeArgsForDialect(dialectToUse, argsForSql);
|
|
3760
3995
|
validateDistinct(model, normalizedArgs.distinct);
|
|
3761
3996
|
validateOrderBy(model, normalizedArgs.orderBy);
|
|
3762
3997
|
validateCursor(model, normalizedArgs.cursor);
|
|
@@ -3772,8 +4007,21 @@ function buildSelectSql(input) {
|
|
|
3772
4007
|
});
|
|
3773
4008
|
return constructFinalSql(spec);
|
|
3774
4009
|
}
|
|
3775
|
-
|
|
3776
|
-
|
|
4010
|
+
|
|
4011
|
+
// src/builder/shared/comparison-builder.ts
|
|
4012
|
+
function buildComparisons(expr, filter, params, dialect, builder, excludeKeys = /* @__PURE__ */ new Set(["mode"])) {
|
|
4013
|
+
const out = [];
|
|
4014
|
+
for (const [op, val] of Object.entries(filter)) {
|
|
4015
|
+
if (excludeKeys.has(op) || val === void 0) continue;
|
|
4016
|
+
const built = builder(expr, op, val, params, dialect);
|
|
4017
|
+
if (built && built.trim().length > 0) {
|
|
4018
|
+
out.push(built);
|
|
4019
|
+
}
|
|
4020
|
+
}
|
|
4021
|
+
return out;
|
|
4022
|
+
}
|
|
4023
|
+
|
|
4024
|
+
// src/builder/aggregates.ts
|
|
3777
4025
|
var AGGREGATES = [
|
|
3778
4026
|
["_sum", "SUM"],
|
|
3779
4027
|
["_avg", "AVG"],
|
|
@@ -3788,16 +4036,16 @@ var COMPARISON_OPS = {
|
|
|
3788
4036
|
[Ops.LT]: "<",
|
|
3789
4037
|
[Ops.LTE]: "<="
|
|
3790
4038
|
};
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
4039
|
+
var HAVING_ALLOWED_OPS = /* @__PURE__ */ new Set([
|
|
4040
|
+
Ops.EQUALS,
|
|
4041
|
+
Ops.NOT,
|
|
4042
|
+
Ops.GT,
|
|
4043
|
+
Ops.GTE,
|
|
4044
|
+
Ops.LT,
|
|
4045
|
+
Ops.LTE,
|
|
4046
|
+
Ops.IN,
|
|
4047
|
+
Ops.NOT_IN
|
|
4048
|
+
]);
|
|
3801
4049
|
function isTruthySelection(v) {
|
|
3802
4050
|
return v === true;
|
|
3803
4051
|
}
|
|
@@ -3839,24 +4087,10 @@ function normalizeLogicalValue2(operator, value) {
|
|
|
3839
4087
|
}
|
|
3840
4088
|
throw new Error(`${operator} must be an object or array of objects in HAVING`);
|
|
3841
4089
|
}
|
|
3842
|
-
function
|
|
3843
|
-
|
|
3844
|
-
const field = m.get(fieldName);
|
|
3845
|
-
if (!field) {
|
|
4090
|
+
function assertHavingOp(op) {
|
|
4091
|
+
if (!HAVING_ALLOWED_OPS.has(op)) {
|
|
3846
4092
|
throw new Error(
|
|
3847
|
-
|
|
3848
|
-
);
|
|
3849
|
-
}
|
|
3850
|
-
if (field.isRelation) {
|
|
3851
|
-
throw new Error(`${ctx} does not support relation field '${fieldName}'`);
|
|
3852
|
-
}
|
|
3853
|
-
return { name: field.name, type: field.type };
|
|
3854
|
-
}
|
|
3855
|
-
function assertAggregateFieldType(aggKey, fieldType, fieldName, modelName) {
|
|
3856
|
-
const baseType = fieldType.replace(/\[\]|\?/g, "");
|
|
3857
|
-
if ((aggKey === "_sum" || aggKey === "_avg") && !NUMERIC_TYPES.has(baseType)) {
|
|
3858
|
-
throw new Error(
|
|
3859
|
-
`Cannot use ${aggKey} on non-numeric field '${fieldName}' (type: ${fieldType}) on model ${modelName}`
|
|
4093
|
+
`Unsupported HAVING operator '${op}'. Allowed: ${[...HAVING_ALLOWED_OPS].join(", ")}`
|
|
3860
4094
|
);
|
|
3861
4095
|
}
|
|
3862
4096
|
}
|
|
@@ -3886,6 +4120,7 @@ function buildBinaryComparison(expr, op, val, params) {
|
|
|
3886
4120
|
return `${expr} ${sqlOp} ${placeholder}`;
|
|
3887
4121
|
}
|
|
3888
4122
|
function buildSimpleComparison(expr, op, val, params, dialect) {
|
|
4123
|
+
assertHavingOp(op);
|
|
3889
4124
|
if (val === null) return buildNullComparison(expr, op);
|
|
3890
4125
|
if (op === Ops.NOT && isPlainObject(val)) {
|
|
3891
4126
|
return buildNotComposite(
|
|
@@ -3974,20 +4209,19 @@ function assertHavingAggTarget(aggKey, field, model) {
|
|
|
3974
4209
|
}
|
|
3975
4210
|
return;
|
|
3976
4211
|
}
|
|
3977
|
-
|
|
3978
|
-
|
|
4212
|
+
if (aggKey === "_sum" || aggKey === "_avg") {
|
|
4213
|
+
assertNumericField(model, field, "HAVING");
|
|
4214
|
+
} else {
|
|
4215
|
+
assertScalarField(model, field, "HAVING");
|
|
4216
|
+
}
|
|
3979
4217
|
}
|
|
3980
4218
|
function buildHavingOpsForExpr(expr, filter, params, dialect) {
|
|
3981
|
-
|
|
3982
|
-
for (const [op, val] of Object.entries(filter)) {
|
|
3983
|
-
if (op === "mode") continue;
|
|
3984
|
-
const built = buildSimpleComparison(expr, op, val, params, dialect);
|
|
3985
|
-
if (built && built.trim().length > 0) out.push(built);
|
|
3986
|
-
}
|
|
3987
|
-
return out;
|
|
4219
|
+
return buildComparisons(expr, filter, params, dialect, buildSimpleComparison);
|
|
3988
4220
|
}
|
|
3989
4221
|
function buildHavingForAggregateFirstShape(aggKey, target, alias, params, dialect, model) {
|
|
3990
|
-
if (!isPlainObject(target))
|
|
4222
|
+
if (!isPlainObject(target)) {
|
|
4223
|
+
throw new Error(`HAVING '${aggKey}' must be an object`);
|
|
4224
|
+
}
|
|
3991
4225
|
const out = [];
|
|
3992
4226
|
for (const [field, filter] of Object.entries(target)) {
|
|
3993
4227
|
assertHavingAggTarget(aggKey, field, model);
|
|
@@ -3998,30 +4232,39 @@ function buildHavingForAggregateFirstShape(aggKey, target, alias, params, dialec
|
|
|
3998
4232
|
return out;
|
|
3999
4233
|
}
|
|
4000
4234
|
function buildHavingForFieldFirstShape(fieldName, target, alias, params, dialect, model) {
|
|
4001
|
-
if (!isPlainObject(target))
|
|
4002
|
-
|
|
4235
|
+
if (!isPlainObject(target)) {
|
|
4236
|
+
throw new Error(`HAVING '${fieldName}' must be an object`);
|
|
4237
|
+
}
|
|
4238
|
+
assertScalarField(model, fieldName, "HAVING");
|
|
4003
4239
|
const out = [];
|
|
4004
4240
|
const obj = target;
|
|
4005
4241
|
const keys = ["_count", "_sum", "_avg", "_min", "_max"];
|
|
4006
4242
|
for (const aggKey of keys) {
|
|
4007
4243
|
const aggFilter = obj[aggKey];
|
|
4008
4244
|
if (!isPlainObject(aggFilter)) continue;
|
|
4009
|
-
|
|
4245
|
+
if (aggKey === "_sum" || aggKey === "_avg") {
|
|
4246
|
+
assertNumericField(model, fieldName, "HAVING");
|
|
4247
|
+
}
|
|
4010
4248
|
const entries = Object.entries(aggFilter);
|
|
4011
4249
|
if (entries.length === 0) continue;
|
|
4012
4250
|
const expr = aggExprForField(aggKey, fieldName, alias, model);
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4251
|
+
const clauses = buildComparisons(
|
|
4252
|
+
expr,
|
|
4253
|
+
aggFilter,
|
|
4254
|
+
params,
|
|
4255
|
+
dialect,
|
|
4256
|
+
buildSimpleComparison
|
|
4257
|
+
);
|
|
4258
|
+
out.push(...clauses);
|
|
4018
4259
|
}
|
|
4019
4260
|
return out;
|
|
4020
4261
|
}
|
|
4021
4262
|
function buildHavingClause(having, alias, params, model, dialect) {
|
|
4022
4263
|
if (!isNotNullish(having)) return "";
|
|
4023
4264
|
const d = dialect != null ? dialect : getGlobalDialect();
|
|
4024
|
-
if (!isPlainObject(having))
|
|
4265
|
+
if (!isPlainObject(having)) {
|
|
4266
|
+
throw new Error("having must be an object");
|
|
4267
|
+
}
|
|
4025
4268
|
return buildHavingNode(having, alias, params, d, model);
|
|
4026
4269
|
}
|
|
4027
4270
|
function normalizeCountArg(v) {
|
|
@@ -4035,18 +4278,8 @@ function pushCountAllField(fields) {
|
|
|
4035
4278
|
`${SQL_TEMPLATES.COUNT_ALL} ${SQL_TEMPLATES.AS} ${quote("_count._all")}`
|
|
4036
4279
|
);
|
|
4037
4280
|
}
|
|
4038
|
-
function assertCountableScalarField(
|
|
4039
|
-
|
|
4040
|
-
if (!field) {
|
|
4041
|
-
throw new Error(
|
|
4042
|
-
`Field '${fieldName}' does not exist on model ${model.name}`
|
|
4043
|
-
);
|
|
4044
|
-
}
|
|
4045
|
-
if (field.isRelation) {
|
|
4046
|
-
throw new Error(
|
|
4047
|
-
`Cannot use _count on relation field '${fieldName}' on model ${model.name}`
|
|
4048
|
-
);
|
|
4049
|
-
}
|
|
4281
|
+
function assertCountableScalarField(model, fieldName) {
|
|
4282
|
+
assertScalarField(model, fieldName, "_count");
|
|
4050
4283
|
}
|
|
4051
4284
|
function pushCountField(fields, alias, fieldName, model) {
|
|
4052
4285
|
const outAlias = `_count.${fieldName}`;
|
|
@@ -4054,7 +4287,7 @@ function pushCountField(fields, alias, fieldName, model) {
|
|
|
4054
4287
|
`COUNT(${col(alias, fieldName, model)}) ${SQL_TEMPLATES.AS} ${quote(outAlias)}`
|
|
4055
4288
|
);
|
|
4056
4289
|
}
|
|
4057
|
-
function addCountFields(fields, countArg, alias, model
|
|
4290
|
+
function addCountFields(fields, countArg, alias, model) {
|
|
4058
4291
|
if (!isNotNullish(countArg)) return;
|
|
4059
4292
|
if (countArg === true) {
|
|
4060
4293
|
pushCountAllField(fields);
|
|
@@ -4068,7 +4301,7 @@ function addCountFields(fields, countArg, alias, model, fieldMap) {
|
|
|
4068
4301
|
([f, v]) => f !== "_all" && isTruthySelection(v)
|
|
4069
4302
|
);
|
|
4070
4303
|
for (const [f] of selected) {
|
|
4071
|
-
assertCountableScalarField(
|
|
4304
|
+
assertCountableScalarField(model, f);
|
|
4072
4305
|
pushCountField(fields, alias, f, model);
|
|
4073
4306
|
}
|
|
4074
4307
|
}
|
|
@@ -4076,19 +4309,12 @@ function getAggregateSelectionObject(args, agg) {
|
|
|
4076
4309
|
const obj = args[agg];
|
|
4077
4310
|
return isPlainObject(obj) ? obj : void 0;
|
|
4078
4311
|
}
|
|
4079
|
-
function assertAggregatableScalarField(
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
);
|
|
4085
|
-
}
|
|
4086
|
-
if (field.isRelation) {
|
|
4087
|
-
throw new Error(
|
|
4088
|
-
`Cannot use ${agg} on relation field '${fieldName}' on model ${model.name}`
|
|
4089
|
-
);
|
|
4312
|
+
function assertAggregatableScalarField(model, agg, fieldName) {
|
|
4313
|
+
if (agg === "_sum" || agg === "_avg") {
|
|
4314
|
+
assertNumericField(model, fieldName, agg);
|
|
4315
|
+
} else {
|
|
4316
|
+
assertScalarField(model, fieldName, agg);
|
|
4090
4317
|
}
|
|
4091
|
-
return field;
|
|
4092
4318
|
}
|
|
4093
4319
|
function pushAggregateFieldSql(fields, aggFn, alias, agg, fieldName, model) {
|
|
4094
4320
|
const outAlias = `${agg}.${fieldName}`;
|
|
@@ -4096,7 +4322,7 @@ function pushAggregateFieldSql(fields, aggFn, alias, agg, fieldName, model) {
|
|
|
4096
4322
|
`${aggFn}(${col(alias, fieldName, model)}) ${SQL_TEMPLATES.AS} ${quote(outAlias)}`
|
|
4097
4323
|
);
|
|
4098
4324
|
}
|
|
4099
|
-
function addAggregateFields(fields, args, alias, model
|
|
4325
|
+
function addAggregateFields(fields, args, alias, model) {
|
|
4100
4326
|
for (const [agg, aggFn] of AGGREGATES) {
|
|
4101
4327
|
const obj = getAggregateSelectionObject(args, agg);
|
|
4102
4328
|
if (!obj) continue;
|
|
@@ -4104,23 +4330,16 @@ function addAggregateFields(fields, args, alias, model, fieldMap) {
|
|
|
4104
4330
|
if (fieldName === "_all")
|
|
4105
4331
|
throw new Error(`'${agg}' does not support '_all'`);
|
|
4106
4332
|
if (!isTruthySelection(selection)) continue;
|
|
4107
|
-
|
|
4108
|
-
fieldMap,
|
|
4109
|
-
model,
|
|
4110
|
-
agg,
|
|
4111
|
-
fieldName
|
|
4112
|
-
);
|
|
4113
|
-
assertAggregateFieldType(agg, field.type, fieldName, model.name);
|
|
4333
|
+
assertAggregatableScalarField(model, agg, fieldName);
|
|
4114
4334
|
pushAggregateFieldSql(fields, aggFn, alias, agg, fieldName, model);
|
|
4115
4335
|
}
|
|
4116
4336
|
}
|
|
4117
4337
|
}
|
|
4118
4338
|
function buildAggregateFields(args, alias, model) {
|
|
4119
4339
|
const fields = [];
|
|
4120
|
-
const fieldMap = getModelFieldMap(model);
|
|
4121
4340
|
const countArg = normalizeCountArg(args._count);
|
|
4122
|
-
addCountFields(fields, countArg, alias, model
|
|
4123
|
-
addAggregateFields(fields, args, alias, model
|
|
4341
|
+
addCountFields(fields, countArg, alias, model);
|
|
4342
|
+
addAggregateFields(fields, args, alias, model);
|
|
4124
4343
|
return fields;
|
|
4125
4344
|
}
|
|
4126
4345
|
function buildAggregateSql(args, whereResult, tableName, alias, model) {
|
|
@@ -4157,32 +4376,24 @@ function assertGroupByBy(args, model) {
|
|
|
4157
4376
|
if (bySet.size !== byFields.length) {
|
|
4158
4377
|
throw new Error("buildGroupBySql: by must not contain duplicates");
|
|
4159
4378
|
}
|
|
4160
|
-
const modelFieldMap = getModelFieldMap(model);
|
|
4161
4379
|
for (const f of byFields) {
|
|
4162
|
-
|
|
4163
|
-
if (!field) {
|
|
4164
|
-
throw new Error(
|
|
4165
|
-
`groupBy.by references unknown field '${f}' on model ${model.name}`
|
|
4166
|
-
);
|
|
4167
|
-
}
|
|
4168
|
-
if (field.isRelation) {
|
|
4169
|
-
throw new Error(
|
|
4170
|
-
`groupBy.by does not support relation field '${f}' on model ${model.name}`
|
|
4171
|
-
);
|
|
4172
|
-
}
|
|
4380
|
+
assertScalarField(model, f, "groupBy.by");
|
|
4173
4381
|
}
|
|
4174
4382
|
return byFields;
|
|
4175
4383
|
}
|
|
4176
4384
|
function buildGroupBySelectParts(args, alias, model, byFields) {
|
|
4177
4385
|
const groupCols = byFields.map((f) => col(alias, f, model));
|
|
4386
|
+
const selectCols = byFields.map((f) => colWithAlias(alias, f, model));
|
|
4178
4387
|
const groupFields = groupCols.join(SQL_SEPARATORS.FIELD_LIST);
|
|
4179
4388
|
const aggFields = buildAggregateFields(args, alias, model);
|
|
4180
|
-
const selectFields = isNonEmptyArray(aggFields) ?
|
|
4389
|
+
const selectFields = isNonEmptyArray(aggFields) ? selectCols.concat(aggFields).join(SQL_SEPARATORS.FIELD_LIST) : selectCols.join(SQL_SEPARATORS.FIELD_LIST);
|
|
4181
4390
|
return { groupCols, groupFields, selectFields };
|
|
4182
4391
|
}
|
|
4183
4392
|
function buildGroupByHaving(args, alias, params, model, dialect) {
|
|
4184
4393
|
if (!isNotNullish(args.having)) return "";
|
|
4185
|
-
if (!isPlainObject(args.having))
|
|
4394
|
+
if (!isPlainObject(args.having)) {
|
|
4395
|
+
throw new Error("having must be an object");
|
|
4396
|
+
}
|
|
4186
4397
|
const h = buildHavingClause(args.having, alias, params, model, dialect);
|
|
4187
4398
|
if (!h || h.trim().length === 0) return "";
|
|
4188
4399
|
return `${SQL_TEMPLATES.HAVING} ${h}`;
|
|
@@ -4215,63 +4426,61 @@ function buildGroupBySql(args, whereResult, tableName, alias, model, dialect) {
|
|
|
4215
4426
|
const snapshot = params.snapshot();
|
|
4216
4427
|
validateSelectQuery(sql);
|
|
4217
4428
|
validateParamConsistency(sql, [...whereResult.params, ...snapshot.params]);
|
|
4429
|
+
const mergedParams = [...whereResult.params, ...snapshot.params];
|
|
4218
4430
|
return Object.freeze({
|
|
4219
4431
|
sql,
|
|
4220
|
-
params: Object.freeze(
|
|
4432
|
+
params: Object.freeze(mergedParams),
|
|
4221
4433
|
paramMappings: Object.freeze([
|
|
4222
4434
|
...whereResult.paramMappings,
|
|
4223
4435
|
...snapshot.mappings
|
|
4224
4436
|
])
|
|
4225
4437
|
});
|
|
4226
4438
|
}
|
|
4227
|
-
function buildCountSql(whereResult, tableName, alias, skip,
|
|
4439
|
+
function buildCountSql(whereResult, tableName, alias, skip, _dialect) {
|
|
4228
4440
|
assertSafeAlias(alias);
|
|
4229
4441
|
assertSafeTableRef(tableName);
|
|
4230
|
-
|
|
4442
|
+
if (skip !== void 0 && skip !== null) {
|
|
4443
|
+
if (schemaParser.isDynamicParameter(skip)) {
|
|
4444
|
+
throw new Error(
|
|
4445
|
+
"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."
|
|
4446
|
+
);
|
|
4447
|
+
}
|
|
4448
|
+
if (typeof skip === "string") {
|
|
4449
|
+
const s = skip.trim();
|
|
4450
|
+
if (s.length > 0) {
|
|
4451
|
+
const n = Number(s);
|
|
4452
|
+
if (Number.isFinite(n) && Number.isInteger(n) && n > 0) {
|
|
4453
|
+
throw new Error(
|
|
4454
|
+
"count() with skip is not supported because it produces nondeterministic results. Use findMany().length or add explicit orderBy to ensure deterministic behavior."
|
|
4455
|
+
);
|
|
4456
|
+
}
|
|
4457
|
+
}
|
|
4458
|
+
}
|
|
4459
|
+
if (typeof skip === "number" && Number.isFinite(skip) && Number.isInteger(skip) && skip > 0) {
|
|
4460
|
+
throw new Error(
|
|
4461
|
+
"count() with skip is not supported because it produces nondeterministic results. Use findMany().length or add explicit orderBy to ensure deterministic behavior."
|
|
4462
|
+
);
|
|
4463
|
+
}
|
|
4464
|
+
}
|
|
4231
4465
|
const whereClause = isValidWhereClause(whereResult.clause) ? `${SQL_TEMPLATES.WHERE} ${whereResult.clause}` : "";
|
|
4232
|
-
const params = createParamStore(whereResult.nextParamIndex);
|
|
4233
|
-
const baseSubSelect = [
|
|
4234
|
-
SQL_TEMPLATES.SELECT,
|
|
4235
|
-
"1",
|
|
4236
|
-
SQL_TEMPLATES.FROM,
|
|
4237
|
-
tableName,
|
|
4238
|
-
alias,
|
|
4239
|
-
whereClause
|
|
4240
|
-
].filter((x) => x && String(x).trim().length > 0).join(" ").trim();
|
|
4241
|
-
const normalizedSkip = normalizeSkipLike(skip);
|
|
4242
|
-
const subSelect = applyCountSkip(baseSubSelect, normalizedSkip, params, d);
|
|
4243
4466
|
const sql = [
|
|
4244
4467
|
SQL_TEMPLATES.SELECT,
|
|
4245
4468
|
SQL_TEMPLATES.COUNT_ALL,
|
|
4246
4469
|
SQL_TEMPLATES.AS,
|
|
4247
4470
|
quote("_count._all"),
|
|
4248
4471
|
SQL_TEMPLATES.FROM,
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4472
|
+
tableName,
|
|
4473
|
+
alias,
|
|
4474
|
+
whereClause
|
|
4252
4475
|
].filter((x) => x && String(x).trim().length > 0).join(" ").trim();
|
|
4253
4476
|
validateSelectQuery(sql);
|
|
4254
|
-
|
|
4255
|
-
const mergedParams = [...whereResult.params, ...snapshot.params];
|
|
4256
|
-
validateParamConsistency(sql, mergedParams);
|
|
4477
|
+
validateParamConsistency(sql, whereResult.params);
|
|
4257
4478
|
return Object.freeze({
|
|
4258
4479
|
sql,
|
|
4259
|
-
params: Object.freeze(
|
|
4260
|
-
paramMappings: Object.freeze([
|
|
4261
|
-
...whereResult.paramMappings,
|
|
4262
|
-
...snapshot.mappings
|
|
4263
|
-
])
|
|
4480
|
+
params: Object.freeze([...whereResult.params]),
|
|
4481
|
+
paramMappings: Object.freeze([...whereResult.paramMappings])
|
|
4264
4482
|
});
|
|
4265
4483
|
}
|
|
4266
|
-
function applyCountSkip(subSelect, normalizedSkip, params, dialect) {
|
|
4267
|
-
const shouldApply = schemaParser.isDynamicParameter(normalizedSkip) || typeof normalizedSkip === "number" && normalizedSkip > 0;
|
|
4268
|
-
if (!shouldApply) return subSelect;
|
|
4269
|
-
const placeholder = addAutoScoped(params, normalizedSkip, "count.skip");
|
|
4270
|
-
if (dialect === "sqlite") {
|
|
4271
|
-
return `${subSelect} ${SQL_TEMPLATES.LIMIT} -1 ${SQL_TEMPLATES.OFFSET} ${placeholder}`;
|
|
4272
|
-
}
|
|
4273
|
-
return `${subSelect} ${SQL_TEMPLATES.OFFSET} ${placeholder}`;
|
|
4274
|
-
}
|
|
4275
4484
|
function safeAlias(input) {
|
|
4276
4485
|
const raw = String(input).toLowerCase();
|
|
4277
4486
|
const cleaned = raw.replace(/[^a-z0-9_]/g, "_");
|
|
@@ -4421,8 +4630,12 @@ function buildAndNormalizeSql(args) {
|
|
|
4421
4630
|
);
|
|
4422
4631
|
}
|
|
4423
4632
|
function finalizeDirective(args) {
|
|
4424
|
-
const { directive, normalizedSql, normalizedMappings } = args;
|
|
4425
|
-
|
|
4633
|
+
const { directive, normalizedSql, normalizedMappings, dialect } = args;
|
|
4634
|
+
const params = normalizedMappings.map((m) => {
|
|
4635
|
+
var _a;
|
|
4636
|
+
return (_a = m.value) != null ? _a : void 0;
|
|
4637
|
+
});
|
|
4638
|
+
validateParamConsistencyByDialect(normalizedSql, params, dialect);
|
|
4426
4639
|
const { staticParams, dynamicKeys } = buildParamsFromMappings(normalizedMappings);
|
|
4427
4640
|
return {
|
|
4428
4641
|
method: directive.method,
|
|
@@ -4459,7 +4672,8 @@ function generateSQL(directive) {
|
|
|
4459
4672
|
return finalizeDirective({
|
|
4460
4673
|
directive,
|
|
4461
4674
|
normalizedSql: normalized.sql,
|
|
4462
|
-
normalizedMappings: normalized.paramMappings
|
|
4675
|
+
normalizedMappings: normalized.paramMappings,
|
|
4676
|
+
dialect
|
|
4463
4677
|
});
|
|
4464
4678
|
}
|
|
4465
4679
|
|
|
@@ -4836,9 +5050,7 @@ function buildSQLFull(model, models, method, args, dialect) {
|
|
|
4836
5050
|
whereResult,
|
|
4837
5051
|
tableName,
|
|
4838
5052
|
alias,
|
|
4839
|
-
args.skip
|
|
4840
|
-
dialect
|
|
4841
|
-
);
|
|
5053
|
+
args.skip);
|
|
4842
5054
|
break;
|
|
4843
5055
|
default:
|
|
4844
5056
|
result = buildSelectSql({
|