prisma-sql 1.44.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 -562
- package/dist/generator.cjs.map +1 -1
- package/dist/generator.js +808 -562
- package/dist/generator.js.map +1 -1
- package/dist/index.cjs +765 -560
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +765 -560
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -48,20 +48,13 @@ var SQL_SEPARATORS = Object.freeze({
|
|
|
48
48
|
CONDITION_OR: " OR ",
|
|
49
49
|
ORDER_BY: ", "
|
|
50
50
|
});
|
|
51
|
-
var
|
|
51
|
+
var ALIAS_FORBIDDEN_KEYWORDS = /* @__PURE__ */ new Set([
|
|
52
52
|
"select",
|
|
53
53
|
"from",
|
|
54
54
|
"where",
|
|
55
|
-
"
|
|
56
|
-
"or",
|
|
57
|
-
"not",
|
|
58
|
-
"in",
|
|
59
|
-
"like",
|
|
60
|
-
"between",
|
|
55
|
+
"having",
|
|
61
56
|
"order",
|
|
62
|
-
"by",
|
|
63
57
|
"group",
|
|
64
|
-
"having",
|
|
65
58
|
"limit",
|
|
66
59
|
"offset",
|
|
67
60
|
"join",
|
|
@@ -69,14 +62,42 @@ var SQL_RESERVED_WORDS = /* @__PURE__ */ new Set([
|
|
|
69
62
|
"left",
|
|
70
63
|
"right",
|
|
71
64
|
"outer",
|
|
72
|
-
"
|
|
65
|
+
"cross",
|
|
66
|
+
"full",
|
|
67
|
+
"and",
|
|
68
|
+
"or",
|
|
69
|
+
"not",
|
|
70
|
+
"by",
|
|
73
71
|
"as",
|
|
72
|
+
"on",
|
|
73
|
+
"union",
|
|
74
|
+
"intersect",
|
|
75
|
+
"except",
|
|
76
|
+
"case",
|
|
77
|
+
"when",
|
|
78
|
+
"then",
|
|
79
|
+
"else",
|
|
80
|
+
"end"
|
|
81
|
+
]);
|
|
82
|
+
var SQL_KEYWORDS = /* @__PURE__ */ new Set([
|
|
83
|
+
...ALIAS_FORBIDDEN_KEYWORDS,
|
|
84
|
+
"user",
|
|
85
|
+
"users",
|
|
74
86
|
"table",
|
|
75
87
|
"column",
|
|
76
88
|
"index",
|
|
77
|
-
"user",
|
|
78
|
-
"users",
|
|
79
89
|
"values",
|
|
90
|
+
"in",
|
|
91
|
+
"like",
|
|
92
|
+
"between",
|
|
93
|
+
"is",
|
|
94
|
+
"exists",
|
|
95
|
+
"null",
|
|
96
|
+
"true",
|
|
97
|
+
"false",
|
|
98
|
+
"all",
|
|
99
|
+
"any",
|
|
100
|
+
"some",
|
|
80
101
|
"update",
|
|
81
102
|
"insert",
|
|
82
103
|
"delete",
|
|
@@ -87,25 +108,9 @@ var SQL_RESERVED_WORDS = /* @__PURE__ */ new Set([
|
|
|
87
108
|
"grant",
|
|
88
109
|
"revoke",
|
|
89
110
|
"exec",
|
|
90
|
-
"execute"
|
|
91
|
-
"union",
|
|
92
|
-
"intersect",
|
|
93
|
-
"except",
|
|
94
|
-
"case",
|
|
95
|
-
"when",
|
|
96
|
-
"then",
|
|
97
|
-
"else",
|
|
98
|
-
"end",
|
|
99
|
-
"null",
|
|
100
|
-
"true",
|
|
101
|
-
"false",
|
|
102
|
-
"is",
|
|
103
|
-
"exists",
|
|
104
|
-
"all",
|
|
105
|
-
"any",
|
|
106
|
-
"some"
|
|
111
|
+
"execute"
|
|
107
112
|
]);
|
|
108
|
-
var
|
|
113
|
+
var SQL_RESERVED_WORDS = SQL_KEYWORDS;
|
|
109
114
|
var DEFAULT_WHERE_CLAUSE = "1=1";
|
|
110
115
|
var SPECIAL_FIELDS = Object.freeze({
|
|
111
116
|
ID: "id"
|
|
@@ -184,12 +189,48 @@ var LIMITS = Object.freeze({
|
|
|
184
189
|
});
|
|
185
190
|
|
|
186
191
|
// src/utils/normalize-value.ts
|
|
187
|
-
|
|
192
|
+
var MAX_DEPTH = 20;
|
|
193
|
+
function normalizeValue(value, seen = /* @__PURE__ */ new WeakSet(), depth = 0) {
|
|
194
|
+
if (depth > MAX_DEPTH) {
|
|
195
|
+
throw new Error(`Max normalization depth exceeded (${MAX_DEPTH} levels)`);
|
|
196
|
+
}
|
|
188
197
|
if (value instanceof Date) {
|
|
198
|
+
const t = value.getTime();
|
|
199
|
+
if (!Number.isFinite(t)) {
|
|
200
|
+
throw new Error("Invalid Date value in SQL params");
|
|
201
|
+
}
|
|
189
202
|
return value.toISOString();
|
|
190
203
|
}
|
|
204
|
+
if (typeof value === "bigint") {
|
|
205
|
+
return value.toString();
|
|
206
|
+
}
|
|
191
207
|
if (Array.isArray(value)) {
|
|
192
|
-
|
|
208
|
+
const arrRef = value;
|
|
209
|
+
if (seen.has(arrRef)) {
|
|
210
|
+
throw new Error("Circular reference in SQL params");
|
|
211
|
+
}
|
|
212
|
+
seen.add(arrRef);
|
|
213
|
+
const out = value.map((v) => normalizeValue(v, seen, depth + 1));
|
|
214
|
+
seen.delete(arrRef);
|
|
215
|
+
return out;
|
|
216
|
+
}
|
|
217
|
+
if (value && typeof value === "object") {
|
|
218
|
+
if (value instanceof Uint8Array) return value;
|
|
219
|
+
if (typeof Buffer !== "undefined" && Buffer.isBuffer(value)) return value;
|
|
220
|
+
const proto = Object.getPrototypeOf(value);
|
|
221
|
+
const isPlain = proto === Object.prototype || proto === null;
|
|
222
|
+
if (!isPlain) return value;
|
|
223
|
+
const obj = value;
|
|
224
|
+
if (seen.has(obj)) {
|
|
225
|
+
throw new Error("Circular reference in SQL params");
|
|
226
|
+
}
|
|
227
|
+
seen.add(obj);
|
|
228
|
+
const out = {};
|
|
229
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
230
|
+
out[k] = normalizeValue(v, seen, depth + 1);
|
|
231
|
+
}
|
|
232
|
+
seen.delete(obj);
|
|
233
|
+
return out;
|
|
193
234
|
}
|
|
194
235
|
return value;
|
|
195
236
|
}
|
|
@@ -370,7 +411,7 @@ function prepareArrayParam(value, dialect) {
|
|
|
370
411
|
throw new Error("prepareArrayParam requires array value");
|
|
371
412
|
}
|
|
372
413
|
if (dialect === "postgres") {
|
|
373
|
-
return value.map(normalizeValue);
|
|
414
|
+
return value.map((v) => normalizeValue(v));
|
|
374
415
|
}
|
|
375
416
|
return JSON.stringify(value);
|
|
376
417
|
}
|
|
@@ -442,36 +483,46 @@ function createError(message, ctx, code = "VALIDATION_ERROR") {
|
|
|
442
483
|
}
|
|
443
484
|
|
|
444
485
|
// src/builder/shared/model-field-cache.ts
|
|
445
|
-
var
|
|
446
|
-
|
|
486
|
+
var MODEL_CACHE = /* @__PURE__ */ new WeakMap();
|
|
487
|
+
function ensureFullCache(model) {
|
|
488
|
+
let cache = MODEL_CACHE.get(model);
|
|
489
|
+
if (!cache) {
|
|
490
|
+
const fieldInfo = /* @__PURE__ */ new Map();
|
|
491
|
+
const scalarFields = /* @__PURE__ */ new Set();
|
|
492
|
+
const relationFields = /* @__PURE__ */ new Set();
|
|
493
|
+
const columnMap = /* @__PURE__ */ new Map();
|
|
494
|
+
for (const f of model.fields) {
|
|
495
|
+
const info = {
|
|
496
|
+
name: f.name,
|
|
497
|
+
dbName: f.dbName || f.name,
|
|
498
|
+
type: f.type,
|
|
499
|
+
isRelation: !!f.isRelation,
|
|
500
|
+
isRequired: !!f.isRequired
|
|
501
|
+
};
|
|
502
|
+
fieldInfo.set(f.name, info);
|
|
503
|
+
if (info.isRelation) {
|
|
504
|
+
relationFields.add(f.name);
|
|
505
|
+
} else {
|
|
506
|
+
scalarFields.add(f.name);
|
|
507
|
+
columnMap.set(f.name, info.dbName);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
cache = { fieldInfo, scalarFields, relationFields, columnMap };
|
|
511
|
+
MODEL_CACHE.set(model, cache);
|
|
512
|
+
}
|
|
513
|
+
return cache;
|
|
514
|
+
}
|
|
515
|
+
function getFieldInfo(model, fieldName) {
|
|
516
|
+
return ensureFullCache(model).fieldInfo.get(fieldName);
|
|
517
|
+
}
|
|
447
518
|
function getScalarFieldSet(model) {
|
|
448
|
-
|
|
449
|
-
if (cached) return cached;
|
|
450
|
-
const s = /* @__PURE__ */ new Set();
|
|
451
|
-
for (const f of model.fields) if (!f.isRelation) s.add(f.name);
|
|
452
|
-
SCALAR_SET_CACHE.set(model, s);
|
|
453
|
-
return s;
|
|
519
|
+
return ensureFullCache(model).scalarFields;
|
|
454
520
|
}
|
|
455
521
|
function getRelationFieldSet(model) {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
const s = /* @__PURE__ */ new Set();
|
|
459
|
-
for (const f of model.fields) if (f.isRelation) s.add(f.name);
|
|
460
|
-
RELATION_SET_CACHE.set(model, s);
|
|
461
|
-
return s;
|
|
462
|
-
}
|
|
463
|
-
var COLUMN_MAP_CACHE = /* @__PURE__ */ new WeakMap();
|
|
522
|
+
return ensureFullCache(model).relationFields;
|
|
523
|
+
}
|
|
464
524
|
function getColumnMap(model) {
|
|
465
|
-
|
|
466
|
-
if (cached) return cached;
|
|
467
|
-
const map = /* @__PURE__ */ new Map();
|
|
468
|
-
for (const f of model.fields) {
|
|
469
|
-
if (!f.isRelation) {
|
|
470
|
-
map.set(f.name, f.dbName || f.name);
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
COLUMN_MAP_CACHE.set(model, map);
|
|
474
|
-
return map;
|
|
525
|
+
return ensureFullCache(model).columnMap;
|
|
475
526
|
}
|
|
476
527
|
|
|
477
528
|
// src/builder/shared/validators/sql-validators.ts
|
|
@@ -531,7 +582,7 @@ function scanDollarPlaceholders(sql, markUpTo) {
|
|
|
531
582
|
}
|
|
532
583
|
return { count, min, max, seen };
|
|
533
584
|
}
|
|
534
|
-
function
|
|
585
|
+
function assertNoGapsDollar(scan, rangeMin, rangeMax, sql) {
|
|
535
586
|
for (let k = rangeMin; k <= rangeMax; k++) {
|
|
536
587
|
if (scan.seen[k] !== 1) {
|
|
537
588
|
throw new Error(
|
|
@@ -559,7 +610,7 @@ function validateParamConsistency(sql, params) {
|
|
|
559
610
|
`CRITICAL: Parameter mismatch - SQL max placeholder is $${scan.max} but ${paramLen} params provided. This will cause SQL execution to fail. SQL: ${sqlPreview(sql)}`
|
|
560
611
|
);
|
|
561
612
|
}
|
|
562
|
-
|
|
613
|
+
assertNoGapsDollar(scan, 1, scan.max, sql);
|
|
563
614
|
}
|
|
564
615
|
function needsQuoting(id) {
|
|
565
616
|
if (!isNonEmptyString(id)) return true;
|
|
@@ -577,14 +628,11 @@ function validateParamConsistencyFragment(sql, params) {
|
|
|
577
628
|
`CRITICAL: Parameter mismatch - SQL references $${scan.max} but only ${paramLen} params provided. SQL: ${sqlPreview(sql)}`
|
|
578
629
|
);
|
|
579
630
|
}
|
|
580
|
-
|
|
631
|
+
assertNoGapsDollar(scan, scan.min, scan.max, sql);
|
|
581
632
|
}
|
|
582
633
|
function assertOrThrow(condition, message) {
|
|
583
634
|
if (!condition) throw new Error(message);
|
|
584
635
|
}
|
|
585
|
-
function dialectPlaceholderPrefix(dialect) {
|
|
586
|
-
return dialect === "sqlite" ? "?" : "$";
|
|
587
|
-
}
|
|
588
636
|
function parseSqlitePlaceholderIndices(sql) {
|
|
589
637
|
const re = /\?(?:(\d+))?/g;
|
|
590
638
|
const indices = [];
|
|
@@ -604,112 +652,70 @@ function parseSqlitePlaceholderIndices(sql) {
|
|
|
604
652
|
}
|
|
605
653
|
return { indices, sawNumbered, sawAnonymous };
|
|
606
654
|
}
|
|
607
|
-
function parseDollarPlaceholderIndices(sql) {
|
|
608
|
-
const re = /\$(\d+)/g;
|
|
609
|
-
const indices = [];
|
|
610
|
-
for (const m of sql.matchAll(re)) indices.push(parseInt(m[1], 10));
|
|
611
|
-
return indices;
|
|
612
|
-
}
|
|
613
|
-
function getPlaceholderIndices(sql, dialect) {
|
|
614
|
-
if (dialect === "sqlite") return parseSqlitePlaceholderIndices(sql);
|
|
615
|
-
return {
|
|
616
|
-
indices: parseDollarPlaceholderIndices(sql),
|
|
617
|
-
sawNumbered: false,
|
|
618
|
-
sawAnonymous: false
|
|
619
|
-
};
|
|
620
|
-
}
|
|
621
655
|
function maxIndex(indices) {
|
|
622
656
|
return indices.length > 0 ? Math.max(...indices) : 0;
|
|
623
657
|
}
|
|
624
|
-
function
|
|
625
|
-
assertOrThrow(
|
|
626
|
-
!(sawNumbered && sawAnonymous),
|
|
627
|
-
`CRITICAL: Mixed sqlite placeholders ('?' and '?NNN') are not supported.`
|
|
628
|
-
);
|
|
629
|
-
}
|
|
630
|
-
function ensurePlaceholderMaxMatchesMappingsLength(max, mappingsLength, dialect) {
|
|
631
|
-
assertOrThrow(
|
|
632
|
-
max === mappingsLength,
|
|
633
|
-
`CRITICAL: SQL placeholder max mismatch - max is ${dialectPlaceholderPrefix(dialect)}${max}, but mappings length is ${mappingsLength}.`
|
|
634
|
-
);
|
|
635
|
-
}
|
|
636
|
-
function ensureSequentialPlaceholders(placeholders, max, dialect) {
|
|
637
|
-
const prefix = dialectPlaceholderPrefix(dialect);
|
|
658
|
+
function ensureSequentialIndices(seen, max, prefix) {
|
|
638
659
|
for (let i = 1; i <= max; i++) {
|
|
639
660
|
assertOrThrow(
|
|
640
|
-
|
|
661
|
+
seen.has(i),
|
|
641
662
|
`CRITICAL: Missing SQL placeholder ${prefix}${i} - placeholders must be sequential 1..${max}.`
|
|
642
663
|
);
|
|
643
664
|
}
|
|
644
665
|
}
|
|
645
|
-
function
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
666
|
+
function validateSqlitePlaceholders(sql, params) {
|
|
667
|
+
const paramLen = params.length;
|
|
668
|
+
const { indices, sawNumbered, sawAnonymous } = parseSqlitePlaceholderIndices(sql);
|
|
669
|
+
if (indices.length === 0) {
|
|
670
|
+
if (paramLen !== 0) {
|
|
671
|
+
throw new Error(
|
|
672
|
+
`CRITICAL: Parameter mismatch - SQL has no sqlite placeholders but ${paramLen} params provided. SQL: ${sqlPreview(sql)}`
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
652
677
|
assertOrThrow(
|
|
653
|
-
!
|
|
654
|
-
`CRITICAL:
|
|
678
|
+
!(sawNumbered && sawAnonymous),
|
|
679
|
+
`CRITICAL: Mixed sqlite placeholders ('?' and '?NNN') are not supported.`
|
|
655
680
|
);
|
|
656
|
-
|
|
657
|
-
}
|
|
658
|
-
function ensureMappingIndexExistsInSql(placeholders, index) {
|
|
681
|
+
const max = maxIndex(indices);
|
|
659
682
|
assertOrThrow(
|
|
660
|
-
|
|
661
|
-
`CRITICAL:
|
|
683
|
+
max === paramLen,
|
|
684
|
+
`CRITICAL: SQL placeholder max mismatch - max is ?${max}, but params length is ${paramLen}. SQL: ${sqlPreview(sql)}`
|
|
662
685
|
);
|
|
686
|
+
const set = new Set(indices);
|
|
687
|
+
ensureSequentialIndices(set, max, "?");
|
|
663
688
|
}
|
|
664
|
-
function
|
|
665
|
-
|
|
666
|
-
!(mapping.dynamicName !== void 0 && mapping.value !== void 0),
|
|
667
|
-
`CRITICAL: ParamMap ${mapping.index} has both dynamicName and value`
|
|
668
|
-
);
|
|
669
|
-
assertOrThrow(
|
|
670
|
-
!(mapping.dynamicName === void 0 && mapping.value === void 0),
|
|
671
|
-
`CRITICAL: ParamMap ${mapping.index} has neither dynamicName nor value`
|
|
672
|
-
);
|
|
689
|
+
function validateDollarPlaceholders(sql, params) {
|
|
690
|
+
validateParamConsistency(sql, params);
|
|
673
691
|
}
|
|
674
|
-
function
|
|
675
|
-
const
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
mappingIndices.has(i),
|
|
679
|
-
`CRITICAL: Missing ParamMap for placeholder ${prefix}${i} - mappings must cover 1..${max} with no gaps.`
|
|
680
|
-
);
|
|
681
|
-
}
|
|
692
|
+
function detectPlaceholderStyle(sql) {
|
|
693
|
+
const hasDollar = /\$\d+/.test(sql);
|
|
694
|
+
const hasSqliteQ = /\?(?:\d+)?/.test(sql);
|
|
695
|
+
return { hasDollar, hasSqliteQ };
|
|
682
696
|
}
|
|
683
|
-
function
|
|
684
|
-
const
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
697
|
+
function validateParamConsistencyByDialect(sql, params, dialect) {
|
|
698
|
+
const { hasDollar, hasSqliteQ } = detectPlaceholderStyle(sql);
|
|
699
|
+
if (dialect !== "sqlite") {
|
|
700
|
+
if (hasSqliteQ && !hasDollar) {
|
|
701
|
+
throw new Error(
|
|
702
|
+
`CRITICAL: Non-sqlite dialect query contains sqlite '?' placeholders. SQL: ${sqlPreview(sql)}`
|
|
703
|
+
);
|
|
704
|
+
}
|
|
705
|
+
return validateDollarPlaceholders(sql, params);
|
|
690
706
|
}
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
sql,
|
|
696
|
-
dialect
|
|
697
|
-
);
|
|
698
|
-
if (dialect === "sqlite") {
|
|
699
|
-
ensureNoMixedSqlitePlaceholders(sawNumbered, sawAnonymous);
|
|
707
|
+
if (hasDollar && hasSqliteQ) {
|
|
708
|
+
throw new Error(
|
|
709
|
+
`CRITICAL: Mixed placeholder styles ($N and ?/ ?NNN) are not supported. SQL: ${sqlPreview(sql)}`
|
|
710
|
+
);
|
|
700
711
|
}
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
const max = maxIndex(indices);
|
|
704
|
-
ensurePlaceholderMaxMatchesMappingsLength(max, mappings.length, dialect);
|
|
705
|
-
ensureSequentialPlaceholders(placeholders, max, dialect);
|
|
706
|
-
validateMappingsAgainstPlaceholders(mappings, placeholders, max, dialect);
|
|
712
|
+
if (hasSqliteQ) return validateSqlitePlaceholders(sql, params);
|
|
713
|
+
return validateDollarPlaceholders(sql, params);
|
|
707
714
|
}
|
|
708
715
|
|
|
709
716
|
// src/builder/shared/sql-utils.ts
|
|
710
|
-
var NUL = String.fromCharCode(0);
|
|
711
717
|
function containsControlChars(s) {
|
|
712
|
-
return
|
|
718
|
+
return /[\u0000-\u001F\u007F]/.test(s);
|
|
713
719
|
}
|
|
714
720
|
function assertNoControlChars(label, s) {
|
|
715
721
|
if (containsControlChars(s)) {
|
|
@@ -928,16 +934,46 @@ function buildTableReference(schemaName, tableName, dialect) {
|
|
|
928
934
|
return `"${safeSchema}"."${safeTable}"`;
|
|
929
935
|
}
|
|
930
936
|
function assertSafeAlias(alias) {
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
throw new Error("alias is required and cannot be empty");
|
|
937
|
+
if (typeof alias !== "string") {
|
|
938
|
+
throw new Error(`Invalid alias: expected string, got ${typeof alias}`);
|
|
934
939
|
}
|
|
935
|
-
|
|
936
|
-
|
|
940
|
+
const a = alias.trim();
|
|
941
|
+
if (a.length === 0) {
|
|
942
|
+
throw new Error("Invalid alias: required and cannot be empty");
|
|
937
943
|
}
|
|
938
|
-
if (
|
|
944
|
+
if (a !== alias) {
|
|
945
|
+
throw new Error("Invalid alias: leading/trailing whitespace");
|
|
946
|
+
}
|
|
947
|
+
if (/[\u0000-\u001F\u007F]/.test(a)) {
|
|
948
|
+
throw new Error(
|
|
949
|
+
"Invalid alias: contains unsafe characters (control characters)"
|
|
950
|
+
);
|
|
951
|
+
}
|
|
952
|
+
if (a.includes('"') || a.includes("'") || a.includes("`")) {
|
|
953
|
+
throw new Error("Invalid alias: contains unsafe characters (quotes)");
|
|
954
|
+
}
|
|
955
|
+
if (a.includes(";")) {
|
|
956
|
+
throw new Error("Invalid alias: contains unsafe characters (semicolon)");
|
|
957
|
+
}
|
|
958
|
+
if (a.includes("--") || a.includes("/*") || a.includes("*/")) {
|
|
959
|
+
throw new Error(
|
|
960
|
+
"Invalid alias: contains unsafe characters (SQL comment tokens)"
|
|
961
|
+
);
|
|
962
|
+
}
|
|
963
|
+
if (/\s/.test(a)) {
|
|
964
|
+
throw new Error(
|
|
965
|
+
"Invalid alias: must be a simple identifier without whitespace"
|
|
966
|
+
);
|
|
967
|
+
}
|
|
968
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(a)) {
|
|
969
|
+
throw new Error(
|
|
970
|
+
`Invalid alias: must be a simple identifier (alphanumeric with underscores): "${alias}"`
|
|
971
|
+
);
|
|
972
|
+
}
|
|
973
|
+
const lowered = a.toLowerCase();
|
|
974
|
+
if (ALIAS_FORBIDDEN_KEYWORDS.has(lowered)) {
|
|
939
975
|
throw new Error(
|
|
940
|
-
`alias
|
|
976
|
+
`Invalid alias: '${alias}' is a SQL keyword that would break query parsing. Forbidden aliases: ${[...ALIAS_FORBIDDEN_KEYWORDS].join(", ")}`
|
|
941
977
|
);
|
|
942
978
|
}
|
|
943
979
|
}
|
|
@@ -979,7 +1015,9 @@ function isValidRelationField(field) {
|
|
|
979
1015
|
if (fk.length === 0) return false;
|
|
980
1016
|
const refsRaw = field.references;
|
|
981
1017
|
const refs = normalizeKeyList(refsRaw);
|
|
982
|
-
if (refs.length === 0)
|
|
1018
|
+
if (refs.length === 0) {
|
|
1019
|
+
return fk.length === 1;
|
|
1020
|
+
}
|
|
983
1021
|
if (refs.length !== fk.length) return false;
|
|
984
1022
|
return true;
|
|
985
1023
|
}
|
|
@@ -994,6 +1032,8 @@ function getReferenceFieldNames(field, foreignKeyCount) {
|
|
|
994
1032
|
return refs;
|
|
995
1033
|
}
|
|
996
1034
|
function joinCondition(field, parentModel, childModel, parentAlias, childAlias) {
|
|
1035
|
+
assertSafeAlias(parentAlias);
|
|
1036
|
+
assertSafeAlias(childAlias);
|
|
997
1037
|
const fkFields = normalizeKeyList(field.foreignKey);
|
|
998
1038
|
if (fkFields.length === 0) {
|
|
999
1039
|
throw createError(
|
|
@@ -1143,6 +1183,32 @@ function normalizeOrderByInput(orderBy, parseValue) {
|
|
|
1143
1183
|
throw new Error("orderBy must be an object or array of objects");
|
|
1144
1184
|
}
|
|
1145
1185
|
|
|
1186
|
+
// src/builder/shared/order-by-determinism.ts
|
|
1187
|
+
function modelHasScalarId(model) {
|
|
1188
|
+
if (!model) return false;
|
|
1189
|
+
return getScalarFieldSet(model).has("id");
|
|
1190
|
+
}
|
|
1191
|
+
function hasIdTiebreaker(orderBy, parse) {
|
|
1192
|
+
if (!isNotNullish(orderBy)) return false;
|
|
1193
|
+
const normalized = normalizeOrderByInput(orderBy, parse);
|
|
1194
|
+
return normalized.some(
|
|
1195
|
+
(obj) => Object.prototype.hasOwnProperty.call(obj, "id")
|
|
1196
|
+
);
|
|
1197
|
+
}
|
|
1198
|
+
function addIdTiebreaker(orderBy) {
|
|
1199
|
+
if (Array.isArray(orderBy)) return [...orderBy, { id: "asc" }];
|
|
1200
|
+
return [orderBy, { id: "asc" }];
|
|
1201
|
+
}
|
|
1202
|
+
function ensureDeterministicOrderByInput(args) {
|
|
1203
|
+
const { orderBy, model, parseValue } = args;
|
|
1204
|
+
if (!modelHasScalarId(model)) return orderBy;
|
|
1205
|
+
if (!isNotNullish(orderBy)) {
|
|
1206
|
+
return { id: "asc" };
|
|
1207
|
+
}
|
|
1208
|
+
if (hasIdTiebreaker(orderBy, parseValue)) return orderBy;
|
|
1209
|
+
return addIdTiebreaker(orderBy);
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1146
1212
|
// src/builder/pagination.ts
|
|
1147
1213
|
var MAX_LIMIT_OFFSET = 2147483647;
|
|
1148
1214
|
function parseDirectionRaw(raw, errorLabel) {
|
|
@@ -1205,7 +1271,15 @@ function hasNonNullishProp(v, key) {
|
|
|
1205
1271
|
}
|
|
1206
1272
|
function normalizeIntegerOrDynamic(name, v) {
|
|
1207
1273
|
if (isDynamicParameter(v)) return v;
|
|
1208
|
-
|
|
1274
|
+
const result = normalizeIntLike(name, v, {
|
|
1275
|
+
min: Number.MIN_SAFE_INTEGER,
|
|
1276
|
+
max: MAX_LIMIT_OFFSET,
|
|
1277
|
+
allowZero: true
|
|
1278
|
+
});
|
|
1279
|
+
if (result === void 0) {
|
|
1280
|
+
throw new Error(`${name} normalization returned undefined`);
|
|
1281
|
+
}
|
|
1282
|
+
return result;
|
|
1209
1283
|
}
|
|
1210
1284
|
function readSkipTake(relArgs) {
|
|
1211
1285
|
const hasSkip = hasNonNullishProp(relArgs, "skip");
|
|
@@ -1271,7 +1345,7 @@ function buildCursorFilterParts(cursor, cursorAlias, params, model) {
|
|
|
1271
1345
|
const placeholdersByField = /* @__PURE__ */ new Map();
|
|
1272
1346
|
const parts = [];
|
|
1273
1347
|
for (const [field, value] of entries) {
|
|
1274
|
-
const c = `${cursorAlias}.${
|
|
1348
|
+
const c = `${cursorAlias}.${quoteColumn(model, field)}`;
|
|
1275
1349
|
if (value === null) {
|
|
1276
1350
|
parts.push(`${c} IS NULL`);
|
|
1277
1351
|
continue;
|
|
@@ -1285,13 +1359,6 @@ function buildCursorFilterParts(cursor, cursorAlias, params, model) {
|
|
|
1285
1359
|
placeholdersByField
|
|
1286
1360
|
};
|
|
1287
1361
|
}
|
|
1288
|
-
function cursorValueExpr(tableName, cursorAlias, cursorWhereSql, field, model) {
|
|
1289
|
-
const colName = quote(field);
|
|
1290
|
-
return `(SELECT ${cursorAlias}.${colName} ${SQL_TEMPLATES.FROM} ${tableName} ${cursorAlias} ${SQL_TEMPLATES.WHERE} ${cursorWhereSql} ${SQL_TEMPLATES.LIMIT} 1)`;
|
|
1291
|
-
}
|
|
1292
|
-
function buildCursorRowExistsExpr(tableName, cursorAlias, cursorWhereSql) {
|
|
1293
|
-
return `EXISTS (${SQL_TEMPLATES.SELECT} 1 ${SQL_TEMPLATES.FROM} ${tableName} ${cursorAlias} ${SQL_TEMPLATES.WHERE} ${cursorWhereSql} ${SQL_TEMPLATES.LIMIT} 1)`;
|
|
1294
|
-
}
|
|
1295
1362
|
function buildCursorEqualityExpr(columnExpr, valueExpr) {
|
|
1296
1363
|
return `((${valueExpr} IS NULL AND ${columnExpr} IS NULL) OR (${valueExpr} IS NOT NULL AND ${columnExpr} = ${valueExpr}))`;
|
|
1297
1364
|
}
|
|
@@ -1330,7 +1397,7 @@ function buildOrderEntries(orderBy) {
|
|
|
1330
1397
|
} else {
|
|
1331
1398
|
entries.push({
|
|
1332
1399
|
field,
|
|
1333
|
-
direction: value.
|
|
1400
|
+
direction: value.direction,
|
|
1334
1401
|
nulls: value.nulls
|
|
1335
1402
|
});
|
|
1336
1403
|
}
|
|
@@ -1338,16 +1405,53 @@ function buildOrderEntries(orderBy) {
|
|
|
1338
1405
|
}
|
|
1339
1406
|
return entries;
|
|
1340
1407
|
}
|
|
1408
|
+
function buildCursorCteSelectList(cursorEntries, orderEntries, model) {
|
|
1409
|
+
const set = /* @__PURE__ */ new Set();
|
|
1410
|
+
for (const [f] of cursorEntries) set.add(f);
|
|
1411
|
+
for (const e of orderEntries) set.add(e.field);
|
|
1412
|
+
const cols = [...set].map((f) => quoteColumn(model, f));
|
|
1413
|
+
if (cols.length === 0) {
|
|
1414
|
+
throw new Error("cursor cte select list is empty");
|
|
1415
|
+
}
|
|
1416
|
+
return cols.join(SQL_SEPARATORS.FIELD_LIST);
|
|
1417
|
+
}
|
|
1418
|
+
function truncateIdent(name, maxLen) {
|
|
1419
|
+
const s = String(name);
|
|
1420
|
+
if (s.length <= maxLen) return s;
|
|
1421
|
+
return s.slice(0, maxLen);
|
|
1422
|
+
}
|
|
1423
|
+
function buildCursorNames(outerAlias) {
|
|
1424
|
+
const maxLen = 63;
|
|
1425
|
+
const base = outerAlias.toLowerCase();
|
|
1426
|
+
const cteName = truncateIdent(`__tp_cursor_${base}`, maxLen);
|
|
1427
|
+
const srcAlias = truncateIdent(`__tp_cursor_src_${base}`, maxLen);
|
|
1428
|
+
if (cteName === outerAlias || srcAlias === outerAlias) {
|
|
1429
|
+
return {
|
|
1430
|
+
cteName: truncateIdent(`__tp_cursor_${base}_x`, maxLen),
|
|
1431
|
+
srcAlias: truncateIdent(`__tp_cursor_src_${base}_x`, maxLen)
|
|
1432
|
+
};
|
|
1433
|
+
}
|
|
1434
|
+
return { cteName, srcAlias };
|
|
1435
|
+
}
|
|
1341
1436
|
function buildCursorCondition(cursor, orderBy, tableName, alias, params, dialect, model) {
|
|
1342
1437
|
var _a;
|
|
1438
|
+
assertSafeTableRef(tableName);
|
|
1439
|
+
assertSafeAlias(alias);
|
|
1343
1440
|
const d = dialect != null ? dialect : getGlobalDialect();
|
|
1344
1441
|
const cursorEntries = Object.entries(cursor);
|
|
1345
1442
|
if (cursorEntries.length === 0) {
|
|
1346
1443
|
throw new Error("cursor must have at least one field");
|
|
1347
1444
|
}
|
|
1348
|
-
const
|
|
1349
|
-
|
|
1350
|
-
|
|
1445
|
+
const { cteName, srcAlias } = buildCursorNames(alias);
|
|
1446
|
+
assertSafeAlias(cteName);
|
|
1447
|
+
assertSafeAlias(srcAlias);
|
|
1448
|
+
const { whereSql: cursorWhereSql, placeholdersByField } = buildCursorFilterParts(cursor, srcAlias, params, model);
|
|
1449
|
+
const deterministicOrderBy = ensureDeterministicOrderByInput({
|
|
1450
|
+
orderBy,
|
|
1451
|
+
model,
|
|
1452
|
+
parseValue: parseOrderByValue
|
|
1453
|
+
});
|
|
1454
|
+
let orderEntries = buildOrderEntries(deterministicOrderBy);
|
|
1351
1455
|
if (orderEntries.length === 0) {
|
|
1352
1456
|
orderEntries = cursorEntries.map(([field]) => ({
|
|
1353
1457
|
field,
|
|
@@ -1356,11 +1460,21 @@ function buildCursorCondition(cursor, orderBy, tableName, alias, params, dialect
|
|
|
1356
1460
|
} else {
|
|
1357
1461
|
orderEntries = ensureCursorFieldsInOrder(orderEntries, cursorEntries);
|
|
1358
1462
|
}
|
|
1359
|
-
const
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1463
|
+
const cursorOrderBy = orderEntries.map(
|
|
1464
|
+
(e) => `${srcAlias}.${quoteColumn(model, e.field)} ${e.direction.toUpperCase()}`
|
|
1465
|
+
).join(", ");
|
|
1466
|
+
const selectList = buildCursorCteSelectList(
|
|
1467
|
+
cursorEntries,
|
|
1468
|
+
orderEntries,
|
|
1469
|
+
model
|
|
1363
1470
|
);
|
|
1471
|
+
const cte = `${cteName} AS (
|
|
1472
|
+
SELECT ${selectList} FROM ${tableName} ${srcAlias}
|
|
1473
|
+
WHERE ${cursorWhereSql}
|
|
1474
|
+
ORDER BY ${cursorOrderBy}
|
|
1475
|
+
LIMIT 1
|
|
1476
|
+
)`;
|
|
1477
|
+
const existsExpr = `EXISTS (SELECT 1 FROM ${cteName})`;
|
|
1364
1478
|
const outerCursorMatch = buildOuterCursorMatch(
|
|
1365
1479
|
cursor,
|
|
1366
1480
|
alias,
|
|
@@ -1368,34 +1482,31 @@ function buildCursorCondition(cursor, orderBy, tableName, alias, params, dialect
|
|
|
1368
1482
|
params,
|
|
1369
1483
|
model
|
|
1370
1484
|
);
|
|
1485
|
+
const getValueExpr = (field) => {
|
|
1486
|
+
return `(SELECT ${quoteColumn(model, field)} FROM ${cteName})`;
|
|
1487
|
+
};
|
|
1371
1488
|
const orClauses = [];
|
|
1372
1489
|
for (let level = 0; level < orderEntries.length; level++) {
|
|
1373
1490
|
const andParts = [];
|
|
1374
1491
|
for (let i = 0; i < level; i++) {
|
|
1375
1492
|
const e2 = orderEntries[i];
|
|
1376
1493
|
const c2 = col(alias, e2.field, model);
|
|
1377
|
-
const v2 =
|
|
1378
|
-
tableName,
|
|
1379
|
-
cursorAlias,
|
|
1380
|
-
cursorWhereSql,
|
|
1381
|
-
e2.field);
|
|
1494
|
+
const v2 = getValueExpr(e2.field);
|
|
1382
1495
|
andParts.push(buildCursorEqualityExpr(c2, v2));
|
|
1383
1496
|
}
|
|
1384
1497
|
const e = orderEntries[level];
|
|
1385
1498
|
const c = col(alias, e.field, model);
|
|
1386
|
-
const v =
|
|
1387
|
-
tableName,
|
|
1388
|
-
cursorAlias,
|
|
1389
|
-
cursorWhereSql,
|
|
1390
|
-
e.field);
|
|
1499
|
+
const v = getValueExpr(e.field);
|
|
1391
1500
|
const nulls = (_a = e.nulls) != null ? _a : defaultNullsFor(d, e.direction);
|
|
1392
1501
|
andParts.push(buildCursorInequalityExpr(c, e.direction, nulls, v));
|
|
1393
1502
|
orClauses.push(`(${andParts.join(SQL_SEPARATORS.CONDITION_AND)})`);
|
|
1394
1503
|
}
|
|
1395
1504
|
const exclusive = orClauses.join(SQL_SEPARATORS.CONDITION_OR);
|
|
1396
|
-
|
|
1505
|
+
const condition = `(${existsExpr} ${SQL_SEPARATORS.CONDITION_AND} ((${exclusive})${SQL_SEPARATORS.CONDITION_OR}(${outerCursorMatch})))`;
|
|
1506
|
+
return { cte, condition };
|
|
1397
1507
|
}
|
|
1398
1508
|
function buildOrderBy(orderBy, alias, dialect, model) {
|
|
1509
|
+
assertSafeAlias(alias);
|
|
1399
1510
|
const entries = buildOrderEntries(orderBy);
|
|
1400
1511
|
if (entries.length === 0) return "";
|
|
1401
1512
|
const d = dialect != null ? dialect : getGlobalDialect();
|
|
@@ -1495,6 +1606,11 @@ function buildScalarOperator(expr, op, val, params, mode, fieldType, dialect) {
|
|
|
1495
1606
|
}
|
|
1496
1607
|
return handleInOperator(expr, op, val, params, dialect);
|
|
1497
1608
|
}
|
|
1609
|
+
if (op === Ops.EQUALS && mode === Modes.INSENSITIVE && !isNotNullish(dialect)) {
|
|
1610
|
+
throw createError(`Insensitive equals requires a SQL dialect`, {
|
|
1611
|
+
operator: op
|
|
1612
|
+
});
|
|
1613
|
+
}
|
|
1498
1614
|
return handleComparisonOperator(expr, op, val, params);
|
|
1499
1615
|
}
|
|
1500
1616
|
function handleNullValue(expr, op) {
|
|
@@ -1510,6 +1626,28 @@ function normalizeMode(v) {
|
|
|
1510
1626
|
function handleNotOperator(expr, val, params, outerMode, fieldType, dialect) {
|
|
1511
1627
|
const innerMode = normalizeMode(val.mode);
|
|
1512
1628
|
const effectiveMode = innerMode != null ? innerMode : outerMode;
|
|
1629
|
+
const entries = Object.entries(val).filter(
|
|
1630
|
+
([k, v]) => k !== "mode" && v !== void 0
|
|
1631
|
+
);
|
|
1632
|
+
if (entries.length === 0) return "";
|
|
1633
|
+
if (!isNotNullish(dialect)) {
|
|
1634
|
+
const clauses = [];
|
|
1635
|
+
for (const [subOp, subVal] of entries) {
|
|
1636
|
+
const sub = buildScalarOperator(
|
|
1637
|
+
expr,
|
|
1638
|
+
subOp,
|
|
1639
|
+
subVal,
|
|
1640
|
+
params,
|
|
1641
|
+
effectiveMode,
|
|
1642
|
+
fieldType,
|
|
1643
|
+
void 0
|
|
1644
|
+
);
|
|
1645
|
+
if (sub && sub.trim().length > 0) clauses.push(`(${sub})`);
|
|
1646
|
+
}
|
|
1647
|
+
if (clauses.length === 0) return "";
|
|
1648
|
+
if (clauses.length === 1) return `${SQL_TEMPLATES.NOT} ${clauses[0]}`;
|
|
1649
|
+
return `${SQL_TEMPLATES.NOT} (${clauses.join(` ${SQL_TEMPLATES.AND} `)})`;
|
|
1650
|
+
}
|
|
1513
1651
|
return buildNotComposite(
|
|
1514
1652
|
expr,
|
|
1515
1653
|
val,
|
|
@@ -1724,6 +1862,7 @@ function handleArrayIsEmpty(expr, val, dialect) {
|
|
|
1724
1862
|
|
|
1725
1863
|
// src/builder/where/operators-json.ts
|
|
1726
1864
|
var SAFE_JSON_PATH_SEGMENT = /^[a-zA-Z_]\w*$/;
|
|
1865
|
+
var MAX_PATH_SEGMENT_LENGTH = 255;
|
|
1727
1866
|
function validateJsonPathSegments(segments) {
|
|
1728
1867
|
for (const segment of segments) {
|
|
1729
1868
|
if (typeof segment !== "string") {
|
|
@@ -1732,6 +1871,12 @@ function validateJsonPathSegments(segments) {
|
|
|
1732
1871
|
value: segment
|
|
1733
1872
|
});
|
|
1734
1873
|
}
|
|
1874
|
+
if (segment.length > MAX_PATH_SEGMENT_LENGTH) {
|
|
1875
|
+
throw createError(
|
|
1876
|
+
`JSON path segment too long: max ${MAX_PATH_SEGMENT_LENGTH} characters`,
|
|
1877
|
+
{ operator: Ops.PATH, value: `[${segment.length} chars]` }
|
|
1878
|
+
);
|
|
1879
|
+
}
|
|
1735
1880
|
if (!SAFE_JSON_PATH_SEGMENT.test(segment)) {
|
|
1736
1881
|
throw createError(
|
|
1737
1882
|
`Invalid JSON path segment: '${segment}'. Must be alphanumeric with underscores, starting with letter or underscore.`,
|
|
@@ -1820,6 +1965,9 @@ function handleJsonWildcard(expr, op, val, params, wildcards, dialect) {
|
|
|
1820
1965
|
|
|
1821
1966
|
// src/builder/where/relations.ts
|
|
1822
1967
|
var NO_JOINS = Object.freeze([]);
|
|
1968
|
+
function freezeJoins(items) {
|
|
1969
|
+
return Object.freeze([...items]);
|
|
1970
|
+
}
|
|
1823
1971
|
function isListRelation(fieldType) {
|
|
1824
1972
|
return typeof fieldType === "string" && fieldType.endsWith("[]");
|
|
1825
1973
|
}
|
|
@@ -1882,7 +2030,7 @@ function buildListRelationFilters(args) {
|
|
|
1882
2030
|
const whereClause = `${relAlias}.${quote(checkField.name)} IS NULL`;
|
|
1883
2031
|
return Object.freeze({
|
|
1884
2032
|
clause: whereClause,
|
|
1885
|
-
joins: [leftJoinSql]
|
|
2033
|
+
joins: freezeJoins([leftJoinSql])
|
|
1886
2034
|
});
|
|
1887
2035
|
}
|
|
1888
2036
|
}
|
|
@@ -2087,7 +2235,7 @@ function assertValidOperator(fieldName, op, fieldType, path, modelName) {
|
|
|
2087
2235
|
Ops.HAS_EVERY,
|
|
2088
2236
|
Ops.IS_EMPTY
|
|
2089
2237
|
]);
|
|
2090
|
-
const
|
|
2238
|
+
const JSON_OPS2 = /* @__PURE__ */ new Set([
|
|
2091
2239
|
Ops.PATH,
|
|
2092
2240
|
Ops.STRING_CONTAINS,
|
|
2093
2241
|
Ops.STRING_STARTS_WITH,
|
|
@@ -2104,7 +2252,7 @@ function assertValidOperator(fieldName, op, fieldType, path, modelName) {
|
|
|
2104
2252
|
modelName
|
|
2105
2253
|
});
|
|
2106
2254
|
}
|
|
2107
|
-
const isJsonOp =
|
|
2255
|
+
const isJsonOp = JSON_OPS2.has(op);
|
|
2108
2256
|
const isFieldJson = isJsonType(fieldType);
|
|
2109
2257
|
const jsonOpMismatch = isJsonOp && !isFieldJson;
|
|
2110
2258
|
if (jsonOpMismatch) {
|
|
@@ -2118,6 +2266,14 @@ function assertValidOperator(fieldName, op, fieldType, path, modelName) {
|
|
|
2118
2266
|
}
|
|
2119
2267
|
|
|
2120
2268
|
// src/builder/where/builder.ts
|
|
2269
|
+
var MAX_QUERY_DEPTH = 50;
|
|
2270
|
+
var EMPTY_JOINS = Object.freeze([]);
|
|
2271
|
+
var JSON_OPS = /* @__PURE__ */ new Set([
|
|
2272
|
+
Ops.PATH,
|
|
2273
|
+
Ops.STRING_CONTAINS,
|
|
2274
|
+
Ops.STRING_STARTS_WITH,
|
|
2275
|
+
Ops.STRING_ENDS_WITH
|
|
2276
|
+
]);
|
|
2121
2277
|
var WhereBuilder = class {
|
|
2122
2278
|
build(where, ctx) {
|
|
2123
2279
|
if (!isPlainObject(where)) {
|
|
@@ -2129,8 +2285,6 @@ var WhereBuilder = class {
|
|
|
2129
2285
|
return buildWhereInternal(where, ctx, this);
|
|
2130
2286
|
}
|
|
2131
2287
|
};
|
|
2132
|
-
var MAX_QUERY_DEPTH = 50;
|
|
2133
|
-
var EMPTY_JOINS = Object.freeze([]);
|
|
2134
2288
|
var whereBuilderInstance = new WhereBuilder();
|
|
2135
2289
|
function freezeResult(clause, joins = EMPTY_JOINS) {
|
|
2136
2290
|
return Object.freeze({ clause, joins });
|
|
@@ -2307,16 +2461,8 @@ function buildOperator(expr, op, val, ctx, mode, fieldType) {
|
|
|
2307
2461
|
if (fieldType && isArrayType(fieldType)) {
|
|
2308
2462
|
return buildArrayOperator(expr, op, val, ctx.params, fieldType, ctx.dialect);
|
|
2309
2463
|
}
|
|
2310
|
-
if (fieldType && isJsonType(fieldType)) {
|
|
2311
|
-
|
|
2312
|
-
Ops.PATH,
|
|
2313
|
-
Ops.STRING_CONTAINS,
|
|
2314
|
-
Ops.STRING_STARTS_WITH,
|
|
2315
|
-
Ops.STRING_ENDS_WITH
|
|
2316
|
-
]);
|
|
2317
|
-
if (JSON_OPS.has(op)) {
|
|
2318
|
-
return buildJsonOperator(expr, op, val, ctx.params, ctx.dialect);
|
|
2319
|
-
}
|
|
2464
|
+
if (fieldType && isJsonType(fieldType) && JSON_OPS.has(op)) {
|
|
2465
|
+
return buildJsonOperator(expr, op, val, ctx.params, ctx.dialect);
|
|
2320
2466
|
}
|
|
2321
2467
|
return buildScalarOperator(
|
|
2322
2468
|
expr,
|
|
@@ -2337,7 +2483,7 @@ function toSafeSqlIdentifier(input) {
|
|
|
2337
2483
|
const base = startsOk ? cleaned : `_${cleaned}`;
|
|
2338
2484
|
const fallback = base.length > 0 ? base : "_t";
|
|
2339
2485
|
const lowered = fallback.toLowerCase();
|
|
2340
|
-
return
|
|
2486
|
+
return ALIAS_FORBIDDEN_KEYWORDS.has(lowered) ? `_${lowered}` : lowered;
|
|
2341
2487
|
}
|
|
2342
2488
|
function createAliasGenerator(maxAliases = 1e4) {
|
|
2343
2489
|
let counter = 0;
|
|
@@ -2547,6 +2693,7 @@ function toPublicResult(clause, joins, params) {
|
|
|
2547
2693
|
// src/builder/where.ts
|
|
2548
2694
|
function buildWhereClause(where, options) {
|
|
2549
2695
|
var _a, _b, _c, _d, _e;
|
|
2696
|
+
assertSafeAlias(options.alias);
|
|
2550
2697
|
const dialect = options.dialect || getGlobalDialect();
|
|
2551
2698
|
const params = (_a = options.params) != null ? _a : createParamStore();
|
|
2552
2699
|
const ctx = {
|
|
@@ -2715,6 +2862,9 @@ function buildRelationSelect(relArgs, relModel, relAlias) {
|
|
|
2715
2862
|
}
|
|
2716
2863
|
|
|
2717
2864
|
// src/builder/select/includes.ts
|
|
2865
|
+
var MAX_INCLUDE_DEPTH = 10;
|
|
2866
|
+
var MAX_TOTAL_SUBQUERIES = 100;
|
|
2867
|
+
var MAX_TOTAL_INCLUDES = 50;
|
|
2718
2868
|
function getRelationTableReference(relModel, dialect) {
|
|
2719
2869
|
return buildTableReference(
|
|
2720
2870
|
SQL_TEMPLATES.PUBLIC_SCHEMA,
|
|
@@ -2760,107 +2910,24 @@ function relationEntriesFromArgs(args, model) {
|
|
|
2760
2910
|
pushFrom(args.select);
|
|
2761
2911
|
return out;
|
|
2762
2912
|
}
|
|
2763
|
-
function assertScalarField(model, fieldName) {
|
|
2764
|
-
const f = model.fields.find((x) => x.name === fieldName);
|
|
2765
|
-
if (!f) {
|
|
2766
|
-
throw new Error(
|
|
2767
|
-
`orderBy references unknown field '${fieldName}' on model ${model.name}`
|
|
2768
|
-
);
|
|
2769
|
-
}
|
|
2770
|
-
if (f.isRelation) {
|
|
2771
|
-
throw new Error(
|
|
2772
|
-
`orderBy does not support relation field '${fieldName}' on model ${model.name}`
|
|
2773
|
-
);
|
|
2774
|
-
}
|
|
2775
|
-
}
|
|
2776
|
-
function validateOrderByDirection(fieldName, v) {
|
|
2777
|
-
const s = String(v).toLowerCase();
|
|
2778
|
-
if (s !== "asc" && s !== "desc") {
|
|
2779
|
-
throw new Error(
|
|
2780
|
-
`Invalid orderBy direction for '${fieldName}': ${String(v)}`
|
|
2781
|
-
);
|
|
2782
|
-
}
|
|
2783
|
-
}
|
|
2784
|
-
function validateOrderByObject(fieldName, v) {
|
|
2785
|
-
if (!("sort" in v)) {
|
|
2786
|
-
throw new Error(
|
|
2787
|
-
`orderBy for '${fieldName}' must be 'asc' | 'desc' or { sort, nulls? }`
|
|
2788
|
-
);
|
|
2789
|
-
}
|
|
2790
|
-
validateOrderByDirection(fieldName, v.sort);
|
|
2791
|
-
if ("nulls" in v && isNotNullish(v.nulls)) {
|
|
2792
|
-
const n = String(v.nulls).toLowerCase();
|
|
2793
|
-
if (n !== "first" && n !== "last") {
|
|
2794
|
-
throw new Error(
|
|
2795
|
-
`Invalid orderBy.nulls for '${fieldName}': ${String(v.nulls)}`
|
|
2796
|
-
);
|
|
2797
|
-
}
|
|
2798
|
-
}
|
|
2799
|
-
const allowed = /* @__PURE__ */ new Set(["sort", "nulls"]);
|
|
2800
|
-
for (const k of Object.keys(v)) {
|
|
2801
|
-
if (!allowed.has(k)) {
|
|
2802
|
-
throw new Error(`Unsupported orderBy key '${k}' for field '${fieldName}'`);
|
|
2803
|
-
}
|
|
2804
|
-
}
|
|
2805
|
-
}
|
|
2806
|
-
function normalizeOrderByFieldName(name) {
|
|
2807
|
-
const fieldName = String(name).trim();
|
|
2808
|
-
if (fieldName.length === 0) {
|
|
2809
|
-
throw new Error("orderBy field name cannot be empty");
|
|
2810
|
-
}
|
|
2811
|
-
return fieldName;
|
|
2812
|
-
}
|
|
2813
|
-
function requirePlainObjectForOrderByEntry(v) {
|
|
2814
|
-
if (typeof v !== "object" || v === null || Array.isArray(v)) {
|
|
2815
|
-
throw new Error("orderBy array entries must be objects");
|
|
2816
|
-
}
|
|
2817
|
-
return v;
|
|
2818
|
-
}
|
|
2819
|
-
function parseSingleFieldOrderByObject(obj) {
|
|
2820
|
-
const entries = Object.entries(obj);
|
|
2821
|
-
if (entries.length !== 1) {
|
|
2822
|
-
throw new Error("orderBy array entries must have exactly one field");
|
|
2823
|
-
}
|
|
2824
|
-
const fieldName = normalizeOrderByFieldName(entries[0][0]);
|
|
2825
|
-
return [fieldName, entries[0][1]];
|
|
2826
|
-
}
|
|
2827
|
-
function parseOrderByArray(orderBy) {
|
|
2828
|
-
return orderBy.map(
|
|
2829
|
-
(item) => parseSingleFieldOrderByObject(requirePlainObjectForOrderByEntry(item))
|
|
2830
|
-
);
|
|
2831
|
-
}
|
|
2832
|
-
function parseOrderByObject(orderBy) {
|
|
2833
|
-
const out = [];
|
|
2834
|
-
for (const [k, v] of Object.entries(orderBy)) {
|
|
2835
|
-
out.push([normalizeOrderByFieldName(k), v]);
|
|
2836
|
-
}
|
|
2837
|
-
return out;
|
|
2838
|
-
}
|
|
2839
|
-
function getOrderByEntries(orderBy) {
|
|
2840
|
-
if (!isNotNullish(orderBy)) return [];
|
|
2841
|
-
if (Array.isArray(orderBy)) {
|
|
2842
|
-
return parseOrderByArray(orderBy);
|
|
2843
|
-
}
|
|
2844
|
-
if (typeof orderBy === "object" && orderBy !== null) {
|
|
2845
|
-
return parseOrderByObject(orderBy);
|
|
2846
|
-
}
|
|
2847
|
-
throw new Error("orderBy must be an object or array of objects");
|
|
2848
|
-
}
|
|
2849
2913
|
function validateOrderByForModel(model, orderBy) {
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2914
|
+
if (!isNotNullish(orderBy)) return;
|
|
2915
|
+
const scalarSet = getScalarFieldSet(model);
|
|
2916
|
+
const normalized = normalizeOrderByInput(orderBy, parseOrderByValue);
|
|
2917
|
+
for (const item of normalized) {
|
|
2918
|
+
const entries = Object.entries(item);
|
|
2919
|
+
if (entries.length !== 1) {
|
|
2920
|
+
throw new Error("orderBy array entries must have exactly one field");
|
|
2856
2921
|
}
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2922
|
+
const fieldName = String(entries[0][0]).trim();
|
|
2923
|
+
if (fieldName.length === 0) {
|
|
2924
|
+
throw new Error("orderBy field name cannot be empty");
|
|
2925
|
+
}
|
|
2926
|
+
if (!scalarSet.has(fieldName)) {
|
|
2927
|
+
throw new Error(
|
|
2928
|
+
`orderBy references unknown or non-scalar field '${fieldName}' on model ${model.name}`
|
|
2929
|
+
);
|
|
2860
2930
|
}
|
|
2861
|
-
throw new Error(
|
|
2862
|
-
`orderBy for '${fieldName}' must be 'asc' | 'desc' or { sort, nulls? }`
|
|
2863
|
-
);
|
|
2864
2931
|
}
|
|
2865
2932
|
}
|
|
2866
2933
|
function appendLimitOffset(sql, dialect, params, takeVal, skipVal, scope) {
|
|
@@ -2889,7 +2956,10 @@ function readWhereInput(relArgs) {
|
|
|
2889
2956
|
function readOrderByInput(relArgs) {
|
|
2890
2957
|
if (!isPlainObject(relArgs)) return { hasOrderBy: false, orderBy: void 0 };
|
|
2891
2958
|
if (!("orderBy" in relArgs)) return { hasOrderBy: false, orderBy: void 0 };
|
|
2892
|
-
return {
|
|
2959
|
+
return {
|
|
2960
|
+
hasOrderBy: true,
|
|
2961
|
+
orderBy: relArgs.orderBy
|
|
2962
|
+
};
|
|
2893
2963
|
}
|
|
2894
2964
|
function extractRelationPaginationConfig(relArgs) {
|
|
2895
2965
|
const { hasOrderBy, orderBy: rawOrderByInput } = readOrderByInput(relArgs);
|
|
@@ -2919,36 +2989,28 @@ function maybeReverseNegativeTake(takeVal, hasOrderBy, orderByInput) {
|
|
|
2919
2989
|
orderByInput: reverseOrderByInput(orderByInput)
|
|
2920
2990
|
};
|
|
2921
2991
|
}
|
|
2922
|
-
function
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
);
|
|
2927
|
-
}
|
|
2928
|
-
function modelHasScalarId(relModel) {
|
|
2929
|
-
const idField = relModel.fields.find((f) => f.name === "id");
|
|
2930
|
-
return Boolean(idField && !idField.isRelation);
|
|
2931
|
-
}
|
|
2932
|
-
function addIdTiebreaker(orderByInput) {
|
|
2933
|
-
if (Array.isArray(orderByInput)) return [...orderByInput, { id: "asc" }];
|
|
2934
|
-
return [orderByInput, { id: "asc" }];
|
|
2935
|
-
}
|
|
2936
|
-
function ensureDeterministicOrderBy(relModel, hasOrderBy, orderByInput, hasPagination) {
|
|
2937
|
-
if (!hasPagination) {
|
|
2938
|
-
if (hasOrderBy && isNotNullish(orderByInput)) {
|
|
2939
|
-
validateOrderByForModel(relModel, orderByInput);
|
|
2992
|
+
function ensureDeterministicOrderByForInclude(args) {
|
|
2993
|
+
if (!args.hasPagination) {
|
|
2994
|
+
if (args.hasOrderBy && isNotNullish(args.orderByInput)) {
|
|
2995
|
+
validateOrderByForModel(args.relModel, args.orderByInput);
|
|
2940
2996
|
}
|
|
2941
|
-
return orderByInput;
|
|
2997
|
+
return args.orderByInput;
|
|
2942
2998
|
}
|
|
2943
|
-
if (!hasOrderBy) {
|
|
2944
|
-
return
|
|
2999
|
+
if (!args.hasOrderBy) {
|
|
3000
|
+
return ensureDeterministicOrderByInput({
|
|
3001
|
+
orderBy: void 0,
|
|
3002
|
+
model: args.relModel,
|
|
3003
|
+
parseValue: parseOrderByValue
|
|
3004
|
+
});
|
|
2945
3005
|
}
|
|
2946
|
-
if (isNotNullish(orderByInput)) {
|
|
2947
|
-
validateOrderByForModel(relModel, orderByInput);
|
|
3006
|
+
if (isNotNullish(args.orderByInput)) {
|
|
3007
|
+
validateOrderByForModel(args.relModel, args.orderByInput);
|
|
2948
3008
|
}
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
3009
|
+
return ensureDeterministicOrderByInput({
|
|
3010
|
+
orderBy: args.orderByInput,
|
|
3011
|
+
model: args.relModel,
|
|
3012
|
+
parseValue: parseOrderByValue
|
|
3013
|
+
});
|
|
2952
3014
|
}
|
|
2953
3015
|
function buildSelectWithNestedIncludes(relArgs, relModel, relAlias, ctx) {
|
|
2954
3016
|
let relSelect = buildRelationSelect(relArgs, relModel, relAlias);
|
|
@@ -2959,7 +3021,10 @@ function buildSelectWithNestedIncludes(relArgs, relModel, relAlias, ctx) {
|
|
|
2959
3021
|
relAlias,
|
|
2960
3022
|
ctx.aliasGen,
|
|
2961
3023
|
ctx.params,
|
|
2962
|
-
ctx.dialect
|
|
3024
|
+
ctx.dialect,
|
|
3025
|
+
ctx.visitPath || [],
|
|
3026
|
+
(ctx.depth || 0) + 1,
|
|
3027
|
+
ctx.stats
|
|
2963
3028
|
) : [];
|
|
2964
3029
|
if (isNonEmptyArray(nestedIncludes)) {
|
|
2965
3030
|
const emptyJson = ctx.dialect === "postgres" ? `'[]'::json` : `json('[]')`;
|
|
@@ -3109,12 +3174,12 @@ function buildSingleInclude(relName, relArgs, field, relModel, ctx) {
|
|
|
3109
3174
|
paginationConfig.orderBy
|
|
3110
3175
|
);
|
|
3111
3176
|
const hasPagination = paginationConfig.hasSkip || paginationConfig.hasTake;
|
|
3112
|
-
const finalOrderByInput =
|
|
3177
|
+
const finalOrderByInput = ensureDeterministicOrderByForInclude({
|
|
3113
3178
|
relModel,
|
|
3114
|
-
paginationConfig.hasOrderBy,
|
|
3115
|
-
adjusted.orderByInput,
|
|
3179
|
+
hasOrderBy: paginationConfig.hasOrderBy,
|
|
3180
|
+
orderByInput: adjusted.orderByInput,
|
|
3116
3181
|
hasPagination
|
|
3117
|
-
);
|
|
3182
|
+
});
|
|
3118
3183
|
const orderBySql = buildOrderBySql(
|
|
3119
3184
|
finalOrderByInput,
|
|
3120
3185
|
relAlias,
|
|
@@ -3155,12 +3220,48 @@ function buildSingleInclude(relName, relArgs, field, relModel, ctx) {
|
|
|
3155
3220
|
ctx
|
|
3156
3221
|
});
|
|
3157
3222
|
}
|
|
3158
|
-
function buildIncludeSqlInternal(args, model, schemas, parentAlias, aliasGen, params, dialect) {
|
|
3223
|
+
function buildIncludeSqlInternal(args, model, schemas, parentAlias, aliasGen, params, dialect, visitPath = [], depth = 0, stats) {
|
|
3224
|
+
if (!stats) {
|
|
3225
|
+
stats = { totalIncludes: 0, totalSubqueries: 0, maxDepth: 0 };
|
|
3226
|
+
}
|
|
3227
|
+
if (depth > MAX_INCLUDE_DEPTH) {
|
|
3228
|
+
throw new Error(
|
|
3229
|
+
`Maximum include depth of ${MAX_INCLUDE_DEPTH} exceeded. Path: ${visitPath.join(" -> ")}. Deep includes cause exponential SQL complexity and performance issues.`
|
|
3230
|
+
);
|
|
3231
|
+
}
|
|
3232
|
+
stats.maxDepth = Math.max(stats.maxDepth, depth);
|
|
3159
3233
|
const includes = [];
|
|
3160
3234
|
const entries = relationEntriesFromArgs(args, model);
|
|
3161
3235
|
for (const [relName, relArgs] of entries) {
|
|
3162
3236
|
if (relArgs === false) continue;
|
|
3237
|
+
stats.totalIncludes++;
|
|
3238
|
+
if (stats.totalIncludes > MAX_TOTAL_INCLUDES) {
|
|
3239
|
+
throw new Error(
|
|
3240
|
+
`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.`
|
|
3241
|
+
);
|
|
3242
|
+
}
|
|
3243
|
+
stats.totalSubqueries++;
|
|
3244
|
+
if (stats.totalSubqueries > MAX_TOTAL_SUBQUERIES) {
|
|
3245
|
+
throw new Error(
|
|
3246
|
+
`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.`
|
|
3247
|
+
);
|
|
3248
|
+
}
|
|
3163
3249
|
const resolved = resolveRelationOrThrow(model, schemas, relName);
|
|
3250
|
+
const relationPath = `${model.name}.${relName}`;
|
|
3251
|
+
const currentPath = [...visitPath, relationPath];
|
|
3252
|
+
if (visitPath.includes(relationPath)) {
|
|
3253
|
+
throw new Error(
|
|
3254
|
+
`Circular include detected: ${currentPath.join(" -> ")}. Relation '${relationPath}' creates an infinite loop.`
|
|
3255
|
+
);
|
|
3256
|
+
}
|
|
3257
|
+
const modelOccurrences = currentPath.filter(
|
|
3258
|
+
(p) => p.startsWith(`${resolved.relModel.name}.`)
|
|
3259
|
+
).length;
|
|
3260
|
+
if (modelOccurrences > 2) {
|
|
3261
|
+
throw new Error(
|
|
3262
|
+
`Include too deeply nested: model '${resolved.relModel.name}' appears ${modelOccurrences} times in path: ${currentPath.join(" -> ")}`
|
|
3263
|
+
);
|
|
3264
|
+
}
|
|
3164
3265
|
const include = buildSingleInclude(
|
|
3165
3266
|
relName,
|
|
3166
3267
|
relArgs,
|
|
@@ -3172,7 +3273,10 @@ function buildIncludeSqlInternal(args, model, schemas, parentAlias, aliasGen, pa
|
|
|
3172
3273
|
parentAlias,
|
|
3173
3274
|
aliasGen,
|
|
3174
3275
|
dialect,
|
|
3175
|
-
params
|
|
3276
|
+
params,
|
|
3277
|
+
visitPath: currentPath,
|
|
3278
|
+
depth: depth + 1,
|
|
3279
|
+
stats
|
|
3176
3280
|
}
|
|
3177
3281
|
);
|
|
3178
3282
|
includes.push(include);
|
|
@@ -3181,6 +3285,11 @@ function buildIncludeSqlInternal(args, model, schemas, parentAlias, aliasGen, pa
|
|
|
3181
3285
|
}
|
|
3182
3286
|
function buildIncludeSql(args, model, schemas, parentAlias, params, dialect) {
|
|
3183
3287
|
const aliasGen = createAliasGenerator();
|
|
3288
|
+
const stats = {
|
|
3289
|
+
totalIncludes: 0,
|
|
3290
|
+
totalSubqueries: 0,
|
|
3291
|
+
maxDepth: 0
|
|
3292
|
+
};
|
|
3184
3293
|
return buildIncludeSqlInternal(
|
|
3185
3294
|
args,
|
|
3186
3295
|
model,
|
|
@@ -3188,7 +3297,10 @@ function buildIncludeSql(args, model, schemas, parentAlias, params, dialect) {
|
|
|
3188
3297
|
parentAlias,
|
|
3189
3298
|
aliasGen,
|
|
3190
3299
|
params,
|
|
3191
|
-
dialect
|
|
3300
|
+
dialect,
|
|
3301
|
+
[],
|
|
3302
|
+
0,
|
|
3303
|
+
stats
|
|
3192
3304
|
);
|
|
3193
3305
|
}
|
|
3194
3306
|
function resolveCountRelationOrThrow(relName, model, schemas) {
|
|
@@ -3204,6 +3316,11 @@ function resolveCountRelationOrThrow(relName, model, schemas) {
|
|
|
3204
3316
|
`_count.${relName} references unknown relation on model ${model.name}`
|
|
3205
3317
|
);
|
|
3206
3318
|
}
|
|
3319
|
+
if (!isValidRelationField(field)) {
|
|
3320
|
+
throw new Error(
|
|
3321
|
+
`_count.${relName} has invalid relation metadata on model ${model.name}`
|
|
3322
|
+
);
|
|
3323
|
+
}
|
|
3207
3324
|
const relModel = schemas.find((m) => m.name === field.relatedModel);
|
|
3208
3325
|
if (!relModel) {
|
|
3209
3326
|
throw new Error(
|
|
@@ -3212,31 +3329,81 @@ function resolveCountRelationOrThrow(relName, model, schemas) {
|
|
|
3212
3329
|
}
|
|
3213
3330
|
return { field, relModel };
|
|
3214
3331
|
}
|
|
3215
|
-
function
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3332
|
+
function defaultReferencesForCount(fkCount) {
|
|
3333
|
+
if (fkCount === 1) return ["id"];
|
|
3334
|
+
throw new Error(
|
|
3335
|
+
"Relation count for composite keys requires explicit references matching foreignKey length"
|
|
3336
|
+
);
|
|
3219
3337
|
}
|
|
3220
|
-
function
|
|
3338
|
+
function resolveCountKeyPairs(field) {
|
|
3221
3339
|
const fkFields = normalizeKeyList(field.foreignKey);
|
|
3222
|
-
|
|
3223
|
-
|
|
3340
|
+
if (fkFields.length === 0) {
|
|
3341
|
+
throw new Error("Relation count requires foreignKey");
|
|
3342
|
+
}
|
|
3343
|
+
const refsRaw = field.references;
|
|
3344
|
+
const refs = normalizeKeyList(refsRaw);
|
|
3345
|
+
const refFields = refs.length > 0 ? refs : defaultReferencesForCount(fkFields.length);
|
|
3346
|
+
if (refFields.length !== fkFields.length) {
|
|
3347
|
+
throw new Error(
|
|
3348
|
+
"Relation count requires references count to match foreignKey count"
|
|
3349
|
+
);
|
|
3350
|
+
}
|
|
3351
|
+
const relKeyFields = field.isForeignKeyLocal ? refFields : fkFields;
|
|
3352
|
+
const parentKeyFields = field.isForeignKeyLocal ? fkFields : refFields;
|
|
3353
|
+
return { relKeyFields, parentKeyFields };
|
|
3354
|
+
}
|
|
3355
|
+
function aliasQualifiedColumn(alias, model, field) {
|
|
3356
|
+
return `${alias}.${quoteColumn(model, field)}`;
|
|
3357
|
+
}
|
|
3358
|
+
function subqueryForCount(args) {
|
|
3359
|
+
const selectKeys = args.relKeyFields.map(
|
|
3360
|
+
(f, i) => `${aliasQualifiedColumn(args.countAlias, args.relModel, f)} AS "__fk${i}"`
|
|
3361
|
+
).join(SQL_SEPARATORS.FIELD_LIST);
|
|
3362
|
+
const groupByKeys = args.relKeyFields.map((f) => aliasQualifiedColumn(args.countAlias, args.relModel, f)).join(SQL_SEPARATORS.FIELD_LIST);
|
|
3363
|
+
const cntExpr = args.dialect === "postgres" ? "COUNT(*)::int AS __cnt" : "COUNT(*) AS __cnt";
|
|
3364
|
+
return `(SELECT ${selectKeys}${SQL_SEPARATORS.FIELD_LIST}${cntExpr} FROM ${args.relTable} ${args.countAlias} GROUP BY ${groupByKeys})`;
|
|
3365
|
+
}
|
|
3366
|
+
function leftJoinOnForCount(args) {
|
|
3367
|
+
const parts = args.parentKeyFields.map(
|
|
3368
|
+
(f, i) => `${args.joinAlias}."__fk${i}" = ${aliasQualifiedColumn(args.parentAlias, args.parentModel, f)}`
|
|
3369
|
+
);
|
|
3370
|
+
return parts.length === 1 ? parts[0] : `(${parts.join(" AND ")})`;
|
|
3224
3371
|
}
|
|
3225
|
-
function
|
|
3226
|
-
|
|
3372
|
+
function nextAliasAvoiding(aliasGen, base, forbidden) {
|
|
3373
|
+
let a = aliasGen.next(base);
|
|
3374
|
+
while (forbidden.has(a)) {
|
|
3375
|
+
a = aliasGen.next(base);
|
|
3376
|
+
}
|
|
3377
|
+
return a;
|
|
3227
3378
|
}
|
|
3228
3379
|
function buildCountJoinAndPair(args) {
|
|
3229
3380
|
const relTable = getRelationTableReference(args.relModel, args.dialect);
|
|
3230
|
-
const
|
|
3231
|
-
const
|
|
3232
|
-
const
|
|
3233
|
-
args.
|
|
3381
|
+
const { relKeyFields, parentKeyFields } = resolveCountKeyPairs(args.field);
|
|
3382
|
+
const forbidden = /* @__PURE__ */ new Set([args.parentAlias]);
|
|
3383
|
+
const countAlias = nextAliasAvoiding(
|
|
3384
|
+
args.aliasGen,
|
|
3385
|
+
`__tp_cnt_${args.relName}`,
|
|
3386
|
+
forbidden
|
|
3387
|
+
);
|
|
3388
|
+
forbidden.add(countAlias);
|
|
3389
|
+
const subquery = subqueryForCount({
|
|
3390
|
+
dialect: args.dialect,
|
|
3234
3391
|
relTable,
|
|
3235
3392
|
countAlias,
|
|
3236
|
-
|
|
3393
|
+
relModel: args.relModel,
|
|
3394
|
+
relKeyFields
|
|
3395
|
+
});
|
|
3396
|
+
const joinAlias = nextAliasAvoiding(
|
|
3397
|
+
args.aliasGen,
|
|
3398
|
+
`__tp_cnt_j_${args.relName}`,
|
|
3399
|
+
forbidden
|
|
3237
3400
|
);
|
|
3238
|
-
const
|
|
3239
|
-
|
|
3401
|
+
const leftJoinOn = leftJoinOnForCount({
|
|
3402
|
+
joinAlias,
|
|
3403
|
+
parentAlias: args.parentAlias,
|
|
3404
|
+
parentModel: args.parentModel,
|
|
3405
|
+
parentKeyFields
|
|
3406
|
+
});
|
|
3240
3407
|
return {
|
|
3241
3408
|
joinSql: `LEFT JOIN ${subquery} ${joinAlias} ON ${leftJoinOn}`,
|
|
3242
3409
|
pairSql: `${sqlStringLiteral(args.relName)}, COALESCE(${joinAlias}.__cnt, 0)`
|
|
@@ -3245,6 +3412,7 @@ function buildCountJoinAndPair(args) {
|
|
|
3245
3412
|
function buildRelationCountSql(countSelect, model, schemas, parentAlias, _params, dialect) {
|
|
3246
3413
|
const joins = [];
|
|
3247
3414
|
const pairs = [];
|
|
3415
|
+
const aliasGen = createAliasGenerator();
|
|
3248
3416
|
for (const [relName, shouldCount] of Object.entries(countSelect)) {
|
|
3249
3417
|
if (!shouldCount) continue;
|
|
3250
3418
|
const resolved = resolveCountRelationOrThrow(relName, model, schemas);
|
|
@@ -3252,8 +3420,10 @@ function buildRelationCountSql(countSelect, model, schemas, parentAlias, _params
|
|
|
3252
3420
|
relName,
|
|
3253
3421
|
field: resolved.field,
|
|
3254
3422
|
relModel: resolved.relModel,
|
|
3423
|
+
parentModel: model,
|
|
3255
3424
|
parentAlias,
|
|
3256
|
-
dialect
|
|
3425
|
+
dialect,
|
|
3426
|
+
aliasGen
|
|
3257
3427
|
});
|
|
3258
3428
|
joins.push(built.joinSql);
|
|
3259
3429
|
pairs.push(built.pairSql);
|
|
@@ -3265,16 +3435,21 @@ function buildRelationCountSql(countSelect, model, schemas, parentAlias, _params
|
|
|
3265
3435
|
}
|
|
3266
3436
|
|
|
3267
3437
|
// src/builder/select/assembly.ts
|
|
3268
|
-
var
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
}
|
|
3438
|
+
var ALIAS_CAPTURE = "([A-Za-z_][A-Za-z0-9_]*)";
|
|
3439
|
+
var COLUMN_PART = '(?:"([^"]+)"|([a-z_][a-z0-9_]*))';
|
|
3440
|
+
var AS_PART = `(?:\\s+AS\\s+${COLUMN_PART})?`;
|
|
3441
|
+
var SIMPLE_COLUMN_PATTERN = `^${ALIAS_CAPTURE}\\.${COLUMN_PART}${AS_PART}$`;
|
|
3442
|
+
var SIMPLE_COLUMN_RE = new RegExp(SIMPLE_COLUMN_PATTERN, "i");
|
|
3272
3443
|
function joinNonEmpty(parts, sep) {
|
|
3273
3444
|
return parts.filter((s) => s.trim().length > 0).join(sep);
|
|
3274
3445
|
}
|
|
3275
3446
|
function buildWhereSql(conditions) {
|
|
3276
3447
|
if (!isNonEmptyArray(conditions)) return "";
|
|
3277
|
-
|
|
3448
|
+
const parts = [
|
|
3449
|
+
SQL_TEMPLATES.WHERE,
|
|
3450
|
+
conditions.join(SQL_SEPARATORS.CONDITION_AND)
|
|
3451
|
+
];
|
|
3452
|
+
return ` ${parts.join(" ")}`;
|
|
3278
3453
|
}
|
|
3279
3454
|
function buildJoinsSql(...joinGroups) {
|
|
3280
3455
|
const all = [];
|
|
@@ -3289,37 +3464,39 @@ function buildSelectList(baseSelect, extraCols) {
|
|
|
3289
3464
|
if (base && extra) return `${base}${SQL_SEPARATORS.FIELD_LIST}${extra}`;
|
|
3290
3465
|
return base || extra;
|
|
3291
3466
|
}
|
|
3292
|
-
function finalizeSql(sql, params) {
|
|
3467
|
+
function finalizeSql(sql, params, dialect) {
|
|
3293
3468
|
const snapshot = params.snapshot();
|
|
3294
3469
|
validateSelectQuery(sql);
|
|
3295
|
-
|
|
3470
|
+
validateParamConsistencyByDialect(sql, snapshot.params, dialect);
|
|
3296
3471
|
return Object.freeze({
|
|
3297
3472
|
sql,
|
|
3298
|
-
params:
|
|
3473
|
+
params: snapshot.params,
|
|
3299
3474
|
paramMappings: Object.freeze(snapshot.mappings)
|
|
3300
3475
|
});
|
|
3301
3476
|
}
|
|
3302
|
-
function parseSimpleScalarSelect(select,
|
|
3303
|
-
var _a, _b;
|
|
3477
|
+
function parseSimpleScalarSelect(select, fromAlias) {
|
|
3478
|
+
var _a, _b, _c, _d;
|
|
3304
3479
|
const raw = select.trim();
|
|
3305
3480
|
if (raw.length === 0) return [];
|
|
3306
|
-
let re = SIMPLE_SELECT_RE_CACHE.get(alias);
|
|
3307
|
-
if (!re) {
|
|
3308
|
-
const safeAlias2 = alias.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3309
|
-
re = new RegExp(`^${safeAlias2}\\.(?:"([^"]+)"|([a-z_][a-z0-9_]*))$`, "i");
|
|
3310
|
-
SIMPLE_SELECT_RE_CACHE.set(alias, re);
|
|
3311
|
-
}
|
|
3312
3481
|
const parts = raw.split(SQL_SEPARATORS.FIELD_LIST);
|
|
3313
3482
|
const names = [];
|
|
3314
3483
|
for (const part of parts) {
|
|
3315
3484
|
const p = part.trim();
|
|
3316
|
-
const m = p.match(
|
|
3485
|
+
const m = p.match(SIMPLE_COLUMN_RE);
|
|
3317
3486
|
if (!m) {
|
|
3318
3487
|
throw new Error(
|
|
3319
|
-
`sqlite distinct emulation requires scalar select fields to be simple columns. Got: ${p}`
|
|
3488
|
+
`sqlite distinct emulation requires scalar select fields to be simple columns (optionally with AS). Got: ${p}`
|
|
3320
3489
|
);
|
|
3321
3490
|
}
|
|
3322
|
-
const
|
|
3491
|
+
const actualAlias = m[1];
|
|
3492
|
+
if (actualAlias.toLowerCase() !== fromAlias.toLowerCase()) {
|
|
3493
|
+
throw new Error(
|
|
3494
|
+
`Expected alias '${fromAlias}', got '${actualAlias}' in: ${p}`
|
|
3495
|
+
);
|
|
3496
|
+
}
|
|
3497
|
+
const columnName = ((_b = (_a = m[2]) != null ? _a : m[3]) != null ? _b : "").trim();
|
|
3498
|
+
const outAlias = ((_d = (_c = m[4]) != null ? _c : m[5]) != null ? _d : "").trim();
|
|
3499
|
+
const name = outAlias.length > 0 ? outAlias : columnName;
|
|
3323
3500
|
if (name.length === 0) {
|
|
3324
3501
|
throw new Error(`Failed to parse selected column name from: ${p}`);
|
|
3325
3502
|
}
|
|
@@ -3328,18 +3505,18 @@ function parseSimpleScalarSelect(select, alias) {
|
|
|
3328
3505
|
return names;
|
|
3329
3506
|
}
|
|
3330
3507
|
function replaceOrderByAlias(orderBy, fromAlias, outerAlias) {
|
|
3331
|
-
const
|
|
3332
|
-
|
|
3333
|
-
|
|
3508
|
+
const src = String(fromAlias);
|
|
3509
|
+
if (src.length === 0) return orderBy;
|
|
3510
|
+
const escaped = src.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3511
|
+
const re = new RegExp(`\\b${escaped}\\.`, "gi");
|
|
3512
|
+
return orderBy.replace(re, `${outerAlias}.`);
|
|
3334
3513
|
}
|
|
3335
3514
|
function buildDistinctColumns(distinct, fromAlias, model) {
|
|
3336
3515
|
return distinct.map((f) => col(fromAlias, f, model)).join(SQL_SEPARATORS.FIELD_LIST);
|
|
3337
3516
|
}
|
|
3338
3517
|
function buildOutputColumns(scalarNames, includeNames, hasCount) {
|
|
3339
3518
|
const outputCols = [...scalarNames, ...includeNames];
|
|
3340
|
-
if (hasCount)
|
|
3341
|
-
outputCols.push("_count");
|
|
3342
|
-
}
|
|
3519
|
+
if (hasCount) outputCols.push("_count");
|
|
3343
3520
|
const formatted = outputCols.map((n) => quote(n)).join(SQL_SEPARATORS.FIELD_LIST);
|
|
3344
3521
|
if (!isNonEmptyString(formatted)) {
|
|
3345
3522
|
throw new Error("distinct emulation requires at least one output column");
|
|
@@ -3348,9 +3525,10 @@ function buildOutputColumns(scalarNames, includeNames, hasCount) {
|
|
|
3348
3525
|
}
|
|
3349
3526
|
function buildWindowOrder(args) {
|
|
3350
3527
|
const { baseOrder, idField, fromAlias, model } = args;
|
|
3528
|
+
const fromLower = String(fromAlias).toLowerCase();
|
|
3351
3529
|
const orderFields = baseOrder.split(SQL_SEPARATORS.ORDER_BY).map((s) => s.trim().toLowerCase());
|
|
3352
3530
|
const hasIdInOrder = orderFields.some(
|
|
3353
|
-
(f) => f.startsWith(`${
|
|
3531
|
+
(f) => f.startsWith(`${fromLower}.id `) || f.startsWith(`${fromLower}."id" `)
|
|
3354
3532
|
);
|
|
3355
3533
|
if (hasIdInOrder) return baseOrder;
|
|
3356
3534
|
const idTiebreaker = idField ? `, ${col(fromAlias, "id", model)} ASC` : "";
|
|
@@ -3385,15 +3563,37 @@ function buildSqliteDistinctQuery(spec, selectWithIncludes, countJoins) {
|
|
|
3385
3563
|
const outerOrder = isNonEmptyString(orderBy) ? replaceOrderByAlias(orderBy, from.alias, `"__tp_distinct"`) : replaceOrderByAlias(fallbackOrder, from.alias, `"__tp_distinct"`);
|
|
3386
3564
|
const joins = buildJoinsSql(whereJoins, countJoins);
|
|
3387
3565
|
const conditions = [];
|
|
3388
|
-
if (whereClause && whereClause !== "1=1")
|
|
3389
|
-
conditions.push(whereClause);
|
|
3390
|
-
}
|
|
3566
|
+
if (whereClause && whereClause !== "1=1") conditions.push(whereClause);
|
|
3391
3567
|
const whereSql = buildWhereSql(conditions);
|
|
3392
3568
|
const innerSelectList = selectWithIncludes.trim();
|
|
3393
3569
|
const innerComma = innerSelectList.length > 0 ? SQL_SEPARATORS.FIELD_LIST : "";
|
|
3394
|
-
const
|
|
3395
|
-
|
|
3396
|
-
|
|
3570
|
+
const innerParts = [
|
|
3571
|
+
SQL_TEMPLATES.SELECT,
|
|
3572
|
+
innerSelectList + innerComma,
|
|
3573
|
+
`ROW_NUMBER() OVER (PARTITION BY ${distinctCols} ORDER BY ${windowOrder})`,
|
|
3574
|
+
SQL_TEMPLATES.AS,
|
|
3575
|
+
'"__tp_rn"',
|
|
3576
|
+
SQL_TEMPLATES.FROM,
|
|
3577
|
+
from.table,
|
|
3578
|
+
from.alias
|
|
3579
|
+
];
|
|
3580
|
+
if (joins) innerParts.push(joins);
|
|
3581
|
+
if (whereSql) innerParts.push(whereSql);
|
|
3582
|
+
const inner = innerParts.filter(Boolean).join(" ");
|
|
3583
|
+
const outerParts = [
|
|
3584
|
+
SQL_TEMPLATES.SELECT,
|
|
3585
|
+
outerSelectCols,
|
|
3586
|
+
SQL_TEMPLATES.FROM,
|
|
3587
|
+
`(${inner})`,
|
|
3588
|
+
SQL_TEMPLATES.AS,
|
|
3589
|
+
'"__tp_distinct"',
|
|
3590
|
+
SQL_TEMPLATES.WHERE,
|
|
3591
|
+
'"__tp_rn" = 1'
|
|
3592
|
+
];
|
|
3593
|
+
if (isNonEmptyString(outerOrder)) {
|
|
3594
|
+
outerParts.push(SQL_TEMPLATES.ORDER_BY, outerOrder);
|
|
3595
|
+
}
|
|
3596
|
+
return outerParts.filter(Boolean).join(" ");
|
|
3397
3597
|
}
|
|
3398
3598
|
function buildIncludeColumns(spec) {
|
|
3399
3599
|
var _a, _b;
|
|
@@ -3521,6 +3721,7 @@ function constructFinalSql(spec) {
|
|
|
3521
3721
|
orderBy,
|
|
3522
3722
|
distinct,
|
|
3523
3723
|
method,
|
|
3724
|
+
cursorCte,
|
|
3524
3725
|
cursorClause,
|
|
3525
3726
|
params,
|
|
3526
3727
|
dialect,
|
|
@@ -3535,9 +3736,13 @@ function constructFinalSql(spec) {
|
|
|
3535
3736
|
const spec2 = withCountJoins(spec, countJoins, whereJoins);
|
|
3536
3737
|
let sql2 = buildSqliteDistinctQuery(spec2, selectWithIncludes).trim();
|
|
3537
3738
|
sql2 = appendPagination(sql2, spec);
|
|
3538
|
-
return finalizeSql(sql2, params);
|
|
3739
|
+
return finalizeSql(sql2, params, dialect);
|
|
3539
3740
|
}
|
|
3540
|
-
const parts = [
|
|
3741
|
+
const parts = [];
|
|
3742
|
+
if (cursorCte) {
|
|
3743
|
+
parts.push(`WITH ${cursorCte}`);
|
|
3744
|
+
}
|
|
3745
|
+
parts.push(SQL_TEMPLATES.SELECT);
|
|
3541
3746
|
const distinctOn = dialect === "postgres" ? buildPostgresDistinctOnClause(from.alias, distinct, model) : null;
|
|
3542
3747
|
if (distinctOn) parts.push(distinctOn);
|
|
3543
3748
|
const baseSelect = (select != null ? select : "").trim();
|
|
@@ -3553,7 +3758,41 @@ function constructFinalSql(spec) {
|
|
|
3553
3758
|
if (isNonEmptyString(orderBy)) parts.push(SQL_TEMPLATES.ORDER_BY, orderBy);
|
|
3554
3759
|
let sql = parts.join(" ").trim();
|
|
3555
3760
|
sql = appendPagination(sql, spec);
|
|
3556
|
-
return finalizeSql(sql, params);
|
|
3761
|
+
return finalizeSql(sql, params, dialect);
|
|
3762
|
+
}
|
|
3763
|
+
|
|
3764
|
+
// src/builder/shared/validators/field-assertions.ts
|
|
3765
|
+
var NUMERIC_TYPES = /* @__PURE__ */ new Set(["Int", "Float", "Decimal", "BigInt"]);
|
|
3766
|
+
function assertScalarField(model, fieldName, context) {
|
|
3767
|
+
const field = getFieldInfo(model, fieldName);
|
|
3768
|
+
if (!field) {
|
|
3769
|
+
throw createError(
|
|
3770
|
+
`${context} references unknown field '${fieldName}' on model ${model.name}`,
|
|
3771
|
+
{
|
|
3772
|
+
field: fieldName,
|
|
3773
|
+
modelName: model.name,
|
|
3774
|
+
availableFields: model.fields.map((f) => f.name)
|
|
3775
|
+
}
|
|
3776
|
+
);
|
|
3777
|
+
}
|
|
3778
|
+
if (field.isRelation) {
|
|
3779
|
+
throw createError(
|
|
3780
|
+
`${context} does not support relation field '${fieldName}'`,
|
|
3781
|
+
{ field: fieldName, modelName: model.name }
|
|
3782
|
+
);
|
|
3783
|
+
}
|
|
3784
|
+
return field;
|
|
3785
|
+
}
|
|
3786
|
+
function assertNumericField(model, fieldName, context) {
|
|
3787
|
+
const field = assertScalarField(model, fieldName, context);
|
|
3788
|
+
const baseType = field.type.replace(/\[\]|\?/g, "");
|
|
3789
|
+
if (!NUMERIC_TYPES.has(baseType)) {
|
|
3790
|
+
throw createError(
|
|
3791
|
+
`${context} requires numeric field, got '${field.type}'`,
|
|
3792
|
+
{ field: fieldName, modelName: model.name }
|
|
3793
|
+
);
|
|
3794
|
+
}
|
|
3795
|
+
return field;
|
|
3557
3796
|
}
|
|
3558
3797
|
|
|
3559
3798
|
// src/builder/select.ts
|
|
@@ -3586,7 +3825,7 @@ function buildPostgresDistinctOrderBy(distinctFields, existing) {
|
|
|
3586
3825
|
}
|
|
3587
3826
|
return next;
|
|
3588
3827
|
}
|
|
3589
|
-
function applyPostgresDistinctOrderBy(args
|
|
3828
|
+
function applyPostgresDistinctOrderBy(args) {
|
|
3590
3829
|
const distinctFields = normalizeDistinctFields(args.distinct);
|
|
3591
3830
|
if (distinctFields.length === 0) return args;
|
|
3592
3831
|
if (!isNotNullish(args.orderBy)) return args;
|
|
@@ -3596,19 +3835,6 @@ function applyPostgresDistinctOrderBy(args, _model) {
|
|
|
3596
3835
|
orderBy: buildPostgresDistinctOrderBy(distinctFields, existing)
|
|
3597
3836
|
});
|
|
3598
3837
|
}
|
|
3599
|
-
function assertScalarFieldOnModel(model, fieldName, ctx) {
|
|
3600
|
-
const f = model.fields.find((x) => x.name === fieldName);
|
|
3601
|
-
if (!f) {
|
|
3602
|
-
throw new Error(
|
|
3603
|
-
`${ctx} references unknown field '${fieldName}' on model ${model.name}`
|
|
3604
|
-
);
|
|
3605
|
-
}
|
|
3606
|
-
if (f.isRelation) {
|
|
3607
|
-
throw new Error(
|
|
3608
|
-
`${ctx} does not support relation field '${fieldName}' on model ${model.name}`
|
|
3609
|
-
);
|
|
3610
|
-
}
|
|
3611
|
-
}
|
|
3612
3838
|
function validateDistinct(model, distinct) {
|
|
3613
3839
|
if (!isNotNullish(distinct) || !isNonEmptyArray(distinct)) return;
|
|
3614
3840
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -3619,24 +3845,24 @@ function validateDistinct(model, distinct) {
|
|
|
3619
3845
|
throw new Error(`distinct must not contain duplicates (field: '${f}')`);
|
|
3620
3846
|
}
|
|
3621
3847
|
seen.add(f);
|
|
3622
|
-
|
|
3848
|
+
assertScalarField(model, f, "distinct");
|
|
3623
3849
|
}
|
|
3624
3850
|
}
|
|
3625
|
-
function validateOrderByValue(fieldName, v) {
|
|
3626
|
-
parseOrderByValue(v, fieldName);
|
|
3627
|
-
}
|
|
3628
3851
|
function validateOrderBy(model, orderBy) {
|
|
3629
3852
|
if (!isNotNullish(orderBy)) return;
|
|
3630
3853
|
const items = normalizeOrderByInput2(orderBy);
|
|
3631
3854
|
if (items.length === 0) return;
|
|
3632
3855
|
for (const it of items) {
|
|
3633
3856
|
const entries = Object.entries(it);
|
|
3857
|
+
if (entries.length !== 1) {
|
|
3858
|
+
throw new Error("orderBy array entries must have exactly one field");
|
|
3859
|
+
}
|
|
3634
3860
|
const fieldName = String(entries[0][0]).trim();
|
|
3635
3861
|
if (fieldName.length === 0) {
|
|
3636
3862
|
throw new Error("orderBy field name cannot be empty");
|
|
3637
3863
|
}
|
|
3638
|
-
|
|
3639
|
-
|
|
3864
|
+
assertScalarField(model, fieldName, "orderBy");
|
|
3865
|
+
parseOrderByValue(entries[0][1], fieldName);
|
|
3640
3866
|
}
|
|
3641
3867
|
}
|
|
3642
3868
|
function validateCursor(model, cursor) {
|
|
@@ -3653,7 +3879,7 @@ function validateCursor(model, cursor) {
|
|
|
3653
3879
|
if (f.length === 0) {
|
|
3654
3880
|
throw new Error("cursor field name cannot be empty");
|
|
3655
3881
|
}
|
|
3656
|
-
|
|
3882
|
+
assertScalarField(model, f, "cursor");
|
|
3657
3883
|
}
|
|
3658
3884
|
}
|
|
3659
3885
|
function resolveDialect(dialect) {
|
|
@@ -3672,20 +3898,21 @@ function normalizeArgsForNegativeTake(method, args) {
|
|
|
3672
3898
|
orderBy: reverseOrderByInput(args.orderBy)
|
|
3673
3899
|
});
|
|
3674
3900
|
}
|
|
3675
|
-
function normalizeArgsForDialect(dialect, args
|
|
3901
|
+
function normalizeArgsForDialect(dialect, args) {
|
|
3676
3902
|
if (dialect !== "postgres") return args;
|
|
3677
3903
|
return applyPostgresDistinctOrderBy(args);
|
|
3678
3904
|
}
|
|
3679
3905
|
function buildCursorClauseIfAny(input) {
|
|
3680
|
-
const { cursor, orderBy, tableName, alias, params, dialect } = input;
|
|
3681
|
-
if (!isNotNullish(cursor)) return
|
|
3906
|
+
const { cursor, orderBy, tableName, alias, params, dialect, model } = input;
|
|
3907
|
+
if (!isNotNullish(cursor)) return {};
|
|
3682
3908
|
return buildCursorCondition(
|
|
3683
3909
|
cursor,
|
|
3684
3910
|
orderBy,
|
|
3685
3911
|
tableName,
|
|
3686
3912
|
alias,
|
|
3687
3913
|
params,
|
|
3688
|
-
dialect
|
|
3914
|
+
dialect,
|
|
3915
|
+
model
|
|
3689
3916
|
);
|
|
3690
3917
|
}
|
|
3691
3918
|
function buildSelectSpec(input) {
|
|
@@ -3724,14 +3951,20 @@ function buildSelectSpec(input) {
|
|
|
3724
3951
|
params,
|
|
3725
3952
|
dialect
|
|
3726
3953
|
);
|
|
3727
|
-
const
|
|
3954
|
+
const cursorResult = buildCursorClauseIfAny({
|
|
3728
3955
|
cursor,
|
|
3729
3956
|
orderBy: normalizedArgs.orderBy,
|
|
3730
3957
|
tableName,
|
|
3731
3958
|
alias,
|
|
3732
3959
|
params,
|
|
3733
|
-
dialect
|
|
3960
|
+
dialect,
|
|
3961
|
+
model
|
|
3734
3962
|
});
|
|
3963
|
+
if (dialect === "sqlite" && isNonEmptyArray(normalizedArgs.distinct) && cursorResult.condition) {
|
|
3964
|
+
throw new Error(
|
|
3965
|
+
"Cursor pagination with distinct is not supported in SQLite due to window function limitations. Use findMany with skip/take instead, or remove distinct."
|
|
3966
|
+
);
|
|
3967
|
+
}
|
|
3735
3968
|
return {
|
|
3736
3969
|
select: selectFields,
|
|
3737
3970
|
includes,
|
|
@@ -3742,7 +3975,8 @@ function buildSelectSpec(input) {
|
|
|
3742
3975
|
pagination: { take, skip },
|
|
3743
3976
|
distinct: normalizedArgs.distinct,
|
|
3744
3977
|
method,
|
|
3745
|
-
|
|
3978
|
+
cursorCte: cursorResult.cte,
|
|
3979
|
+
cursorClause: cursorResult.condition,
|
|
3746
3980
|
params,
|
|
3747
3981
|
dialect,
|
|
3748
3982
|
model,
|
|
@@ -3756,9 +3990,7 @@ function buildSelectSql(input) {
|
|
|
3756
3990
|
assertSafeTableRef(from.tableName);
|
|
3757
3991
|
const dialectToUse = resolveDialect(dialect);
|
|
3758
3992
|
const argsForSql = normalizeArgsForNegativeTake(method, args);
|
|
3759
|
-
const normalizedArgs = normalizeArgsForDialect(
|
|
3760
|
-
dialectToUse,
|
|
3761
|
-
argsForSql);
|
|
3993
|
+
const normalizedArgs = normalizeArgsForDialect(dialectToUse, argsForSql);
|
|
3762
3994
|
validateDistinct(model, normalizedArgs.distinct);
|
|
3763
3995
|
validateOrderBy(model, normalizedArgs.orderBy);
|
|
3764
3996
|
validateCursor(model, normalizedArgs.cursor);
|
|
@@ -3774,8 +4006,21 @@ function buildSelectSql(input) {
|
|
|
3774
4006
|
});
|
|
3775
4007
|
return constructFinalSql(spec);
|
|
3776
4008
|
}
|
|
3777
|
-
|
|
3778
|
-
|
|
4009
|
+
|
|
4010
|
+
// src/builder/shared/comparison-builder.ts
|
|
4011
|
+
function buildComparisons(expr, filter, params, dialect, builder, excludeKeys = /* @__PURE__ */ new Set(["mode"])) {
|
|
4012
|
+
const out = [];
|
|
4013
|
+
for (const [op, val] of Object.entries(filter)) {
|
|
4014
|
+
if (excludeKeys.has(op) || val === void 0) continue;
|
|
4015
|
+
const built = builder(expr, op, val, params, dialect);
|
|
4016
|
+
if (built && built.trim().length > 0) {
|
|
4017
|
+
out.push(built);
|
|
4018
|
+
}
|
|
4019
|
+
}
|
|
4020
|
+
return out;
|
|
4021
|
+
}
|
|
4022
|
+
|
|
4023
|
+
// src/builder/aggregates.ts
|
|
3779
4024
|
var AGGREGATES = [
|
|
3780
4025
|
["_sum", "SUM"],
|
|
3781
4026
|
["_avg", "AVG"],
|
|
@@ -3790,19 +4035,16 @@ var COMPARISON_OPS = {
|
|
|
3790
4035
|
[Ops.LT]: "<",
|
|
3791
4036
|
[Ops.LTE]: "<="
|
|
3792
4037
|
};
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
MODEL_FIELD_CACHE.set(model, m);
|
|
3804
|
-
return m;
|
|
3805
|
-
}
|
|
4038
|
+
var HAVING_ALLOWED_OPS = /* @__PURE__ */ new Set([
|
|
4039
|
+
Ops.EQUALS,
|
|
4040
|
+
Ops.NOT,
|
|
4041
|
+
Ops.GT,
|
|
4042
|
+
Ops.GTE,
|
|
4043
|
+
Ops.LT,
|
|
4044
|
+
Ops.LTE,
|
|
4045
|
+
Ops.IN,
|
|
4046
|
+
Ops.NOT_IN
|
|
4047
|
+
]);
|
|
3806
4048
|
function isTruthySelection(v) {
|
|
3807
4049
|
return v === true;
|
|
3808
4050
|
}
|
|
@@ -3844,24 +4086,10 @@ function normalizeLogicalValue2(operator, value) {
|
|
|
3844
4086
|
}
|
|
3845
4087
|
throw new Error(`${operator} must be an object or array of objects in HAVING`);
|
|
3846
4088
|
}
|
|
3847
|
-
function
|
|
3848
|
-
|
|
3849
|
-
const field = m.get(fieldName);
|
|
3850
|
-
if (!field) {
|
|
4089
|
+
function assertHavingOp(op) {
|
|
4090
|
+
if (!HAVING_ALLOWED_OPS.has(op)) {
|
|
3851
4091
|
throw new Error(
|
|
3852
|
-
|
|
3853
|
-
);
|
|
3854
|
-
}
|
|
3855
|
-
if (field.isRelation) {
|
|
3856
|
-
throw new Error(`${ctx} does not support relation field '${fieldName}'`);
|
|
3857
|
-
}
|
|
3858
|
-
return { name: field.name, type: field.type };
|
|
3859
|
-
}
|
|
3860
|
-
function assertAggregateFieldType(aggKey, fieldType, fieldName, modelName) {
|
|
3861
|
-
const baseType = fieldType.replace(/\[\]|\?/g, "");
|
|
3862
|
-
if ((aggKey === "_sum" || aggKey === "_avg") && !NUMERIC_TYPES.has(baseType)) {
|
|
3863
|
-
throw new Error(
|
|
3864
|
-
`Cannot use ${aggKey} on non-numeric field '${fieldName}' (type: ${fieldType}) on model ${modelName}`
|
|
4092
|
+
`Unsupported HAVING operator '${op}'. Allowed: ${[...HAVING_ALLOWED_OPS].join(", ")}`
|
|
3865
4093
|
);
|
|
3866
4094
|
}
|
|
3867
4095
|
}
|
|
@@ -3891,6 +4119,7 @@ function buildBinaryComparison(expr, op, val, params) {
|
|
|
3891
4119
|
return `${expr} ${sqlOp} ${placeholder}`;
|
|
3892
4120
|
}
|
|
3893
4121
|
function buildSimpleComparison(expr, op, val, params, dialect) {
|
|
4122
|
+
assertHavingOp(op);
|
|
3894
4123
|
if (val === null) return buildNullComparison(expr, op);
|
|
3895
4124
|
if (op === Ops.NOT && isPlainObject(val)) {
|
|
3896
4125
|
return buildNotComposite(
|
|
@@ -3979,20 +4208,19 @@ function assertHavingAggTarget(aggKey, field, model) {
|
|
|
3979
4208
|
}
|
|
3980
4209
|
return;
|
|
3981
4210
|
}
|
|
3982
|
-
|
|
3983
|
-
|
|
4211
|
+
if (aggKey === "_sum" || aggKey === "_avg") {
|
|
4212
|
+
assertNumericField(model, field, "HAVING");
|
|
4213
|
+
} else {
|
|
4214
|
+
assertScalarField(model, field, "HAVING");
|
|
4215
|
+
}
|
|
3984
4216
|
}
|
|
3985
4217
|
function buildHavingOpsForExpr(expr, filter, params, dialect) {
|
|
3986
|
-
|
|
3987
|
-
for (const [op, val] of Object.entries(filter)) {
|
|
3988
|
-
if (op === "mode") continue;
|
|
3989
|
-
const built = buildSimpleComparison(expr, op, val, params, dialect);
|
|
3990
|
-
if (built && built.trim().length > 0) out.push(built);
|
|
3991
|
-
}
|
|
3992
|
-
return out;
|
|
4218
|
+
return buildComparisons(expr, filter, params, dialect, buildSimpleComparison);
|
|
3993
4219
|
}
|
|
3994
4220
|
function buildHavingForAggregateFirstShape(aggKey, target, alias, params, dialect, model) {
|
|
3995
|
-
if (!isPlainObject(target))
|
|
4221
|
+
if (!isPlainObject(target)) {
|
|
4222
|
+
throw new Error(`HAVING '${aggKey}' must be an object`);
|
|
4223
|
+
}
|
|
3996
4224
|
const out = [];
|
|
3997
4225
|
for (const [field, filter] of Object.entries(target)) {
|
|
3998
4226
|
assertHavingAggTarget(aggKey, field, model);
|
|
@@ -4003,30 +4231,39 @@ function buildHavingForAggregateFirstShape(aggKey, target, alias, params, dialec
|
|
|
4003
4231
|
return out;
|
|
4004
4232
|
}
|
|
4005
4233
|
function buildHavingForFieldFirstShape(fieldName, target, alias, params, dialect, model) {
|
|
4006
|
-
if (!isPlainObject(target))
|
|
4007
|
-
|
|
4234
|
+
if (!isPlainObject(target)) {
|
|
4235
|
+
throw new Error(`HAVING '${fieldName}' must be an object`);
|
|
4236
|
+
}
|
|
4237
|
+
assertScalarField(model, fieldName, "HAVING");
|
|
4008
4238
|
const out = [];
|
|
4009
4239
|
const obj = target;
|
|
4010
4240
|
const keys = ["_count", "_sum", "_avg", "_min", "_max"];
|
|
4011
4241
|
for (const aggKey of keys) {
|
|
4012
4242
|
const aggFilter = obj[aggKey];
|
|
4013
4243
|
if (!isPlainObject(aggFilter)) continue;
|
|
4014
|
-
|
|
4244
|
+
if (aggKey === "_sum" || aggKey === "_avg") {
|
|
4245
|
+
assertNumericField(model, fieldName, "HAVING");
|
|
4246
|
+
}
|
|
4015
4247
|
const entries = Object.entries(aggFilter);
|
|
4016
4248
|
if (entries.length === 0) continue;
|
|
4017
4249
|
const expr = aggExprForField(aggKey, fieldName, alias, model);
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4250
|
+
const clauses = buildComparisons(
|
|
4251
|
+
expr,
|
|
4252
|
+
aggFilter,
|
|
4253
|
+
params,
|
|
4254
|
+
dialect,
|
|
4255
|
+
buildSimpleComparison
|
|
4256
|
+
);
|
|
4257
|
+
out.push(...clauses);
|
|
4023
4258
|
}
|
|
4024
4259
|
return out;
|
|
4025
4260
|
}
|
|
4026
4261
|
function buildHavingClause(having, alias, params, model, dialect) {
|
|
4027
4262
|
if (!isNotNullish(having)) return "";
|
|
4028
4263
|
const d = dialect != null ? dialect : getGlobalDialect();
|
|
4029
|
-
if (!isPlainObject(having))
|
|
4264
|
+
if (!isPlainObject(having)) {
|
|
4265
|
+
throw new Error("having must be an object");
|
|
4266
|
+
}
|
|
4030
4267
|
return buildHavingNode(having, alias, params, d, model);
|
|
4031
4268
|
}
|
|
4032
4269
|
function normalizeCountArg(v) {
|
|
@@ -4040,18 +4277,8 @@ function pushCountAllField(fields) {
|
|
|
4040
4277
|
`${SQL_TEMPLATES.COUNT_ALL} ${SQL_TEMPLATES.AS} ${quote("_count._all")}`
|
|
4041
4278
|
);
|
|
4042
4279
|
}
|
|
4043
|
-
function assertCountableScalarField(
|
|
4044
|
-
|
|
4045
|
-
if (!field) {
|
|
4046
|
-
throw new Error(
|
|
4047
|
-
`Field '${fieldName}' does not exist on model ${model.name}`
|
|
4048
|
-
);
|
|
4049
|
-
}
|
|
4050
|
-
if (field.isRelation) {
|
|
4051
|
-
throw new Error(
|
|
4052
|
-
`Cannot use _count on relation field '${fieldName}' on model ${model.name}`
|
|
4053
|
-
);
|
|
4054
|
-
}
|
|
4280
|
+
function assertCountableScalarField(model, fieldName) {
|
|
4281
|
+
assertScalarField(model, fieldName, "_count");
|
|
4055
4282
|
}
|
|
4056
4283
|
function pushCountField(fields, alias, fieldName, model) {
|
|
4057
4284
|
const outAlias = `_count.${fieldName}`;
|
|
@@ -4059,7 +4286,7 @@ function pushCountField(fields, alias, fieldName, model) {
|
|
|
4059
4286
|
`COUNT(${col(alias, fieldName, model)}) ${SQL_TEMPLATES.AS} ${quote(outAlias)}`
|
|
4060
4287
|
);
|
|
4061
4288
|
}
|
|
4062
|
-
function addCountFields(fields, countArg, alias, model
|
|
4289
|
+
function addCountFields(fields, countArg, alias, model) {
|
|
4063
4290
|
if (!isNotNullish(countArg)) return;
|
|
4064
4291
|
if (countArg === true) {
|
|
4065
4292
|
pushCountAllField(fields);
|
|
@@ -4073,7 +4300,7 @@ function addCountFields(fields, countArg, alias, model, fieldMap) {
|
|
|
4073
4300
|
([f, v]) => f !== "_all" && isTruthySelection(v)
|
|
4074
4301
|
);
|
|
4075
4302
|
for (const [f] of selected) {
|
|
4076
|
-
assertCountableScalarField(
|
|
4303
|
+
assertCountableScalarField(model, f);
|
|
4077
4304
|
pushCountField(fields, alias, f, model);
|
|
4078
4305
|
}
|
|
4079
4306
|
}
|
|
@@ -4081,19 +4308,12 @@ function getAggregateSelectionObject(args, agg) {
|
|
|
4081
4308
|
const obj = args[agg];
|
|
4082
4309
|
return isPlainObject(obj) ? obj : void 0;
|
|
4083
4310
|
}
|
|
4084
|
-
function assertAggregatableScalarField(
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
);
|
|
4090
|
-
}
|
|
4091
|
-
if (field.isRelation) {
|
|
4092
|
-
throw new Error(
|
|
4093
|
-
`Cannot use ${agg} on relation field '${fieldName}' on model ${model.name}`
|
|
4094
|
-
);
|
|
4311
|
+
function assertAggregatableScalarField(model, agg, fieldName) {
|
|
4312
|
+
if (agg === "_sum" || agg === "_avg") {
|
|
4313
|
+
assertNumericField(model, fieldName, agg);
|
|
4314
|
+
} else {
|
|
4315
|
+
assertScalarField(model, fieldName, agg);
|
|
4095
4316
|
}
|
|
4096
|
-
return field;
|
|
4097
4317
|
}
|
|
4098
4318
|
function pushAggregateFieldSql(fields, aggFn, alias, agg, fieldName, model) {
|
|
4099
4319
|
const outAlias = `${agg}.${fieldName}`;
|
|
@@ -4101,7 +4321,7 @@ function pushAggregateFieldSql(fields, aggFn, alias, agg, fieldName, model) {
|
|
|
4101
4321
|
`${aggFn}(${col(alias, fieldName, model)}) ${SQL_TEMPLATES.AS} ${quote(outAlias)}`
|
|
4102
4322
|
);
|
|
4103
4323
|
}
|
|
4104
|
-
function addAggregateFields(fields, args, alias, model
|
|
4324
|
+
function addAggregateFields(fields, args, alias, model) {
|
|
4105
4325
|
for (const [agg, aggFn] of AGGREGATES) {
|
|
4106
4326
|
const obj = getAggregateSelectionObject(args, agg);
|
|
4107
4327
|
if (!obj) continue;
|
|
@@ -4109,23 +4329,16 @@ function addAggregateFields(fields, args, alias, model, fieldMap) {
|
|
|
4109
4329
|
if (fieldName === "_all")
|
|
4110
4330
|
throw new Error(`'${agg}' does not support '_all'`);
|
|
4111
4331
|
if (!isTruthySelection(selection)) continue;
|
|
4112
|
-
|
|
4113
|
-
fieldMap,
|
|
4114
|
-
model,
|
|
4115
|
-
agg,
|
|
4116
|
-
fieldName
|
|
4117
|
-
);
|
|
4118
|
-
assertAggregateFieldType(agg, field.type, fieldName, model.name);
|
|
4332
|
+
assertAggregatableScalarField(model, agg, fieldName);
|
|
4119
4333
|
pushAggregateFieldSql(fields, aggFn, alias, agg, fieldName, model);
|
|
4120
4334
|
}
|
|
4121
4335
|
}
|
|
4122
4336
|
}
|
|
4123
4337
|
function buildAggregateFields(args, alias, model) {
|
|
4124
4338
|
const fields = [];
|
|
4125
|
-
const fieldMap = getModelFieldMap(model);
|
|
4126
4339
|
const countArg = normalizeCountArg(args._count);
|
|
4127
|
-
addCountFields(fields, countArg, alias, model
|
|
4128
|
-
addAggregateFields(fields, args, alias, model
|
|
4340
|
+
addCountFields(fields, countArg, alias, model);
|
|
4341
|
+
addAggregateFields(fields, args, alias, model);
|
|
4129
4342
|
return fields;
|
|
4130
4343
|
}
|
|
4131
4344
|
function buildAggregateSql(args, whereResult, tableName, alias, model) {
|
|
@@ -4149,7 +4362,7 @@ function buildAggregateSql(args, whereResult, tableName, alias, model) {
|
|
|
4149
4362
|
validateParamConsistency(sql, whereResult.params);
|
|
4150
4363
|
return Object.freeze({
|
|
4151
4364
|
sql,
|
|
4152
|
-
params: Object.freeze(
|
|
4365
|
+
params: Object.freeze([...whereResult.params]),
|
|
4153
4366
|
paramMappings: Object.freeze([...whereResult.paramMappings])
|
|
4154
4367
|
});
|
|
4155
4368
|
}
|
|
@@ -4162,32 +4375,24 @@ function assertGroupByBy(args, model) {
|
|
|
4162
4375
|
if (bySet.size !== byFields.length) {
|
|
4163
4376
|
throw new Error("buildGroupBySql: by must not contain duplicates");
|
|
4164
4377
|
}
|
|
4165
|
-
const modelFieldMap = getModelFieldMap(model);
|
|
4166
4378
|
for (const f of byFields) {
|
|
4167
|
-
|
|
4168
|
-
if (!field) {
|
|
4169
|
-
throw new Error(
|
|
4170
|
-
`groupBy.by references unknown field '${f}' on model ${model.name}`
|
|
4171
|
-
);
|
|
4172
|
-
}
|
|
4173
|
-
if (field.isRelation) {
|
|
4174
|
-
throw new Error(
|
|
4175
|
-
`groupBy.by does not support relation field '${f}' on model ${model.name}`
|
|
4176
|
-
);
|
|
4177
|
-
}
|
|
4379
|
+
assertScalarField(model, f, "groupBy.by");
|
|
4178
4380
|
}
|
|
4179
4381
|
return byFields;
|
|
4180
4382
|
}
|
|
4181
4383
|
function buildGroupBySelectParts(args, alias, model, byFields) {
|
|
4182
4384
|
const groupCols = byFields.map((f) => col(alias, f, model));
|
|
4385
|
+
const selectCols = byFields.map((f) => colWithAlias(alias, f, model));
|
|
4183
4386
|
const groupFields = groupCols.join(SQL_SEPARATORS.FIELD_LIST);
|
|
4184
4387
|
const aggFields = buildAggregateFields(args, alias, model);
|
|
4185
|
-
const selectFields = isNonEmptyArray(aggFields) ?
|
|
4388
|
+
const selectFields = isNonEmptyArray(aggFields) ? selectCols.concat(aggFields).join(SQL_SEPARATORS.FIELD_LIST) : selectCols.join(SQL_SEPARATORS.FIELD_LIST);
|
|
4186
4389
|
return { groupCols, groupFields, selectFields };
|
|
4187
4390
|
}
|
|
4188
4391
|
function buildGroupByHaving(args, alias, params, model, dialect) {
|
|
4189
4392
|
if (!isNotNullish(args.having)) return "";
|
|
4190
|
-
if (!isPlainObject(args.having))
|
|
4393
|
+
if (!isPlainObject(args.having)) {
|
|
4394
|
+
throw new Error("having must be an object");
|
|
4395
|
+
}
|
|
4191
4396
|
const h = buildHavingClause(args.having, alias, params, model, dialect);
|
|
4192
4397
|
if (!h || h.trim().length === 0) return "";
|
|
4193
4398
|
return `${SQL_TEMPLATES.HAVING} ${h}`;
|
|
@@ -4223,61 +4428,58 @@ function buildGroupBySql(args, whereResult, tableName, alias, model, dialect) {
|
|
|
4223
4428
|
const mergedParams = [...whereResult.params, ...snapshot.params];
|
|
4224
4429
|
return Object.freeze({
|
|
4225
4430
|
sql,
|
|
4226
|
-
params: Object.freeze(
|
|
4431
|
+
params: Object.freeze(mergedParams),
|
|
4227
4432
|
paramMappings: Object.freeze([
|
|
4228
4433
|
...whereResult.paramMappings,
|
|
4229
4434
|
...snapshot.mappings
|
|
4230
4435
|
])
|
|
4231
4436
|
});
|
|
4232
4437
|
}
|
|
4233
|
-
function buildCountSql(whereResult, tableName, alias, skip,
|
|
4438
|
+
function buildCountSql(whereResult, tableName, alias, skip, _dialect) {
|
|
4234
4439
|
assertSafeAlias(alias);
|
|
4235
4440
|
assertSafeTableRef(tableName);
|
|
4236
|
-
|
|
4441
|
+
if (skip !== void 0 && skip !== null) {
|
|
4442
|
+
if (isDynamicParameter(skip)) {
|
|
4443
|
+
throw new Error(
|
|
4444
|
+
"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."
|
|
4445
|
+
);
|
|
4446
|
+
}
|
|
4447
|
+
if (typeof skip === "string") {
|
|
4448
|
+
const s = skip.trim();
|
|
4449
|
+
if (s.length > 0) {
|
|
4450
|
+
const n = Number(s);
|
|
4451
|
+
if (Number.isFinite(n) && Number.isInteger(n) && n > 0) {
|
|
4452
|
+
throw new Error(
|
|
4453
|
+
"count() with skip is not supported because it produces nondeterministic results. Use findMany().length or add explicit orderBy to ensure deterministic behavior."
|
|
4454
|
+
);
|
|
4455
|
+
}
|
|
4456
|
+
}
|
|
4457
|
+
}
|
|
4458
|
+
if (typeof skip === "number" && Number.isFinite(skip) && Number.isInteger(skip) && skip > 0) {
|
|
4459
|
+
throw new Error(
|
|
4460
|
+
"count() with skip is not supported because it produces nondeterministic results. Use findMany().length or add explicit orderBy to ensure deterministic behavior."
|
|
4461
|
+
);
|
|
4462
|
+
}
|
|
4463
|
+
}
|
|
4237
4464
|
const whereClause = isValidWhereClause(whereResult.clause) ? `${SQL_TEMPLATES.WHERE} ${whereResult.clause}` : "";
|
|
4238
|
-
const params = createParamStore(whereResult.nextParamIndex);
|
|
4239
|
-
const baseSubSelect = [
|
|
4240
|
-
SQL_TEMPLATES.SELECT,
|
|
4241
|
-
"1",
|
|
4242
|
-
SQL_TEMPLATES.FROM,
|
|
4243
|
-
tableName,
|
|
4244
|
-
alias,
|
|
4245
|
-
whereClause
|
|
4246
|
-
].filter((x) => x && String(x).trim().length > 0).join(" ").trim();
|
|
4247
|
-
const normalizedSkip = normalizeSkipLike(skip);
|
|
4248
|
-
const subSelect = applyCountSkip(baseSubSelect, normalizedSkip, params, d);
|
|
4249
4465
|
const sql = [
|
|
4250
4466
|
SQL_TEMPLATES.SELECT,
|
|
4251
4467
|
SQL_TEMPLATES.COUNT_ALL,
|
|
4252
4468
|
SQL_TEMPLATES.AS,
|
|
4253
4469
|
quote("_count._all"),
|
|
4254
4470
|
SQL_TEMPLATES.FROM,
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4471
|
+
tableName,
|
|
4472
|
+
alias,
|
|
4473
|
+
whereClause
|
|
4258
4474
|
].filter((x) => x && String(x).trim().length > 0).join(" ").trim();
|
|
4259
4475
|
validateSelectQuery(sql);
|
|
4260
|
-
|
|
4261
|
-
const mergedParams = [...whereResult.params, ...snapshot.params];
|
|
4262
|
-
validateParamConsistency(sql, mergedParams);
|
|
4476
|
+
validateParamConsistency(sql, whereResult.params);
|
|
4263
4477
|
return Object.freeze({
|
|
4264
4478
|
sql,
|
|
4265
|
-
params: Object.freeze(
|
|
4266
|
-
paramMappings: Object.freeze([
|
|
4267
|
-
...whereResult.paramMappings,
|
|
4268
|
-
...snapshot.mappings
|
|
4269
|
-
])
|
|
4479
|
+
params: Object.freeze([...whereResult.params]),
|
|
4480
|
+
paramMappings: Object.freeze([...whereResult.paramMappings])
|
|
4270
4481
|
});
|
|
4271
4482
|
}
|
|
4272
|
-
function applyCountSkip(subSelect, normalizedSkip, params, dialect) {
|
|
4273
|
-
const shouldApply = isDynamicParameter(normalizedSkip) || typeof normalizedSkip === "number" && normalizedSkip > 0;
|
|
4274
|
-
if (!shouldApply) return subSelect;
|
|
4275
|
-
const placeholder = addAutoScoped(params, normalizedSkip, "count.skip");
|
|
4276
|
-
if (dialect === "sqlite") {
|
|
4277
|
-
return `${subSelect} ${SQL_TEMPLATES.LIMIT} -1 ${SQL_TEMPLATES.OFFSET} ${placeholder}`;
|
|
4278
|
-
}
|
|
4279
|
-
return `${subSelect} ${SQL_TEMPLATES.OFFSET} ${placeholder}`;
|
|
4280
|
-
}
|
|
4281
4483
|
function safeAlias(input) {
|
|
4282
4484
|
const raw = String(input).toLowerCase();
|
|
4283
4485
|
const cleaned = raw.replace(/[^a-z0-9_]/g, "_");
|
|
@@ -4427,8 +4629,12 @@ function buildAndNormalizeSql(args) {
|
|
|
4427
4629
|
);
|
|
4428
4630
|
}
|
|
4429
4631
|
function finalizeDirective(args) {
|
|
4430
|
-
const { directive, normalizedSql, normalizedMappings } = args;
|
|
4431
|
-
|
|
4632
|
+
const { directive, normalizedSql, normalizedMappings, dialect } = args;
|
|
4633
|
+
const params = normalizedMappings.map((m) => {
|
|
4634
|
+
var _a;
|
|
4635
|
+
return (_a = m.value) != null ? _a : void 0;
|
|
4636
|
+
});
|
|
4637
|
+
validateParamConsistencyByDialect(normalizedSql, params, dialect);
|
|
4432
4638
|
const { staticParams, dynamicKeys } = buildParamsFromMappings(normalizedMappings);
|
|
4433
4639
|
return {
|
|
4434
4640
|
method: directive.method,
|
|
@@ -4465,7 +4671,8 @@ function generateSQL(directive) {
|
|
|
4465
4671
|
return finalizeDirective({
|
|
4466
4672
|
directive,
|
|
4467
4673
|
normalizedSql: normalized.sql,
|
|
4468
|
-
normalizedMappings: normalized.paramMappings
|
|
4674
|
+
normalizedMappings: normalized.paramMappings,
|
|
4675
|
+
dialect
|
|
4469
4676
|
});
|
|
4470
4677
|
}
|
|
4471
4678
|
|
|
@@ -4842,9 +5049,7 @@ function buildSQLFull(model, models, method, args, dialect) {
|
|
|
4842
5049
|
whereResult,
|
|
4843
5050
|
tableName,
|
|
4844
5051
|
alias,
|
|
4845
|
-
args.skip
|
|
4846
|
-
dialect
|
|
4847
|
-
);
|
|
5052
|
+
args.skip);
|
|
4848
5053
|
break;
|
|
4849
5054
|
default:
|
|
4850
5055
|
result = buildSelectSql({
|