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