prisma-sql 1.58.0 → 1.59.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 +186 -229
- package/dist/generator.cjs.map +1 -1
- package/dist/generator.js +186 -229
- package/dist/generator.js.map +1 -1
- package/dist/index.cjs +459 -233
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +13 -13
- package/dist/index.d.ts +13 -13
- package/dist/index.js +459 -233
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/readme.md +0 -1
package/dist/index.cjs
CHANGED
|
@@ -187,7 +187,8 @@ var REGEX_CACHE = {
|
|
|
187
187
|
var LIMITS = Object.freeze({
|
|
188
188
|
MAX_QUERY_DEPTH: 50,
|
|
189
189
|
MAX_ARRAY_SIZE: 1e4,
|
|
190
|
-
MAX_STRING_LENGTH: 1e4
|
|
190
|
+
MAX_STRING_LENGTH: 1e4,
|
|
191
|
+
MAX_HAVING_DEPTH: 50
|
|
191
192
|
});
|
|
192
193
|
|
|
193
194
|
// src/utils/normalize-value.ts
|
|
@@ -250,6 +251,15 @@ function setGlobalDialect(dialect) {
|
|
|
250
251
|
function getGlobalDialect() {
|
|
251
252
|
return globalDialect;
|
|
252
253
|
}
|
|
254
|
+
function withDialect(dialect, fn) {
|
|
255
|
+
const prev = globalDialect;
|
|
256
|
+
globalDialect = dialect;
|
|
257
|
+
try {
|
|
258
|
+
return fn();
|
|
259
|
+
} finally {
|
|
260
|
+
globalDialect = prev;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
253
263
|
function assertNonEmpty(value, name) {
|
|
254
264
|
if (!value || value.trim().length === 0) {
|
|
255
265
|
throw new Error(`${name} is required and cannot be empty`);
|
|
@@ -484,73 +494,6 @@ function createError(message, ctx, code = "VALIDATION_ERROR") {
|
|
|
484
494
|
return new SqlBuilderError(parts.join("\n"), code, ctx);
|
|
485
495
|
}
|
|
486
496
|
|
|
487
|
-
// src/builder/shared/model-field-cache.ts
|
|
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
|
-
}
|
|
498
|
-
function ensureFullCache(model) {
|
|
499
|
-
let cache = MODEL_CACHE.get(model);
|
|
500
|
-
if (!cache) {
|
|
501
|
-
const fieldInfo = /* @__PURE__ */ new Map();
|
|
502
|
-
const scalarFields = /* @__PURE__ */ new Set();
|
|
503
|
-
const relationFields = /* @__PURE__ */ new Set();
|
|
504
|
-
const columnMap = /* @__PURE__ */ new Map();
|
|
505
|
-
const fieldByName = /* @__PURE__ */ new Map();
|
|
506
|
-
const quotedColumns = /* @__PURE__ */ new Map();
|
|
507
|
-
for (const f of model.fields) {
|
|
508
|
-
const info = {
|
|
509
|
-
name: f.name,
|
|
510
|
-
dbName: f.dbName || f.name,
|
|
511
|
-
type: f.type,
|
|
512
|
-
isRelation: !!f.isRelation,
|
|
513
|
-
isRequired: !!f.isRequired
|
|
514
|
-
};
|
|
515
|
-
fieldInfo.set(f.name, info);
|
|
516
|
-
fieldByName.set(f.name, f);
|
|
517
|
-
if (info.isRelation) {
|
|
518
|
-
relationFields.add(f.name);
|
|
519
|
-
} else {
|
|
520
|
-
scalarFields.add(f.name);
|
|
521
|
-
const dbName = info.dbName;
|
|
522
|
-
columnMap.set(f.name, dbName);
|
|
523
|
-
quotedColumns.set(f.name, quote(dbName));
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
cache = {
|
|
527
|
-
fieldInfo,
|
|
528
|
-
scalarFields,
|
|
529
|
-
relationFields,
|
|
530
|
-
columnMap,
|
|
531
|
-
fieldByName,
|
|
532
|
-
quotedColumns
|
|
533
|
-
};
|
|
534
|
-
MODEL_CACHE.set(model, cache);
|
|
535
|
-
}
|
|
536
|
-
return cache;
|
|
537
|
-
}
|
|
538
|
-
function getFieldInfo(model, fieldName) {
|
|
539
|
-
return ensureFullCache(model).fieldInfo.get(fieldName);
|
|
540
|
-
}
|
|
541
|
-
function getScalarFieldSet(model) {
|
|
542
|
-
return ensureFullCache(model).scalarFields;
|
|
543
|
-
}
|
|
544
|
-
function getRelationFieldSet(model) {
|
|
545
|
-
return ensureFullCache(model).relationFields;
|
|
546
|
-
}
|
|
547
|
-
function getColumnMap(model) {
|
|
548
|
-
return ensureFullCache(model).columnMap;
|
|
549
|
-
}
|
|
550
|
-
function getQuotedColumn(model, fieldName) {
|
|
551
|
-
return ensureFullCache(model).quotedColumns.get(fieldName);
|
|
552
|
-
}
|
|
553
|
-
|
|
554
497
|
// src/builder/shared/validators/sql-validators.ts
|
|
555
498
|
function isValidWhereClause(clause) {
|
|
556
499
|
return isNotNullish(clause) && clause.trim().length > 0 && clause !== DEFAULT_WHERE_CLAUSE;
|
|
@@ -694,6 +637,78 @@ function needsQuoting(identifier) {
|
|
|
694
637
|
return false;
|
|
695
638
|
}
|
|
696
639
|
|
|
640
|
+
// src/builder/shared/model-field-cache.ts
|
|
641
|
+
var MODEL_CACHE = /* @__PURE__ */ new WeakMap();
|
|
642
|
+
function quoteIdent(id) {
|
|
643
|
+
if (typeof id !== "string" || id.trim().length === 0) {
|
|
644
|
+
throw new Error("quoteIdent: identifier is required and cannot be empty");
|
|
645
|
+
}
|
|
646
|
+
if (/[\u0000-\u001F\u007F]/.test(id)) {
|
|
647
|
+
throw new Error(
|
|
648
|
+
`quoteIdent: identifier contains invalid characters: ${JSON.stringify(id)}`
|
|
649
|
+
);
|
|
650
|
+
}
|
|
651
|
+
if (needsQuoting(id)) {
|
|
652
|
+
return `"${id.replace(/"/g, '""')}"`;
|
|
653
|
+
}
|
|
654
|
+
return id;
|
|
655
|
+
}
|
|
656
|
+
function ensureFullCache(model) {
|
|
657
|
+
let cache = MODEL_CACHE.get(model);
|
|
658
|
+
if (!cache) {
|
|
659
|
+
const fieldInfo = /* @__PURE__ */ new Map();
|
|
660
|
+
const scalarFields = /* @__PURE__ */ new Set();
|
|
661
|
+
const relationFields = /* @__PURE__ */ new Set();
|
|
662
|
+
const columnMap = /* @__PURE__ */ new Map();
|
|
663
|
+
const fieldByName = /* @__PURE__ */ new Map();
|
|
664
|
+
const quotedColumns = /* @__PURE__ */ new Map();
|
|
665
|
+
for (const f of model.fields) {
|
|
666
|
+
const info = {
|
|
667
|
+
name: f.name,
|
|
668
|
+
dbName: f.dbName || f.name,
|
|
669
|
+
type: f.type,
|
|
670
|
+
isRelation: !!f.isRelation,
|
|
671
|
+
isRequired: !!f.isRequired
|
|
672
|
+
};
|
|
673
|
+
fieldInfo.set(f.name, info);
|
|
674
|
+
fieldByName.set(f.name, f);
|
|
675
|
+
if (info.isRelation) {
|
|
676
|
+
relationFields.add(f.name);
|
|
677
|
+
} else {
|
|
678
|
+
scalarFields.add(f.name);
|
|
679
|
+
const dbName = info.dbName;
|
|
680
|
+
columnMap.set(f.name, dbName);
|
|
681
|
+
quotedColumns.set(f.name, quoteIdent(dbName));
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
cache = {
|
|
685
|
+
fieldInfo,
|
|
686
|
+
scalarFields,
|
|
687
|
+
relationFields,
|
|
688
|
+
columnMap,
|
|
689
|
+
fieldByName,
|
|
690
|
+
quotedColumns
|
|
691
|
+
};
|
|
692
|
+
MODEL_CACHE.set(model, cache);
|
|
693
|
+
}
|
|
694
|
+
return cache;
|
|
695
|
+
}
|
|
696
|
+
function getFieldInfo(model, fieldName) {
|
|
697
|
+
return ensureFullCache(model).fieldInfo.get(fieldName);
|
|
698
|
+
}
|
|
699
|
+
function getScalarFieldSet(model) {
|
|
700
|
+
return ensureFullCache(model).scalarFields;
|
|
701
|
+
}
|
|
702
|
+
function getRelationFieldSet(model) {
|
|
703
|
+
return ensureFullCache(model).relationFields;
|
|
704
|
+
}
|
|
705
|
+
function getColumnMap(model) {
|
|
706
|
+
return ensureFullCache(model).columnMap;
|
|
707
|
+
}
|
|
708
|
+
function getQuotedColumn(model, fieldName) {
|
|
709
|
+
return ensureFullCache(model).quotedColumns.get(fieldName);
|
|
710
|
+
}
|
|
711
|
+
|
|
697
712
|
// src/builder/shared/sql-utils.ts
|
|
698
713
|
function containsControlChars(s) {
|
|
699
714
|
return /[\u0000-\u001F\u007F]/.test(s);
|
|
@@ -836,7 +851,7 @@ function assertSafeQualifiedName(input) {
|
|
|
836
851
|
}
|
|
837
852
|
}
|
|
838
853
|
}
|
|
839
|
-
function
|
|
854
|
+
function quote(id) {
|
|
840
855
|
if (isEmptyString(id)) {
|
|
841
856
|
throw new Error("quote: identifier is required and cannot be empty");
|
|
842
857
|
}
|
|
@@ -856,13 +871,13 @@ function resolveColumnName(model, fieldName) {
|
|
|
856
871
|
return columnMap.get(fieldName) || fieldName;
|
|
857
872
|
}
|
|
858
873
|
function quoteColumn(model, fieldName) {
|
|
859
|
-
if (!model) return
|
|
874
|
+
if (!model) return quote(fieldName);
|
|
860
875
|
const cached = getQuotedColumn(model, fieldName);
|
|
861
|
-
return cached ||
|
|
876
|
+
return cached || quote(fieldName);
|
|
862
877
|
}
|
|
863
878
|
function col(alias, field, model) {
|
|
864
879
|
const columnName = model ? getColumnMap(model).get(field) || field : field;
|
|
865
|
-
const quoted = model ? getQuotedColumn(model, field) ||
|
|
880
|
+
const quoted = model ? getQuotedColumn(model, field) || quote(columnName) : quote(columnName);
|
|
866
881
|
return alias + "." + quoted;
|
|
867
882
|
}
|
|
868
883
|
function colWithAlias(alias, field, model) {
|
|
@@ -873,9 +888,9 @@ function colWithAlias(alias, field, model) {
|
|
|
873
888
|
throw new Error("colWithAlias: field is required and cannot be empty");
|
|
874
889
|
}
|
|
875
890
|
const columnName = resolveColumnName(model, field);
|
|
876
|
-
const columnRef = alias + "." + (model ? getQuotedColumn(model, field) ||
|
|
891
|
+
const columnRef = alias + "." + (model ? getQuotedColumn(model, field) || quote(columnName) : quote(columnName));
|
|
877
892
|
if (columnName !== field) {
|
|
878
|
-
return columnRef + " AS " +
|
|
893
|
+
return columnRef + " AS " + quote(field);
|
|
879
894
|
}
|
|
880
895
|
return columnRef;
|
|
881
896
|
}
|
|
@@ -898,7 +913,7 @@ function buildTableReference(schemaName, tableName, dialect) {
|
|
|
898
913
|
}
|
|
899
914
|
const d = dialect != null ? dialect : "postgres";
|
|
900
915
|
if (d === "sqlite") {
|
|
901
|
-
return
|
|
916
|
+
return quote(tableName);
|
|
902
917
|
}
|
|
903
918
|
if (isEmptyString(schemaName)) {
|
|
904
919
|
throw new Error(
|
|
@@ -1563,6 +1578,7 @@ function buildOrderEntries(orderBy) {
|
|
|
1563
1578
|
}
|
|
1564
1579
|
return entries;
|
|
1565
1580
|
}
|
|
1581
|
+
var MAX_NOT_DEPTH = 50;
|
|
1566
1582
|
function buildNotComposite(expr, val, params, dialect, buildOp, separator) {
|
|
1567
1583
|
const entries = Object.entries(val).filter(
|
|
1568
1584
|
([k, v]) => k !== "mode" && v !== void 0
|
|
@@ -1577,13 +1593,18 @@ function buildNotComposite(expr, val, params, dialect, buildOp, separator) {
|
|
|
1577
1593
|
if (clauses.length === 1) return `${SQL_TEMPLATES.NOT} ${clauses[0]}`;
|
|
1578
1594
|
return `${SQL_TEMPLATES.NOT} (${clauses.join(separator)})`;
|
|
1579
1595
|
}
|
|
1580
|
-
function buildScalarOperator(expr, op, val, params, mode, fieldType, dialect) {
|
|
1596
|
+
function buildScalarOperator(expr, op, val, params, mode, fieldType, dialect, depth = 0) {
|
|
1581
1597
|
if (val === void 0) return "";
|
|
1598
|
+
if (depth > MAX_NOT_DEPTH) {
|
|
1599
|
+
throw new Error(
|
|
1600
|
+
`NOT operator nesting too deep (max ${MAX_NOT_DEPTH} levels). This usually indicates a circular reference or adversarial input.`
|
|
1601
|
+
);
|
|
1602
|
+
}
|
|
1582
1603
|
if (val === null) {
|
|
1583
1604
|
return handleNullValue(expr, op);
|
|
1584
1605
|
}
|
|
1585
1606
|
if (op === Ops.NOT && isPlainObject(val)) {
|
|
1586
|
-
return handleNotOperator(expr, val, params, mode, fieldType, dialect);
|
|
1607
|
+
return handleNotOperator(expr, val, params, mode, fieldType, dialect, depth);
|
|
1587
1608
|
}
|
|
1588
1609
|
if (op === Ops.NOT) {
|
|
1589
1610
|
const placeholder = params.addAuto(val);
|
|
@@ -1629,7 +1650,7 @@ function normalizeMode(v) {
|
|
|
1629
1650
|
if (v === Modes.DEFAULT) return Modes.DEFAULT;
|
|
1630
1651
|
return void 0;
|
|
1631
1652
|
}
|
|
1632
|
-
function handleNotOperator(expr, val, params, outerMode, fieldType, dialect) {
|
|
1653
|
+
function handleNotOperator(expr, val, params, outerMode, fieldType, dialect, depth = 0) {
|
|
1633
1654
|
const innerMode = normalizeMode(val.mode);
|
|
1634
1655
|
const effectiveMode = innerMode != null ? innerMode : outerMode;
|
|
1635
1656
|
const entries = Object.entries(val).filter(
|
|
@@ -1646,7 +1667,8 @@ function handleNotOperator(expr, val, params, outerMode, fieldType, dialect) {
|
|
|
1646
1667
|
params,
|
|
1647
1668
|
effectiveMode,
|
|
1648
1669
|
fieldType,
|
|
1649
|
-
void 0
|
|
1670
|
+
void 0,
|
|
1671
|
+
depth + 1
|
|
1650
1672
|
);
|
|
1651
1673
|
if (sub && sub.trim().length > 0) clauses.push(`(${sub})`);
|
|
1652
1674
|
}
|
|
@@ -1659,23 +1681,20 @@ function handleNotOperator(expr, val, params, outerMode, fieldType, dialect) {
|
|
|
1659
1681
|
val,
|
|
1660
1682
|
params,
|
|
1661
1683
|
dialect,
|
|
1662
|
-
(e, subOp, subVal, p, d) => buildScalarOperator(
|
|
1684
|
+
(e, subOp, subVal, p, d) => buildScalarOperator(
|
|
1685
|
+
e,
|
|
1686
|
+
subOp,
|
|
1687
|
+
subVal,
|
|
1688
|
+
p,
|
|
1689
|
+
effectiveMode,
|
|
1690
|
+
fieldType,
|
|
1691
|
+
d,
|
|
1692
|
+
depth + 1
|
|
1693
|
+
),
|
|
1663
1694
|
` ${SQL_TEMPLATES.AND} `
|
|
1664
1695
|
);
|
|
1665
1696
|
}
|
|
1666
|
-
function buildDynamicLikePattern(op, placeholder
|
|
1667
|
-
if (dialect === "postgres") {
|
|
1668
|
-
switch (op) {
|
|
1669
|
-
case Ops.CONTAINS:
|
|
1670
|
-
return `('%' || ${placeholder} || '%')`;
|
|
1671
|
-
case Ops.STARTS_WITH:
|
|
1672
|
-
return `(${placeholder} || '%')`;
|
|
1673
|
-
case Ops.ENDS_WITH:
|
|
1674
|
-
return `('%' || ${placeholder})`;
|
|
1675
|
-
default:
|
|
1676
|
-
return placeholder;
|
|
1677
|
-
}
|
|
1678
|
-
}
|
|
1697
|
+
function buildDynamicLikePattern(op, placeholder) {
|
|
1679
1698
|
switch (op) {
|
|
1680
1699
|
case Ops.CONTAINS:
|
|
1681
1700
|
return `('%' || ${placeholder} || '%')`;
|
|
@@ -1691,7 +1710,7 @@ function handleLikeOperator(expr, op, val, params, mode, dialect) {
|
|
|
1691
1710
|
if (val === void 0) return "";
|
|
1692
1711
|
if (schemaParser.isDynamicParameter(val)) {
|
|
1693
1712
|
const placeholder2 = params.addAuto(val);
|
|
1694
|
-
const patternExpr = buildDynamicLikePattern(op, placeholder2
|
|
1713
|
+
const patternExpr = buildDynamicLikePattern(op, placeholder2);
|
|
1695
1714
|
if (mode === Modes.INSENSITIVE) {
|
|
1696
1715
|
return caseInsensitiveLike(expr, patternExpr, dialect);
|
|
1697
1716
|
}
|
|
@@ -1977,7 +1996,7 @@ function freezeJoins(items) {
|
|
|
1977
1996
|
function isListRelation(fieldType) {
|
|
1978
1997
|
return typeof fieldType === "string" && fieldType.endsWith("[]");
|
|
1979
1998
|
}
|
|
1980
|
-
function buildToOneNullCheck(field, parentAlias, relTable, relAlias, join, wantNull) {
|
|
1999
|
+
function buildToOneNullCheck(field, parentModel, parentAlias, relTable, relAlias, join, wantNull) {
|
|
1981
2000
|
const isLocal = field.isForeignKeyLocal === true;
|
|
1982
2001
|
const fkFields = normalizeKeyList(field.foreignKey);
|
|
1983
2002
|
if (isLocal) {
|
|
@@ -1987,8 +2006,7 @@ function buildToOneNullCheck(field, parentAlias, relTable, relAlias, join, wantN
|
|
|
1987
2006
|
});
|
|
1988
2007
|
}
|
|
1989
2008
|
const parts = fkFields.map((fk) => {
|
|
1990
|
-
const
|
|
1991
|
-
const expr = `${parentAlias}."${safe}"`;
|
|
2009
|
+
const expr = `${parentAlias}.${quoteColumn(parentModel, fk)}`;
|
|
1992
2010
|
return wantNull ? `${expr} ${SQL_TEMPLATES.IS_NULL}` : `${expr} ${SQL_TEMPLATES.IS_NOT_NULL}`;
|
|
1993
2011
|
});
|
|
1994
2012
|
if (parts.length === 1) return parts[0];
|
|
@@ -2033,7 +2051,7 @@ function buildListRelationFilters(args) {
|
|
|
2033
2051
|
) || relModel.fields.find((f) => !f.isRelation && f.name === "id");
|
|
2034
2052
|
if (checkField) {
|
|
2035
2053
|
const leftJoinSql = `LEFT JOIN ${relTable} ${relAlias} ON ${join}`;
|
|
2036
|
-
const whereClause = `${relAlias}.${
|
|
2054
|
+
const whereClause = `${relAlias}.${quote(checkField.name)} IS NULL`;
|
|
2037
2055
|
return Object.freeze({
|
|
2038
2056
|
clause: whereClause,
|
|
2039
2057
|
joins: freezeJoins([leftJoinSql])
|
|
@@ -2126,6 +2144,7 @@ function buildToOneRelationFilters(args) {
|
|
|
2126
2144
|
const wantNull = filterKey === "is";
|
|
2127
2145
|
const clause2 = buildToOneNullCheck(
|
|
2128
2146
|
field,
|
|
2147
|
+
ctx.model,
|
|
2129
2148
|
ctx.alias,
|
|
2130
2149
|
relTable,
|
|
2131
2150
|
relAlias,
|
|
@@ -2355,7 +2374,8 @@ function buildWhereInternal(where, ctx, builder) {
|
|
|
2355
2374
|
}
|
|
2356
2375
|
const allJoins = [];
|
|
2357
2376
|
const clauses = [];
|
|
2358
|
-
for (const
|
|
2377
|
+
for (const key in where) {
|
|
2378
|
+
const value = where[key];
|
|
2359
2379
|
if (value === void 0) continue;
|
|
2360
2380
|
const result = buildWhereEntry(key, value, ctx, builder);
|
|
2361
2381
|
appendResult(result, clauses, allJoins);
|
|
@@ -2605,8 +2625,6 @@ function createStoreInternal(startIndex, initialParams = [], initialMappings = [
|
|
|
2605
2625
|
}
|
|
2606
2626
|
let dirty = true;
|
|
2607
2627
|
let cachedSnapshot = null;
|
|
2608
|
-
let frozenParams = null;
|
|
2609
|
-
let frozenMappings = null;
|
|
2610
2628
|
function assertCanAdd() {
|
|
2611
2629
|
if (index > MAX_PARAM_INDEX) {
|
|
2612
2630
|
throw new Error(
|
|
@@ -2658,17 +2676,13 @@ function createStoreInternal(startIndex, initialParams = [], initialMappings = [
|
|
|
2658
2676
|
}
|
|
2659
2677
|
function snapshot() {
|
|
2660
2678
|
if (!dirty && cachedSnapshot) return cachedSnapshot;
|
|
2661
|
-
if (!frozenParams) frozenParams = Object.freeze(params.slice());
|
|
2662
|
-
if (!frozenMappings) frozenMappings = Object.freeze(mappings.slice());
|
|
2663
2679
|
const snap = {
|
|
2664
2680
|
index,
|
|
2665
|
-
params:
|
|
2666
|
-
mappings:
|
|
2681
|
+
params: params.slice(),
|
|
2682
|
+
mappings: mappings.slice()
|
|
2667
2683
|
};
|
|
2668
2684
|
cachedSnapshot = snap;
|
|
2669
2685
|
dirty = false;
|
|
2670
|
-
frozenParams = null;
|
|
2671
|
-
frozenMappings = null;
|
|
2672
2686
|
return snap;
|
|
2673
2687
|
}
|
|
2674
2688
|
return {
|
|
@@ -3598,7 +3612,7 @@ function buildDistinctColumns(distinct, fromAlias, model) {
|
|
|
3598
3612
|
}
|
|
3599
3613
|
function buildOutputColumns(scalarNames, includeNames, hasCount) {
|
|
3600
3614
|
const outputCols = hasCount ? [...scalarNames, ...includeNames, "_count"] : [...scalarNames, ...includeNames];
|
|
3601
|
-
const formatted = outputCols.map((n) =>
|
|
3615
|
+
const formatted = outputCols.map((n) => quote(n)).join(SQL_SEPARATORS.FIELD_LIST);
|
|
3602
3616
|
if (!isNonEmptyString(formatted)) {
|
|
3603
3617
|
throw new Error("distinct emulation requires at least one output column");
|
|
3604
3618
|
}
|
|
@@ -3694,7 +3708,7 @@ function buildIncludeColumns(spec) {
|
|
|
3694
3708
|
dialect
|
|
3695
3709
|
);
|
|
3696
3710
|
if (countBuild.jsonPairs) {
|
|
3697
|
-
countCols = jsonBuildObject(countBuild.jsonPairs, dialect) + " " + SQL_TEMPLATES.AS + " " +
|
|
3711
|
+
countCols = jsonBuildObject(countBuild.jsonPairs, dialect) + " " + SQL_TEMPLATES.AS + " " + quote("_count");
|
|
3698
3712
|
}
|
|
3699
3713
|
countJoins = countBuild.joins;
|
|
3700
3714
|
}
|
|
@@ -3707,7 +3721,7 @@ function buildIncludeColumns(spec) {
|
|
|
3707
3721
|
const emptyJson = dialect === "postgres" ? `'[]'::json` : `json('[]')`;
|
|
3708
3722
|
const includeCols = hasIncludes ? includes.map((inc) => {
|
|
3709
3723
|
const expr = inc.isOneToOne ? "(" + inc.sql + ")" : "COALESCE((" + inc.sql + "), " + emptyJson + ")";
|
|
3710
|
-
return expr + " " + SQL_TEMPLATES.AS + " " +
|
|
3724
|
+
return expr + " " + SQL_TEMPLATES.AS + " " + quote(inc.name);
|
|
3711
3725
|
}).join(SQL_SEPARATORS.FIELD_LIST) : "";
|
|
3712
3726
|
const allCols = joinNonEmpty(
|
|
3713
3727
|
[includeCols, countCols],
|
|
@@ -4072,6 +4086,7 @@ function buildComparisons(expr, filter, params, dialect, builder, excludeKeys =
|
|
|
4072
4086
|
}
|
|
4073
4087
|
|
|
4074
4088
|
// src/builder/aggregates.ts
|
|
4089
|
+
var MAX_NOT_DEPTH2 = 50;
|
|
4075
4090
|
var AGGREGATES = [
|
|
4076
4091
|
["_sum", "SUM"],
|
|
4077
4092
|
["_avg", "AVG"],
|
|
@@ -4186,8 +4201,13 @@ function buildBinaryComparison(expr, op, val, params) {
|
|
|
4186
4201
|
const placeholder = addHavingParam(params, op, val);
|
|
4187
4202
|
return expr + " " + sqlOp + " " + placeholder;
|
|
4188
4203
|
}
|
|
4189
|
-
function buildSimpleComparison(expr, op, val, params, dialect) {
|
|
4204
|
+
function buildSimpleComparison(expr, op, val, params, dialect, depth = 0) {
|
|
4190
4205
|
assertHavingOp(op);
|
|
4206
|
+
if (depth > MAX_NOT_DEPTH2) {
|
|
4207
|
+
throw new Error(
|
|
4208
|
+
`NOT operator nesting too deep in HAVING (max ${MAX_NOT_DEPTH2} levels).`
|
|
4209
|
+
);
|
|
4210
|
+
}
|
|
4191
4211
|
if (val === null) return buildNullComparison(expr, op);
|
|
4192
4212
|
if (op === Ops.NOT && isPlainObject(val)) {
|
|
4193
4213
|
return buildNotComposite(
|
|
@@ -4195,7 +4215,7 @@ function buildSimpleComparison(expr, op, val, params, dialect) {
|
|
|
4195
4215
|
val,
|
|
4196
4216
|
params,
|
|
4197
4217
|
dialect,
|
|
4198
|
-
buildSimpleComparison,
|
|
4218
|
+
(e, subOp, subVal, p, d) => buildSimpleComparison(e, subOp, subVal, p, d, depth + 1),
|
|
4199
4219
|
SQL_SEPARATORS.CONDITION_AND
|
|
4200
4220
|
);
|
|
4201
4221
|
}
|
|
@@ -4212,23 +4232,36 @@ function combineLogical(key, subClauses) {
|
|
|
4212
4232
|
if (key === LogicalOps.NOT) return negateClauses(subClauses);
|
|
4213
4233
|
return subClauses.join(" " + key + " ");
|
|
4214
4234
|
}
|
|
4215
|
-
function buildHavingNode(node, alias, params, dialect, model) {
|
|
4235
|
+
function buildHavingNode(node, alias, params, dialect, model, depth = 0) {
|
|
4236
|
+
if (depth > LIMITS.MAX_HAVING_DEPTH) {
|
|
4237
|
+
throw new Error(
|
|
4238
|
+
`HAVING clause nesting too deep (max ${LIMITS.MAX_HAVING_DEPTH} levels). This usually indicates a circular reference.`
|
|
4239
|
+
);
|
|
4240
|
+
}
|
|
4216
4241
|
const clauses = [];
|
|
4217
4242
|
for (const key in node) {
|
|
4218
4243
|
if (!Object.prototype.hasOwnProperty.call(node, key)) continue;
|
|
4219
4244
|
const value = node[key];
|
|
4220
|
-
const built = buildHavingEntry(
|
|
4245
|
+
const built = buildHavingEntry(
|
|
4246
|
+
key,
|
|
4247
|
+
value,
|
|
4248
|
+
alias,
|
|
4249
|
+
params,
|
|
4250
|
+
dialect,
|
|
4251
|
+
model,
|
|
4252
|
+
depth
|
|
4253
|
+
);
|
|
4221
4254
|
for (const c of built) {
|
|
4222
4255
|
if (c && c.length > 0) clauses.push(c);
|
|
4223
4256
|
}
|
|
4224
4257
|
}
|
|
4225
4258
|
return clauses.join(SQL_SEPARATORS.CONDITION_AND);
|
|
4226
4259
|
}
|
|
4227
|
-
function buildLogicalClause2(key, value, alias, params, dialect, model) {
|
|
4260
|
+
function buildLogicalClause2(key, value, alias, params, dialect, model, depth = 0) {
|
|
4228
4261
|
const items = normalizeLogicalValue2(key, value);
|
|
4229
4262
|
const subClauses = [];
|
|
4230
4263
|
for (const it of items) {
|
|
4231
|
-
const c = buildHavingNode(it, alias, params, dialect, model);
|
|
4264
|
+
const c = buildHavingNode(it, alias, params, dialect, model, depth + 1);
|
|
4232
4265
|
if (c && c.length > 0) subClauses.push("(" + c + ")");
|
|
4233
4266
|
}
|
|
4234
4267
|
if (subClauses.length === 0) return "";
|
|
@@ -4287,7 +4320,7 @@ function buildHavingForFieldFirstShape(fieldName, target, alias, params, dialect
|
|
|
4287
4320
|
}
|
|
4288
4321
|
return out;
|
|
4289
4322
|
}
|
|
4290
|
-
function buildHavingEntry(key, value, alias, params, dialect, model) {
|
|
4323
|
+
function buildHavingEntry(key, value, alias, params, dialect, model, depth = 0) {
|
|
4291
4324
|
if (isLogicalKey(key)) {
|
|
4292
4325
|
const logical = buildLogicalClause2(
|
|
4293
4326
|
key,
|
|
@@ -4295,7 +4328,8 @@ function buildHavingEntry(key, value, alias, params, dialect, model) {
|
|
|
4295
4328
|
alias,
|
|
4296
4329
|
params,
|
|
4297
4330
|
dialect,
|
|
4298
|
-
model
|
|
4331
|
+
model,
|
|
4332
|
+
depth
|
|
4299
4333
|
);
|
|
4300
4334
|
return logical ? [logical] : [];
|
|
4301
4335
|
}
|
|
@@ -4322,7 +4356,7 @@ function buildHavingClause(having, alias, params, model, dialect) {
|
|
|
4322
4356
|
if (!isNotNullish(having)) return "";
|
|
4323
4357
|
const d = dialect != null ? dialect : getGlobalDialect();
|
|
4324
4358
|
if (!isPlainObject(having)) throw new Error("having must be an object");
|
|
4325
|
-
return buildHavingNode(having, alias, params, d, model);
|
|
4359
|
+
return buildHavingNode(having, alias, params, d, model, 0);
|
|
4326
4360
|
}
|
|
4327
4361
|
function normalizeCountArg(v) {
|
|
4328
4362
|
if (!isNotNullish(v)) return void 0;
|
|
@@ -4332,13 +4366,13 @@ function normalizeCountArg(v) {
|
|
|
4332
4366
|
}
|
|
4333
4367
|
function pushCountAllField(fields) {
|
|
4334
4368
|
fields.push(
|
|
4335
|
-
SQL_TEMPLATES.COUNT_ALL + " " + SQL_TEMPLATES.AS + " " +
|
|
4369
|
+
SQL_TEMPLATES.COUNT_ALL + " " + SQL_TEMPLATES.AS + " " + quote("_count._all")
|
|
4336
4370
|
);
|
|
4337
4371
|
}
|
|
4338
4372
|
function pushCountField(fields, alias, fieldName, model) {
|
|
4339
4373
|
const outAlias = "_count." + fieldName;
|
|
4340
4374
|
fields.push(
|
|
4341
|
-
"COUNT(" + col(alias, fieldName, model) + ") " + SQL_TEMPLATES.AS + " " +
|
|
4375
|
+
"COUNT(" + col(alias, fieldName, model) + ") " + SQL_TEMPLATES.AS + " " + quote(outAlias)
|
|
4342
4376
|
);
|
|
4343
4377
|
}
|
|
4344
4378
|
function addCountFields(fields, countArg, alias, model) {
|
|
@@ -4375,7 +4409,7 @@ function assertAggregatableScalarField(model, agg, fieldName) {
|
|
|
4375
4409
|
function pushAggregateFieldSql(fields, aggFn, alias, agg, fieldName, model) {
|
|
4376
4410
|
const outAlias = agg + "." + fieldName;
|
|
4377
4411
|
fields.push(
|
|
4378
|
-
aggFn + "(" + col(alias, fieldName, model) + ") " + SQL_TEMPLATES.AS + " " +
|
|
4412
|
+
aggFn + "(" + col(alias, fieldName, model) + ") " + SQL_TEMPLATES.AS + " " + quote(outAlias)
|
|
4379
4413
|
);
|
|
4380
4414
|
}
|
|
4381
4415
|
function addAggregateFields(fields, args, alias, model) {
|
|
@@ -4522,7 +4556,7 @@ function buildCountSql(whereResult, tableName, alias, skip, _dialect) {
|
|
|
4522
4556
|
SQL_TEMPLATES.SELECT,
|
|
4523
4557
|
SQL_TEMPLATES.COUNT_ALL,
|
|
4524
4558
|
SQL_TEMPLATES.AS,
|
|
4525
|
-
|
|
4559
|
+
quote("_count._all"),
|
|
4526
4560
|
SQL_TEMPLATES.FROM,
|
|
4527
4561
|
tableName,
|
|
4528
4562
|
alias
|
|
@@ -4563,15 +4597,21 @@ function buildSqlResult(args) {
|
|
|
4563
4597
|
return buildAggregateSql(processed, whereResult, tableName, alias, modelDef);
|
|
4564
4598
|
}
|
|
4565
4599
|
if (method === "groupBy") {
|
|
4566
|
-
return buildGroupBySql(
|
|
4600
|
+
return buildGroupBySql(
|
|
4601
|
+
processed,
|
|
4602
|
+
whereResult,
|
|
4603
|
+
tableName,
|
|
4604
|
+
alias,
|
|
4605
|
+
modelDef,
|
|
4606
|
+
dialect
|
|
4607
|
+
);
|
|
4567
4608
|
}
|
|
4568
4609
|
if (method === "count") {
|
|
4569
4610
|
return buildCountSql(
|
|
4570
4611
|
whereResult,
|
|
4571
4612
|
tableName,
|
|
4572
4613
|
alias,
|
|
4573
|
-
processed.skip
|
|
4574
|
-
);
|
|
4614
|
+
processed.skip);
|
|
4575
4615
|
}
|
|
4576
4616
|
return buildSelectSql({
|
|
4577
4617
|
method,
|
|
@@ -4611,22 +4651,23 @@ function normalizeSqlAndMappingsForDialect(sql, paramMappings, dialect) {
|
|
|
4611
4651
|
}
|
|
4612
4652
|
function buildParamsFromMappings(mappings) {
|
|
4613
4653
|
const sorted = [...mappings].sort((a, b) => a.index - b.index);
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4654
|
+
const staticParams = [];
|
|
4655
|
+
const dynamicKeys = [];
|
|
4656
|
+
let paramOrder = "";
|
|
4657
|
+
for (const m of sorted) {
|
|
4658
|
+
if (m.dynamicName !== void 0) {
|
|
4659
|
+
dynamicKeys.push(m.dynamicName);
|
|
4660
|
+
paramOrder += "d";
|
|
4661
|
+
} else if (m.value !== void 0) {
|
|
4662
|
+
staticParams.push(m.value);
|
|
4663
|
+
paramOrder += "s";
|
|
4664
|
+
} else {
|
|
4624
4665
|
throw new Error(
|
|
4625
4666
|
`CRITICAL: ParamMap ${m.index} has neither dynamicName nor value`
|
|
4626
4667
|
);
|
|
4627
|
-
}
|
|
4628
|
-
|
|
4629
|
-
|
|
4668
|
+
}
|
|
4669
|
+
}
|
|
4670
|
+
return { staticParams, dynamicKeys, paramOrder };
|
|
4630
4671
|
}
|
|
4631
4672
|
function resolveModelContext(directive) {
|
|
4632
4673
|
const { model, datamodel } = directive.context;
|
|
@@ -4692,12 +4733,13 @@ function finalizeDirective(args) {
|
|
|
4692
4733
|
return (_a = m.value) != null ? _a : void 0;
|
|
4693
4734
|
});
|
|
4694
4735
|
validateParamConsistencyByDialect(normalizedSql, params, dialect);
|
|
4695
|
-
const { staticParams, dynamicKeys } = buildParamsFromMappings(normalizedMappings);
|
|
4736
|
+
const { staticParams, dynamicKeys, paramOrder } = buildParamsFromMappings(normalizedMappings);
|
|
4696
4737
|
return {
|
|
4697
4738
|
method: directive.method,
|
|
4698
4739
|
sql: normalizedSql,
|
|
4699
4740
|
staticParams,
|
|
4700
4741
|
dynamicKeys,
|
|
4742
|
+
paramOrder,
|
|
4701
4743
|
paramMappings: normalizedMappings,
|
|
4702
4744
|
originalDirective: directive
|
|
4703
4745
|
};
|
|
@@ -5017,49 +5059,100 @@ function makeAlias(name) {
|
|
|
5017
5059
|
}
|
|
5018
5060
|
function toSqliteParams(sql, params) {
|
|
5019
5061
|
const reorderedParams = [];
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
5062
|
+
const n = sql.length;
|
|
5063
|
+
let i = 0;
|
|
5064
|
+
let out = "";
|
|
5065
|
+
let mode = "normal";
|
|
5066
|
+
while (i < n) {
|
|
5067
|
+
const ch = sql.charCodeAt(i);
|
|
5068
|
+
if (mode === "normal") {
|
|
5069
|
+
if (ch === 39) {
|
|
5070
|
+
out += sql[i];
|
|
5071
|
+
mode = "single";
|
|
5072
|
+
i++;
|
|
5073
|
+
continue;
|
|
5074
|
+
}
|
|
5075
|
+
if (ch === 34) {
|
|
5076
|
+
out += sql[i];
|
|
5077
|
+
mode = "double";
|
|
5078
|
+
i++;
|
|
5079
|
+
continue;
|
|
5029
5080
|
}
|
|
5030
|
-
if (
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
|
|
5081
|
+
if (ch === 36) {
|
|
5082
|
+
let j = i + 1;
|
|
5083
|
+
let num = 0;
|
|
5084
|
+
let hasDigit = false;
|
|
5085
|
+
while (j < n) {
|
|
5086
|
+
const d = sql.charCodeAt(j);
|
|
5087
|
+
if (d >= 48 && d <= 57) {
|
|
5088
|
+
num = num * 10 + (d - 48);
|
|
5089
|
+
hasDigit = true;
|
|
5090
|
+
j++;
|
|
5091
|
+
} else {
|
|
5092
|
+
break;
|
|
5093
|
+
}
|
|
5094
|
+
}
|
|
5095
|
+
if (hasDigit && num >= 1) {
|
|
5096
|
+
out += "?";
|
|
5097
|
+
reorderedParams.push(params[num - 1]);
|
|
5098
|
+
i = j;
|
|
5099
|
+
continue;
|
|
5100
|
+
}
|
|
5036
5101
|
}
|
|
5102
|
+
out += sql[i];
|
|
5103
|
+
i++;
|
|
5104
|
+
continue;
|
|
5037
5105
|
}
|
|
5106
|
+
if (mode === "single") {
|
|
5107
|
+
out += sql[i];
|
|
5108
|
+
if (ch === 39) {
|
|
5109
|
+
if (i + 1 < n && sql.charCodeAt(i + 1) === 39) {
|
|
5110
|
+
out += sql[i + 1];
|
|
5111
|
+
i += 2;
|
|
5112
|
+
continue;
|
|
5113
|
+
}
|
|
5114
|
+
mode = "normal";
|
|
5115
|
+
}
|
|
5116
|
+
i++;
|
|
5117
|
+
continue;
|
|
5118
|
+
}
|
|
5119
|
+
if (mode === "double") {
|
|
5120
|
+
out += sql[i];
|
|
5121
|
+
if (ch === 34) {
|
|
5122
|
+
if (i + 1 < n && sql.charCodeAt(i + 1) === 34) {
|
|
5123
|
+
out += sql[i + 1];
|
|
5124
|
+
i += 2;
|
|
5125
|
+
continue;
|
|
5126
|
+
}
|
|
5127
|
+
mode = "normal";
|
|
5128
|
+
}
|
|
5129
|
+
i++;
|
|
5130
|
+
continue;
|
|
5131
|
+
}
|
|
5132
|
+
out += sql[i];
|
|
5133
|
+
i++;
|
|
5038
5134
|
}
|
|
5039
|
-
|
|
5040
|
-
return { sql: parts.join(""), params: reorderedParams };
|
|
5135
|
+
return { sql: out, params: reorderedParams };
|
|
5041
5136
|
}
|
|
5042
|
-
function
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
if (typeof obj === "object") {
|
|
5052
|
-
const sorted = {};
|
|
5053
|
-
for (const key of Object.keys(obj).sort()) {
|
|
5054
|
-
const value = obj[key];
|
|
5055
|
-
sorted[key] = schemaParser.isDynamicParameter(value) ? "__DYN__" : normalize(value);
|
|
5137
|
+
function canonicalizeReplacer(key, value) {
|
|
5138
|
+
if (typeof value === "bigint") return `__bigint__${value.toString()}`;
|
|
5139
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
5140
|
+
const obj = value;
|
|
5141
|
+
const sorted = {};
|
|
5142
|
+
for (const k of Object.keys(obj).sort()) {
|
|
5143
|
+
const v = obj[k];
|
|
5144
|
+
if (v && typeof v === "object" && !Array.isArray(v) && Object.keys(v).length === 0) {
|
|
5145
|
+
continue;
|
|
5056
5146
|
}
|
|
5057
|
-
|
|
5147
|
+
sorted[k] = v;
|
|
5058
5148
|
}
|
|
5059
|
-
return
|
|
5149
|
+
return sorted;
|
|
5060
5150
|
}
|
|
5061
|
-
|
|
5062
|
-
|
|
5151
|
+
return value;
|
|
5152
|
+
}
|
|
5153
|
+
function canonicalizeQuery(modelName, method, args, dialect) {
|
|
5154
|
+
if (!args) return `${dialect}:${modelName}:${method}:{}`;
|
|
5155
|
+
return `${dialect}:${modelName}:${method}:${JSON.stringify(args, canonicalizeReplacer)}`;
|
|
5063
5156
|
}
|
|
5064
5157
|
function buildSQLFull(model, models, method, args, dialect) {
|
|
5065
5158
|
const tableName = buildTableReference(
|
|
@@ -5123,46 +5216,20 @@ function buildSQLFull(model, models, method, args, dialect) {
|
|
|
5123
5216
|
}
|
|
5124
5217
|
function buildSQLWithCache(model, models, method, args, dialect) {
|
|
5125
5218
|
const cacheKey = canonicalizeQuery(model.name, method, args, dialect);
|
|
5126
|
-
const
|
|
5127
|
-
if (
|
|
5128
|
-
|
|
5129
|
-
return { sql: cachedSql, params: result2.params };
|
|
5219
|
+
const cached = queryCache.get(cacheKey);
|
|
5220
|
+
if (cached) {
|
|
5221
|
+
return { sql: cached.sql, params: [...cached.params] };
|
|
5130
5222
|
}
|
|
5131
5223
|
const result = buildSQLFull(model, models, method, args, dialect);
|
|
5132
|
-
queryCache.set(cacheKey, result
|
|
5224
|
+
queryCache.set(cacheKey, result);
|
|
5133
5225
|
queryCache.size;
|
|
5134
5226
|
return result;
|
|
5135
5227
|
}
|
|
5136
5228
|
|
|
5137
5229
|
// src/batch.ts
|
|
5138
|
-
function
|
|
5139
|
-
for (let i = 0; i < s.length; i++) {
|
|
5140
|
-
const c = s.charCodeAt(i);
|
|
5141
|
-
if (c <= 31 || c === 127) {
|
|
5142
|
-
throw new Error(`${label} contains control characters`);
|
|
5143
|
-
}
|
|
5144
|
-
}
|
|
5145
|
-
}
|
|
5146
|
-
function assertSafeIdentifier(label, s) {
|
|
5147
|
-
const raw = String(s);
|
|
5148
|
-
if (raw.trim() !== raw) {
|
|
5149
|
-
throw new Error(`${label} must not contain leading/trailing whitespace`);
|
|
5150
|
-
}
|
|
5151
|
-
if (raw.length === 0) throw new Error(`${label} cannot be empty`);
|
|
5152
|
-
assertNoControlChars2(label, raw);
|
|
5153
|
-
if (/[ \t\r\n]/.test(raw)) {
|
|
5154
|
-
throw new Error(`${label} must not contain whitespace`);
|
|
5155
|
-
}
|
|
5156
|
-
if (raw.includes(";")) {
|
|
5157
|
-
throw new Error(`${label} must not contain semicolons`);
|
|
5158
|
-
}
|
|
5159
|
-
if (raw.includes("--") || raw.includes("/*") || raw.includes("*/")) {
|
|
5160
|
-
throw new Error(`${label} must not contain SQL comment tokens`);
|
|
5161
|
-
}
|
|
5162
|
-
}
|
|
5163
|
-
function quoteIdent(id) {
|
|
5230
|
+
function quoteBatchIdent(id) {
|
|
5164
5231
|
const raw = String(id);
|
|
5165
|
-
|
|
5232
|
+
assertSafeAlias(raw);
|
|
5166
5233
|
return `"${raw.replace(/"/g, '""')}"`;
|
|
5167
5234
|
}
|
|
5168
5235
|
function makeBatchAlias(i) {
|
|
@@ -5324,6 +5391,14 @@ function replacePgPlaceholders(sql, replace) {
|
|
|
5324
5391
|
}
|
|
5325
5392
|
return out;
|
|
5326
5393
|
}
|
|
5394
|
+
function containsPgPlaceholder(sql) {
|
|
5395
|
+
let found = false;
|
|
5396
|
+
replacePgPlaceholders(sql, (oldIndex) => {
|
|
5397
|
+
found = true;
|
|
5398
|
+
return `$${oldIndex}`;
|
|
5399
|
+
});
|
|
5400
|
+
return found;
|
|
5401
|
+
}
|
|
5327
5402
|
function reindexParams(sql, params, offset) {
|
|
5328
5403
|
if (!Number.isInteger(offset) || offset < 0) {
|
|
5329
5404
|
throw new Error(`Invalid param offset: ${offset}`);
|
|
@@ -5347,7 +5422,7 @@ function reindexParams(sql, params, offset) {
|
|
|
5347
5422
|
return { sql: reindexed, params: newParams };
|
|
5348
5423
|
}
|
|
5349
5424
|
function wrapQueryForMethod(method, cteName, resultAlias) {
|
|
5350
|
-
const outKey =
|
|
5425
|
+
const outKey = quoteBatchIdent(resultAlias);
|
|
5351
5426
|
switch (method) {
|
|
5352
5427
|
case "findMany":
|
|
5353
5428
|
case "groupBy":
|
|
@@ -5381,9 +5456,6 @@ function looksTooComplexForFilter(sql) {
|
|
|
5381
5456
|
if (s.includes(" distinct ")) return true;
|
|
5382
5457
|
return false;
|
|
5383
5458
|
}
|
|
5384
|
-
function containsPgPlaceholder(sql) {
|
|
5385
|
-
return /\$[1-9][0-9]*/.test(sql);
|
|
5386
|
-
}
|
|
5387
5459
|
function parseSimpleCountSql(sql) {
|
|
5388
5460
|
const trimmed = sql.trim().replace(/;$/, "").trim();
|
|
5389
5461
|
const lower = trimmed.toLowerCase();
|
|
@@ -5431,7 +5503,7 @@ function buildMergedCountBatchSql(queries, keys, aliasesByKey, modelMap, models,
|
|
|
5431
5503
|
if (built.params.length > 0) return null;
|
|
5432
5504
|
if (sharedFrom === null) sharedFrom = parsed.fromSql;
|
|
5433
5505
|
if (sharedFrom !== parsed.fromSql) return null;
|
|
5434
|
-
expressions.push(`count(*) AS ${
|
|
5506
|
+
expressions.push(`count(*) AS ${quoteBatchIdent(alias2)}`);
|
|
5435
5507
|
localKeys.push(key);
|
|
5436
5508
|
localAliases.push(alias2);
|
|
5437
5509
|
continue;
|
|
@@ -5445,7 +5517,7 @@ function buildMergedCountBatchSql(queries, keys, aliasesByKey, modelMap, models,
|
|
|
5445
5517
|
);
|
|
5446
5518
|
for (let p = 0; p < re.params.length; p++) localParams.push(re.params[p]);
|
|
5447
5519
|
expressions.push(
|
|
5448
|
-
`count(*) FILTER (WHERE ${re.sql}) AS ${
|
|
5520
|
+
`count(*) FILTER (WHERE ${re.sql}) AS ${quoteBatchIdent(alias2)}`
|
|
5449
5521
|
);
|
|
5450
5522
|
localKeys.push(key);
|
|
5451
5523
|
localAliases.push(alias2);
|
|
@@ -5478,7 +5550,7 @@ function buildMergedCountBatchSql(queries, keys, aliasesByKey, modelMap, models,
|
|
|
5478
5550
|
for (let k = 0; k < sq.aliases.length; k++) {
|
|
5479
5551
|
const outAlias = sq.aliases[k];
|
|
5480
5552
|
selectParts.push(
|
|
5481
|
-
`${sq.alias}.${
|
|
5553
|
+
`${sq.alias}.${quoteBatchIdent(outAlias)} AS ${quoteBatchIdent(outAlias)}`
|
|
5482
5554
|
);
|
|
5483
5555
|
}
|
|
5484
5556
|
}
|
|
@@ -5585,7 +5657,7 @@ function buildBatchCountSql(queries, modelMap, models, dialect) {
|
|
|
5585
5657
|
const cteName = `count_${i}`;
|
|
5586
5658
|
const resultKey = `count_${i}`;
|
|
5587
5659
|
ctes[i] = `${cteName} AS (${reindexedSql})`;
|
|
5588
|
-
selects[i] = `(SELECT * FROM ${cteName}) AS ${
|
|
5660
|
+
selects[i] = `(SELECT * FROM ${cteName}) AS ${quoteBatchIdent(resultKey)}`;
|
|
5589
5661
|
}
|
|
5590
5662
|
const sql = `WITH ${ctes.join(", ")} SELECT ${selects.join(", ")}`;
|
|
5591
5663
|
return { sql, params: allParams };
|
|
@@ -5702,6 +5774,20 @@ function isolationLevelToPostgresKeyword(level) {
|
|
|
5702
5774
|
return void 0;
|
|
5703
5775
|
}
|
|
5704
5776
|
}
|
|
5777
|
+
function validateTimeout(timeout) {
|
|
5778
|
+
if (typeof timeout !== "number") {
|
|
5779
|
+
throw new Error(
|
|
5780
|
+
`Transaction timeout must be a number, got ${typeof timeout}`
|
|
5781
|
+
);
|
|
5782
|
+
}
|
|
5783
|
+
if (!Number.isFinite(timeout)) {
|
|
5784
|
+
throw new Error(`Transaction timeout must be finite, got ${timeout}`);
|
|
5785
|
+
}
|
|
5786
|
+
if (timeout < 0) {
|
|
5787
|
+
throw new Error(`Transaction timeout must be non-negative, got ${timeout}`);
|
|
5788
|
+
}
|
|
5789
|
+
return Math.floor(timeout);
|
|
5790
|
+
}
|
|
5705
5791
|
function createTransactionExecutor(deps) {
|
|
5706
5792
|
const { modelMap, allModels, dialect, executeRaw, postgresClient } = deps;
|
|
5707
5793
|
return {
|
|
@@ -5724,10 +5810,11 @@ function createTransactionExecutor(deps) {
|
|
|
5724
5810
|
`SET TRANSACTION ISOLATION LEVEL ${isolationLevel.toUpperCase()}`
|
|
5725
5811
|
);
|
|
5726
5812
|
}
|
|
5727
|
-
if (options == null ? void 0 : options.timeout) {
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5813
|
+
if ((options == null ? void 0 : options.timeout) !== void 0 && options.timeout !== null) {
|
|
5814
|
+
const validatedTimeout = validateTimeout(options.timeout);
|
|
5815
|
+
yield sql.unsafe(`SET LOCAL statement_timeout = $1`, [
|
|
5816
|
+
validatedTimeout
|
|
5817
|
+
]);
|
|
5731
5818
|
}
|
|
5732
5819
|
for (const q of queries) {
|
|
5733
5820
|
const model = modelMap.get(q.model);
|
|
@@ -5753,6 +5840,124 @@ function createTransactionExecutor(deps) {
|
|
|
5753
5840
|
}
|
|
5754
5841
|
};
|
|
5755
5842
|
}
|
|
5843
|
+
|
|
5844
|
+
// src/fast-path.ts
|
|
5845
|
+
function getIdField(model) {
|
|
5846
|
+
const idField = model.fields.find((f) => f.name === "id" && !f.isRelation);
|
|
5847
|
+
if (!idField) return null;
|
|
5848
|
+
return {
|
|
5849
|
+
name: "id",
|
|
5850
|
+
dbName: idField.dbName || "id"
|
|
5851
|
+
};
|
|
5852
|
+
}
|
|
5853
|
+
function buildColumnList(model) {
|
|
5854
|
+
const cols = [];
|
|
5855
|
+
for (const f of model.fields) {
|
|
5856
|
+
if (f.isRelation) continue;
|
|
5857
|
+
if (f.name.startsWith("@") || f.name.startsWith("//")) continue;
|
|
5858
|
+
const dbName = f.dbName || f.name;
|
|
5859
|
+
if (dbName !== f.name) {
|
|
5860
|
+
cols.push(`${quote(dbName)} AS ${quote(f.name)}`);
|
|
5861
|
+
} else {
|
|
5862
|
+
cols.push(quote(dbName));
|
|
5863
|
+
}
|
|
5864
|
+
}
|
|
5865
|
+
return cols.join(", ");
|
|
5866
|
+
}
|
|
5867
|
+
function buildSimpleQuery(model, dialect, where, params, suffix = "") {
|
|
5868
|
+
const tableName = buildTableReference(
|
|
5869
|
+
SQL_TEMPLATES.PUBLIC_SCHEMA,
|
|
5870
|
+
model.tableName,
|
|
5871
|
+
dialect
|
|
5872
|
+
);
|
|
5873
|
+
const columns = buildColumnList(model);
|
|
5874
|
+
const sql = `SELECT ${columns} FROM ${tableName} ${where}${suffix}`;
|
|
5875
|
+
return { sql, params };
|
|
5876
|
+
}
|
|
5877
|
+
function norm(value) {
|
|
5878
|
+
return normalizeValue(value);
|
|
5879
|
+
}
|
|
5880
|
+
function tryFastPath(model, method, args, dialect) {
|
|
5881
|
+
const where = args.where;
|
|
5882
|
+
if (method === "findUnique" && isPlainObject(where) && Object.keys(where).length === 1 && "id" in where && !args.select && !args.include) {
|
|
5883
|
+
const idField = getIdField(model);
|
|
5884
|
+
if (!idField) return null;
|
|
5885
|
+
const whereClause = dialect === "sqlite" ? `WHERE ${quote(idField.dbName)} = ?` : `WHERE ${quote(idField.dbName)} = $1`;
|
|
5886
|
+
return buildSimpleQuery(
|
|
5887
|
+
model,
|
|
5888
|
+
dialect,
|
|
5889
|
+
whereClause,
|
|
5890
|
+
[norm(where.id)],
|
|
5891
|
+
" LIMIT 1"
|
|
5892
|
+
);
|
|
5893
|
+
}
|
|
5894
|
+
if (method === "findMany" && isPlainObject(where) && Object.keys(where).length === 1 && "id" in where && !args.select && !args.include && !args.orderBy && !args.take && !args.skip && !args.distinct && !args.cursor) {
|
|
5895
|
+
const idField = getIdField(model);
|
|
5896
|
+
if (!idField) return null;
|
|
5897
|
+
const whereClause = dialect === "sqlite" ? `WHERE ${quote(idField.dbName)} = ?` : `WHERE ${quote(idField.dbName)} = $1`;
|
|
5898
|
+
return buildSimpleQuery(model, dialect, whereClause, [norm(where.id)]);
|
|
5899
|
+
}
|
|
5900
|
+
if (method === "count" && (!where || Object.keys(where).length === 0) && !args.select && !args.skip) {
|
|
5901
|
+
const tableName = buildTableReference(
|
|
5902
|
+
SQL_TEMPLATES.PUBLIC_SCHEMA,
|
|
5903
|
+
model.tableName,
|
|
5904
|
+
dialect
|
|
5905
|
+
);
|
|
5906
|
+
const sql = `SELECT COUNT(*) AS ${quote("_count._all")} FROM ${tableName}`;
|
|
5907
|
+
return { sql, params: [] };
|
|
5908
|
+
}
|
|
5909
|
+
if (method === "findMany" && (!where || Object.keys(where).length === 0) && !args.select && !args.include && !args.orderBy && !args.skip && !args.distinct && !args.cursor && typeof args.take === "number") {
|
|
5910
|
+
const tableName = buildTableReference(
|
|
5911
|
+
SQL_TEMPLATES.PUBLIC_SCHEMA,
|
|
5912
|
+
model.tableName,
|
|
5913
|
+
dialect
|
|
5914
|
+
);
|
|
5915
|
+
const columns = buildColumnList(model);
|
|
5916
|
+
const sql = dialect === "sqlite" ? `SELECT ${columns} FROM ${tableName} LIMIT ?` : `SELECT ${columns} FROM ${tableName} LIMIT $1`;
|
|
5917
|
+
return { sql, params: [norm(args.take)] };
|
|
5918
|
+
}
|
|
5919
|
+
if (method === "findFirst" && isPlainObject(where) && Object.keys(where).length === 1 && !args.select && !args.include && !args.orderBy && !args.skip) {
|
|
5920
|
+
const field = Object.keys(where)[0];
|
|
5921
|
+
const value = where[field];
|
|
5922
|
+
if (value !== null && typeof value !== "object") {
|
|
5923
|
+
const fieldDef = model.fields.find((f) => f.name === field);
|
|
5924
|
+
if (fieldDef && !fieldDef.isRelation) {
|
|
5925
|
+
const columnName = fieldDef.dbName || field;
|
|
5926
|
+
const whereClause = dialect === "sqlite" ? `WHERE ${quote(columnName)} = ?` : `WHERE ${quote(columnName)} = $1`;
|
|
5927
|
+
return buildSimpleQuery(
|
|
5928
|
+
model,
|
|
5929
|
+
dialect,
|
|
5930
|
+
whereClause,
|
|
5931
|
+
[norm(value)],
|
|
5932
|
+
" LIMIT 1"
|
|
5933
|
+
);
|
|
5934
|
+
}
|
|
5935
|
+
}
|
|
5936
|
+
}
|
|
5937
|
+
if (method === "findMany" && isPlainObject(where) && Object.keys(where).length === 1 && !args.select && !args.include && !args.orderBy && !args.take && !args.skip && !args.distinct && !args.cursor) {
|
|
5938
|
+
const field = Object.keys(where)[0];
|
|
5939
|
+
const value = where[field];
|
|
5940
|
+
if (value !== null && typeof value !== "object") {
|
|
5941
|
+
const fieldDef = model.fields.find((f) => f.name === field);
|
|
5942
|
+
if (fieldDef && !fieldDef.isRelation) {
|
|
5943
|
+
const columnName = fieldDef.dbName || field;
|
|
5944
|
+
const whereClause = dialect === "sqlite" ? `WHERE ${quote(columnName)} = ?` : `WHERE ${quote(columnName)} = $1`;
|
|
5945
|
+
return buildSimpleQuery(model, dialect, whereClause, [norm(value)]);
|
|
5946
|
+
}
|
|
5947
|
+
}
|
|
5948
|
+
}
|
|
5949
|
+
if (method === "findMany" && (!where || Object.keys(where).length === 0) && !args.select && !args.include && !args.orderBy && !args.take && !args.skip && !args.distinct && !args.cursor) {
|
|
5950
|
+
const tableName = buildTableReference(
|
|
5951
|
+
SQL_TEMPLATES.PUBLIC_SCHEMA,
|
|
5952
|
+
model.tableName,
|
|
5953
|
+
dialect
|
|
5954
|
+
);
|
|
5955
|
+
const columns = buildColumnList(model);
|
|
5956
|
+
const sql = `SELECT ${columns} FROM ${tableName}`;
|
|
5957
|
+
return { sql, params: [] };
|
|
5958
|
+
}
|
|
5959
|
+
return null;
|
|
5960
|
+
}
|
|
5756
5961
|
var ACCELERATED_METHODS = /* @__PURE__ */ new Set([
|
|
5757
5962
|
"findMany",
|
|
5758
5963
|
"findFirst",
|
|
@@ -5832,8 +6037,13 @@ function createExecuteQuery(client, dialect) {
|
|
|
5832
6037
|
});
|
|
5833
6038
|
}
|
|
5834
6039
|
function logAcceleratedError(debug, dialect, modelName, method, error) {
|
|
5835
|
-
|
|
5836
|
-
console.
|
|
6040
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
6041
|
+
console.warn(
|
|
6042
|
+
`[prisma-sql] ${modelName}.${method} acceleration failed, falling back to Prisma: ${msg}`
|
|
6043
|
+
);
|
|
6044
|
+
if (debug && error instanceof Error && error.stack) {
|
|
6045
|
+
console.warn(error.stack);
|
|
6046
|
+
}
|
|
5837
6047
|
}
|
|
5838
6048
|
function canAccelerate(deps, modelName, method) {
|
|
5839
6049
|
if (!ACCELERATED_METHODS.has(method)) return false;
|
|
@@ -5842,18 +6052,34 @@ function canAccelerate(deps, modelName, method) {
|
|
|
5842
6052
|
}
|
|
5843
6053
|
function runAccelerated(deps, modelName, method, model, args) {
|
|
5844
6054
|
return __async(this, null, function* () {
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
6055
|
+
return withDialect(deps.dialect, () => __async(null, null, function* () {
|
|
6056
|
+
const fastPath = tryFastPath(model, method, args || {}, deps.dialect);
|
|
6057
|
+
if (fastPath) {
|
|
6058
|
+
if (deps.debug) {
|
|
6059
|
+
console.log(`[${deps.dialect}] ${modelName}.${method} \u26A1 FAST PATH`);
|
|
6060
|
+
console.log("SQL:", fastPath.sql);
|
|
6061
|
+
console.log("Params:", fastPath.params);
|
|
6062
|
+
}
|
|
6063
|
+
const results2 = yield deps.executeQuery(
|
|
6064
|
+
method,
|
|
6065
|
+
fastPath.sql,
|
|
6066
|
+
fastPath.params
|
|
6067
|
+
);
|
|
6068
|
+
return transformQueryResults(method, results2);
|
|
6069
|
+
}
|
|
6070
|
+
const results = yield executeWithTiming({
|
|
6071
|
+
modelName,
|
|
6072
|
+
method,
|
|
6073
|
+
model,
|
|
6074
|
+
allModels: deps.allModels,
|
|
6075
|
+
args: args || {},
|
|
6076
|
+
dialect: deps.dialect,
|
|
6077
|
+
debug: deps.debug,
|
|
6078
|
+
executeQuery: deps.executeQuery,
|
|
6079
|
+
onQuery: deps.onQuery
|
|
6080
|
+
});
|
|
6081
|
+
return transformQueryResults(method, results);
|
|
6082
|
+
}));
|
|
5857
6083
|
});
|
|
5858
6084
|
}
|
|
5859
6085
|
function handleMethodCall(ctx, method, args, deps) {
|