prisma-sql 1.48.0 → 1.49.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 +461 -263
- package/dist/generator.cjs.map +1 -1
- package/dist/generator.js +461 -263
- package/dist/generator.js.map +1 -1
- package/dist/index.cjs +460 -263
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +460 -263
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -43,6 +43,7 @@ var __async = (__this, __arguments, generator) => {
|
|
|
43
43
|
};
|
|
44
44
|
|
|
45
45
|
// src/builder/shared/constants.ts
|
|
46
|
+
var IS_PRODUCTION = process.env.NODE_ENV === "production";
|
|
46
47
|
var SQL_SEPARATORS = Object.freeze({
|
|
47
48
|
FIELD_LIST: ", ",
|
|
48
49
|
CONDITION_AND: " AND ",
|
|
@@ -485,6 +486,15 @@ function createError(message, ctx, code = "VALIDATION_ERROR") {
|
|
|
485
486
|
|
|
486
487
|
// src/builder/shared/model-field-cache.ts
|
|
487
488
|
var MODEL_CACHE = /* @__PURE__ */ new WeakMap();
|
|
489
|
+
function quote(id) {
|
|
490
|
+
const needsQuoting2 = !/^[a-z_][a-z0-9_]*$/.test(id) || /^(select|from|where|having|order|group|limit|offset|join|inner|left|right|outer|cross|full|and|or|not|by|as|on|union|intersect|except|case|when|then|else|end|user|users|table|column|index|values|in|like|between|is|exists|null|true|false|all|any|some|update|insert|delete|create|drop|alter|truncate|grant|revoke|exec|execute)$/i.test(
|
|
491
|
+
id
|
|
492
|
+
);
|
|
493
|
+
if (needsQuoting2) {
|
|
494
|
+
return `"${id.replace(/"/g, '""')}"`;
|
|
495
|
+
}
|
|
496
|
+
return id;
|
|
497
|
+
}
|
|
488
498
|
function ensureFullCache(model) {
|
|
489
499
|
let cache = MODEL_CACHE.get(model);
|
|
490
500
|
if (!cache) {
|
|
@@ -492,6 +502,8 @@ function ensureFullCache(model) {
|
|
|
492
502
|
const scalarFields = /* @__PURE__ */ new Set();
|
|
493
503
|
const relationFields = /* @__PURE__ */ new Set();
|
|
494
504
|
const columnMap = /* @__PURE__ */ new Map();
|
|
505
|
+
const fieldByName = /* @__PURE__ */ new Map();
|
|
506
|
+
const quotedColumns = /* @__PURE__ */ new Map();
|
|
495
507
|
for (const f of model.fields) {
|
|
496
508
|
const info = {
|
|
497
509
|
name: f.name,
|
|
@@ -501,14 +513,24 @@ function ensureFullCache(model) {
|
|
|
501
513
|
isRequired: !!f.isRequired
|
|
502
514
|
};
|
|
503
515
|
fieldInfo.set(f.name, info);
|
|
516
|
+
fieldByName.set(f.name, f);
|
|
504
517
|
if (info.isRelation) {
|
|
505
518
|
relationFields.add(f.name);
|
|
506
519
|
} else {
|
|
507
520
|
scalarFields.add(f.name);
|
|
508
|
-
|
|
521
|
+
const dbName = info.dbName;
|
|
522
|
+
columnMap.set(f.name, dbName);
|
|
523
|
+
quotedColumns.set(f.name, quote(dbName));
|
|
509
524
|
}
|
|
510
525
|
}
|
|
511
|
-
cache = {
|
|
526
|
+
cache = {
|
|
527
|
+
fieldInfo,
|
|
528
|
+
scalarFields,
|
|
529
|
+
relationFields,
|
|
530
|
+
columnMap,
|
|
531
|
+
fieldByName,
|
|
532
|
+
quotedColumns
|
|
533
|
+
};
|
|
512
534
|
MODEL_CACHE.set(model, cache);
|
|
513
535
|
}
|
|
514
536
|
return cache;
|
|
@@ -525,6 +547,9 @@ function getRelationFieldSet(model) {
|
|
|
525
547
|
function getColumnMap(model) {
|
|
526
548
|
return ensureFullCache(model).columnMap;
|
|
527
549
|
}
|
|
550
|
+
function getQuotedColumn(model, fieldName) {
|
|
551
|
+
return ensureFullCache(model).quotedColumns.get(fieldName);
|
|
552
|
+
}
|
|
528
553
|
|
|
529
554
|
// src/builder/shared/validators/sql-validators.ts
|
|
530
555
|
function isValidWhereClause(clause) {
|
|
@@ -540,6 +565,7 @@ function sqlPreview(sql) {
|
|
|
540
565
|
return `${s.slice(0, 160)}...`;
|
|
541
566
|
}
|
|
542
567
|
function validateSelectQuery(sql) {
|
|
568
|
+
if (IS_PRODUCTION) return;
|
|
543
569
|
if (!hasValidContent(sql)) {
|
|
544
570
|
throw new Error("CRITICAL: Generated empty SQL query");
|
|
545
571
|
}
|
|
@@ -598,6 +624,7 @@ function assertNoGapsDollar(scan, rangeMin, rangeMax, sql) {
|
|
|
598
624
|
}
|
|
599
625
|
}
|
|
600
626
|
function validateParamConsistency(sql, params) {
|
|
627
|
+
if (IS_PRODUCTION) return;
|
|
601
628
|
const paramLen = params.length;
|
|
602
629
|
const scan = scanDollarPlaceholders(sql, paramLen);
|
|
603
630
|
if (paramLen === 0) {
|
|
@@ -634,6 +661,7 @@ function countQuestionMarkPlaceholders(sql) {
|
|
|
634
661
|
return count;
|
|
635
662
|
}
|
|
636
663
|
function validateQuestionMarkConsistency(sql, params) {
|
|
664
|
+
if (IS_PRODUCTION) return;
|
|
637
665
|
const expected = params.length;
|
|
638
666
|
const found = countQuestionMarkPlaceholders(sql);
|
|
639
667
|
if (expected !== found) {
|
|
@@ -643,6 +671,7 @@ function validateQuestionMarkConsistency(sql, params) {
|
|
|
643
671
|
}
|
|
644
672
|
}
|
|
645
673
|
function validateParamConsistencyByDialect(sql, params, dialect) {
|
|
674
|
+
if (IS_PRODUCTION) return;
|
|
646
675
|
if (dialect === "postgres") {
|
|
647
676
|
validateParamConsistency(sql, params);
|
|
648
677
|
return;
|
|
@@ -804,7 +833,7 @@ function assertSafeQualifiedName(tableRef) {
|
|
|
804
833
|
}
|
|
805
834
|
}
|
|
806
835
|
}
|
|
807
|
-
function
|
|
836
|
+
function quote2(id) {
|
|
808
837
|
if (isEmptyString(id)) {
|
|
809
838
|
throw new Error("quote: identifier is required and cannot be empty");
|
|
810
839
|
}
|
|
@@ -824,17 +853,14 @@ function resolveColumnName(model, fieldName) {
|
|
|
824
853
|
return columnMap.get(fieldName) || fieldName;
|
|
825
854
|
}
|
|
826
855
|
function quoteColumn(model, fieldName) {
|
|
827
|
-
|
|
856
|
+
if (!model) return quote2(fieldName);
|
|
857
|
+
const cached = getQuotedColumn(model, fieldName);
|
|
858
|
+
return cached || quote2(fieldName);
|
|
828
859
|
}
|
|
829
860
|
function col(alias, field, model) {
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
if (isEmptyString(field)) {
|
|
834
|
-
throw new Error("col: field is required and cannot be empty");
|
|
835
|
-
}
|
|
836
|
-
const columnName = resolveColumnName(model, field);
|
|
837
|
-
return `${alias}.${quote(columnName)}`;
|
|
861
|
+
const columnName = model ? getColumnMap(model).get(field) || field : field;
|
|
862
|
+
const quoted = model ? getQuotedColumn(model, field) || quote2(columnName) : quote2(columnName);
|
|
863
|
+
return alias + "." + quoted;
|
|
838
864
|
}
|
|
839
865
|
function colWithAlias(alias, field, model) {
|
|
840
866
|
if (isEmptyString(alias)) {
|
|
@@ -844,9 +870,9 @@ function colWithAlias(alias, field, model) {
|
|
|
844
870
|
throw new Error("colWithAlias: field is required and cannot be empty");
|
|
845
871
|
}
|
|
846
872
|
const columnName = resolveColumnName(model, field);
|
|
847
|
-
const columnRef =
|
|
873
|
+
const columnRef = alias + "." + (model ? getQuotedColumn(model, field) || quote2(columnName) : quote2(columnName));
|
|
848
874
|
if (columnName !== field) {
|
|
849
|
-
return
|
|
875
|
+
return columnRef + " AS " + quote2(field);
|
|
850
876
|
}
|
|
851
877
|
return columnRef;
|
|
852
878
|
}
|
|
@@ -869,7 +895,7 @@ function buildTableReference(schemaName, tableName, dialect) {
|
|
|
869
895
|
}
|
|
870
896
|
const d = dialect != null ? dialect : "postgres";
|
|
871
897
|
if (d === "sqlite") {
|
|
872
|
-
return
|
|
898
|
+
return quote2(tableName);
|
|
873
899
|
}
|
|
874
900
|
if (isEmptyString(schemaName)) {
|
|
875
901
|
throw new Error(
|
|
@@ -1197,6 +1223,7 @@ function assertNumericField(model, fieldName, context) {
|
|
|
1197
1223
|
|
|
1198
1224
|
// src/builder/pagination.ts
|
|
1199
1225
|
var MAX_LIMIT_OFFSET = 2147483647;
|
|
1226
|
+
var ORDER_BY_ALLOWED_KEYS = /* @__PURE__ */ new Set(["sort", "nulls"]);
|
|
1200
1227
|
function parseDirectionRaw(raw, errorLabel) {
|
|
1201
1228
|
const s = String(raw).toLowerCase();
|
|
1202
1229
|
if (s === "asc" || s === "desc") return s;
|
|
@@ -1215,9 +1242,8 @@ function requireOrderByObject(v, errorPrefix) {
|
|
|
1215
1242
|
return v;
|
|
1216
1243
|
}
|
|
1217
1244
|
function assertAllowedOrderByKeys(obj, fieldName) {
|
|
1218
|
-
const allowed = /* @__PURE__ */ new Set(["sort", "nulls"]);
|
|
1219
1245
|
for (const k of Object.keys(obj)) {
|
|
1220
|
-
if (!
|
|
1246
|
+
if (!ORDER_BY_ALLOWED_KEYS.has(k)) {
|
|
1221
1247
|
throw new Error(
|
|
1222
1248
|
fieldName ? `Unsupported orderBy key '${k}' for field '${fieldName}'` : `Unsupported orderBy key '${k}'`
|
|
1223
1249
|
);
|
|
@@ -1284,17 +1310,17 @@ function buildOrderByFragment(entries, alias, dialect, model) {
|
|
|
1284
1310
|
const c = col(alias, e.field, model);
|
|
1285
1311
|
if (dialect === "postgres") {
|
|
1286
1312
|
const nulls = isNotNullish(e.nulls) ? ` NULLS ${e.nulls.toUpperCase()}` : "";
|
|
1287
|
-
out.push(
|
|
1313
|
+
out.push(c + " " + dir + nulls);
|
|
1288
1314
|
continue;
|
|
1289
1315
|
}
|
|
1290
1316
|
if (isNotNullish(e.nulls)) {
|
|
1291
1317
|
const isNullExpr = `(${c} IS NULL)`;
|
|
1292
1318
|
const nullRankDir = e.nulls === "first" ? "DESC" : "ASC";
|
|
1293
|
-
out.push(
|
|
1294
|
-
out.push(
|
|
1319
|
+
out.push(isNullExpr + " " + nullRankDir);
|
|
1320
|
+
out.push(c + " " + dir);
|
|
1295
1321
|
continue;
|
|
1296
1322
|
}
|
|
1297
|
-
out.push(
|
|
1323
|
+
out.push(c + " " + dir);
|
|
1298
1324
|
}
|
|
1299
1325
|
return out.join(SQL_SEPARATORS.ORDER_BY);
|
|
1300
1326
|
}
|
|
@@ -1303,79 +1329,47 @@ function defaultNullsFor(dialect, direction) {
|
|
|
1303
1329
|
return direction === "asc" ? "first" : "last";
|
|
1304
1330
|
}
|
|
1305
1331
|
function ensureCursorFieldsInOrder(orderEntries, cursorEntries) {
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1332
|
+
if (cursorEntries.length === 0) return orderEntries;
|
|
1333
|
+
const existing = /* @__PURE__ */ new Set();
|
|
1334
|
+
for (let i = 0; i < orderEntries.length; i++)
|
|
1335
|
+
existing.add(orderEntries[i].field);
|
|
1336
|
+
let out = null;
|
|
1337
|
+
for (let i = 0; i < cursorEntries.length; i++) {
|
|
1338
|
+
const field = cursorEntries[i][0];
|
|
1310
1339
|
if (!existing.has(field)) {
|
|
1340
|
+
if (!out) out = orderEntries.slice();
|
|
1311
1341
|
out.push({ field, direction: "asc" });
|
|
1312
|
-
existing.
|
|
1342
|
+
existing.add(field);
|
|
1313
1343
|
}
|
|
1314
1344
|
}
|
|
1315
|
-
return out;
|
|
1345
|
+
return out != null ? out : orderEntries;
|
|
1316
1346
|
}
|
|
1317
1347
|
function buildCursorFilterParts(cursor, cursorAlias, params, model) {
|
|
1318
|
-
const entries = Object.entries(cursor);
|
|
1319
|
-
if (entries.length === 0)
|
|
1320
|
-
throw new Error("cursor must have at least one field");
|
|
1321
|
-
const placeholdersByField = /* @__PURE__ */ new Map();
|
|
1322
1348
|
const parts = [];
|
|
1323
|
-
for (const
|
|
1324
|
-
|
|
1349
|
+
for (const field in cursor) {
|
|
1350
|
+
if (!Object.prototype.hasOwnProperty.call(cursor, field)) continue;
|
|
1351
|
+
const value = cursor[field];
|
|
1352
|
+
const c = cursorAlias + "." + quoteColumn(model, field);
|
|
1325
1353
|
if (value === null) {
|
|
1326
|
-
parts.push(
|
|
1354
|
+
parts.push(c + " IS NULL");
|
|
1327
1355
|
continue;
|
|
1328
1356
|
}
|
|
1329
1357
|
const ph = addAutoScoped(params, value, `cursor.filter.${field}`);
|
|
1330
|
-
|
|
1331
|
-
parts.push(`${c} = ${ph}`);
|
|
1358
|
+
parts.push(c + " = " + ph);
|
|
1332
1359
|
}
|
|
1333
1360
|
return {
|
|
1334
|
-
whereSql: parts.length === 1 ? parts[0] :
|
|
1335
|
-
placeholdersByField
|
|
1361
|
+
whereSql: parts.length === 1 ? parts[0] : "(" + parts.join(" AND ") + ")"
|
|
1336
1362
|
};
|
|
1337
1363
|
}
|
|
1338
|
-
function buildCursorEqualityExpr(columnExpr,
|
|
1339
|
-
return `((${
|
|
1364
|
+
function buildCursorEqualityExpr(columnExpr, cursorField) {
|
|
1365
|
+
return `((${cursorField} IS NULL AND ${columnExpr} IS NULL) OR (${cursorField} IS NOT NULL AND ${columnExpr} = ${cursorField}))`;
|
|
1340
1366
|
}
|
|
1341
|
-
function buildCursorInequalityExpr(columnExpr, direction, nulls,
|
|
1367
|
+
function buildCursorInequalityExpr(columnExpr, direction, nulls, cursorField) {
|
|
1342
1368
|
const op = direction === "asc" ? ">" : "<";
|
|
1343
1369
|
if (nulls === "first") {
|
|
1344
|
-
return `(CASE WHEN ${
|
|
1345
|
-
}
|
|
1346
|
-
return `(CASE WHEN ${valueExpr} IS NULL THEN 0=1 ELSE ((${columnExpr} ${op} ${valueExpr}) OR (${columnExpr} IS NULL)) END)`;
|
|
1347
|
-
}
|
|
1348
|
-
function buildOuterCursorMatch(cursor, outerAlias, placeholdersByField, params, model) {
|
|
1349
|
-
const parts = [];
|
|
1350
|
-
for (const [field, value] of Object.entries(cursor)) {
|
|
1351
|
-
const c = col(outerAlias, field, model);
|
|
1352
|
-
if (value === null) {
|
|
1353
|
-
parts.push(`${c} IS NULL`);
|
|
1354
|
-
continue;
|
|
1355
|
-
}
|
|
1356
|
-
const existing = placeholdersByField.get(field);
|
|
1357
|
-
if (typeof existing === "string" && existing.length > 0) {
|
|
1358
|
-
parts.push(`${c} = ${existing}`);
|
|
1359
|
-
continue;
|
|
1360
|
-
}
|
|
1361
|
-
const ph = addAutoScoped(params, value, `cursor.outerMatch.${field}`);
|
|
1362
|
-
parts.push(`${c} = ${ph}`);
|
|
1363
|
-
}
|
|
1364
|
-
return parts.length === 1 ? parts[0] : `(${parts.join(" AND ")})`;
|
|
1365
|
-
}
|
|
1366
|
-
function buildOrderEntries(orderBy) {
|
|
1367
|
-
const normalized = normalizeOrderByInput(orderBy, parseOrderByValue);
|
|
1368
|
-
const entries = [];
|
|
1369
|
-
for (const item of normalized) {
|
|
1370
|
-
for (const [field, value] of Object.entries(item)) {
|
|
1371
|
-
if (typeof value === "string") {
|
|
1372
|
-
entries.push({ field, direction: value });
|
|
1373
|
-
} else {
|
|
1374
|
-
entries.push({ field, direction: value.direction, nulls: value.nulls });
|
|
1375
|
-
}
|
|
1376
|
-
}
|
|
1370
|
+
return `(CASE WHEN ${cursorField} IS NULL THEN (${columnExpr} IS NOT NULL) ELSE (${columnExpr} ${op} ${cursorField}) END)`;
|
|
1377
1371
|
}
|
|
1378
|
-
return
|
|
1372
|
+
return `(CASE WHEN ${cursorField} IS NULL THEN 0=1 ELSE ((${columnExpr} ${op} ${cursorField}) OR (${columnExpr} IS NULL)) END)`;
|
|
1379
1373
|
}
|
|
1380
1374
|
function buildCursorCteSelectList(cursorEntries, orderEntries, model) {
|
|
1381
1375
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -1397,8 +1391,7 @@ function buildCursorCteSelectList(cursorEntries, orderEntries, model) {
|
|
|
1397
1391
|
}
|
|
1398
1392
|
function truncateIdent(name, maxLen) {
|
|
1399
1393
|
const s = String(name);
|
|
1400
|
-
|
|
1401
|
-
return s.slice(0, maxLen);
|
|
1394
|
+
return s.length <= maxLen ? s : s.slice(0, maxLen);
|
|
1402
1395
|
}
|
|
1403
1396
|
function buildCursorNames(outerAlias) {
|
|
1404
1397
|
const maxLen = 63;
|
|
@@ -1415,15 +1408,25 @@ function buildCursorNames(outerAlias) {
|
|
|
1415
1408
|
}
|
|
1416
1409
|
function assertCursorAndOrderFieldsScalar(model, cursor, orderEntries) {
|
|
1417
1410
|
if (!model) return;
|
|
1418
|
-
for (const k
|
|
1419
|
-
|
|
1411
|
+
for (const k in cursor) {
|
|
1412
|
+
if (!Object.prototype.hasOwnProperty.call(cursor, k)) continue;
|
|
1413
|
+
assertScalarField(model, k, "cursor");
|
|
1414
|
+
}
|
|
1415
|
+
for (const e of orderEntries) {
|
|
1416
|
+
assertScalarField(model, e.field, "orderBy");
|
|
1417
|
+
}
|
|
1420
1418
|
}
|
|
1421
1419
|
function buildCursorCondition(cursor, orderBy, tableName, alias, params, dialect, model) {
|
|
1422
1420
|
var _a;
|
|
1423
1421
|
assertSafeTableRef(tableName);
|
|
1424
1422
|
assertSafeAlias(alias);
|
|
1425
1423
|
const d = dialect != null ? dialect : getGlobalDialect();
|
|
1426
|
-
const cursorEntries =
|
|
1424
|
+
const cursorEntries = [];
|
|
1425
|
+
for (const k in cursor) {
|
|
1426
|
+
if (Object.prototype.hasOwnProperty.call(cursor, k)) {
|
|
1427
|
+
cursorEntries.push([k, cursor[k]]);
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1427
1430
|
if (cursorEntries.length === 0)
|
|
1428
1431
|
throw new Error("cursor must have at least one field");
|
|
1429
1432
|
const { cteName, srcAlias } = buildCursorNames(alias);
|
|
@@ -1444,48 +1447,51 @@ function buildCursorCondition(cursor, orderBy, tableName, alias, params, dialect
|
|
|
1444
1447
|
orderEntries = ensureCursorFieldsInOrder(orderEntries, cursorEntries);
|
|
1445
1448
|
}
|
|
1446
1449
|
assertCursorAndOrderFieldsScalar(model, cursor, orderEntries);
|
|
1447
|
-
const { whereSql: cursorWhereSql
|
|
1450
|
+
const { whereSql: cursorWhereSql } = buildCursorFilterParts(
|
|
1451
|
+
cursor,
|
|
1452
|
+
srcAlias,
|
|
1453
|
+
params,
|
|
1454
|
+
model
|
|
1455
|
+
);
|
|
1448
1456
|
const cursorOrderBy = orderEntries.map(
|
|
1449
|
-
(e) =>
|
|
1457
|
+
(e) => srcAlias + "." + quoteColumn(model, e.field) + " " + e.direction.toUpperCase()
|
|
1450
1458
|
).join(", ");
|
|
1451
1459
|
const selectList = buildCursorCteSelectList(
|
|
1452
1460
|
cursorEntries,
|
|
1453
1461
|
orderEntries,
|
|
1454
1462
|
model
|
|
1455
1463
|
);
|
|
1456
|
-
const cte =
|
|
1457
|
-
|
|
1458
|
-
WHERE ${cursorWhereSql}
|
|
1459
|
-
ORDER BY ${cursorOrderBy}
|
|
1460
|
-
LIMIT 1
|
|
1461
|
-
)`;
|
|
1462
|
-
const existsExpr = `EXISTS (SELECT 1 FROM ${cteName})`;
|
|
1463
|
-
const outerCursorMatch = buildOuterCursorMatch(
|
|
1464
|
-
cursor,
|
|
1465
|
-
alias,
|
|
1466
|
-
placeholdersByField,
|
|
1467
|
-
params,
|
|
1468
|
-
model
|
|
1469
|
-
);
|
|
1470
|
-
const getValueExpr = (field) => `(SELECT ${quoteColumn(model, field)} FROM ${cteName})`;
|
|
1464
|
+
const cte = cteName + " AS (\n SELECT " + selectList + " FROM " + tableName + " " + srcAlias + "\n WHERE " + cursorWhereSql + "\n ORDER BY " + cursorOrderBy + "\n LIMIT 1\n )";
|
|
1465
|
+
const existsExpr = "EXISTS (SELECT 1 FROM " + cteName + ")";
|
|
1471
1466
|
const orClauses = [];
|
|
1472
1467
|
for (let level = 0; level < orderEntries.length; level++) {
|
|
1473
1468
|
const andParts = [];
|
|
1474
1469
|
for (let i = 0; i < level; i++) {
|
|
1475
1470
|
const e2 = orderEntries[i];
|
|
1476
1471
|
const c2 = col(alias, e2.field, model);
|
|
1477
|
-
const
|
|
1478
|
-
andParts.push(buildCursorEqualityExpr(c2,
|
|
1472
|
+
const cursorField2 = cteName + "." + quoteColumn(model, e2.field);
|
|
1473
|
+
andParts.push(buildCursorEqualityExpr(c2, cursorField2));
|
|
1479
1474
|
}
|
|
1480
1475
|
const e = orderEntries[level];
|
|
1481
1476
|
const c = col(alias, e.field, model);
|
|
1482
|
-
const
|
|
1477
|
+
const cursorField = cteName + "." + quoteColumn(model, e.field);
|
|
1483
1478
|
const nulls = (_a = e.nulls) != null ? _a : defaultNullsFor(d, e.direction);
|
|
1484
|
-
andParts.push(buildCursorInequalityExpr(c, e.direction, nulls,
|
|
1485
|
-
orClauses.push(
|
|
1479
|
+
andParts.push(buildCursorInequalityExpr(c, e.direction, nulls, cursorField));
|
|
1480
|
+
orClauses.push("(" + andParts.join(SQL_SEPARATORS.CONDITION_AND) + ")");
|
|
1486
1481
|
}
|
|
1487
1482
|
const exclusive = orClauses.join(SQL_SEPARATORS.CONDITION_OR);
|
|
1488
|
-
const
|
|
1483
|
+
const outerMatchParts = [];
|
|
1484
|
+
for (const [field, value] of cursorEntries) {
|
|
1485
|
+
const c = col(alias, field, model);
|
|
1486
|
+
if (value === null) {
|
|
1487
|
+
outerMatchParts.push(c + " IS NULL");
|
|
1488
|
+
continue;
|
|
1489
|
+
}
|
|
1490
|
+
const ph = addAutoScoped(params, value, `cursor.outerMatch.${field}`);
|
|
1491
|
+
outerMatchParts.push(c + " = " + ph);
|
|
1492
|
+
}
|
|
1493
|
+
const outerCursorMatch = outerMatchParts.length === 1 ? outerMatchParts[0] : "(" + outerMatchParts.join(SQL_SEPARATORS.CONDITION_AND) + ")";
|
|
1494
|
+
const condition = "(" + existsExpr + SQL_SEPARATORS.CONDITION_AND + "((" + exclusive + ")" + SQL_SEPARATORS.CONDITION_OR + "(" + outerCursorMatch + ")))";
|
|
1489
1495
|
return { cte, condition };
|
|
1490
1496
|
}
|
|
1491
1497
|
function buildOrderBy(orderBy, alias, dialect, model) {
|
|
@@ -1538,6 +1544,22 @@ function getPaginationParams(method, args) {
|
|
|
1538
1544
|
}
|
|
1539
1545
|
return {};
|
|
1540
1546
|
}
|
|
1547
|
+
function buildOrderEntries(orderBy) {
|
|
1548
|
+
const normalized = normalizeOrderByInput(orderBy, parseOrderByValue);
|
|
1549
|
+
const entries = [];
|
|
1550
|
+
for (const item of normalized) {
|
|
1551
|
+
for (const field in item) {
|
|
1552
|
+
if (!Object.prototype.hasOwnProperty.call(item, field)) continue;
|
|
1553
|
+
const value = item[field];
|
|
1554
|
+
if (typeof value === "string") {
|
|
1555
|
+
entries.push({ field, direction: value });
|
|
1556
|
+
} else {
|
|
1557
|
+
entries.push({ field, direction: value.direction, nulls: value.nulls });
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
return entries;
|
|
1562
|
+
}
|
|
1541
1563
|
function buildNotComposite(expr, val, params, dialect, buildOp, separator) {
|
|
1542
1564
|
const entries = Object.entries(val).filter(
|
|
1543
1565
|
([k, v]) => k !== "mode" && v !== void 0
|
|
@@ -2008,7 +2030,7 @@ function buildListRelationFilters(args) {
|
|
|
2008
2030
|
) || relModel.fields.find((f) => !f.isRelation && f.name === "id");
|
|
2009
2031
|
if (checkField) {
|
|
2010
2032
|
const leftJoinSql = `LEFT JOIN ${relTable} ${relAlias} ON ${join}`;
|
|
2011
|
-
const whereClause = `${relAlias}.${
|
|
2033
|
+
const whereClause = `${relAlias}.${quote2(checkField.name)} IS NULL`;
|
|
2012
2034
|
return Object.freeze({
|
|
2013
2035
|
clause: whereClause,
|
|
2014
2036
|
joins: freezeJoins([leftJoinSql])
|
|
@@ -2459,11 +2481,24 @@ function buildOperator(expr, op, val, ctx, mode, fieldType) {
|
|
|
2459
2481
|
// src/builder/shared/alias-generator.ts
|
|
2460
2482
|
function toSafeSqlIdentifier(input) {
|
|
2461
2483
|
const raw = String(input);
|
|
2462
|
-
const
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2484
|
+
const n = raw.length;
|
|
2485
|
+
let out = "";
|
|
2486
|
+
for (let i = 0; i < n; i++) {
|
|
2487
|
+
const c = raw.charCodeAt(i);
|
|
2488
|
+
const isAZ = c >= 65 && c <= 90 || c >= 97 && c <= 122;
|
|
2489
|
+
const is09 = c >= 48 && c <= 57;
|
|
2490
|
+
const isUnderscore = c === 95;
|
|
2491
|
+
if (isAZ || is09 || isUnderscore) {
|
|
2492
|
+
out += raw[i];
|
|
2493
|
+
} else {
|
|
2494
|
+
out += "_";
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2497
|
+
if (out.length === 0) out = "_t";
|
|
2498
|
+
const c0 = out.charCodeAt(0);
|
|
2499
|
+
const startsOk = c0 >= 65 && c0 <= 90 || c0 >= 97 && c0 <= 122 || c0 === 95;
|
|
2500
|
+
if (!startsOk) out = `_${out}`;
|
|
2501
|
+
const lowered = out.toLowerCase();
|
|
2467
2502
|
return ALIAS_FORBIDDEN_KEYWORDS.has(lowered) ? `_${lowered}` : lowered;
|
|
2468
2503
|
}
|
|
2469
2504
|
function createAliasGenerator(maxAliases = 1e4) {
|
|
@@ -2566,14 +2601,16 @@ function validateState(params, mappings, index) {
|
|
|
2566
2601
|
}
|
|
2567
2602
|
function createStoreInternal(startIndex, initialParams = [], initialMappings = []) {
|
|
2568
2603
|
let index = startIndex;
|
|
2569
|
-
const params = [...initialParams];
|
|
2570
|
-
const mappings = [...initialMappings];
|
|
2604
|
+
const params = initialParams.length > 0 ? [...initialParams] : [];
|
|
2605
|
+
const mappings = initialMappings.length > 0 ? [...initialMappings] : [];
|
|
2571
2606
|
const dynamicNameToIndex = /* @__PURE__ */ new Map();
|
|
2572
|
-
for (const m of
|
|
2607
|
+
for (const m of mappings) {
|
|
2573
2608
|
if (typeof m.dynamicName === "string") {
|
|
2574
2609
|
dynamicNameToIndex.set(m.dynamicName.trim(), m.index);
|
|
2575
2610
|
}
|
|
2576
2611
|
}
|
|
2612
|
+
let dirty = true;
|
|
2613
|
+
let cachedSnapshot = null;
|
|
2577
2614
|
function assertCanAdd() {
|
|
2578
2615
|
if (index > MAX_PARAM_INDEX) {
|
|
2579
2616
|
throw new Error(
|
|
@@ -2581,6 +2618,9 @@ function createStoreInternal(startIndex, initialParams = [], initialMappings = [
|
|
|
2581
2618
|
);
|
|
2582
2619
|
}
|
|
2583
2620
|
}
|
|
2621
|
+
function format(position) {
|
|
2622
|
+
return `$${position}`;
|
|
2623
|
+
}
|
|
2584
2624
|
function normalizeDynamicName(dynamicName) {
|
|
2585
2625
|
const dn = dynamicName.trim();
|
|
2586
2626
|
if (dn.length === 0) {
|
|
@@ -2588,20 +2628,16 @@ function createStoreInternal(startIndex, initialParams = [], initialMappings = [
|
|
|
2588
2628
|
}
|
|
2589
2629
|
return dn;
|
|
2590
2630
|
}
|
|
2591
|
-
function format(position) {
|
|
2592
|
-
return `$${position}`;
|
|
2593
|
-
}
|
|
2594
2631
|
function addDynamic(dynamicName) {
|
|
2595
2632
|
const dn = normalizeDynamicName(dynamicName);
|
|
2596
2633
|
const existing = dynamicNameToIndex.get(dn);
|
|
2597
|
-
if (existing !== void 0)
|
|
2598
|
-
return format(existing);
|
|
2599
|
-
}
|
|
2634
|
+
if (existing !== void 0) return format(existing);
|
|
2600
2635
|
const position = index;
|
|
2601
2636
|
dynamicNameToIndex.set(dn, position);
|
|
2602
2637
|
params.push(void 0);
|
|
2603
2638
|
mappings.push({ index: position, dynamicName: dn });
|
|
2604
2639
|
index++;
|
|
2640
|
+
dirty = true;
|
|
2605
2641
|
return format(position);
|
|
2606
2642
|
}
|
|
2607
2643
|
function addStatic(value) {
|
|
@@ -2610,6 +2646,7 @@ function createStoreInternal(startIndex, initialParams = [], initialMappings = [
|
|
|
2610
2646
|
params.push(normalizedValue);
|
|
2611
2647
|
mappings.push({ index: position, value: normalizedValue });
|
|
2612
2648
|
index++;
|
|
2649
|
+
dirty = true;
|
|
2613
2650
|
return format(position);
|
|
2614
2651
|
}
|
|
2615
2652
|
function add(value, dynamicName) {
|
|
@@ -2624,11 +2661,15 @@ function createStoreInternal(startIndex, initialParams = [], initialMappings = [
|
|
|
2624
2661
|
return add(value);
|
|
2625
2662
|
}
|
|
2626
2663
|
function snapshot() {
|
|
2627
|
-
return
|
|
2664
|
+
if (!dirty && cachedSnapshot) return cachedSnapshot;
|
|
2665
|
+
const snap = {
|
|
2628
2666
|
index,
|
|
2629
|
-
params
|
|
2630
|
-
mappings
|
|
2631
|
-
}
|
|
2667
|
+
params,
|
|
2668
|
+
mappings
|
|
2669
|
+
};
|
|
2670
|
+
cachedSnapshot = snap;
|
|
2671
|
+
dirty = false;
|
|
2672
|
+
return snap;
|
|
2632
2673
|
}
|
|
2633
2674
|
return {
|
|
2634
2675
|
add,
|
|
@@ -2837,7 +2878,7 @@ function getRelationTableReference(relModel, dialect) {
|
|
|
2837
2878
|
dialect
|
|
2838
2879
|
);
|
|
2839
2880
|
}
|
|
2840
|
-
function resolveRelationOrThrow(model, schemas, relName) {
|
|
2881
|
+
function resolveRelationOrThrow(model, schemas, schemaByName, relName) {
|
|
2841
2882
|
const field = model.fields.find((f) => f.name === relName);
|
|
2842
2883
|
if (!isNotNullish(field)) {
|
|
2843
2884
|
throw new Error(
|
|
@@ -2849,10 +2890,16 @@ function resolveRelationOrThrow(model, schemas, relName) {
|
|
|
2849
2890
|
`Invalid relation metadata for '${relName}' on model ${model.name}. This usually indicates a schema parsing error (missing foreignKey/references).`
|
|
2850
2891
|
);
|
|
2851
2892
|
}
|
|
2852
|
-
const
|
|
2893
|
+
const relatedModelName = field.relatedModel;
|
|
2894
|
+
if (!isNotNullish(relatedModelName) || String(relatedModelName).trim().length === 0) {
|
|
2895
|
+
throw new Error(
|
|
2896
|
+
`Relation '${relName}' on model ${model.name} is missing relatedModel metadata.`
|
|
2897
|
+
);
|
|
2898
|
+
}
|
|
2899
|
+
const relModel = schemaByName.get(relatedModelName);
|
|
2853
2900
|
if (!isNotNullish(relModel)) {
|
|
2854
2901
|
throw new Error(
|
|
2855
|
-
`Relation '${relName}' on model ${model.name} references missing model '${
|
|
2902
|
+
`Relation '${relName}' on model ${model.name} references missing model '${relatedModelName}'.`
|
|
2856
2903
|
);
|
|
2857
2904
|
}
|
|
2858
2905
|
return { field, relModel };
|
|
@@ -2971,6 +3018,7 @@ function buildSelectWithNestedIncludes(relArgs, relModel, relAlias, ctx) {
|
|
|
2971
3018
|
relArgs,
|
|
2972
3019
|
relModel,
|
|
2973
3020
|
ctx.schemas,
|
|
3021
|
+
ctx.schemaByName,
|
|
2974
3022
|
relAlias,
|
|
2975
3023
|
ctx.aliasGen,
|
|
2976
3024
|
ctx.params,
|
|
@@ -3159,7 +3207,7 @@ function buildSingleInclude(relName, relArgs, field, relModel, ctx) {
|
|
|
3159
3207
|
ctx
|
|
3160
3208
|
});
|
|
3161
3209
|
}
|
|
3162
|
-
function buildIncludeSqlInternal(args, model, schemas, parentAlias, aliasGen, params, dialect, visitPath = [], depth = 0, stats) {
|
|
3210
|
+
function buildIncludeSqlInternal(args, model, schemas, schemaByName, parentAlias, aliasGen, params, dialect, visitPath = [], depth = 0, stats) {
|
|
3163
3211
|
if (!stats) stats = { totalIncludes: 0, totalSubqueries: 0, maxDepth: 0 };
|
|
3164
3212
|
if (depth > MAX_INCLUDE_DEPTH) {
|
|
3165
3213
|
throw new Error(
|
|
@@ -3183,7 +3231,12 @@ function buildIncludeSqlInternal(args, model, schemas, parentAlias, aliasGen, pa
|
|
|
3183
3231
|
`Query complexity limit exceeded: ${stats.totalSubqueries} subqueries generated. Maximum allowed: ${MAX_TOTAL_SUBQUERIES}. This indicates exponential include nesting. Stats: depth=${stats.maxDepth}, includes=${stats.totalIncludes}. Path: ${visitPath.join(" -> ")}. Simplify your include structure or split into multiple queries.`
|
|
3184
3232
|
);
|
|
3185
3233
|
}
|
|
3186
|
-
const resolved = resolveRelationOrThrow(
|
|
3234
|
+
const resolved = resolveRelationOrThrow(
|
|
3235
|
+
model,
|
|
3236
|
+
schemas,
|
|
3237
|
+
schemaByName,
|
|
3238
|
+
relName
|
|
3239
|
+
);
|
|
3187
3240
|
const relationPath = `${model.name}.${relName}`;
|
|
3188
3241
|
const currentPath = [...visitPath, relationPath];
|
|
3189
3242
|
if (visitPath.includes(relationPath)) {
|
|
@@ -3203,6 +3256,7 @@ function buildIncludeSqlInternal(args, model, schemas, parentAlias, aliasGen, pa
|
|
|
3203
3256
|
buildSingleInclude(relName, relArgs, resolved.field, resolved.relModel, {
|
|
3204
3257
|
model,
|
|
3205
3258
|
schemas,
|
|
3259
|
+
schemaByName,
|
|
3206
3260
|
parentAlias,
|
|
3207
3261
|
aliasGen,
|
|
3208
3262
|
dialect,
|
|
@@ -3222,10 +3276,13 @@ function buildIncludeSql(args, model, schemas, parentAlias, params, dialect) {
|
|
|
3222
3276
|
totalSubqueries: 0,
|
|
3223
3277
|
maxDepth: 0
|
|
3224
3278
|
};
|
|
3279
|
+
const schemaByName = /* @__PURE__ */ new Map();
|
|
3280
|
+
for (const m of schemas) schemaByName.set(m.name, m);
|
|
3225
3281
|
return buildIncludeSqlInternal(
|
|
3226
3282
|
args,
|
|
3227
3283
|
model,
|
|
3228
3284
|
schemas,
|
|
3285
|
+
schemaByName,
|
|
3229
3286
|
parentAlias,
|
|
3230
3287
|
aliasGen,
|
|
3231
3288
|
params,
|
|
@@ -3235,7 +3292,7 @@ function buildIncludeSql(args, model, schemas, parentAlias, params, dialect) {
|
|
|
3235
3292
|
stats
|
|
3236
3293
|
);
|
|
3237
3294
|
}
|
|
3238
|
-
function resolveCountRelationOrThrow(relName, model, schemas) {
|
|
3295
|
+
function resolveCountRelationOrThrow(relName, model, schemas, schemaByName) {
|
|
3239
3296
|
const relationSet = getRelationFieldSet(model);
|
|
3240
3297
|
if (!relationSet.has(relName)) {
|
|
3241
3298
|
throw new Error(
|
|
@@ -3252,10 +3309,16 @@ function resolveCountRelationOrThrow(relName, model, schemas) {
|
|
|
3252
3309
|
`_count.${relName} has invalid relation metadata on model ${model.name}`
|
|
3253
3310
|
);
|
|
3254
3311
|
}
|
|
3255
|
-
const
|
|
3312
|
+
const relatedModelName = field.relatedModel;
|
|
3313
|
+
if (!isNotNullish(relatedModelName) || String(relatedModelName).trim().length === 0) {
|
|
3314
|
+
throw new Error(
|
|
3315
|
+
`_count.${relName} is missing relatedModel metadata on model ${model.name}`
|
|
3316
|
+
);
|
|
3317
|
+
}
|
|
3318
|
+
const relModel = schemaByName.get(relatedModelName);
|
|
3256
3319
|
if (!relModel) {
|
|
3257
3320
|
throw new Error(
|
|
3258
|
-
`Related model '${
|
|
3321
|
+
`Related model '${relatedModelName}' not found for _count.${relName}`
|
|
3259
3322
|
);
|
|
3260
3323
|
}
|
|
3261
3324
|
return { field, relModel };
|
|
@@ -3341,9 +3404,16 @@ function buildRelationCountSql(countSelect, model, schemas, parentAlias, _params
|
|
|
3341
3404
|
const joins = [];
|
|
3342
3405
|
const pairs = [];
|
|
3343
3406
|
const aliasGen = createAliasGenerator();
|
|
3407
|
+
const schemaByName = /* @__PURE__ */ new Map();
|
|
3408
|
+
for (const m of schemas) schemaByName.set(m.name, m);
|
|
3344
3409
|
for (const [relName, shouldCount] of Object.entries(countSelect)) {
|
|
3345
3410
|
if (!shouldCount) continue;
|
|
3346
|
-
const resolved = resolveCountRelationOrThrow(
|
|
3411
|
+
const resolved = resolveCountRelationOrThrow(
|
|
3412
|
+
relName,
|
|
3413
|
+
model,
|
|
3414
|
+
schemas,
|
|
3415
|
+
schemaByName
|
|
3416
|
+
);
|
|
3347
3417
|
const built = buildCountJoinAndPair({
|
|
3348
3418
|
relName,
|
|
3349
3419
|
field: resolved.field,
|
|
@@ -3359,35 +3429,36 @@ function buildRelationCountSql(countSelect, model, schemas, parentAlias, _params
|
|
|
3359
3429
|
return { joins, jsonPairs: pairs.join(SQL_SEPARATORS.FIELD_LIST) };
|
|
3360
3430
|
}
|
|
3361
3431
|
|
|
3362
|
-
// src/builder/
|
|
3363
|
-
var ALIAS_CAPTURE = "([A-Za-z_][A-Za-z0-9_]*)";
|
|
3364
|
-
var COLUMN_PART = '(?:"([^"]+)"|([a-z_][a-z0-9_]*))';
|
|
3365
|
-
var AS_PART = `(?:\\s+AS\\s+${COLUMN_PART})?`;
|
|
3366
|
-
var SIMPLE_COLUMN_PATTERN = `^${ALIAS_CAPTURE}\\.${COLUMN_PART}${AS_PART}$`;
|
|
3367
|
-
var SIMPLE_COLUMN_RE = new RegExp(SIMPLE_COLUMN_PATTERN, "i");
|
|
3432
|
+
// src/builder/shared/string-builder.ts
|
|
3368
3433
|
function joinNonEmpty(parts, sep) {
|
|
3369
|
-
|
|
3434
|
+
let result = "";
|
|
3435
|
+
for (const p of parts) {
|
|
3436
|
+
if (p) {
|
|
3437
|
+
if (result) result += sep;
|
|
3438
|
+
result += p;
|
|
3439
|
+
}
|
|
3440
|
+
}
|
|
3441
|
+
return result;
|
|
3370
3442
|
}
|
|
3371
3443
|
function buildWhereSql(conditions) {
|
|
3372
3444
|
if (!isNonEmptyArray(conditions)) return "";
|
|
3373
|
-
|
|
3374
|
-
SQL_TEMPLATES.WHERE,
|
|
3375
|
-
conditions.join(SQL_SEPARATORS.CONDITION_AND)
|
|
3376
|
-
];
|
|
3377
|
-
return ` ${parts.join(" ")}`;
|
|
3445
|
+
return " " + SQL_TEMPLATES.WHERE + " " + conditions.join(SQL_SEPARATORS.CONDITION_AND);
|
|
3378
3446
|
}
|
|
3379
3447
|
function buildJoinsSql(...joinGroups) {
|
|
3380
3448
|
const all = [];
|
|
3381
3449
|
for (const g of joinGroups) {
|
|
3382
|
-
if (isNonEmptyArray(g))
|
|
3450
|
+
if (isNonEmptyArray(g)) {
|
|
3451
|
+
for (const j of g) all.push(j);
|
|
3452
|
+
}
|
|
3383
3453
|
}
|
|
3384
|
-
return all.length > 0 ?
|
|
3454
|
+
return all.length > 0 ? " " + all.join(" ") : "";
|
|
3385
3455
|
}
|
|
3386
3456
|
function buildSelectList(baseSelect, extraCols) {
|
|
3387
3457
|
const base = baseSelect.trim();
|
|
3388
3458
|
const extra = extraCols.trim();
|
|
3389
|
-
if (base
|
|
3390
|
-
return base
|
|
3459
|
+
if (!base) return extra;
|
|
3460
|
+
if (!extra) return base;
|
|
3461
|
+
return base + SQL_SEPARATORS.FIELD_LIST + extra;
|
|
3391
3462
|
}
|
|
3392
3463
|
function finalizeSql(sql, params, dialect) {
|
|
3393
3464
|
const snapshot = params.snapshot();
|
|
@@ -3397,38 +3468,128 @@ function finalizeSql(sql, params, dialect) {
|
|
|
3397
3468
|
snapshot.params,
|
|
3398
3469
|
dialect === "sqlite" ? "postgres" : dialect
|
|
3399
3470
|
);
|
|
3400
|
-
return
|
|
3471
|
+
return {
|
|
3401
3472
|
sql,
|
|
3402
3473
|
params: snapshot.params,
|
|
3403
|
-
paramMappings:
|
|
3404
|
-
}
|
|
3474
|
+
paramMappings: snapshot.mappings
|
|
3475
|
+
};
|
|
3405
3476
|
}
|
|
3406
3477
|
function parseSimpleScalarSelect(select, fromAlias) {
|
|
3407
|
-
var _a, _b, _c, _d;
|
|
3408
3478
|
const raw = select.trim();
|
|
3409
3479
|
if (raw.length === 0) return [];
|
|
3480
|
+
const fromLower = fromAlias.toLowerCase();
|
|
3410
3481
|
const parts = raw.split(SQL_SEPARATORS.FIELD_LIST);
|
|
3411
3482
|
const names = [];
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
const
|
|
3415
|
-
if (
|
|
3483
|
+
const isIdent = (s) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(s);
|
|
3484
|
+
const readIdentOrQuoted = (s, start) => {
|
|
3485
|
+
const n = s.length;
|
|
3486
|
+
if (start >= n) return { text: "", next: start, quoted: false };
|
|
3487
|
+
if (s.charCodeAt(start) === 34) {
|
|
3488
|
+
let i2 = start + 1;
|
|
3489
|
+
let out = "";
|
|
3490
|
+
let saw = false;
|
|
3491
|
+
while (i2 < n) {
|
|
3492
|
+
const c = s.charCodeAt(i2);
|
|
3493
|
+
if (c === 34) {
|
|
3494
|
+
const next = i2 + 1;
|
|
3495
|
+
if (next < n && s.charCodeAt(next) === 34) {
|
|
3496
|
+
out += '"';
|
|
3497
|
+
saw = true;
|
|
3498
|
+
i2 += 2;
|
|
3499
|
+
continue;
|
|
3500
|
+
}
|
|
3501
|
+
if (!saw)
|
|
3502
|
+
throw new Error(
|
|
3503
|
+
`sqlite distinct emulation: empty quoted identifier in: ${s}`
|
|
3504
|
+
);
|
|
3505
|
+
return { text: out, next: i2 + 1, quoted: true };
|
|
3506
|
+
}
|
|
3507
|
+
out += s[i2];
|
|
3508
|
+
saw = true;
|
|
3509
|
+
i2++;
|
|
3510
|
+
}
|
|
3416
3511
|
throw new Error(
|
|
3417
|
-
`sqlite distinct emulation
|
|
3512
|
+
`sqlite distinct emulation: unterminated quoted identifier in: ${s}`
|
|
3418
3513
|
);
|
|
3419
3514
|
}
|
|
3420
|
-
|
|
3421
|
-
|
|
3515
|
+
let i = start;
|
|
3516
|
+
while (i < n) {
|
|
3517
|
+
const c = s.charCodeAt(i);
|
|
3518
|
+
if (c === 32 || c === 9) break;
|
|
3519
|
+
if (c === 46) break;
|
|
3520
|
+
i++;
|
|
3521
|
+
}
|
|
3522
|
+
return { text: s.slice(start, i), next: i, quoted: false };
|
|
3523
|
+
};
|
|
3524
|
+
const skipSpaces = (s, i) => {
|
|
3525
|
+
while (i < s.length) {
|
|
3526
|
+
const c = s.charCodeAt(i);
|
|
3527
|
+
if (c !== 32 && c !== 9) break;
|
|
3528
|
+
i++;
|
|
3529
|
+
}
|
|
3530
|
+
return i;
|
|
3531
|
+
};
|
|
3532
|
+
for (let idx = 0; idx < parts.length; idx++) {
|
|
3533
|
+
const p = parts[idx].trim();
|
|
3534
|
+
if (p.length === 0) continue;
|
|
3535
|
+
let i = 0;
|
|
3536
|
+
i = skipSpaces(p, i);
|
|
3537
|
+
const a = readIdentOrQuoted(p, i);
|
|
3538
|
+
const actualAlias = a.text.toLowerCase();
|
|
3539
|
+
if (!isIdent(a.text)) {
|
|
3422
3540
|
throw new Error(
|
|
3423
|
-
`
|
|
3541
|
+
`sqlite distinct emulation requires scalar select fields to be simple columns (alias.column). Got: ${p}`
|
|
3424
3542
|
);
|
|
3425
3543
|
}
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3544
|
+
if (actualAlias !== fromLower) {
|
|
3545
|
+
throw new Error(`Expected alias '${fromAlias}', got '${a.text}' in: ${p}`);
|
|
3546
|
+
}
|
|
3547
|
+
i = a.next;
|
|
3548
|
+
if (i >= p.length || p.charCodeAt(i) !== 46) {
|
|
3549
|
+
throw new Error(
|
|
3550
|
+
`sqlite distinct emulation requires scalar select fields to be simple columns (alias.column). Got: ${p}`
|
|
3551
|
+
);
|
|
3552
|
+
}
|
|
3553
|
+
i++;
|
|
3554
|
+
i = skipSpaces(p, i);
|
|
3555
|
+
const colPart = readIdentOrQuoted(p, i);
|
|
3556
|
+
const columnName = colPart.text.trim();
|
|
3557
|
+
if (columnName.length === 0) {
|
|
3430
3558
|
throw new Error(`Failed to parse selected column name from: ${p}`);
|
|
3431
3559
|
}
|
|
3560
|
+
i = colPart.next;
|
|
3561
|
+
i = skipSpaces(p, i);
|
|
3562
|
+
let outAlias = "";
|
|
3563
|
+
if (i < p.length) {
|
|
3564
|
+
const rest = p.slice(i).trim();
|
|
3565
|
+
if (rest.length > 0) {
|
|
3566
|
+
const m = rest.match(/^AS\s+/i);
|
|
3567
|
+
if (!m) {
|
|
3568
|
+
throw new Error(
|
|
3569
|
+
`sqlite distinct emulation requires scalar select fields to be simple columns (optionally with AS). Got: ${p}`
|
|
3570
|
+
);
|
|
3571
|
+
}
|
|
3572
|
+
let j = i;
|
|
3573
|
+
j = skipSpaces(p, j);
|
|
3574
|
+
if (!/^AS\b/i.test(p.slice(j))) {
|
|
3575
|
+
throw new Error(`Failed to parse AS in: ${p}`);
|
|
3576
|
+
}
|
|
3577
|
+
j += 2;
|
|
3578
|
+
j = skipSpaces(p, j);
|
|
3579
|
+
const out = readIdentOrQuoted(p, j);
|
|
3580
|
+
outAlias = out.text.trim();
|
|
3581
|
+
if (outAlias.length === 0) {
|
|
3582
|
+
throw new Error(`Failed to parse output alias from: ${p}`);
|
|
3583
|
+
}
|
|
3584
|
+
j = skipSpaces(p, out.next);
|
|
3585
|
+
if (j !== p.length) {
|
|
3586
|
+
throw new Error(
|
|
3587
|
+
`sqlite distinct emulation requires scalar select fields to be simple columns (optionally with AS). Got: ${p}`
|
|
3588
|
+
);
|
|
3589
|
+
}
|
|
3590
|
+
}
|
|
3591
|
+
}
|
|
3592
|
+
const name = outAlias.length > 0 ? outAlias : columnName;
|
|
3432
3593
|
names.push(name);
|
|
3433
3594
|
}
|
|
3434
3595
|
return names;
|
|
@@ -3438,15 +3599,14 @@ function replaceOrderByAlias(orderBy, fromAlias, outerAlias) {
|
|
|
3438
3599
|
if (src.length === 0) return orderBy;
|
|
3439
3600
|
const escaped = src.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3440
3601
|
const re = new RegExp(`\\b${escaped}\\.`, "gi");
|
|
3441
|
-
return orderBy.replace(re,
|
|
3602
|
+
return orderBy.replace(re, outerAlias + ".");
|
|
3442
3603
|
}
|
|
3443
3604
|
function buildDistinctColumns(distinct, fromAlias, model) {
|
|
3444
3605
|
return distinct.map((f) => col(fromAlias, f, model)).join(SQL_SEPARATORS.FIELD_LIST);
|
|
3445
3606
|
}
|
|
3446
3607
|
function buildOutputColumns(scalarNames, includeNames, hasCount) {
|
|
3447
|
-
const outputCols = [...scalarNames, ...includeNames];
|
|
3448
|
-
|
|
3449
|
-
const formatted = outputCols.map((n) => quote(n)).join(SQL_SEPARATORS.FIELD_LIST);
|
|
3608
|
+
const outputCols = hasCount ? [...scalarNames, ...includeNames, "_count"] : [...scalarNames, ...includeNames];
|
|
3609
|
+
const formatted = outputCols.map((n) => quote2(n)).join(SQL_SEPARATORS.FIELD_LIST);
|
|
3450
3610
|
if (!isNonEmptyString(formatted)) {
|
|
3451
3611
|
throw new Error("distinct emulation requires at least one output column");
|
|
3452
3612
|
}
|
|
@@ -3457,11 +3617,11 @@ function buildWindowOrder(args) {
|
|
|
3457
3617
|
const fromLower = String(fromAlias).toLowerCase();
|
|
3458
3618
|
const orderFields = baseOrder.split(SQL_SEPARATORS.ORDER_BY).map((s) => s.trim().toLowerCase());
|
|
3459
3619
|
const hasIdInOrder = orderFields.some(
|
|
3460
|
-
(f) => f.startsWith(
|
|
3620
|
+
(f) => f.startsWith(fromLower + ".id ") || f.startsWith(fromLower + '."id" ')
|
|
3461
3621
|
);
|
|
3462
3622
|
if (hasIdInOrder) return baseOrder;
|
|
3463
|
-
const idTiebreaker = idField ?
|
|
3464
|
-
return
|
|
3623
|
+
const idTiebreaker = idField ? ", " + col(fromAlias, "id", model) + " ASC" : "";
|
|
3624
|
+
return baseOrder + idTiebreaker;
|
|
3465
3625
|
}
|
|
3466
3626
|
function buildSqliteDistinctQuery(spec, selectWithIncludes, countJoins) {
|
|
3467
3627
|
var _a, _b;
|
|
@@ -3478,7 +3638,7 @@ function buildSqliteDistinctQuery(spec, selectWithIncludes, countJoins) {
|
|
|
3478
3638
|
hasCount
|
|
3479
3639
|
);
|
|
3480
3640
|
const distinctCols = buildDistinctColumns([...distinct], from.alias, model);
|
|
3481
|
-
const fallbackOrder = [...distinct].map((f) =>
|
|
3641
|
+
const fallbackOrder = [...distinct].map((f) => col(from.alias, f, model) + " ASC").join(SQL_SEPARATORS.FIELD_LIST);
|
|
3482
3642
|
const idField = model.fields.find(
|
|
3483
3643
|
(f) => f.name === "id" && !f.isRelation
|
|
3484
3644
|
);
|
|
@@ -3489,7 +3649,7 @@ function buildSqliteDistinctQuery(spec, selectWithIncludes, countJoins) {
|
|
|
3489
3649
|
fromAlias: from.alias,
|
|
3490
3650
|
model
|
|
3491
3651
|
});
|
|
3492
|
-
const outerOrder = isNonEmptyString(orderBy) ? replaceOrderByAlias(orderBy, from.alias,
|
|
3652
|
+
const outerOrder = isNonEmptyString(orderBy) ? replaceOrderByAlias(orderBy, from.alias, '"__tp_distinct"') : replaceOrderByAlias(fallbackOrder, from.alias, '"__tp_distinct"');
|
|
3493
3653
|
const joins = buildJoinsSql(whereJoins, countJoins);
|
|
3494
3654
|
const conditions = [];
|
|
3495
3655
|
if (whereClause && whereClause !== "1=1") conditions.push(whereClause);
|
|
@@ -3499,7 +3659,7 @@ function buildSqliteDistinctQuery(spec, selectWithIncludes, countJoins) {
|
|
|
3499
3659
|
const innerParts = [
|
|
3500
3660
|
SQL_TEMPLATES.SELECT,
|
|
3501
3661
|
innerSelectList + innerComma,
|
|
3502
|
-
|
|
3662
|
+
"ROW_NUMBER() OVER (PARTITION BY " + distinctCols + " ORDER BY " + windowOrder + ")",
|
|
3503
3663
|
SQL_TEMPLATES.AS,
|
|
3504
3664
|
'"__tp_rn"',
|
|
3505
3665
|
SQL_TEMPLATES.FROM,
|
|
@@ -3508,12 +3668,12 @@ function buildSqliteDistinctQuery(spec, selectWithIncludes, countJoins) {
|
|
|
3508
3668
|
];
|
|
3509
3669
|
if (joins) innerParts.push(joins);
|
|
3510
3670
|
if (whereSql) innerParts.push(whereSql);
|
|
3511
|
-
const inner = innerParts.
|
|
3671
|
+
const inner = innerParts.join(" ");
|
|
3512
3672
|
const outerParts = [
|
|
3513
3673
|
SQL_TEMPLATES.SELECT,
|
|
3514
3674
|
outerSelectCols,
|
|
3515
3675
|
SQL_TEMPLATES.FROM,
|
|
3516
|
-
|
|
3676
|
+
"(" + inner + ")",
|
|
3517
3677
|
SQL_TEMPLATES.AS,
|
|
3518
3678
|
'"__tp_distinct"',
|
|
3519
3679
|
SQL_TEMPLATES.WHERE,
|
|
@@ -3522,7 +3682,7 @@ function buildSqliteDistinctQuery(spec, selectWithIncludes, countJoins) {
|
|
|
3522
3682
|
if (isNonEmptyString(outerOrder)) {
|
|
3523
3683
|
outerParts.push(SQL_TEMPLATES.ORDER_BY, outerOrder);
|
|
3524
3684
|
}
|
|
3525
|
-
return outerParts.
|
|
3685
|
+
return outerParts.join(" ");
|
|
3526
3686
|
}
|
|
3527
3687
|
function buildIncludeColumns(spec) {
|
|
3528
3688
|
var _a, _b;
|
|
@@ -3542,7 +3702,7 @@ function buildIncludeColumns(spec) {
|
|
|
3542
3702
|
dialect
|
|
3543
3703
|
);
|
|
3544
3704
|
if (countBuild.jsonPairs) {
|
|
3545
|
-
countCols =
|
|
3705
|
+
countCols = jsonBuildObject(countBuild.jsonPairs, dialect) + " " + SQL_TEMPLATES.AS + " " + quote2("_count");
|
|
3546
3706
|
}
|
|
3547
3707
|
countJoins = countBuild.joins;
|
|
3548
3708
|
}
|
|
@@ -3554,8 +3714,8 @@ function buildIncludeColumns(spec) {
|
|
|
3554
3714
|
}
|
|
3555
3715
|
const emptyJson = dialect === "postgres" ? `'[]'::json` : `json('[]')`;
|
|
3556
3716
|
const includeCols = hasIncludes ? includes.map((inc) => {
|
|
3557
|
-
const expr = inc.isOneToOne ?
|
|
3558
|
-
return
|
|
3717
|
+
const expr = inc.isOneToOne ? "(" + inc.sql + ")" : "COALESCE((" + inc.sql + "), " + emptyJson + ")";
|
|
3718
|
+
return expr + " " + SQL_TEMPLATES.AS + " " + quote2(inc.name);
|
|
3559
3719
|
}).join(SQL_SEPARATORS.FIELD_LIST) : "";
|
|
3560
3720
|
const allCols = joinNonEmpty(
|
|
3561
3721
|
[includeCols, countCols],
|
|
@@ -3623,7 +3783,7 @@ function withCountJoins(spec, countJoins, whereJoins) {
|
|
|
3623
3783
|
function buildPostgresDistinctOnClause(fromAlias, distinct, model) {
|
|
3624
3784
|
if (!isNonEmptyArray(distinct)) return null;
|
|
3625
3785
|
const distinctCols = buildDistinctColumns([...distinct], fromAlias, model);
|
|
3626
|
-
return
|
|
3786
|
+
return SQL_TEMPLATES.DISTINCT_ON + " (" + distinctCols + ")";
|
|
3627
3787
|
}
|
|
3628
3788
|
function pushJoinGroups(parts, ...groups) {
|
|
3629
3789
|
for (const g of groups) {
|
|
@@ -3668,9 +3828,7 @@ function constructFinalSql(spec) {
|
|
|
3668
3828
|
return finalizeSql(sql2, params, dialect);
|
|
3669
3829
|
}
|
|
3670
3830
|
const parts = [];
|
|
3671
|
-
if (cursorCte)
|
|
3672
|
-
parts.push(`WITH ${cursorCte}`);
|
|
3673
|
-
}
|
|
3831
|
+
if (cursorCte) parts.push("WITH " + cursorCte);
|
|
3674
3832
|
parts.push(SQL_TEMPLATES.SELECT);
|
|
3675
3833
|
const distinctOn = dialect === "postgres" ? buildPostgresDistinctOnClause(from.alias, distinct, model) : null;
|
|
3676
3834
|
if (distinctOn) parts.push(distinctOn);
|
|
@@ -3681,6 +3839,10 @@ function constructFinalSql(spec) {
|
|
|
3681
3839
|
}
|
|
3682
3840
|
parts.push(fullSelectList);
|
|
3683
3841
|
parts.push(SQL_TEMPLATES.FROM, from.table, from.alias);
|
|
3842
|
+
if (cursorCte) {
|
|
3843
|
+
const cteName = cursorCte.split(" AS ")[0].trim();
|
|
3844
|
+
parts.push("CROSS JOIN", cteName);
|
|
3845
|
+
}
|
|
3684
3846
|
pushJoinGroups(parts, whereJoins, countJoins);
|
|
3685
3847
|
const conditions = buildConditions(whereClause, cursorClause);
|
|
3686
3848
|
pushWhere(parts, conditions);
|
|
@@ -3903,14 +4065,16 @@ function buildSelectSql(input) {
|
|
|
3903
4065
|
}
|
|
3904
4066
|
|
|
3905
4067
|
// src/builder/shared/comparison-builder.ts
|
|
3906
|
-
|
|
4068
|
+
var DEFAULT_EXCLUDE_KEYS = /* @__PURE__ */ new Set(["mode"]);
|
|
4069
|
+
function buildComparisons(expr, filter, params, dialect, builder, excludeKeys = DEFAULT_EXCLUDE_KEYS) {
|
|
3907
4070
|
const out = [];
|
|
3908
|
-
for (const
|
|
3909
|
-
if (
|
|
4071
|
+
for (const op in filter) {
|
|
4072
|
+
if (!Object.prototype.hasOwnProperty.call(filter, op)) continue;
|
|
4073
|
+
if (excludeKeys.has(op)) continue;
|
|
4074
|
+
const val = filter[op];
|
|
4075
|
+
if (val === void 0) continue;
|
|
3910
4076
|
const built = builder(expr, op, val, params, dialect);
|
|
3911
|
-
if (built && built.trim().length > 0)
|
|
3912
|
-
out.push(built);
|
|
3913
|
-
}
|
|
4077
|
+
if (built && built.trim().length > 0) out.push(built);
|
|
3914
4078
|
}
|
|
3915
4079
|
return out;
|
|
3916
4080
|
}
|
|
@@ -3940,6 +4104,13 @@ var HAVING_ALLOWED_OPS = /* @__PURE__ */ new Set([
|
|
|
3940
4104
|
Ops.IN,
|
|
3941
4105
|
Ops.NOT_IN
|
|
3942
4106
|
]);
|
|
4107
|
+
var HAVING_FIELD_FIRST_AGG_KEYS = Object.freeze([
|
|
4108
|
+
"_count",
|
|
4109
|
+
"_sum",
|
|
4110
|
+
"_avg",
|
|
4111
|
+
"_min",
|
|
4112
|
+
"_max"
|
|
4113
|
+
]);
|
|
3943
4114
|
function isTruthySelection(v) {
|
|
3944
4115
|
return v === true;
|
|
3945
4116
|
}
|
|
@@ -3958,15 +4129,15 @@ function assertHavingOp(op) {
|
|
|
3958
4129
|
}
|
|
3959
4130
|
function aggExprForField(aggKey, field, alias, model) {
|
|
3960
4131
|
if (aggKey === "_count") {
|
|
3961
|
-
return field === "_all" ?
|
|
4132
|
+
return field === "_all" ? "COUNT(*)" : "COUNT(" + col(alias, field, model) + ")";
|
|
3962
4133
|
}
|
|
3963
4134
|
if (field === "_all") {
|
|
3964
4135
|
throw new Error(`'${aggKey}' does not support '_all'`);
|
|
3965
4136
|
}
|
|
3966
|
-
if (aggKey === "_sum") return
|
|
3967
|
-
if (aggKey === "_avg") return
|
|
3968
|
-
if (aggKey === "_min") return
|
|
3969
|
-
return
|
|
4137
|
+
if (aggKey === "_sum") return "SUM(" + col(alias, field, model) + ")";
|
|
4138
|
+
if (aggKey === "_avg") return "AVG(" + col(alias, field, model) + ")";
|
|
4139
|
+
if (aggKey === "_min") return "MIN(" + col(alias, field, model) + ")";
|
|
4140
|
+
return "MAX(" + col(alias, field, model) + ")";
|
|
3970
4141
|
}
|
|
3971
4142
|
function buildComparisonOp(op) {
|
|
3972
4143
|
const sqlOp = COMPARISON_OPS[op];
|
|
@@ -3993,8 +4164,8 @@ function normalizeLogicalValue2(operator, value) {
|
|
|
3993
4164
|
throw new Error(`${operator} must be an object or array of objects in HAVING`);
|
|
3994
4165
|
}
|
|
3995
4166
|
function buildNullComparison(expr, op) {
|
|
3996
|
-
if (op === Ops.EQUALS) return
|
|
3997
|
-
if (op === Ops.NOT) return
|
|
4167
|
+
if (op === Ops.EQUALS) return expr + " " + SQL_TEMPLATES.IS_NULL;
|
|
4168
|
+
if (op === Ops.NOT) return expr + " " + SQL_TEMPLATES.IS_NOT_NULL;
|
|
3998
4169
|
throw new Error(`Operator '${op}' doesn't support null in HAVING`);
|
|
3999
4170
|
}
|
|
4000
4171
|
function buildInComparison(expr, op, val, params, dialect) {
|
|
@@ -4015,7 +4186,7 @@ function buildInComparison(expr, op, val, params, dialect) {
|
|
|
4015
4186
|
function buildBinaryComparison(expr, op, val, params) {
|
|
4016
4187
|
const sqlOp = buildComparisonOp(op);
|
|
4017
4188
|
const placeholder = addHavingParam(params, op, val);
|
|
4018
|
-
return
|
|
4189
|
+
return expr + " " + sqlOp + " " + placeholder;
|
|
4019
4190
|
}
|
|
4020
4191
|
function buildSimpleComparison(expr, op, val, params, dialect) {
|
|
4021
4192
|
assertHavingOp(op);
|
|
@@ -4036,20 +4207,21 @@ function buildSimpleComparison(expr, op, val, params, dialect) {
|
|
|
4036
4207
|
return buildBinaryComparison(expr, op, val, params);
|
|
4037
4208
|
}
|
|
4038
4209
|
function negateClauses(subClauses) {
|
|
4039
|
-
if (subClauses.length === 1) return
|
|
4040
|
-
return
|
|
4210
|
+
if (subClauses.length === 1) return SQL_TEMPLATES.NOT + " " + subClauses[0];
|
|
4211
|
+
return SQL_TEMPLATES.NOT + " (" + subClauses.join(SQL_SEPARATORS.CONDITION_AND) + ")";
|
|
4041
4212
|
}
|
|
4042
4213
|
function combineLogical(key, subClauses) {
|
|
4043
4214
|
if (key === LogicalOps.NOT) return negateClauses(subClauses);
|
|
4044
|
-
return subClauses.join(
|
|
4215
|
+
return subClauses.join(" " + key + " ");
|
|
4045
4216
|
}
|
|
4046
4217
|
function buildHavingNode(node, alias, params, dialect, model) {
|
|
4047
4218
|
const clauses = [];
|
|
4048
|
-
const
|
|
4049
|
-
|
|
4219
|
+
for (const key in node) {
|
|
4220
|
+
if (!Object.prototype.hasOwnProperty.call(node, key)) continue;
|
|
4221
|
+
const value = node[key];
|
|
4050
4222
|
const built = buildHavingEntry(key, value, alias, params, dialect, model);
|
|
4051
4223
|
for (const c of built) {
|
|
4052
|
-
if (c && c.
|
|
4224
|
+
if (c && c.length > 0) clauses.push(c);
|
|
4053
4225
|
}
|
|
4054
4226
|
}
|
|
4055
4227
|
return clauses.join(SQL_SEPARATORS.CONDITION_AND);
|
|
@@ -4059,7 +4231,7 @@ function buildLogicalClause2(key, value, alias, params, dialect, model) {
|
|
|
4059
4231
|
const subClauses = [];
|
|
4060
4232
|
for (const it of items) {
|
|
4061
4233
|
const c = buildHavingNode(it, alias, params, dialect, model);
|
|
4062
|
-
if (c && c.
|
|
4234
|
+
if (c && c.length > 0) subClauses.push("(" + c + ")");
|
|
4063
4235
|
}
|
|
4064
4236
|
if (subClauses.length === 0) return "";
|
|
4065
4237
|
return combineLogical(key, subClauses);
|
|
@@ -4084,11 +4256,23 @@ function buildHavingForAggregateFirstShape(aggKey, target, alias, params, dialec
|
|
|
4084
4256
|
throw new Error(`HAVING '${aggKey}' must be an object`);
|
|
4085
4257
|
}
|
|
4086
4258
|
const out = [];
|
|
4087
|
-
|
|
4259
|
+
const targetObj = target;
|
|
4260
|
+
for (const field in targetObj) {
|
|
4261
|
+
if (!Object.prototype.hasOwnProperty.call(targetObj, field)) continue;
|
|
4088
4262
|
assertHavingAggTarget(aggKey, field, model);
|
|
4089
|
-
|
|
4263
|
+
const filter = targetObj[field];
|
|
4264
|
+
if (!isPlainObject(filter)) continue;
|
|
4265
|
+
const filterObj = filter;
|
|
4266
|
+
let hasAny = false;
|
|
4267
|
+
for (const k in filterObj) {
|
|
4268
|
+
if (Object.prototype.hasOwnProperty.call(filterObj, k)) {
|
|
4269
|
+
hasAny = true;
|
|
4270
|
+
break;
|
|
4271
|
+
}
|
|
4272
|
+
}
|
|
4273
|
+
if (!hasAny) continue;
|
|
4090
4274
|
const expr = aggExprForField(aggKey, field, alias, model);
|
|
4091
|
-
out.push(...buildHavingOpsForExpr(expr,
|
|
4275
|
+
out.push(...buildHavingOpsForExpr(expr, filterObj, params, dialect));
|
|
4092
4276
|
}
|
|
4093
4277
|
return out;
|
|
4094
4278
|
}
|
|
@@ -4099,16 +4283,23 @@ function buildHavingForFieldFirstShape(fieldName, target, alias, params, dialect
|
|
|
4099
4283
|
assertScalarField(model, fieldName, "HAVING");
|
|
4100
4284
|
const out = [];
|
|
4101
4285
|
const obj = target;
|
|
4102
|
-
const
|
|
4103
|
-
for (const aggKey of keys) {
|
|
4286
|
+
for (const aggKey of HAVING_FIELD_FIRST_AGG_KEYS) {
|
|
4104
4287
|
const aggFilter = obj[aggKey];
|
|
4105
4288
|
if (!isPlainObject(aggFilter)) continue;
|
|
4106
|
-
|
|
4289
|
+
const aggFilterObj = aggFilter;
|
|
4290
|
+
let hasAny = false;
|
|
4291
|
+
for (const k in aggFilterObj) {
|
|
4292
|
+
if (Object.prototype.hasOwnProperty.call(aggFilterObj, k)) {
|
|
4293
|
+
hasAny = true;
|
|
4294
|
+
break;
|
|
4295
|
+
}
|
|
4296
|
+
}
|
|
4297
|
+
if (!hasAny) continue;
|
|
4107
4298
|
if (aggKey === "_sum" || aggKey === "_avg") {
|
|
4108
4299
|
assertNumericField(model, fieldName, "HAVING");
|
|
4109
4300
|
}
|
|
4110
4301
|
const expr = aggExprForField(aggKey, fieldName, alias, model);
|
|
4111
|
-
out.push(...buildHavingOpsForExpr(expr,
|
|
4302
|
+
out.push(...buildHavingOpsForExpr(expr, aggFilterObj, params, dialect));
|
|
4112
4303
|
}
|
|
4113
4304
|
return out;
|
|
4114
4305
|
}
|
|
@@ -4157,13 +4348,13 @@ function normalizeCountArg(v) {
|
|
|
4157
4348
|
}
|
|
4158
4349
|
function pushCountAllField(fields) {
|
|
4159
4350
|
fields.push(
|
|
4160
|
-
|
|
4351
|
+
SQL_TEMPLATES.COUNT_ALL + " " + SQL_TEMPLATES.AS + " " + quote2("_count._all")
|
|
4161
4352
|
);
|
|
4162
4353
|
}
|
|
4163
4354
|
function pushCountField(fields, alias, fieldName, model) {
|
|
4164
|
-
const outAlias =
|
|
4355
|
+
const outAlias = "_count." + fieldName;
|
|
4165
4356
|
fields.push(
|
|
4166
|
-
|
|
4357
|
+
"COUNT(" + col(alias, fieldName, model) + ") " + SQL_TEMPLATES.AS + " " + quote2(outAlias)
|
|
4167
4358
|
);
|
|
4168
4359
|
}
|
|
4169
4360
|
function addCountFields(fields, countArg, alias, model) {
|
|
@@ -4176,12 +4367,14 @@ function addCountFields(fields, countArg, alias, model) {
|
|
|
4176
4367
|
if (countArg._all === true) {
|
|
4177
4368
|
pushCountAllField(fields);
|
|
4178
4369
|
}
|
|
4179
|
-
const
|
|
4180
|
-
(
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4370
|
+
for (const f in countArg) {
|
|
4371
|
+
if (!Object.prototype.hasOwnProperty.call(countArg, f)) continue;
|
|
4372
|
+
if (f === "_all") continue;
|
|
4373
|
+
const v = countArg[f];
|
|
4374
|
+
if (isTruthySelection(v)) {
|
|
4375
|
+
assertScalarField(model, f, "_count");
|
|
4376
|
+
pushCountField(fields, alias, f, model);
|
|
4377
|
+
}
|
|
4185
4378
|
}
|
|
4186
4379
|
}
|
|
4187
4380
|
function getAggregateSelectionObject(args, agg) {
|
|
@@ -4196,16 +4389,18 @@ function assertAggregatableScalarField(model, agg, fieldName) {
|
|
|
4196
4389
|
}
|
|
4197
4390
|
}
|
|
4198
4391
|
function pushAggregateFieldSql(fields, aggFn, alias, agg, fieldName, model) {
|
|
4199
|
-
const outAlias =
|
|
4392
|
+
const outAlias = agg + "." + fieldName;
|
|
4200
4393
|
fields.push(
|
|
4201
|
-
|
|
4394
|
+
aggFn + "(" + col(alias, fieldName, model) + ") " + SQL_TEMPLATES.AS + " " + quote2(outAlias)
|
|
4202
4395
|
);
|
|
4203
4396
|
}
|
|
4204
4397
|
function addAggregateFields(fields, args, alias, model) {
|
|
4205
4398
|
for (const [agg, aggFn] of AGGREGATES) {
|
|
4206
4399
|
const obj = getAggregateSelectionObject(args, agg);
|
|
4207
4400
|
if (!obj) continue;
|
|
4208
|
-
for (const
|
|
4401
|
+
for (const fieldName in obj) {
|
|
4402
|
+
if (!Object.prototype.hasOwnProperty.call(obj, fieldName)) continue;
|
|
4403
|
+
const selection = obj[fieldName];
|
|
4209
4404
|
if (fieldName === "_all")
|
|
4210
4405
|
throw new Error(`'${agg}' does not support '_all'`);
|
|
4211
4406
|
if (!isTruthySelection(selection)) continue;
|
|
@@ -4229,22 +4424,23 @@ function buildAggregateSql(args, whereResult, tableName, alias, model) {
|
|
|
4229
4424
|
throw new Error("buildAggregateSql requires at least one aggregate field");
|
|
4230
4425
|
}
|
|
4231
4426
|
const selectClause = aggFields.join(SQL_SEPARATORS.FIELD_LIST);
|
|
4232
|
-
const whereClause = isValidWhereClause(whereResult.clause) ?
|
|
4233
|
-
const
|
|
4427
|
+
const whereClause = isValidWhereClause(whereResult.clause) ? SQL_TEMPLATES.WHERE + " " + whereResult.clause : "";
|
|
4428
|
+
const parts = [
|
|
4234
4429
|
SQL_TEMPLATES.SELECT,
|
|
4235
4430
|
selectClause,
|
|
4236
4431
|
SQL_TEMPLATES.FROM,
|
|
4237
4432
|
tableName,
|
|
4238
|
-
alias
|
|
4239
|
-
|
|
4240
|
-
|
|
4433
|
+
alias
|
|
4434
|
+
];
|
|
4435
|
+
if (whereClause) parts.push(whereClause);
|
|
4436
|
+
const sql = parts.join(" ").trim();
|
|
4241
4437
|
validateSelectQuery(sql);
|
|
4242
4438
|
validateParamConsistency(sql, whereResult.params);
|
|
4243
|
-
return
|
|
4439
|
+
return {
|
|
4244
4440
|
sql,
|
|
4245
|
-
params:
|
|
4246
|
-
paramMappings:
|
|
4247
|
-
}
|
|
4441
|
+
params: whereResult.params,
|
|
4442
|
+
paramMappings: whereResult.paramMappings
|
|
4443
|
+
};
|
|
4248
4444
|
}
|
|
4249
4445
|
function assertGroupByBy(args, model) {
|
|
4250
4446
|
if (!isNotNullish(args.by) || !isNonEmptyArray(args.by)) {
|
|
@@ -4272,8 +4468,8 @@ function buildGroupByHaving(args, alias, params, model, dialect) {
|
|
|
4272
4468
|
if (!isNotNullish(args.having)) return "";
|
|
4273
4469
|
if (!isPlainObject(args.having)) throw new Error("having must be an object");
|
|
4274
4470
|
const h = buildHavingClause(args.having, alias, params, model, dialect);
|
|
4275
|
-
if (!h || h.
|
|
4276
|
-
return
|
|
4471
|
+
if (!h || h.length === 0) return "";
|
|
4472
|
+
return SQL_TEMPLATES.HAVING + " " + h;
|
|
4277
4473
|
}
|
|
4278
4474
|
function buildGroupBySql(args, whereResult, tableName, alias, model, dialect) {
|
|
4279
4475
|
assertSafeAlias(alias);
|
|
@@ -4288,29 +4484,28 @@ function buildGroupBySql(args, whereResult, tableName, alias, model, dialect) {
|
|
|
4288
4484
|
byFields
|
|
4289
4485
|
);
|
|
4290
4486
|
const havingClause = buildGroupByHaving(args, alias, params, model, d);
|
|
4291
|
-
const whereClause = isValidWhereClause(whereResult.clause) ?
|
|
4292
|
-
const
|
|
4487
|
+
const whereClause = isValidWhereClause(whereResult.clause) ? SQL_TEMPLATES.WHERE + " " + whereResult.clause : "";
|
|
4488
|
+
const parts = [
|
|
4293
4489
|
SQL_TEMPLATES.SELECT,
|
|
4294
4490
|
selectFields,
|
|
4295
4491
|
SQL_TEMPLATES.FROM,
|
|
4296
4492
|
tableName,
|
|
4297
|
-
alias
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4493
|
+
alias
|
|
4494
|
+
];
|
|
4495
|
+
if (whereClause) parts.push(whereClause);
|
|
4496
|
+
parts.push(SQL_TEMPLATES.GROUP_BY, groupFields);
|
|
4497
|
+
if (havingClause) parts.push(havingClause);
|
|
4498
|
+
const sql = parts.join(" ").trim();
|
|
4303
4499
|
const snapshot = params.snapshot();
|
|
4500
|
+
const allParams = [...whereResult.params, ...snapshot.params];
|
|
4501
|
+
const allMappings = [...whereResult.paramMappings, ...snapshot.mappings];
|
|
4304
4502
|
validateSelectQuery(sql);
|
|
4305
|
-
validateParamConsistency(sql,
|
|
4306
|
-
return
|
|
4503
|
+
validateParamConsistency(sql, allParams);
|
|
4504
|
+
return {
|
|
4307
4505
|
sql,
|
|
4308
|
-
params:
|
|
4309
|
-
paramMappings:
|
|
4310
|
-
|
|
4311
|
-
...snapshot.mappings
|
|
4312
|
-
])
|
|
4313
|
-
});
|
|
4506
|
+
params: allParams,
|
|
4507
|
+
paramMappings: allMappings
|
|
4508
|
+
};
|
|
4314
4509
|
}
|
|
4315
4510
|
function buildCountSql(whereResult, tableName, alias, skip, _dialect) {
|
|
4316
4511
|
assertSafeAlias(alias);
|
|
@@ -4338,24 +4533,25 @@ function buildCountSql(whereResult, tableName, alias, skip, _dialect) {
|
|
|
4338
4533
|
);
|
|
4339
4534
|
}
|
|
4340
4535
|
}
|
|
4341
|
-
const whereClause = isValidWhereClause(whereResult.clause) ?
|
|
4342
|
-
const
|
|
4536
|
+
const whereClause = isValidWhereClause(whereResult.clause) ? SQL_TEMPLATES.WHERE + " " + whereResult.clause : "";
|
|
4537
|
+
const parts = [
|
|
4343
4538
|
SQL_TEMPLATES.SELECT,
|
|
4344
4539
|
SQL_TEMPLATES.COUNT_ALL,
|
|
4345
4540
|
SQL_TEMPLATES.AS,
|
|
4346
|
-
|
|
4541
|
+
quote2("_count._all"),
|
|
4347
4542
|
SQL_TEMPLATES.FROM,
|
|
4348
4543
|
tableName,
|
|
4349
|
-
alias
|
|
4350
|
-
|
|
4351
|
-
|
|
4544
|
+
alias
|
|
4545
|
+
];
|
|
4546
|
+
if (whereClause) parts.push(whereClause);
|
|
4547
|
+
const sql = parts.join(" ").trim();
|
|
4352
4548
|
validateSelectQuery(sql);
|
|
4353
4549
|
validateParamConsistency(sql, whereResult.params);
|
|
4354
|
-
return
|
|
4550
|
+
return {
|
|
4355
4551
|
sql,
|
|
4356
|
-
params:
|
|
4357
|
-
paramMappings:
|
|
4358
|
-
}
|
|
4552
|
+
params: whereResult.params,
|
|
4553
|
+
paramMappings: whereResult.paramMappings
|
|
4554
|
+
};
|
|
4359
4555
|
}
|
|
4360
4556
|
function safeAlias(input) {
|
|
4361
4557
|
const raw = String(input).toLowerCase();
|
|
@@ -4859,7 +5055,7 @@ function toSqliteParams(sql, params) {
|
|
|
4859
5055
|
parts.push(sql.substring(lastIndex));
|
|
4860
5056
|
return { sql: parts.join(""), params: reorderedParams };
|
|
4861
5057
|
}
|
|
4862
|
-
function canonicalizeQuery(modelName, method, args) {
|
|
5058
|
+
function canonicalizeQuery(modelName, method, args, dialect) {
|
|
4863
5059
|
function normalize(obj) {
|
|
4864
5060
|
if (obj === null || obj === void 0) return obj;
|
|
4865
5061
|
if (obj instanceof Date) {
|
|
@@ -4879,7 +5075,7 @@ function canonicalizeQuery(modelName, method, args) {
|
|
|
4879
5075
|
return obj;
|
|
4880
5076
|
}
|
|
4881
5077
|
const canonical = normalize(args);
|
|
4882
|
-
return `${modelName}:${method}:${JSON.stringify(canonical)}`;
|
|
5078
|
+
return `${dialect}:${modelName}:${method}:${JSON.stringify(canonical)}`;
|
|
4883
5079
|
}
|
|
4884
5080
|
function buildSQLFull(model, models, method, args, dialect) {
|
|
4885
5081
|
const tableName = buildTableReference(
|
|
@@ -4942,7 +5138,7 @@ function buildSQLFull(model, models, method, args, dialect) {
|
|
|
4942
5138
|
return dialect === "sqlite" ? toSqliteParams(result.sql, result.params) : { sql: result.sql, params: [...result.params] };
|
|
4943
5139
|
}
|
|
4944
5140
|
function buildSQLWithCache(model, models, method, args, dialect) {
|
|
4945
|
-
const cacheKey = canonicalizeQuery(model.name, method, args);
|
|
5141
|
+
const cacheKey = canonicalizeQuery(model.name, method, args, dialect);
|
|
4946
5142
|
const cachedSql = queryCache.get(cacheKey);
|
|
4947
5143
|
if (cachedSql) {
|
|
4948
5144
|
const result2 = buildSQLFull(model, models, method, args, dialect);
|
|
@@ -4950,6 +5146,7 @@ function buildSQLWithCache(model, models, method, args, dialect) {
|
|
|
4950
5146
|
}
|
|
4951
5147
|
const result = buildSQLFull(model, models, method, args, dialect);
|
|
4952
5148
|
queryCache.set(cacheKey, result.sql);
|
|
5149
|
+
queryCache.size;
|
|
4953
5150
|
return result;
|
|
4954
5151
|
}
|
|
4955
5152
|
var ACCELERATED_METHODS = /* @__PURE__ */ new Set([
|