prisma-sql 1.45.0 → 1.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/generator.cjs +248 -364
- package/dist/generator.cjs.map +1 -1
- package/dist/generator.js +248 -364
- package/dist/generator.js.map +1 -1
- package/dist/index.cjs +225 -349
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +225 -349
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/generator.cjs
CHANGED
|
@@ -56,7 +56,7 @@ var require_package = __commonJS({
|
|
|
56
56
|
"package.json"(exports$1, module) {
|
|
57
57
|
module.exports = {
|
|
58
58
|
name: "prisma-sql",
|
|
59
|
-
version: "1.
|
|
59
|
+
version: "1.46.0",
|
|
60
60
|
description: "Convert Prisma queries to optimized SQL with type safety. 2-7x faster than Prisma Client.",
|
|
61
61
|
main: "dist/index.cjs",
|
|
62
62
|
module: "dist/index.js",
|
|
@@ -644,20 +644,21 @@ function isEmptyWhere(where) {
|
|
|
644
644
|
if (!isNotNullish(where)) return true;
|
|
645
645
|
return Object.keys(where).length === 0;
|
|
646
646
|
}
|
|
647
|
+
function sqlPreview(sql) {
|
|
648
|
+
const s = String(sql);
|
|
649
|
+
if (s.length <= 160) return s;
|
|
650
|
+
return `${s.slice(0, 160)}...`;
|
|
651
|
+
}
|
|
647
652
|
function validateSelectQuery(sql) {
|
|
648
653
|
if (!hasValidContent(sql)) {
|
|
649
654
|
throw new Error("CRITICAL: Generated empty SQL query");
|
|
650
655
|
}
|
|
651
656
|
if (!hasRequiredKeywords(sql)) {
|
|
652
|
-
throw new Error(
|
|
653
|
-
`CRITICAL: Invalid SQL structure. SQL: ${sql.substring(0, 100)}...`
|
|
654
|
-
);
|
|
657
|
+
throw new Error(`CRITICAL: Invalid SQL structure. SQL: ${sqlPreview(sql)}`);
|
|
655
658
|
}
|
|
656
659
|
}
|
|
657
|
-
function
|
|
658
|
-
|
|
659
|
-
}
|
|
660
|
-
function parseDollarNumber(sql, start, n) {
|
|
660
|
+
function parseDollarNumber(sql, start) {
|
|
661
|
+
const n = sql.length;
|
|
661
662
|
let i = start;
|
|
662
663
|
let num = 0;
|
|
663
664
|
let hasDigit = false;
|
|
@@ -668,14 +669,14 @@ function parseDollarNumber(sql, start, n) {
|
|
|
668
669
|
num = num * 10 + (c - 48);
|
|
669
670
|
i++;
|
|
670
671
|
}
|
|
671
|
-
if (!hasDigit || num <= 0) return { next: i, num: 0
|
|
672
|
-
return { next: i, num
|
|
672
|
+
if (!hasDigit || num <= 0) return { next: i, num: 0 };
|
|
673
|
+
return { next: i, num };
|
|
673
674
|
}
|
|
674
675
|
function scanDollarPlaceholders(sql, markUpTo) {
|
|
675
676
|
const seen = new Uint8Array(markUpTo + 1);
|
|
676
|
-
let count = 0;
|
|
677
677
|
let min = Number.POSITIVE_INFINITY;
|
|
678
678
|
let max = 0;
|
|
679
|
+
let sawAny = false;
|
|
679
680
|
const n = sql.length;
|
|
680
681
|
let i = 0;
|
|
681
682
|
while (i < n) {
|
|
@@ -683,15 +684,19 @@ function scanDollarPlaceholders(sql, markUpTo) {
|
|
|
683
684
|
i++;
|
|
684
685
|
continue;
|
|
685
686
|
}
|
|
686
|
-
const
|
|
687
|
-
i = next;
|
|
688
|
-
|
|
689
|
-
|
|
687
|
+
const parsed = parseDollarNumber(sql, i + 1);
|
|
688
|
+
i = parsed.next;
|
|
689
|
+
const num = parsed.num;
|
|
690
|
+
if (num === 0) continue;
|
|
691
|
+
sawAny = true;
|
|
690
692
|
if (num < min) min = num;
|
|
691
693
|
if (num > max) max = num;
|
|
692
694
|
if (num <= markUpTo) seen[num] = 1;
|
|
693
695
|
}
|
|
694
|
-
|
|
696
|
+
if (!sawAny) {
|
|
697
|
+
return { min: 0, max: 0, seen, sawAny: false };
|
|
698
|
+
}
|
|
699
|
+
return { min, max, seen, sawAny: true };
|
|
695
700
|
}
|
|
696
701
|
function assertNoGapsDollar(scan, rangeMin, rangeMax, sql) {
|
|
697
702
|
for (let k = rangeMin; k <= rangeMax; k++) {
|
|
@@ -704,124 +709,70 @@ function assertNoGapsDollar(scan, rangeMin, rangeMax, sql) {
|
|
|
704
709
|
}
|
|
705
710
|
function validateParamConsistency(sql, params) {
|
|
706
711
|
const paramLen = params.length;
|
|
707
|
-
if (paramLen === 0) {
|
|
708
|
-
if (sql.indexOf("$") === -1) return;
|
|
709
|
-
}
|
|
710
712
|
const scan = scanDollarPlaceholders(sql, paramLen);
|
|
711
|
-
if (
|
|
712
|
-
if (
|
|
713
|
+
if (paramLen === 0) {
|
|
714
|
+
if (scan.sawAny) {
|
|
713
715
|
throw new Error(
|
|
714
|
-
`CRITICAL:
|
|
716
|
+
`CRITICAL: SQL contains placeholders but params is empty. SQL: ${sqlPreview(sql)}`
|
|
715
717
|
);
|
|
716
718
|
}
|
|
717
719
|
return;
|
|
718
720
|
}
|
|
719
|
-
if (scan.
|
|
721
|
+
if (!scan.sawAny) {
|
|
720
722
|
throw new Error(
|
|
721
|
-
`CRITICAL:
|
|
723
|
+
`CRITICAL: SQL is missing placeholders ($1..$${paramLen}) but params has length ${paramLen}. SQL: ${sqlPreview(sql)}`
|
|
722
724
|
);
|
|
723
725
|
}
|
|
724
|
-
|
|
725
|
-
}
|
|
726
|
-
function needsQuoting(id) {
|
|
727
|
-
if (!isNonEmptyString(id)) return true;
|
|
728
|
-
const isKeyword = SQL_KEYWORDS.has(id.toLowerCase());
|
|
729
|
-
if (isKeyword) return true;
|
|
730
|
-
const isValidIdentifier = REGEX_CACHE.VALID_IDENTIFIER.test(id);
|
|
731
|
-
return !isValidIdentifier;
|
|
732
|
-
}
|
|
733
|
-
function validateParamConsistencyFragment(sql, params) {
|
|
734
|
-
const paramLen = params.length;
|
|
735
|
-
const scan = scanDollarPlaceholders(sql, paramLen);
|
|
736
|
-
if (scan.max === 0) return;
|
|
737
|
-
if (scan.max > paramLen) {
|
|
726
|
+
if (scan.min !== 1) {
|
|
738
727
|
throw new Error(
|
|
739
|
-
`CRITICAL:
|
|
728
|
+
`CRITICAL: Placeholder range must start at $1, got min=$${scan.min}. SQL: ${sqlPreview(sql)}`
|
|
740
729
|
);
|
|
741
730
|
}
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
}
|
|
747
|
-
function parseSqlitePlaceholderIndices(sql) {
|
|
748
|
-
const re = /\?(?:(\d+))?/g;
|
|
749
|
-
const indices = [];
|
|
750
|
-
let anonCount = 0;
|
|
751
|
-
let sawNumbered = false;
|
|
752
|
-
let sawAnonymous = false;
|
|
753
|
-
for (const m of sql.matchAll(re)) {
|
|
754
|
-
const n = m[1];
|
|
755
|
-
if (n) {
|
|
756
|
-
sawNumbered = true;
|
|
757
|
-
indices.push(parseInt(n, 10));
|
|
758
|
-
} else {
|
|
759
|
-
sawAnonymous = true;
|
|
760
|
-
anonCount += 1;
|
|
761
|
-
indices.push(anonCount);
|
|
762
|
-
}
|
|
731
|
+
if (scan.max !== paramLen) {
|
|
732
|
+
throw new Error(
|
|
733
|
+
`CRITICAL: Placeholder max must match params length. max=$${scan.max}, params=${paramLen}. SQL: ${sqlPreview(sql)}`
|
|
734
|
+
);
|
|
763
735
|
}
|
|
764
|
-
|
|
736
|
+
assertNoGapsDollar(scan, 1, paramLen, sql);
|
|
765
737
|
}
|
|
766
|
-
function
|
|
767
|
-
|
|
738
|
+
function countQuestionMarkPlaceholders(sql) {
|
|
739
|
+
const s = String(sql);
|
|
740
|
+
let count = 0;
|
|
741
|
+
for (let i = 0; i < s.length; i++) {
|
|
742
|
+
if (s.charCodeAt(i) === 63) count++;
|
|
743
|
+
}
|
|
744
|
+
return count;
|
|
768
745
|
}
|
|
769
|
-
function
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
746
|
+
function validateQuestionMarkConsistency(sql, params) {
|
|
747
|
+
const expected = params.length;
|
|
748
|
+
const found = countQuestionMarkPlaceholders(sql);
|
|
749
|
+
if (expected !== found) {
|
|
750
|
+
throw new Error(
|
|
751
|
+
`CRITICAL: Parameter mismatch - expected ${expected} '?' placeholders, found ${found}. SQL: ${sqlPreview(sql)}`
|
|
774
752
|
);
|
|
775
753
|
}
|
|
776
754
|
}
|
|
777
|
-
function
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
if (indices.length === 0) {
|
|
781
|
-
if (paramLen !== 0) {
|
|
782
|
-
throw new Error(
|
|
783
|
-
`CRITICAL: Parameter mismatch - SQL has no sqlite placeholders but ${paramLen} params provided. SQL: ${sqlPreview(sql)}`
|
|
784
|
-
);
|
|
785
|
-
}
|
|
755
|
+
function validateParamConsistencyByDialect(sql, params, dialect) {
|
|
756
|
+
if (dialect === "postgres") {
|
|
757
|
+
validateParamConsistency(sql, params);
|
|
786
758
|
return;
|
|
787
759
|
}
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
);
|
|
792
|
-
const max = maxIndex(indices);
|
|
793
|
-
assertOrThrow(
|
|
794
|
-
max === paramLen,
|
|
795
|
-
`CRITICAL: SQL placeholder max mismatch - max is ?${max}, but params length is ${paramLen}. SQL: ${sqlPreview(sql)}`
|
|
796
|
-
);
|
|
797
|
-
const set = new Set(indices);
|
|
798
|
-
ensureSequentialIndices(set, max, "?");
|
|
799
|
-
}
|
|
800
|
-
function validateDollarPlaceholders(sql, params) {
|
|
801
|
-
validateParamConsistency(sql, params);
|
|
802
|
-
}
|
|
803
|
-
function detectPlaceholderStyle(sql) {
|
|
804
|
-
const hasDollar = /\$\d+/.test(sql);
|
|
805
|
-
const hasSqliteQ = /\?(?:\d+)?/.test(sql);
|
|
806
|
-
return { hasDollar, hasSqliteQ };
|
|
807
|
-
}
|
|
808
|
-
function validateParamConsistencyByDialect(sql, params, dialect) {
|
|
809
|
-
const { hasDollar, hasSqliteQ } = detectPlaceholderStyle(sql);
|
|
810
|
-
if (dialect !== "sqlite") {
|
|
811
|
-
if (hasSqliteQ && !hasDollar) {
|
|
812
|
-
throw new Error(
|
|
813
|
-
`CRITICAL: Non-sqlite dialect query contains sqlite '?' placeholders. SQL: ${sqlPreview(sql)}`
|
|
814
|
-
);
|
|
815
|
-
}
|
|
816
|
-
return validateDollarPlaceholders(sql, params);
|
|
760
|
+
if (dialect === "sqlite") {
|
|
761
|
+
validateQuestionMarkConsistency(sql, params);
|
|
762
|
+
return;
|
|
817
763
|
}
|
|
818
|
-
if (
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
);
|
|
764
|
+
if (dialect === "mysql" || dialect === "mariadb") {
|
|
765
|
+
validateQuestionMarkConsistency(sql, params);
|
|
766
|
+
return;
|
|
822
767
|
}
|
|
823
|
-
|
|
824
|
-
|
|
768
|
+
validateParamConsistency(sql, params);
|
|
769
|
+
}
|
|
770
|
+
function needsQuoting(identifier) {
|
|
771
|
+
const s = String(identifier);
|
|
772
|
+
if (!REGEX_CACHE.VALID_IDENTIFIER.test(s)) return true;
|
|
773
|
+
const lowered = s.toLowerCase();
|
|
774
|
+
if (SQL_KEYWORDS.has(lowered)) return true;
|
|
775
|
+
return false;
|
|
825
776
|
}
|
|
826
777
|
|
|
827
778
|
// src/builder/shared/sql-utils.ts
|
|
@@ -1320,6 +1271,40 @@ function ensureDeterministicOrderByInput(args) {
|
|
|
1320
1271
|
return addIdTiebreaker(orderBy);
|
|
1321
1272
|
}
|
|
1322
1273
|
|
|
1274
|
+
// src/builder/shared/validators/field-assertions.ts
|
|
1275
|
+
var NUMERIC_TYPES = /* @__PURE__ */ new Set(["Int", "Float", "Decimal", "BigInt"]);
|
|
1276
|
+
function assertScalarField(model, fieldName, context) {
|
|
1277
|
+
const field = getFieldInfo(model, fieldName);
|
|
1278
|
+
if (!field) {
|
|
1279
|
+
throw createError(
|
|
1280
|
+
`${context} references unknown field '${fieldName}' on model ${model.name}`,
|
|
1281
|
+
{
|
|
1282
|
+
field: fieldName,
|
|
1283
|
+
modelName: model.name,
|
|
1284
|
+
availableFields: model.fields.map((f) => f.name)
|
|
1285
|
+
}
|
|
1286
|
+
);
|
|
1287
|
+
}
|
|
1288
|
+
if (field.isRelation) {
|
|
1289
|
+
throw createError(
|
|
1290
|
+
`${context} does not support relation field '${fieldName}'`,
|
|
1291
|
+
{ field: fieldName, modelName: model.name }
|
|
1292
|
+
);
|
|
1293
|
+
}
|
|
1294
|
+
return field;
|
|
1295
|
+
}
|
|
1296
|
+
function assertNumericField(model, fieldName, context) {
|
|
1297
|
+
const field = assertScalarField(model, fieldName, context);
|
|
1298
|
+
const baseType = field.type.replace(/\[\]|\?/g, "");
|
|
1299
|
+
if (!NUMERIC_TYPES.has(baseType)) {
|
|
1300
|
+
throw createError(
|
|
1301
|
+
`${context} requires numeric field, got '${field.type}'`,
|
|
1302
|
+
{ field: fieldName, modelName: model.name }
|
|
1303
|
+
);
|
|
1304
|
+
}
|
|
1305
|
+
return field;
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1323
1308
|
// src/builder/pagination.ts
|
|
1324
1309
|
var MAX_LIMIT_OFFSET = 2147483647;
|
|
1325
1310
|
function parseDirectionRaw(raw, errorLabel) {
|
|
@@ -1360,38 +1345,31 @@ function parseOrderByValue(v, fieldName) {
|
|
|
1360
1345
|
assertAllowedOrderByKeys(obj, fieldName);
|
|
1361
1346
|
return { direction, nulls };
|
|
1362
1347
|
}
|
|
1363
|
-
function normalizeFiniteInteger(name, v) {
|
|
1364
|
-
if (typeof v !== "number" || !Number.isFinite(v) || !Number.isInteger(v)) {
|
|
1365
|
-
throw new Error(`${name} must be an integer`);
|
|
1366
|
-
}
|
|
1367
|
-
return v;
|
|
1368
|
-
}
|
|
1369
1348
|
function normalizeNonNegativeInt(name, v) {
|
|
1370
1349
|
if (schemaParser.isDynamicParameter(v)) return v;
|
|
1371
|
-
const
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
return
|
|
1379
|
-
}
|
|
1380
|
-
function hasNonNullishProp(v, key) {
|
|
1381
|
-
return isPlainObject(v) && key in v && isNotNullish(v[key]);
|
|
1350
|
+
const result = normalizeIntLike(name, v, {
|
|
1351
|
+
min: 0,
|
|
1352
|
+
max: MAX_LIMIT_OFFSET,
|
|
1353
|
+
allowZero: true
|
|
1354
|
+
});
|
|
1355
|
+
if (result === void 0)
|
|
1356
|
+
throw new Error(`${name} normalization returned undefined`);
|
|
1357
|
+
return result;
|
|
1382
1358
|
}
|
|
1383
|
-
function
|
|
1359
|
+
function normalizeIntAllowNegative(name, v) {
|
|
1384
1360
|
if (schemaParser.isDynamicParameter(v)) return v;
|
|
1385
1361
|
const result = normalizeIntLike(name, v, {
|
|
1386
1362
|
min: Number.MIN_SAFE_INTEGER,
|
|
1387
1363
|
max: MAX_LIMIT_OFFSET,
|
|
1388
1364
|
allowZero: true
|
|
1389
1365
|
});
|
|
1390
|
-
if (result === void 0)
|
|
1366
|
+
if (result === void 0)
|
|
1391
1367
|
throw new Error(`${name} normalization returned undefined`);
|
|
1392
|
-
}
|
|
1393
1368
|
return result;
|
|
1394
1369
|
}
|
|
1370
|
+
function hasNonNullishProp(v, key) {
|
|
1371
|
+
return isPlainObject(v) && key in v && isNotNullish(v[key]);
|
|
1372
|
+
}
|
|
1395
1373
|
function readSkipTake(relArgs) {
|
|
1396
1374
|
const hasSkip = hasNonNullishProp(relArgs, "skip");
|
|
1397
1375
|
const hasTake = hasNonNullishProp(relArgs, "take");
|
|
@@ -1405,7 +1383,7 @@ function readSkipTake(relArgs) {
|
|
|
1405
1383
|
}
|
|
1406
1384
|
const obj = relArgs;
|
|
1407
1385
|
const skipVal = hasSkip ? normalizeNonNegativeInt("skip", obj.skip) : void 0;
|
|
1408
|
-
const takeVal = hasTake ?
|
|
1386
|
+
const takeVal = hasTake ? normalizeIntAllowNegative("take", obj.take) : void 0;
|
|
1409
1387
|
return { hasSkip, hasTake, skipVal, takeVal };
|
|
1410
1388
|
}
|
|
1411
1389
|
function buildOrderByFragment(entries, alias, dialect, model) {
|
|
@@ -1431,9 +1409,7 @@ function buildOrderByFragment(entries, alias, dialect, model) {
|
|
|
1431
1409
|
return out.join(SQL_SEPARATORS.ORDER_BY);
|
|
1432
1410
|
}
|
|
1433
1411
|
function defaultNullsFor(dialect, direction) {
|
|
1434
|
-
if (dialect === "postgres")
|
|
1435
|
-
return direction === "asc" ? "last" : "first";
|
|
1436
|
-
}
|
|
1412
|
+
if (dialect === "postgres") return direction === "asc" ? "last" : "first";
|
|
1437
1413
|
return direction === "asc" ? "first" : "last";
|
|
1438
1414
|
}
|
|
1439
1415
|
function ensureCursorFieldsInOrder(orderEntries, cursorEntries) {
|
|
@@ -1450,9 +1426,8 @@ function ensureCursorFieldsInOrder(orderEntries, cursorEntries) {
|
|
|
1450
1426
|
}
|
|
1451
1427
|
function buildCursorFilterParts(cursor, cursorAlias, params, model) {
|
|
1452
1428
|
const entries = Object.entries(cursor);
|
|
1453
|
-
if (entries.length === 0)
|
|
1429
|
+
if (entries.length === 0)
|
|
1454
1430
|
throw new Error("cursor must have at least one field");
|
|
1455
|
-
}
|
|
1456
1431
|
const placeholdersByField = /* @__PURE__ */ new Map();
|
|
1457
1432
|
const parts = [];
|
|
1458
1433
|
for (const [field, value] of entries) {
|
|
@@ -1506,25 +1481,29 @@ function buildOrderEntries(orderBy) {
|
|
|
1506
1481
|
if (typeof value === "string") {
|
|
1507
1482
|
entries.push({ field, direction: value });
|
|
1508
1483
|
} else {
|
|
1509
|
-
entries.push({
|
|
1510
|
-
field,
|
|
1511
|
-
direction: value.direction,
|
|
1512
|
-
nulls: value.nulls
|
|
1513
|
-
});
|
|
1484
|
+
entries.push({ field, direction: value.direction, nulls: value.nulls });
|
|
1514
1485
|
}
|
|
1515
1486
|
}
|
|
1516
1487
|
}
|
|
1517
1488
|
return entries;
|
|
1518
1489
|
}
|
|
1519
1490
|
function buildCursorCteSelectList(cursorEntries, orderEntries, model) {
|
|
1520
|
-
const
|
|
1521
|
-
|
|
1522
|
-
for (const
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1491
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1492
|
+
const ordered = [];
|
|
1493
|
+
for (const [f] of cursorEntries) {
|
|
1494
|
+
if (!seen.has(f)) {
|
|
1495
|
+
seen.add(f);
|
|
1496
|
+
ordered.push(f);
|
|
1497
|
+
}
|
|
1526
1498
|
}
|
|
1527
|
-
|
|
1499
|
+
for (const e of orderEntries) {
|
|
1500
|
+
if (!seen.has(e.field)) {
|
|
1501
|
+
seen.add(e.field);
|
|
1502
|
+
ordered.push(e.field);
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
if (ordered.length === 0) throw new Error("cursor cte select list is empty");
|
|
1506
|
+
return ordered.map((f) => quoteColumn(model, f)).join(SQL_SEPARATORS.FIELD_LIST);
|
|
1528
1507
|
}
|
|
1529
1508
|
function truncateIdent(name, maxLen) {
|
|
1530
1509
|
const s = String(name);
|
|
@@ -1544,19 +1523,22 @@ function buildCursorNames(outerAlias) {
|
|
|
1544
1523
|
}
|
|
1545
1524
|
return { cteName, srcAlias };
|
|
1546
1525
|
}
|
|
1526
|
+
function assertCursorAndOrderFieldsScalar(model, cursor, orderEntries) {
|
|
1527
|
+
if (!model) return;
|
|
1528
|
+
for (const k of Object.keys(cursor)) assertScalarField(model, k, "cursor");
|
|
1529
|
+
for (const e of orderEntries) assertScalarField(model, e.field, "orderBy");
|
|
1530
|
+
}
|
|
1547
1531
|
function buildCursorCondition(cursor, orderBy, tableName, alias, params, dialect, model) {
|
|
1548
1532
|
var _a;
|
|
1549
1533
|
assertSafeTableRef(tableName);
|
|
1550
1534
|
assertSafeAlias(alias);
|
|
1551
1535
|
const d = dialect != null ? dialect : getGlobalDialect();
|
|
1552
1536
|
const cursorEntries = Object.entries(cursor);
|
|
1553
|
-
if (cursorEntries.length === 0)
|
|
1537
|
+
if (cursorEntries.length === 0)
|
|
1554
1538
|
throw new Error("cursor must have at least one field");
|
|
1555
|
-
}
|
|
1556
1539
|
const { cteName, srcAlias } = buildCursorNames(alias);
|
|
1557
1540
|
assertSafeAlias(cteName);
|
|
1558
1541
|
assertSafeAlias(srcAlias);
|
|
1559
|
-
const { whereSql: cursorWhereSql, placeholdersByField } = buildCursorFilterParts(cursor, srcAlias, params, model);
|
|
1560
1542
|
const deterministicOrderBy = ensureDeterministicOrderByInput({
|
|
1561
1543
|
orderBy,
|
|
1562
1544
|
model,
|
|
@@ -1571,6 +1553,8 @@ function buildCursorCondition(cursor, orderBy, tableName, alias, params, dialect
|
|
|
1571
1553
|
} else {
|
|
1572
1554
|
orderEntries = ensureCursorFieldsInOrder(orderEntries, cursorEntries);
|
|
1573
1555
|
}
|
|
1556
|
+
assertCursorAndOrderFieldsScalar(model, cursor, orderEntries);
|
|
1557
|
+
const { whereSql: cursorWhereSql, placeholdersByField } = buildCursorFilterParts(cursor, srcAlias, params, model);
|
|
1574
1558
|
const cursorOrderBy = orderEntries.map(
|
|
1575
1559
|
(e) => `${srcAlias}.${quoteColumn(model, e.field)} ${e.direction.toUpperCase()}`
|
|
1576
1560
|
).join(", ");
|
|
@@ -1593,9 +1577,7 @@ function buildCursorCondition(cursor, orderBy, tableName, alias, params, dialect
|
|
|
1593
1577
|
params,
|
|
1594
1578
|
model
|
|
1595
1579
|
);
|
|
1596
|
-
const getValueExpr = (field) => {
|
|
1597
|
-
return `(SELECT ${quoteColumn(model, field)} FROM ${cteName})`;
|
|
1598
|
-
};
|
|
1580
|
+
const getValueExpr = (field) => `(SELECT ${quoteColumn(model, field)} FROM ${cteName})`;
|
|
1599
1581
|
const orClauses = [];
|
|
1600
1582
|
for (let level = 0; level < orderEntries.length; level++) {
|
|
1601
1583
|
const andParts = [];
|
|
@@ -1639,9 +1621,7 @@ function normalizeTakeLike(v) {
|
|
|
1639
1621
|
max: MAX_LIMIT_OFFSET,
|
|
1640
1622
|
allowZero: true
|
|
1641
1623
|
});
|
|
1642
|
-
if (typeof n === "number")
|
|
1643
|
-
if (n === 0) return 0;
|
|
1644
|
-
}
|
|
1624
|
+
if (typeof n === "number" && n === 0) return 0;
|
|
1645
1625
|
return n;
|
|
1646
1626
|
}
|
|
1647
1627
|
function normalizeSkipLike(v) {
|
|
@@ -2820,22 +2800,6 @@ function buildWhereClause(where, options) {
|
|
|
2820
2800
|
};
|
|
2821
2801
|
const result = whereBuilderInstance.build(where, ctx);
|
|
2822
2802
|
const publicResult = toPublicResult(result.clause, result.joins, params);
|
|
2823
|
-
if (!options.isSubquery) {
|
|
2824
|
-
const nums = [...publicResult.clause.matchAll(/\$(\d+)/g)].map(
|
|
2825
|
-
(m) => parseInt(m[1], 10)
|
|
2826
|
-
);
|
|
2827
|
-
if (nums.length > 0) {
|
|
2828
|
-
const min = Math.min(...nums);
|
|
2829
|
-
if (min === 1) {
|
|
2830
|
-
validateParamConsistency(publicResult.clause, publicResult.params);
|
|
2831
|
-
} else {
|
|
2832
|
-
validateParamConsistencyFragment(
|
|
2833
|
-
publicResult.clause,
|
|
2834
|
-
publicResult.params
|
|
2835
|
-
);
|
|
2836
|
-
}
|
|
2837
|
-
}
|
|
2838
|
-
}
|
|
2839
2803
|
return publicResult;
|
|
2840
2804
|
}
|
|
2841
2805
|
|
|
@@ -3031,9 +2995,8 @@ function validateOrderByForModel(model, orderBy) {
|
|
|
3031
2995
|
throw new Error("orderBy array entries must have exactly one field");
|
|
3032
2996
|
}
|
|
3033
2997
|
const fieldName = String(entries[0][0]).trim();
|
|
3034
|
-
if (fieldName.length === 0)
|
|
2998
|
+
if (fieldName.length === 0)
|
|
3035
2999
|
throw new Error("orderBy field name cannot be empty");
|
|
3036
|
-
}
|
|
3037
3000
|
if (!scalarSet.has(fieldName)) {
|
|
3038
3001
|
throw new Error(
|
|
3039
3002
|
`orderBy references unknown or non-scalar field '${fieldName}' on model ${model.name}`
|
|
@@ -3092,33 +3055,22 @@ function extractRelationPaginationConfig(relArgs) {
|
|
|
3092
3055
|
function maybeReverseNegativeTake(takeVal, hasOrderBy, orderByInput) {
|
|
3093
3056
|
if (typeof takeVal !== "number") return { takeVal, orderByInput };
|
|
3094
3057
|
if (takeVal >= 0) return { takeVal, orderByInput };
|
|
3095
|
-
if (!hasOrderBy)
|
|
3058
|
+
if (!hasOrderBy)
|
|
3096
3059
|
throw new Error("Negative take requires orderBy for deterministic results");
|
|
3097
|
-
}
|
|
3098
3060
|
return {
|
|
3099
3061
|
takeVal: Math.abs(takeVal),
|
|
3100
3062
|
orderByInput: reverseOrderByInput(orderByInput)
|
|
3101
3063
|
};
|
|
3102
3064
|
}
|
|
3103
|
-
function
|
|
3065
|
+
function finalizeOrderByForInclude(args) {
|
|
3066
|
+
if (args.hasOrderBy && isNotNullish(args.orderByInput)) {
|
|
3067
|
+
validateOrderByForModel(args.relModel, args.orderByInput);
|
|
3068
|
+
}
|
|
3104
3069
|
if (!args.hasPagination) {
|
|
3105
|
-
if (args.hasOrderBy && isNotNullish(args.orderByInput)) {
|
|
3106
|
-
validateOrderByForModel(args.relModel, args.orderByInput);
|
|
3107
|
-
}
|
|
3108
3070
|
return args.orderByInput;
|
|
3109
3071
|
}
|
|
3110
|
-
if (!args.hasOrderBy) {
|
|
3111
|
-
return ensureDeterministicOrderByInput({
|
|
3112
|
-
orderBy: void 0,
|
|
3113
|
-
model: args.relModel,
|
|
3114
|
-
parseValue: parseOrderByValue
|
|
3115
|
-
});
|
|
3116
|
-
}
|
|
3117
|
-
if (isNotNullish(args.orderByInput)) {
|
|
3118
|
-
validateOrderByForModel(args.relModel, args.orderByInput);
|
|
3119
|
-
}
|
|
3120
3072
|
return ensureDeterministicOrderByInput({
|
|
3121
|
-
orderBy: args.orderByInput,
|
|
3073
|
+
orderBy: args.hasOrderBy ? args.orderByInput : void 0,
|
|
3122
3074
|
model: args.relModel,
|
|
3123
3075
|
parseValue: parseOrderByValue
|
|
3124
3076
|
});
|
|
@@ -3222,11 +3174,7 @@ function buildListIncludeSpec(args) {
|
|
|
3222
3174
|
joinPredicate: args.joinPredicate,
|
|
3223
3175
|
whereClause: args.whereClause
|
|
3224
3176
|
});
|
|
3225
|
-
return Object.freeze({
|
|
3226
|
-
name: args.relName,
|
|
3227
|
-
sql: sql2,
|
|
3228
|
-
isOneToOne: false
|
|
3229
|
-
});
|
|
3177
|
+
return Object.freeze({ name: args.relName, sql: sql2, isOneToOne: false });
|
|
3230
3178
|
}
|
|
3231
3179
|
const rowAlias = args.ctx.aliasGen.next(`${args.relName}_row`);
|
|
3232
3180
|
let base = buildBaseSql({
|
|
@@ -3237,9 +3185,7 @@ function buildListIncludeSpec(args) {
|
|
|
3237
3185
|
joinPredicate: args.joinPredicate,
|
|
3238
3186
|
whereClause: args.whereClause
|
|
3239
3187
|
});
|
|
3240
|
-
if (args.orderBySql) {
|
|
3241
|
-
base += ` ${SQL_TEMPLATES.ORDER_BY} ${args.orderBySql}`;
|
|
3242
|
-
}
|
|
3188
|
+
if (args.orderBySql) base += ` ${SQL_TEMPLATES.ORDER_BY} ${args.orderBySql}`;
|
|
3243
3189
|
base = appendLimitOffset(
|
|
3244
3190
|
base,
|
|
3245
3191
|
args.ctx.dialect,
|
|
@@ -3250,11 +3196,7 @@ function buildListIncludeSpec(args) {
|
|
|
3250
3196
|
);
|
|
3251
3197
|
const selectExpr = jsonAgg("row", args.ctx.dialect);
|
|
3252
3198
|
const sql = `${SQL_TEMPLATES.SELECT} ${selectExpr} ${SQL_TEMPLATES.FROM} (${base}) ${rowAlias}`;
|
|
3253
|
-
return Object.freeze({
|
|
3254
|
-
name: args.relName,
|
|
3255
|
-
sql,
|
|
3256
|
-
isOneToOne: false
|
|
3257
|
-
});
|
|
3199
|
+
return Object.freeze({ name: args.relName, sql, isOneToOne: false });
|
|
3258
3200
|
}
|
|
3259
3201
|
function buildSingleInclude(relName, relArgs, field, relModel, ctx) {
|
|
3260
3202
|
const relTable = getRelationTableReference(relModel, ctx.dialect);
|
|
@@ -3285,7 +3227,7 @@ function buildSingleInclude(relName, relArgs, field, relModel, ctx) {
|
|
|
3285
3227
|
paginationConfig.orderBy
|
|
3286
3228
|
);
|
|
3287
3229
|
const hasPagination = paginationConfig.hasSkip || paginationConfig.hasTake;
|
|
3288
|
-
const finalOrderByInput =
|
|
3230
|
+
const finalOrderByInput = finalizeOrderByForInclude({
|
|
3289
3231
|
relModel,
|
|
3290
3232
|
hasOrderBy: paginationConfig.hasOrderBy,
|
|
3291
3233
|
orderByInput: adjusted.orderByInput,
|
|
@@ -3311,11 +3253,7 @@ function buildSingleInclude(relName, relArgs, field, relModel, ctx) {
|
|
|
3311
3253
|
skipVal: paginationConfig.skipVal,
|
|
3312
3254
|
ctx
|
|
3313
3255
|
});
|
|
3314
|
-
return Object.freeze({
|
|
3315
|
-
name: relName,
|
|
3316
|
-
sql,
|
|
3317
|
-
isOneToOne: true
|
|
3318
|
-
});
|
|
3256
|
+
return Object.freeze({ name: relName, sql, isOneToOne: true });
|
|
3319
3257
|
}
|
|
3320
3258
|
return buildListIncludeSpec({
|
|
3321
3259
|
relName,
|
|
@@ -3332,9 +3270,7 @@ function buildSingleInclude(relName, relArgs, field, relModel, ctx) {
|
|
|
3332
3270
|
});
|
|
3333
3271
|
}
|
|
3334
3272
|
function buildIncludeSqlInternal(args, model, schemas, parentAlias, aliasGen, params, dialect, visitPath = [], depth = 0, stats) {
|
|
3335
|
-
if (!stats) {
|
|
3336
|
-
stats = { totalIncludes: 0, totalSubqueries: 0, maxDepth: 0 };
|
|
3337
|
-
}
|
|
3273
|
+
if (!stats) stats = { totalIncludes: 0, totalSubqueries: 0, maxDepth: 0 };
|
|
3338
3274
|
if (depth > MAX_INCLUDE_DEPTH) {
|
|
3339
3275
|
throw new Error(
|
|
3340
3276
|
`Maximum include depth of ${MAX_INCLUDE_DEPTH} exceeded. Path: ${visitPath.join(" -> ")}. Deep includes cause exponential SQL complexity and performance issues.`
|
|
@@ -3373,12 +3309,8 @@ function buildIncludeSqlInternal(args, model, schemas, parentAlias, aliasGen, pa
|
|
|
3373
3309
|
`Include too deeply nested: model '${resolved.relModel.name}' appears ${modelOccurrences} times in path: ${currentPath.join(" -> ")}`
|
|
3374
3310
|
);
|
|
3375
3311
|
}
|
|
3376
|
-
|
|
3377
|
-
relName,
|
|
3378
|
-
relArgs,
|
|
3379
|
-
resolved.field,
|
|
3380
|
-
resolved.relModel,
|
|
3381
|
-
{
|
|
3312
|
+
includes.push(
|
|
3313
|
+
buildSingleInclude(relName, relArgs, resolved.field, resolved.relModel, {
|
|
3382
3314
|
model,
|
|
3383
3315
|
schemas,
|
|
3384
3316
|
parentAlias,
|
|
@@ -3388,9 +3320,8 @@ function buildIncludeSqlInternal(args, model, schemas, parentAlias, aliasGen, pa
|
|
|
3388
3320
|
visitPath: currentPath,
|
|
3389
3321
|
depth: depth + 1,
|
|
3390
3322
|
stats
|
|
3391
|
-
}
|
|
3323
|
+
})
|
|
3392
3324
|
);
|
|
3393
|
-
includes.push(include);
|
|
3394
3325
|
}
|
|
3395
3326
|
return includes;
|
|
3396
3327
|
}
|
|
@@ -3422,11 +3353,10 @@ function resolveCountRelationOrThrow(relName, model, schemas) {
|
|
|
3422
3353
|
);
|
|
3423
3354
|
}
|
|
3424
3355
|
const field = model.fields.find((f) => f.name === relName);
|
|
3425
|
-
if (!field)
|
|
3356
|
+
if (!field)
|
|
3426
3357
|
throw new Error(
|
|
3427
3358
|
`_count.${relName} references unknown relation on model ${model.name}`
|
|
3428
3359
|
);
|
|
3429
|
-
}
|
|
3430
3360
|
if (!isValidRelationField(field)) {
|
|
3431
3361
|
throw new Error(
|
|
3432
3362
|
`_count.${relName} has invalid relation metadata on model ${model.name}`
|
|
@@ -3448,9 +3378,8 @@ function defaultReferencesForCount(fkCount) {
|
|
|
3448
3378
|
}
|
|
3449
3379
|
function resolveCountKeyPairs(field) {
|
|
3450
3380
|
const fkFields = normalizeKeyList(field.foreignKey);
|
|
3451
|
-
if (fkFields.length === 0)
|
|
3381
|
+
if (fkFields.length === 0)
|
|
3452
3382
|
throw new Error("Relation count requires foreignKey");
|
|
3453
|
-
}
|
|
3454
3383
|
const refsRaw = field.references;
|
|
3455
3384
|
const refs = normalizeKeyList(refsRaw);
|
|
3456
3385
|
const refFields = refs.length > 0 ? refs : defaultReferencesForCount(fkFields.length);
|
|
@@ -3482,9 +3411,7 @@ function leftJoinOnForCount(args) {
|
|
|
3482
3411
|
}
|
|
3483
3412
|
function nextAliasAvoiding(aliasGen, base, forbidden) {
|
|
3484
3413
|
let a = aliasGen.next(base);
|
|
3485
|
-
while (forbidden.has(a))
|
|
3486
|
-
a = aliasGen.next(base);
|
|
3487
|
-
}
|
|
3414
|
+
while (forbidden.has(a)) a = aliasGen.next(base);
|
|
3488
3415
|
return a;
|
|
3489
3416
|
}
|
|
3490
3417
|
function buildCountJoinAndPair(args) {
|
|
@@ -3539,10 +3466,7 @@ function buildRelationCountSql(countSelect, model, schemas, parentAlias, _params
|
|
|
3539
3466
|
joins.push(built.joinSql);
|
|
3540
3467
|
pairs.push(built.pairSql);
|
|
3541
3468
|
}
|
|
3542
|
-
return {
|
|
3543
|
-
joins,
|
|
3544
|
-
jsonPairs: pairs.join(SQL_SEPARATORS.FIELD_LIST)
|
|
3545
|
-
};
|
|
3469
|
+
return { joins, jsonPairs: pairs.join(SQL_SEPARATORS.FIELD_LIST) };
|
|
3546
3470
|
}
|
|
3547
3471
|
|
|
3548
3472
|
// src/builder/select/assembly.ts
|
|
@@ -3578,7 +3502,11 @@ function buildSelectList(baseSelect, extraCols) {
|
|
|
3578
3502
|
function finalizeSql(sql, params, dialect) {
|
|
3579
3503
|
const snapshot = params.snapshot();
|
|
3580
3504
|
validateSelectQuery(sql);
|
|
3581
|
-
validateParamConsistencyByDialect(
|
|
3505
|
+
validateParamConsistencyByDialect(
|
|
3506
|
+
sql,
|
|
3507
|
+
snapshot.params,
|
|
3508
|
+
dialect === "sqlite" ? "postgres" : dialect
|
|
3509
|
+
);
|
|
3582
3510
|
return Object.freeze({
|
|
3583
3511
|
sql,
|
|
3584
3512
|
params: snapshot.params,
|
|
@@ -3872,40 +3800,6 @@ function constructFinalSql(spec) {
|
|
|
3872
3800
|
return finalizeSql(sql, params, dialect);
|
|
3873
3801
|
}
|
|
3874
3802
|
|
|
3875
|
-
// src/builder/shared/validators/field-assertions.ts
|
|
3876
|
-
var NUMERIC_TYPES = /* @__PURE__ */ new Set(["Int", "Float", "Decimal", "BigInt"]);
|
|
3877
|
-
function assertScalarField(model, fieldName, context) {
|
|
3878
|
-
const field = getFieldInfo(model, fieldName);
|
|
3879
|
-
if (!field) {
|
|
3880
|
-
throw createError(
|
|
3881
|
-
`${context} references unknown field '${fieldName}' on model ${model.name}`,
|
|
3882
|
-
{
|
|
3883
|
-
field: fieldName,
|
|
3884
|
-
modelName: model.name,
|
|
3885
|
-
availableFields: model.fields.map((f) => f.name)
|
|
3886
|
-
}
|
|
3887
|
-
);
|
|
3888
|
-
}
|
|
3889
|
-
if (field.isRelation) {
|
|
3890
|
-
throw createError(
|
|
3891
|
-
`${context} does not support relation field '${fieldName}'`,
|
|
3892
|
-
{ field: fieldName, modelName: model.name }
|
|
3893
|
-
);
|
|
3894
|
-
}
|
|
3895
|
-
return field;
|
|
3896
|
-
}
|
|
3897
|
-
function assertNumericField(model, fieldName, context) {
|
|
3898
|
-
const field = assertScalarField(model, fieldName, context);
|
|
3899
|
-
const baseType = field.type.replace(/\[\]|\?/g, "");
|
|
3900
|
-
if (!NUMERIC_TYPES.has(baseType)) {
|
|
3901
|
-
throw createError(
|
|
3902
|
-
`${context} requires numeric field, got '${field.type}'`,
|
|
3903
|
-
{ field: fieldName, modelName: model.name }
|
|
3904
|
-
);
|
|
3905
|
-
}
|
|
3906
|
-
return field;
|
|
3907
|
-
}
|
|
3908
|
-
|
|
3909
3803
|
// src/builder/select.ts
|
|
3910
3804
|
function normalizeOrderByInput2(orderBy) {
|
|
3911
3805
|
return normalizeOrderByInput(orderBy, parseOrderByValue);
|
|
@@ -4159,6 +4053,19 @@ var HAVING_ALLOWED_OPS = /* @__PURE__ */ new Set([
|
|
|
4159
4053
|
function isTruthySelection(v) {
|
|
4160
4054
|
return v === true;
|
|
4161
4055
|
}
|
|
4056
|
+
function isLogicalKey(key) {
|
|
4057
|
+
return key === LogicalOps.AND || key === LogicalOps.OR || key === LogicalOps.NOT;
|
|
4058
|
+
}
|
|
4059
|
+
function isAggregateKey(key) {
|
|
4060
|
+
return key === "_count" || key === "_sum" || key === "_avg" || key === "_min" || key === "_max";
|
|
4061
|
+
}
|
|
4062
|
+
function assertHavingOp(op) {
|
|
4063
|
+
if (!HAVING_ALLOWED_OPS.has(op)) {
|
|
4064
|
+
throw new Error(
|
|
4065
|
+
`Unsupported HAVING operator '${op}'. Allowed: ${[...HAVING_ALLOWED_OPS].join(", ")}`
|
|
4066
|
+
);
|
|
4067
|
+
}
|
|
4068
|
+
}
|
|
4162
4069
|
function aggExprForField(aggKey, field, alias, model) {
|
|
4163
4070
|
if (aggKey === "_count") {
|
|
4164
4071
|
return field === "_all" ? `COUNT(*)` : `COUNT(${col(alias, field, model)})`;
|
|
@@ -4192,18 +4099,9 @@ function normalizeLogicalValue2(operator, value) {
|
|
|
4192
4099
|
}
|
|
4193
4100
|
return out;
|
|
4194
4101
|
}
|
|
4195
|
-
if (isPlainObject(value))
|
|
4196
|
-
return [value];
|
|
4197
|
-
}
|
|
4102
|
+
if (isPlainObject(value)) return [value];
|
|
4198
4103
|
throw new Error(`${operator} must be an object or array of objects in HAVING`);
|
|
4199
4104
|
}
|
|
4200
|
-
function assertHavingOp(op) {
|
|
4201
|
-
if (!HAVING_ALLOWED_OPS.has(op)) {
|
|
4202
|
-
throw new Error(
|
|
4203
|
-
`Unsupported HAVING operator '${op}'. Allowed: ${[...HAVING_ALLOWED_OPS].join(", ")}`
|
|
4204
|
-
);
|
|
4205
|
-
}
|
|
4206
|
-
}
|
|
4207
4105
|
function buildNullComparison(expr, op) {
|
|
4208
4106
|
if (op === Ops.EQUALS) return `${expr} ${SQL_TEMPLATES.IS_NULL}`;
|
|
4209
4107
|
if (op === Ops.NOT) return `${expr} ${SQL_TEMPLATES.IS_NOT_NULL}`;
|
|
@@ -4247,12 +4145,6 @@ function buildSimpleComparison(expr, op, val, params, dialect) {
|
|
|
4247
4145
|
}
|
|
4248
4146
|
return buildBinaryComparison(expr, op, val, params);
|
|
4249
4147
|
}
|
|
4250
|
-
function isLogicalKey(key) {
|
|
4251
|
-
return key === LogicalOps.AND || key === LogicalOps.OR || key === LogicalOps.NOT;
|
|
4252
|
-
}
|
|
4253
|
-
function isAggregateKey(key) {
|
|
4254
|
-
return key === "_count" || key === "_sum" || key === "_avg" || key === "_min" || key === "_max";
|
|
4255
|
-
}
|
|
4256
4148
|
function negateClauses(subClauses) {
|
|
4257
4149
|
if (subClauses.length === 1) return `${SQL_TEMPLATES.NOT} ${subClauses[0]}`;
|
|
4258
4150
|
return `${SQL_TEMPLATES.NOT} (${subClauses.join(SQL_SEPARATORS.CONDITION_AND)})`;
|
|
@@ -4261,50 +4153,10 @@ function combineLogical(key, subClauses) {
|
|
|
4261
4153
|
if (key === LogicalOps.NOT) return negateClauses(subClauses);
|
|
4262
4154
|
return subClauses.join(` ${key} `);
|
|
4263
4155
|
}
|
|
4264
|
-
function buildLogicalClause2(key, value, alias, params, dialect, model) {
|
|
4265
|
-
const items = normalizeLogicalValue2(key, value);
|
|
4266
|
-
const subClauses = [];
|
|
4267
|
-
for (const it of items) {
|
|
4268
|
-
const c = buildHavingNode(it, alias, params, dialect, model);
|
|
4269
|
-
if (c && c !== "") subClauses.push(`(${c})`);
|
|
4270
|
-
}
|
|
4271
|
-
if (subClauses.length === 0) return "";
|
|
4272
|
-
return combineLogical(key, subClauses);
|
|
4273
|
-
}
|
|
4274
|
-
function buildHavingEntry(key, value, alias, params, dialect, model) {
|
|
4275
|
-
if (isLogicalKey(key)) {
|
|
4276
|
-
const logical = buildLogicalClause2(
|
|
4277
|
-
key,
|
|
4278
|
-
value,
|
|
4279
|
-
alias,
|
|
4280
|
-
params,
|
|
4281
|
-
dialect,
|
|
4282
|
-
model
|
|
4283
|
-
);
|
|
4284
|
-
return logical ? [logical] : [];
|
|
4285
|
-
}
|
|
4286
|
-
if (isAggregateKey(key)) {
|
|
4287
|
-
return buildHavingForAggregateFirstShape(
|
|
4288
|
-
key,
|
|
4289
|
-
value,
|
|
4290
|
-
alias,
|
|
4291
|
-
params,
|
|
4292
|
-
dialect,
|
|
4293
|
-
model
|
|
4294
|
-
);
|
|
4295
|
-
}
|
|
4296
|
-
return buildHavingForFieldFirstShape(
|
|
4297
|
-
key,
|
|
4298
|
-
value,
|
|
4299
|
-
alias,
|
|
4300
|
-
params,
|
|
4301
|
-
dialect,
|
|
4302
|
-
model
|
|
4303
|
-
);
|
|
4304
|
-
}
|
|
4305
4156
|
function buildHavingNode(node, alias, params, dialect, model) {
|
|
4306
4157
|
const clauses = [];
|
|
4307
|
-
|
|
4158
|
+
const entries = Object.entries(node);
|
|
4159
|
+
for (const [key, value] of entries) {
|
|
4308
4160
|
const built = buildHavingEntry(key, value, alias, params, dialect, model);
|
|
4309
4161
|
for (const c of built) {
|
|
4310
4162
|
if (c && c.trim().length > 0) clauses.push(c);
|
|
@@ -4312,11 +4164,20 @@ function buildHavingNode(node, alias, params, dialect, model) {
|
|
|
4312
4164
|
}
|
|
4313
4165
|
return clauses.join(SQL_SEPARATORS.CONDITION_AND);
|
|
4314
4166
|
}
|
|
4167
|
+
function buildLogicalClause2(key, value, alias, params, dialect, model) {
|
|
4168
|
+
const items = normalizeLogicalValue2(key, value);
|
|
4169
|
+
const subClauses = [];
|
|
4170
|
+
for (const it of items) {
|
|
4171
|
+
const c = buildHavingNode(it, alias, params, dialect, model);
|
|
4172
|
+
if (c && c.trim().length > 0) subClauses.push(`(${c})`);
|
|
4173
|
+
}
|
|
4174
|
+
if (subClauses.length === 0) return "";
|
|
4175
|
+
return combineLogical(key, subClauses);
|
|
4176
|
+
}
|
|
4315
4177
|
function assertHavingAggTarget(aggKey, field, model) {
|
|
4316
4178
|
if (field === "_all") {
|
|
4317
|
-
if (aggKey !== "_count")
|
|
4179
|
+
if (aggKey !== "_count")
|
|
4318
4180
|
throw new Error(`HAVING '${aggKey}' does not support '_all'`);
|
|
4319
|
-
}
|
|
4320
4181
|
return;
|
|
4321
4182
|
}
|
|
4322
4183
|
if (aggKey === "_sum" || aggKey === "_avg") {
|
|
@@ -4352,29 +4213,50 @@ function buildHavingForFieldFirstShape(fieldName, target, alias, params, dialect
|
|
|
4352
4213
|
for (const aggKey of keys) {
|
|
4353
4214
|
const aggFilter = obj[aggKey];
|
|
4354
4215
|
if (!isPlainObject(aggFilter)) continue;
|
|
4216
|
+
if (Object.keys(aggFilter).length === 0) continue;
|
|
4355
4217
|
if (aggKey === "_sum" || aggKey === "_avg") {
|
|
4356
4218
|
assertNumericField(model, fieldName, "HAVING");
|
|
4357
4219
|
}
|
|
4358
|
-
const entries = Object.entries(aggFilter);
|
|
4359
|
-
if (entries.length === 0) continue;
|
|
4360
4220
|
const expr = aggExprForField(aggKey, fieldName, alias, model);
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4221
|
+
out.push(...buildHavingOpsForExpr(expr, aggFilter, params, dialect));
|
|
4222
|
+
}
|
|
4223
|
+
return out;
|
|
4224
|
+
}
|
|
4225
|
+
function buildHavingEntry(key, value, alias, params, dialect, model) {
|
|
4226
|
+
if (isLogicalKey(key)) {
|
|
4227
|
+
const logical = buildLogicalClause2(
|
|
4228
|
+
key,
|
|
4229
|
+
value,
|
|
4230
|
+
alias,
|
|
4364
4231
|
params,
|
|
4365
4232
|
dialect,
|
|
4366
|
-
|
|
4233
|
+
model
|
|
4367
4234
|
);
|
|
4368
|
-
|
|
4235
|
+
return logical ? [logical] : [];
|
|
4369
4236
|
}
|
|
4370
|
-
|
|
4237
|
+
if (isAggregateKey(key)) {
|
|
4238
|
+
return buildHavingForAggregateFirstShape(
|
|
4239
|
+
key,
|
|
4240
|
+
value,
|
|
4241
|
+
alias,
|
|
4242
|
+
params,
|
|
4243
|
+
dialect,
|
|
4244
|
+
model
|
|
4245
|
+
);
|
|
4246
|
+
}
|
|
4247
|
+
return buildHavingForFieldFirstShape(
|
|
4248
|
+
key,
|
|
4249
|
+
value,
|
|
4250
|
+
alias,
|
|
4251
|
+
params,
|
|
4252
|
+
dialect,
|
|
4253
|
+
model
|
|
4254
|
+
);
|
|
4371
4255
|
}
|
|
4372
4256
|
function buildHavingClause(having, alias, params, model, dialect) {
|
|
4373
4257
|
if (!isNotNullish(having)) return "";
|
|
4374
4258
|
const d = dialect != null ? dialect : getGlobalDialect();
|
|
4375
|
-
if (!isPlainObject(having))
|
|
4376
|
-
throw new Error("having must be an object");
|
|
4377
|
-
}
|
|
4259
|
+
if (!isPlainObject(having)) throw new Error("having must be an object");
|
|
4378
4260
|
return buildHavingNode(having, alias, params, d, model);
|
|
4379
4261
|
}
|
|
4380
4262
|
function normalizeCountArg(v) {
|
|
@@ -4388,9 +4270,6 @@ function pushCountAllField(fields) {
|
|
|
4388
4270
|
`${SQL_TEMPLATES.COUNT_ALL} ${SQL_TEMPLATES.AS} ${quote("_count._all")}`
|
|
4389
4271
|
);
|
|
4390
4272
|
}
|
|
4391
|
-
function assertCountableScalarField(model, fieldName) {
|
|
4392
|
-
assertScalarField(model, fieldName, "_count");
|
|
4393
|
-
}
|
|
4394
4273
|
function pushCountField(fields, alias, fieldName, model) {
|
|
4395
4274
|
const outAlias = `_count.${fieldName}`;
|
|
4396
4275
|
fields.push(
|
|
@@ -4411,7 +4290,7 @@ function addCountFields(fields, countArg, alias, model) {
|
|
|
4411
4290
|
([f, v]) => f !== "_all" && isTruthySelection(v)
|
|
4412
4291
|
);
|
|
4413
4292
|
for (const [f] of selected) {
|
|
4414
|
-
|
|
4293
|
+
assertScalarField(model, f, "_count");
|
|
4415
4294
|
pushCountField(fields, alias, f, model);
|
|
4416
4295
|
}
|
|
4417
4296
|
}
|
|
@@ -4501,9 +4380,7 @@ function buildGroupBySelectParts(args, alias, model, byFields) {
|
|
|
4501
4380
|
}
|
|
4502
4381
|
function buildGroupByHaving(args, alias, params, model, dialect) {
|
|
4503
4382
|
if (!isNotNullish(args.having)) return "";
|
|
4504
|
-
if (!isPlainObject(args.having))
|
|
4505
|
-
throw new Error("having must be an object");
|
|
4506
|
-
}
|
|
4383
|
+
if (!isPlainObject(args.having)) throw new Error("having must be an object");
|
|
4507
4384
|
const h = buildHavingClause(args.having, alias, params, model, dialect);
|
|
4508
4385
|
if (!h || h.trim().length === 0) return "";
|
|
4509
4386
|
return `${SQL_TEMPLATES.HAVING} ${h}`;
|
|
@@ -4536,10 +4413,9 @@ function buildGroupBySql(args, whereResult, tableName, alias, model, dialect) {
|
|
|
4536
4413
|
const snapshot = params.snapshot();
|
|
4537
4414
|
validateSelectQuery(sql);
|
|
4538
4415
|
validateParamConsistency(sql, [...whereResult.params, ...snapshot.params]);
|
|
4539
|
-
const mergedParams = [...whereResult.params, ...snapshot.params];
|
|
4540
4416
|
return Object.freeze({
|
|
4541
4417
|
sql,
|
|
4542
|
-
params: Object.freeze(
|
|
4418
|
+
params: Object.freeze([...whereResult.params, ...snapshot.params]),
|
|
4543
4419
|
paramMappings: Object.freeze([
|
|
4544
4420
|
...whereResult.paramMappings,
|
|
4545
4421
|
...snapshot.mappings
|
|
@@ -5081,34 +4957,39 @@ function normalizeQuery(args: any): string {
|
|
|
5081
4957
|
|
|
5082
4958
|
function extractDynamicParams(args: any, dynamicKeys: string[]): unknown[] {
|
|
5083
4959
|
const params: unknown[] = []
|
|
5084
|
-
|
|
4960
|
+
|
|
5085
4961
|
for (const key of dynamicKeys) {
|
|
5086
4962
|
const parts = key.split(':')
|
|
5087
4963
|
const lookupKey = parts.length === 2 ? parts[1] : key
|
|
5088
|
-
|
|
5089
|
-
|
|
4964
|
+
|
|
4965
|
+
const value =
|
|
4966
|
+
lookupKey.includes('.') ? getByPath(args, lookupKey) : args?.[lookupKey]
|
|
4967
|
+
|
|
5090
4968
|
if (value === undefined) {
|
|
5091
4969
|
throw new Error(\`Missing required parameter: \${key}\`)
|
|
5092
4970
|
}
|
|
5093
|
-
|
|
4971
|
+
|
|
5094
4972
|
params.push(normalizeValue(value))
|
|
5095
4973
|
}
|
|
5096
|
-
|
|
4974
|
+
|
|
5097
4975
|
return params
|
|
5098
4976
|
}
|
|
5099
4977
|
|
|
4978
|
+
|
|
5100
4979
|
async function executeQuery(client: any, sql: string, params: unknown[]): Promise<unknown[]> {
|
|
4980
|
+
const normalizedParams = normalizeParams(params)
|
|
4981
|
+
|
|
5101
4982
|
if (DIALECT === 'postgres') {
|
|
5102
|
-
return await client.unsafe(sql,
|
|
4983
|
+
return await client.unsafe(sql, normalizedParams)
|
|
5103
4984
|
}
|
|
5104
|
-
|
|
4985
|
+
|
|
5105
4986
|
const stmt = client.prepare(sql)
|
|
5106
|
-
|
|
4987
|
+
|
|
5107
4988
|
if (sql.toUpperCase().includes('COUNT(*) AS')) {
|
|
5108
|
-
return [stmt.get(...
|
|
4989
|
+
return [stmt.get(...normalizedParams)]
|
|
5109
4990
|
}
|
|
5110
|
-
|
|
5111
|
-
return stmt.all(...
|
|
4991
|
+
|
|
4992
|
+
return stmt.all(...normalizedParams)
|
|
5112
4993
|
}
|
|
5113
4994
|
|
|
5114
4995
|
export function speedExtension(config: {
|
|
@@ -5153,18 +5034,21 @@ export function speedExtension(config: {
|
|
|
5153
5034
|
|
|
5154
5035
|
if (prebakedQuery) {
|
|
5155
5036
|
sql = prebakedQuery.sql
|
|
5156
|
-
params = [
|
|
5037
|
+
params = normalizeParams([
|
|
5038
|
+
...prebakedQuery.params,
|
|
5039
|
+
...extractDynamicParams(transformedArgs, prebakedQuery.dynamicKeys),
|
|
5040
|
+
])
|
|
5157
5041
|
prebaked = true
|
|
5158
5042
|
} else {
|
|
5159
5043
|
const model = MODELS.find((m) => m.name === modelName)
|
|
5160
|
-
|
|
5044
|
+
|
|
5161
5045
|
if (!model) {
|
|
5162
5046
|
return this.$parent[modelName][method](args)
|
|
5163
5047
|
}
|
|
5164
5048
|
|
|
5165
5049
|
const result = buildSQL(model, MODELS, method, transformedArgs, DIALECT)
|
|
5166
5050
|
sql = result.sql
|
|
5167
|
-
params = result.params
|
|
5051
|
+
params = normalizeParams(result.params as unknown[])
|
|
5168
5052
|
}
|
|
5169
5053
|
|
|
5170
5054
|
if (debug) {
|