prisma-sql 1.64.0 → 1.65.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 +1119 -239
- package/dist/generator.cjs.map +1 -1
- package/dist/generator.js +1119 -239
- package/dist/generator.js.map +1 -1
- package/dist/index.cjs +1410 -282
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +32 -2
- package/dist/index.d.ts +32 -2
- package/dist/index.js +1409 -283
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/readme.md +1 -1
package/dist/generator.cjs
CHANGED
|
@@ -71,7 +71,7 @@ var require_package = __commonJS({
|
|
|
71
71
|
"package.json"(exports$1, module) {
|
|
72
72
|
module.exports = {
|
|
73
73
|
name: "prisma-sql",
|
|
74
|
-
version: "1.
|
|
74
|
+
version: "1.65.0",
|
|
75
75
|
description: "Convert Prisma queries to optimized SQL with type safety. 2-7x faster than Prisma Client.",
|
|
76
76
|
main: "dist/index.cjs",
|
|
77
77
|
module: "dist/index.js",
|
|
@@ -762,81 +762,6 @@ function needsQuoting(identifier) {
|
|
|
762
762
|
return false;
|
|
763
763
|
}
|
|
764
764
|
|
|
765
|
-
// src/builder/shared/model-field-cache.ts
|
|
766
|
-
var MODEL_CACHE = /* @__PURE__ */ new WeakMap();
|
|
767
|
-
function quoteIdent(id) {
|
|
768
|
-
if (typeof id !== "string" || id.trim().length === 0) {
|
|
769
|
-
throw new Error("quoteIdent: identifier is required and cannot be empty");
|
|
770
|
-
}
|
|
771
|
-
for (let i = 0; i < id.length; i++) {
|
|
772
|
-
const code = id.charCodeAt(i);
|
|
773
|
-
if (code >= 0 && code <= 31 || code === 127) {
|
|
774
|
-
throw new Error(
|
|
775
|
-
`quoteIdent: identifier contains invalid characters: ${JSON.stringify(id)}`
|
|
776
|
-
);
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
if (needsQuoting(id)) {
|
|
780
|
-
return `"${id.replace(/"/g, '""')}"`;
|
|
781
|
-
}
|
|
782
|
-
return id;
|
|
783
|
-
}
|
|
784
|
-
function ensureFullCache(model) {
|
|
785
|
-
let cache = MODEL_CACHE.get(model);
|
|
786
|
-
if (!cache) {
|
|
787
|
-
const fieldInfo = /* @__PURE__ */ new Map();
|
|
788
|
-
const scalarFields = /* @__PURE__ */ new Set();
|
|
789
|
-
const relationFields = /* @__PURE__ */ new Set();
|
|
790
|
-
const columnMap = /* @__PURE__ */ new Map();
|
|
791
|
-
const fieldByName = /* @__PURE__ */ new Map();
|
|
792
|
-
const quotedColumns = /* @__PURE__ */ new Map();
|
|
793
|
-
for (const f of model.fields) {
|
|
794
|
-
const info = {
|
|
795
|
-
name: f.name,
|
|
796
|
-
dbName: f.dbName || f.name,
|
|
797
|
-
type: f.type,
|
|
798
|
-
isRelation: !!f.isRelation,
|
|
799
|
-
isRequired: !!f.isRequired
|
|
800
|
-
};
|
|
801
|
-
fieldInfo.set(f.name, info);
|
|
802
|
-
fieldByName.set(f.name, f);
|
|
803
|
-
if (info.isRelation) {
|
|
804
|
-
relationFields.add(f.name);
|
|
805
|
-
} else {
|
|
806
|
-
scalarFields.add(f.name);
|
|
807
|
-
const dbName = info.dbName;
|
|
808
|
-
columnMap.set(f.name, dbName);
|
|
809
|
-
quotedColumns.set(f.name, quoteIdent(dbName));
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
cache = {
|
|
813
|
-
fieldInfo,
|
|
814
|
-
scalarFields,
|
|
815
|
-
relationFields,
|
|
816
|
-
columnMap,
|
|
817
|
-
fieldByName,
|
|
818
|
-
quotedColumns
|
|
819
|
-
};
|
|
820
|
-
MODEL_CACHE.set(model, cache);
|
|
821
|
-
}
|
|
822
|
-
return cache;
|
|
823
|
-
}
|
|
824
|
-
function getFieldInfo(model, fieldName) {
|
|
825
|
-
return ensureFullCache(model).fieldInfo.get(fieldName);
|
|
826
|
-
}
|
|
827
|
-
function getScalarFieldSet(model) {
|
|
828
|
-
return ensureFullCache(model).scalarFields;
|
|
829
|
-
}
|
|
830
|
-
function getRelationFieldSet(model) {
|
|
831
|
-
return ensureFullCache(model).relationFields;
|
|
832
|
-
}
|
|
833
|
-
function getColumnMap(model) {
|
|
834
|
-
return ensureFullCache(model).columnMap;
|
|
835
|
-
}
|
|
836
|
-
function getQuotedColumn(model, fieldName) {
|
|
837
|
-
return ensureFullCache(model).quotedColumns.get(fieldName);
|
|
838
|
-
}
|
|
839
|
-
|
|
840
765
|
// src/builder/shared/sql-utils.ts
|
|
841
766
|
function containsControlChars(s) {
|
|
842
767
|
for (let i = 0; i < s.length; i++) {
|
|
@@ -1138,6 +1063,70 @@ function normalizeKeyList(input) {
|
|
|
1138
1063
|
return s.length > 0 ? [s] : [];
|
|
1139
1064
|
}
|
|
1140
1065
|
|
|
1066
|
+
// src/builder/shared/model-field-cache.ts
|
|
1067
|
+
var SCALAR_FIELD_CACHE = /* @__PURE__ */ new WeakMap();
|
|
1068
|
+
var RELATION_FIELD_CACHE = /* @__PURE__ */ new WeakMap();
|
|
1069
|
+
var COLUMN_MAP_CACHE = /* @__PURE__ */ new WeakMap();
|
|
1070
|
+
var QUOTED_COLUMN_CACHE = /* @__PURE__ */ new WeakMap();
|
|
1071
|
+
var FIELD_BY_NAME_CACHE = /* @__PURE__ */ new WeakMap();
|
|
1072
|
+
function getScalarFieldSet(model) {
|
|
1073
|
+
let cached = SCALAR_FIELD_CACHE.get(model);
|
|
1074
|
+
if (cached) return cached;
|
|
1075
|
+
const set = /* @__PURE__ */ new Set();
|
|
1076
|
+
for (const f of model.fields) {
|
|
1077
|
+
if (!f.isRelation) set.add(f.name);
|
|
1078
|
+
}
|
|
1079
|
+
SCALAR_FIELD_CACHE.set(model, set);
|
|
1080
|
+
return set;
|
|
1081
|
+
}
|
|
1082
|
+
function getRelationFieldSet(model) {
|
|
1083
|
+
let cached = RELATION_FIELD_CACHE.get(model);
|
|
1084
|
+
if (cached) return cached;
|
|
1085
|
+
const set = /* @__PURE__ */ new Set();
|
|
1086
|
+
for (const f of model.fields) {
|
|
1087
|
+
if (f.isRelation) set.add(f.name);
|
|
1088
|
+
}
|
|
1089
|
+
RELATION_FIELD_CACHE.set(model, set);
|
|
1090
|
+
return set;
|
|
1091
|
+
}
|
|
1092
|
+
function getColumnMap(model) {
|
|
1093
|
+
let cached = COLUMN_MAP_CACHE.get(model);
|
|
1094
|
+
if (cached) return cached;
|
|
1095
|
+
const map = /* @__PURE__ */ new Map();
|
|
1096
|
+
for (const f of model.fields) {
|
|
1097
|
+
if (f.dbName && f.dbName !== f.name) {
|
|
1098
|
+
map.set(f.name, f.dbName);
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
COLUMN_MAP_CACHE.set(model, map);
|
|
1102
|
+
return map;
|
|
1103
|
+
}
|
|
1104
|
+
function getQuotedColumn(model, fieldName) {
|
|
1105
|
+
let cache = QUOTED_COLUMN_CACHE.get(model);
|
|
1106
|
+
if (!cache) {
|
|
1107
|
+
cache = /* @__PURE__ */ new Map();
|
|
1108
|
+
QUOTED_COLUMN_CACHE.set(model, cache);
|
|
1109
|
+
}
|
|
1110
|
+
const cached = cache.get(fieldName);
|
|
1111
|
+
if (cached !== void 0) return cached;
|
|
1112
|
+
const columnMap = getColumnMap(model);
|
|
1113
|
+
const columnName = columnMap.get(fieldName) || fieldName;
|
|
1114
|
+
const quoted = quote(columnName);
|
|
1115
|
+
cache.set(fieldName, quoted);
|
|
1116
|
+
return quoted;
|
|
1117
|
+
}
|
|
1118
|
+
function getFieldByName(model, fieldName) {
|
|
1119
|
+
let cache = FIELD_BY_NAME_CACHE.get(model);
|
|
1120
|
+
if (!cache) {
|
|
1121
|
+
cache = /* @__PURE__ */ new Map();
|
|
1122
|
+
for (const field of model.fields) {
|
|
1123
|
+
cache.set(field.name, field);
|
|
1124
|
+
}
|
|
1125
|
+
FIELD_BY_NAME_CACHE.set(model, cache);
|
|
1126
|
+
}
|
|
1127
|
+
return cache.get(fieldName);
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1141
1130
|
// src/builder/joins.ts
|
|
1142
1131
|
function isRelationField(fieldName, model) {
|
|
1143
1132
|
return getRelationFieldSet(model).has(fieldName);
|
|
@@ -1251,11 +1240,18 @@ var getNextSort = (sortRaw) => {
|
|
|
1251
1240
|
return sortRaw;
|
|
1252
1241
|
};
|
|
1253
1242
|
var flipObjectSort = (obj) => {
|
|
1254
|
-
const
|
|
1255
|
-
const
|
|
1256
|
-
const
|
|
1257
|
-
if (
|
|
1258
|
-
out.
|
|
1243
|
+
const out = __spreadValues({}, obj);
|
|
1244
|
+
const hasSort = Object.prototype.hasOwnProperty.call(obj, "sort");
|
|
1245
|
+
const hasDirection = Object.prototype.hasOwnProperty.call(obj, "direction");
|
|
1246
|
+
if (hasSort) {
|
|
1247
|
+
out.sort = getNextSort(obj.sort);
|
|
1248
|
+
} else if (hasDirection) {
|
|
1249
|
+
out.direction = getNextSort(obj.direction);
|
|
1250
|
+
} else {
|
|
1251
|
+
out.sort = getNextSort(obj.sort);
|
|
1252
|
+
}
|
|
1253
|
+
if (typeof obj.nulls === "string") {
|
|
1254
|
+
out.nulls = flipNulls(obj.nulls);
|
|
1259
1255
|
}
|
|
1260
1256
|
return out;
|
|
1261
1257
|
};
|
|
@@ -1301,7 +1297,7 @@ var normalizePairs = (pairs, parseValue) => {
|
|
|
1301
1297
|
return pairs.map(([field, rawValue]) => {
|
|
1302
1298
|
const parsed = parseValue(rawValue, field);
|
|
1303
1299
|
return {
|
|
1304
|
-
[field]: parsed.nulls !== void 0 ? {
|
|
1300
|
+
[field]: parsed.nulls !== void 0 ? { direction: parsed.direction, nulls: parsed.nulls } : parsed.direction
|
|
1305
1301
|
};
|
|
1306
1302
|
});
|
|
1307
1303
|
};
|
|
@@ -1350,37 +1346,113 @@ function ensureDeterministicOrderByInput(args) {
|
|
|
1350
1346
|
}
|
|
1351
1347
|
|
|
1352
1348
|
// src/builder/shared/validators/field-assertions.ts
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
const field = getFieldInfo(model, fieldName);
|
|
1349
|
+
function assertFieldExists(fieldName, model, path) {
|
|
1350
|
+
const field = getFieldByName(model, fieldName);
|
|
1356
1351
|
if (!field) {
|
|
1357
1352
|
throw createError(
|
|
1358
|
-
|
|
1353
|
+
`Field '${fieldName}' does not exist on model ${model.name}`,
|
|
1359
1354
|
{
|
|
1360
1355
|
field: fieldName,
|
|
1361
1356
|
modelName: model.name,
|
|
1362
|
-
|
|
1357
|
+
path
|
|
1363
1358
|
}
|
|
1364
1359
|
);
|
|
1365
1360
|
}
|
|
1361
|
+
return field;
|
|
1362
|
+
}
|
|
1363
|
+
function assertScalarField(model, fieldName, context) {
|
|
1364
|
+
const field = getFieldByName(model, fieldName);
|
|
1365
|
+
if (!field) {
|
|
1366
|
+
throw new Error(
|
|
1367
|
+
`${context}: field '${fieldName}' does not exist on model '${model.name}'`
|
|
1368
|
+
);
|
|
1369
|
+
}
|
|
1366
1370
|
if (field.isRelation) {
|
|
1367
|
-
throw
|
|
1368
|
-
`${context}
|
|
1369
|
-
{ field: fieldName, modelName: model.name }
|
|
1371
|
+
throw new Error(
|
|
1372
|
+
`${context}: field '${fieldName}' is a relation field, expected scalar field`
|
|
1370
1373
|
);
|
|
1371
1374
|
}
|
|
1372
|
-
return field;
|
|
1373
1375
|
}
|
|
1374
1376
|
function assertNumericField(model, fieldName, context) {
|
|
1375
|
-
|
|
1376
|
-
const
|
|
1377
|
-
if (!
|
|
1377
|
+
assertScalarField(model, fieldName, context);
|
|
1378
|
+
const field = getFieldByName(model, fieldName);
|
|
1379
|
+
if (!field) return;
|
|
1380
|
+
const numericTypes = /* @__PURE__ */ new Set([
|
|
1381
|
+
"Int",
|
|
1382
|
+
"BigInt",
|
|
1383
|
+
"Float",
|
|
1384
|
+
"Decimal",
|
|
1385
|
+
"Int?",
|
|
1386
|
+
"BigInt?",
|
|
1387
|
+
"Float?",
|
|
1388
|
+
"Decimal?"
|
|
1389
|
+
]);
|
|
1390
|
+
if (!numericTypes.has(field.type)) {
|
|
1391
|
+
throw new Error(
|
|
1392
|
+
`${context}: field '${fieldName}' must be numeric (Int, BigInt, Float, Decimal), got '${field.type}'`
|
|
1393
|
+
);
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
function assertValidOperator(fieldName, operator, fieldType, path, modelName) {
|
|
1397
|
+
const stringOps = /* @__PURE__ */ new Set([
|
|
1398
|
+
"equals",
|
|
1399
|
+
"not",
|
|
1400
|
+
"in",
|
|
1401
|
+
"notIn",
|
|
1402
|
+
"lt",
|
|
1403
|
+
"lte",
|
|
1404
|
+
"gt",
|
|
1405
|
+
"gte",
|
|
1406
|
+
"contains",
|
|
1407
|
+
"startsWith",
|
|
1408
|
+
"endsWith",
|
|
1409
|
+
"mode",
|
|
1410
|
+
"search"
|
|
1411
|
+
]);
|
|
1412
|
+
const numericOps = /* @__PURE__ */ new Set([
|
|
1413
|
+
"equals",
|
|
1414
|
+
"not",
|
|
1415
|
+
"in",
|
|
1416
|
+
"notIn",
|
|
1417
|
+
"lt",
|
|
1418
|
+
"lte",
|
|
1419
|
+
"gt",
|
|
1420
|
+
"gte"
|
|
1421
|
+
]);
|
|
1422
|
+
const jsonOps = /* @__PURE__ */ new Set([
|
|
1423
|
+
"equals",
|
|
1424
|
+
"not",
|
|
1425
|
+
"path",
|
|
1426
|
+
"string_contains",
|
|
1427
|
+
"string_starts_with",
|
|
1428
|
+
"string_ends_with",
|
|
1429
|
+
"array_contains",
|
|
1430
|
+
"array_starts_with",
|
|
1431
|
+
"array_ends_with"
|
|
1432
|
+
]);
|
|
1433
|
+
const isString = fieldType === "String" || fieldType === "String?";
|
|
1434
|
+
const isNumeric = ["Int", "BigInt", "Float", "Decimal"].some(
|
|
1435
|
+
(t) => fieldType === t || fieldType === `${t}?`
|
|
1436
|
+
);
|
|
1437
|
+
const isJson = fieldType === "Json" || fieldType === "Json?";
|
|
1438
|
+
if (isString && !stringOps.has(operator)) {
|
|
1378
1439
|
throw createError(
|
|
1379
|
-
|
|
1380
|
-
{ field: fieldName, modelName
|
|
1440
|
+
`Operator '${operator}' is not valid for String field '${fieldName}'`,
|
|
1441
|
+
{ field: fieldName, modelName, path, operator }
|
|
1442
|
+
);
|
|
1443
|
+
}
|
|
1444
|
+
if (isNumeric && !numericOps.has(operator)) {
|
|
1445
|
+
throw createError(
|
|
1446
|
+
`Operator '${operator}' is not valid for numeric field '${fieldName}'`,
|
|
1447
|
+
{ field: fieldName, modelName, path, operator }
|
|
1448
|
+
);
|
|
1449
|
+
}
|
|
1450
|
+
if (isJson && !jsonOps.has(operator)) {
|
|
1451
|
+
throw createError(
|
|
1452
|
+
`Operator '${operator}' is not valid for Json field '${fieldName}'`,
|
|
1453
|
+
{ field: fieldName, modelName, path, operator }
|
|
1381
1454
|
);
|
|
1382
1455
|
}
|
|
1383
|
-
return field;
|
|
1384
1456
|
}
|
|
1385
1457
|
|
|
1386
1458
|
// src/builder/pagination.ts
|
|
@@ -1894,6 +1966,14 @@ function handleInOperator(expr, op, val, params, dialect) {
|
|
|
1894
1966
|
if (val.length === 0) {
|
|
1895
1967
|
return op === Ops.IN ? "0=1" : "1=1";
|
|
1896
1968
|
}
|
|
1969
|
+
if (dialect === "sqlite" && val.length <= 30) {
|
|
1970
|
+
const placeholders = [];
|
|
1971
|
+
for (const item of val) {
|
|
1972
|
+
placeholders.push(params.add(item));
|
|
1973
|
+
}
|
|
1974
|
+
const list = placeholders.join(", ");
|
|
1975
|
+
return op === Ops.IN ? `${expr} IN (${list})` : `${expr} NOT IN (${list})`;
|
|
1976
|
+
}
|
|
1897
1977
|
const paramValue = prepareArrayParam(val, dialect);
|
|
1898
1978
|
const placeholder = params.add(paramValue);
|
|
1899
1979
|
return op === Ops.IN ? inArray(expr, placeholder, dialect) : notInArray(expr, placeholder, dialect);
|
|
@@ -2429,57 +2509,6 @@ function buildNestedRelation(fieldName, value, ctx, whereBuilder) {
|
|
|
2429
2509
|
return buildTopLevelRelation(fieldName, value, ctx, whereBuilder);
|
|
2430
2510
|
}
|
|
2431
2511
|
|
|
2432
|
-
// src/builder/shared/validators/field-validators.ts
|
|
2433
|
-
function assertFieldExists(name, model, path) {
|
|
2434
|
-
const field = model.fields.find((f) => f.name === name);
|
|
2435
|
-
if (!isNotNullish(field)) {
|
|
2436
|
-
throw createError(`Field '${name}' does not exist on '${model.name}'`, {
|
|
2437
|
-
field: name,
|
|
2438
|
-
path,
|
|
2439
|
-
modelName: model.name,
|
|
2440
|
-
availableFields: model.fields.map((f) => f.name)
|
|
2441
|
-
});
|
|
2442
|
-
}
|
|
2443
|
-
return field;
|
|
2444
|
-
}
|
|
2445
|
-
function assertValidOperator(fieldName, op, fieldType, path, modelName) {
|
|
2446
|
-
if (!isNotNullish(fieldType)) return;
|
|
2447
|
-
const ARRAY_OPS = /* @__PURE__ */ new Set([
|
|
2448
|
-
Ops.HAS,
|
|
2449
|
-
Ops.HAS_SOME,
|
|
2450
|
-
Ops.HAS_EVERY,
|
|
2451
|
-
Ops.IS_EMPTY
|
|
2452
|
-
]);
|
|
2453
|
-
const JSON_OPS2 = /* @__PURE__ */ new Set([
|
|
2454
|
-
Ops.PATH,
|
|
2455
|
-
Ops.STRING_CONTAINS,
|
|
2456
|
-
Ops.STRING_STARTS_WITH,
|
|
2457
|
-
Ops.STRING_ENDS_WITH
|
|
2458
|
-
]);
|
|
2459
|
-
const isArrayOp = ARRAY_OPS.has(op);
|
|
2460
|
-
const isFieldArray = isArrayType(fieldType);
|
|
2461
|
-
const arrayOpMismatch = isArrayOp && !isFieldArray;
|
|
2462
|
-
if (arrayOpMismatch) {
|
|
2463
|
-
throw createError(`'${op}' requires array field, got '${fieldType}'`, {
|
|
2464
|
-
operator: op,
|
|
2465
|
-
field: fieldName,
|
|
2466
|
-
path,
|
|
2467
|
-
modelName
|
|
2468
|
-
});
|
|
2469
|
-
}
|
|
2470
|
-
const isJsonOp = JSON_OPS2.has(op);
|
|
2471
|
-
const isFieldJson = isJsonType(fieldType);
|
|
2472
|
-
const jsonOpMismatch = isJsonOp && !isFieldJson;
|
|
2473
|
-
if (jsonOpMismatch) {
|
|
2474
|
-
throw createError(`'${op}' requires JSON field, got '${fieldType}'`, {
|
|
2475
|
-
operator: op,
|
|
2476
|
-
field: fieldName,
|
|
2477
|
-
path,
|
|
2478
|
-
modelName
|
|
2479
|
-
});
|
|
2480
|
-
}
|
|
2481
|
-
}
|
|
2482
|
-
|
|
2483
2512
|
// src/builder/where/builder.ts
|
|
2484
2513
|
var MAX_QUERY_DEPTH = 50;
|
|
2485
2514
|
var EMPTY_JOINS = Object.freeze([]);
|
|
@@ -2536,6 +2565,23 @@ function buildRelationFilter(fieldName, value, ctx, builder) {
|
|
|
2536
2565
|
}
|
|
2537
2566
|
return buildTopLevelRelation(fieldName, value, ctx2, builder);
|
|
2538
2567
|
}
|
|
2568
|
+
function buildSimpleEquality(expr, value, ctx) {
|
|
2569
|
+
if (value === null) return `${expr} ${SQL_TEMPLATES.IS_NULL}`;
|
|
2570
|
+
const placeholder = ctx.params.addAuto(value);
|
|
2571
|
+
return `${expr} = ${placeholder}`;
|
|
2572
|
+
}
|
|
2573
|
+
function isSimpleWhere(where) {
|
|
2574
|
+
const keys = Object.keys(where);
|
|
2575
|
+
if (keys.length !== 1) return false;
|
|
2576
|
+
const key = keys[0];
|
|
2577
|
+
const value = where[key];
|
|
2578
|
+
if (value === null) return true;
|
|
2579
|
+
if (value === void 0) return false;
|
|
2580
|
+
if (typeof value === "string") return true;
|
|
2581
|
+
if (typeof value === "number") return true;
|
|
2582
|
+
if (typeof value === "boolean") return true;
|
|
2583
|
+
return false;
|
|
2584
|
+
}
|
|
2539
2585
|
function buildWhereEntry(key, value, ctx, builder) {
|
|
2540
2586
|
const op = asLogicalOperator(key);
|
|
2541
2587
|
if (op) return buildLogical(op, value, ctx, builder);
|
|
@@ -2562,6 +2608,14 @@ function buildWhereInternal(where, ctx, builder) {
|
|
|
2562
2608
|
if (isEmptyWhere(where)) {
|
|
2563
2609
|
return freezeResult(DEFAULT_WHERE_CLAUSE, EMPTY_JOINS);
|
|
2564
2610
|
}
|
|
2611
|
+
if (isSimpleWhere(where)) {
|
|
2612
|
+
const key = Object.keys(where)[0];
|
|
2613
|
+
const value = where[key];
|
|
2614
|
+
assertFieldExists(key, ctx.model, ctx.path);
|
|
2615
|
+
const expr = col(ctx.alias, key, ctx.model);
|
|
2616
|
+
const clause = buildSimpleEquality(expr, value, ctx);
|
|
2617
|
+
return freezeResult(clause, EMPTY_JOINS);
|
|
2618
|
+
}
|
|
2565
2619
|
const allJoins = [];
|
|
2566
2620
|
const clauses = [];
|
|
2567
2621
|
for (const key in where) {
|
|
@@ -2688,10 +2742,16 @@ function buildOperator(expr, op, val, ctx, mode, fieldType) {
|
|
|
2688
2742
|
}
|
|
2689
2743
|
|
|
2690
2744
|
// src/builder/shared/alias-generator.ts
|
|
2745
|
+
var SAFE_IDENTIFIER_CACHE = /* @__PURE__ */ new Map();
|
|
2691
2746
|
function toSafeSqlIdentifier(input) {
|
|
2747
|
+
const cached = SAFE_IDENTIFIER_CACHE.get(input);
|
|
2748
|
+
if (cached !== void 0) return cached;
|
|
2692
2749
|
const raw = String(input);
|
|
2693
2750
|
const n = raw.length;
|
|
2694
|
-
if (n === 0)
|
|
2751
|
+
if (n === 0) {
|
|
2752
|
+
SAFE_IDENTIFIER_CACHE.set(input, "_t");
|
|
2753
|
+
return "_t";
|
|
2754
|
+
}
|
|
2695
2755
|
let out = "";
|
|
2696
2756
|
for (let i = 0; i < n; i++) {
|
|
2697
2757
|
const c = raw.charCodeAt(i);
|
|
@@ -2703,7 +2763,11 @@ function toSafeSqlIdentifier(input) {
|
|
|
2703
2763
|
const c0 = out.charCodeAt(0);
|
|
2704
2764
|
const startsOk = c0 >= 65 && c0 <= 90 || c0 >= 97 && c0 <= 122 || c0 === 95;
|
|
2705
2765
|
const lowered = (startsOk ? out : `_${out}`).toLowerCase();
|
|
2706
|
-
|
|
2766
|
+
const result = ALIAS_FORBIDDEN_KEYWORDS.has(lowered) ? `_${lowered}` : lowered;
|
|
2767
|
+
if (SAFE_IDENTIFIER_CACHE.size < 1e3) {
|
|
2768
|
+
SAFE_IDENTIFIER_CACHE.set(input, result);
|
|
2769
|
+
}
|
|
2770
|
+
return result;
|
|
2707
2771
|
}
|
|
2708
2772
|
function createAliasGenerator(maxAliases = 1e4) {
|
|
2709
2773
|
let counter = 0;
|
|
@@ -2814,9 +2878,17 @@ function assertCanAddParam(currentIndex) {
|
|
|
2814
2878
|
);
|
|
2815
2879
|
}
|
|
2816
2880
|
}
|
|
2817
|
-
|
|
2881
|
+
var POSTGRES_POSITION_CACHE = new Array(100);
|
|
2882
|
+
for (let i = 0; i < 100; i++) {
|
|
2883
|
+
POSTGRES_POSITION_CACHE[i] = `$${i + 1}`;
|
|
2884
|
+
}
|
|
2885
|
+
function formatPositionPostgres(position) {
|
|
2886
|
+
if (position <= 100) return POSTGRES_POSITION_CACHE[position - 1];
|
|
2818
2887
|
return `$${position}`;
|
|
2819
2888
|
}
|
|
2889
|
+
function formatPositionSqlite(_position) {
|
|
2890
|
+
return "?";
|
|
2891
|
+
}
|
|
2820
2892
|
function validateDynamicName(dynamicName) {
|
|
2821
2893
|
const dn = dynamicName.trim();
|
|
2822
2894
|
if (dn.length === 0) {
|
|
@@ -2824,13 +2896,14 @@ function validateDynamicName(dynamicName) {
|
|
|
2824
2896
|
}
|
|
2825
2897
|
return dn;
|
|
2826
2898
|
}
|
|
2827
|
-
function createStoreInternal(startIndex, initialParams = [], initialMappings = []) {
|
|
2899
|
+
function createStoreInternal(startIndex, dialect, initialParams = [], initialMappings = []) {
|
|
2828
2900
|
let index = startIndex;
|
|
2829
2901
|
const params = initialParams.length > 0 ? initialParams.slice() : [];
|
|
2830
2902
|
const mappings = initialMappings.length > 0 ? initialMappings.slice() : [];
|
|
2831
2903
|
const dynamicNameToIndex = buildDynamicNameIndex(mappings);
|
|
2832
2904
|
let dirty = true;
|
|
2833
2905
|
let cachedSnapshot = null;
|
|
2906
|
+
const formatPosition = dialect === "sqlite" ? formatPositionSqlite : formatPositionPostgres;
|
|
2834
2907
|
function addDynamic(dynamicName) {
|
|
2835
2908
|
const dn = validateDynamicName(dynamicName);
|
|
2836
2909
|
const existing = dynamicNameToIndex.get(dn);
|
|
@@ -2880,10 +2953,13 @@ function createStoreInternal(startIndex, initialParams = [], initialMappings = [
|
|
|
2880
2953
|
snapshot,
|
|
2881
2954
|
get index() {
|
|
2882
2955
|
return index;
|
|
2956
|
+
},
|
|
2957
|
+
get dialect() {
|
|
2958
|
+
return dialect;
|
|
2883
2959
|
}
|
|
2884
2960
|
};
|
|
2885
2961
|
}
|
|
2886
|
-
function createParamStore(startIndex = 1) {
|
|
2962
|
+
function createParamStore(startIndex = 1, dialect = "postgres") {
|
|
2887
2963
|
if (!Number.isInteger(startIndex) || startIndex < 1) {
|
|
2888
2964
|
throw new Error(`Start index must be integer >= 1, got ${startIndex}`);
|
|
2889
2965
|
}
|
|
@@ -2892,12 +2968,13 @@ function createParamStore(startIndex = 1) {
|
|
|
2892
2968
|
`Start index too high (${startIndex}), risk of overflow at MAX_SAFE_INTEGER`
|
|
2893
2969
|
);
|
|
2894
2970
|
}
|
|
2895
|
-
return createStoreInternal(startIndex);
|
|
2971
|
+
return createStoreInternal(startIndex, dialect);
|
|
2896
2972
|
}
|
|
2897
|
-
function createParamStoreFrom(existingParams, existingMappings, nextIndex) {
|
|
2973
|
+
function createParamStoreFrom(existingParams, existingMappings, nextIndex, dialect = "postgres") {
|
|
2898
2974
|
validateState(existingParams, existingMappings, nextIndex);
|
|
2899
2975
|
return createStoreInternal(
|
|
2900
2976
|
nextIndex,
|
|
2977
|
+
dialect,
|
|
2901
2978
|
existingParams.slice(),
|
|
2902
2979
|
existingMappings.slice()
|
|
2903
2980
|
);
|
|
@@ -2938,6 +3015,7 @@ function buildWhereClause(where, options) {
|
|
|
2938
3015
|
}
|
|
2939
3016
|
|
|
2940
3017
|
// src/builder/select/fields.ts
|
|
3018
|
+
var DEFAULT_SELECT_CACHE = /* @__PURE__ */ new WeakMap();
|
|
2941
3019
|
function toSelectEntries(select) {
|
|
2942
3020
|
const out = [];
|
|
2943
3021
|
for (const [k, v] of Object.entries(select)) {
|
|
@@ -2985,13 +3063,29 @@ function buildDefaultScalarFields(model, alias) {
|
|
|
2985
3063
|
}
|
|
2986
3064
|
return out;
|
|
2987
3065
|
}
|
|
3066
|
+
function getDefaultSelectCached(model, alias) {
|
|
3067
|
+
var _a;
|
|
3068
|
+
return (_a = DEFAULT_SELECT_CACHE.get(model)) == null ? void 0 : _a.get(alias);
|
|
3069
|
+
}
|
|
3070
|
+
function cacheDefaultSelect(model, alias, sql) {
|
|
3071
|
+
let cache = DEFAULT_SELECT_CACHE.get(model);
|
|
3072
|
+
if (!cache) {
|
|
3073
|
+
cache = /* @__PURE__ */ new Map();
|
|
3074
|
+
DEFAULT_SELECT_CACHE.set(model, cache);
|
|
3075
|
+
}
|
|
3076
|
+
cache.set(alias, sql);
|
|
3077
|
+
}
|
|
2988
3078
|
function buildSelectFields(args, model, alias) {
|
|
2989
3079
|
const scalarSet = getScalarFieldSet(model);
|
|
2990
3080
|
const relationSet = getRelationFieldSet(model);
|
|
2991
3081
|
if (!isNotNullish(args.select)) {
|
|
2992
|
-
|
|
3082
|
+
const cached = getDefaultSelectCached(model, alias);
|
|
3083
|
+
if (cached) return cached;
|
|
3084
|
+
const result = buildDefaultScalarFields(model, alias).join(
|
|
2993
3085
|
SQL_SEPARATORS.FIELD_LIST
|
|
2994
3086
|
);
|
|
3087
|
+
cacheDefaultSelect(model, alias, result);
|
|
3088
|
+
return result;
|
|
2995
3089
|
}
|
|
2996
3090
|
const entries = toSelectEntries(args.select);
|
|
2997
3091
|
validateSelectKeys(entries, scalarSet, relationSet);
|
|
@@ -3069,8 +3163,6 @@ function buildRelationSelect(relArgs, relModel, relAlias) {
|
|
|
3069
3163
|
}
|
|
3070
3164
|
return buildAllScalarParts(relModel, relAlias).join(SQL_SEPARATORS.FIELD_LIST);
|
|
3071
3165
|
}
|
|
3072
|
-
|
|
3073
|
-
// src/builder/select/includes.ts
|
|
3074
3166
|
var MAX_INCLUDE_DEPTH = 10;
|
|
3075
3167
|
var MAX_TOTAL_SUBQUERIES = 100;
|
|
3076
3168
|
var MAX_TOTAL_INCLUDES = 50;
|
|
@@ -3258,8 +3350,12 @@ function buildWhereParts(whereInput, relModel, relAlias, ctx) {
|
|
|
3258
3350
|
dialect: ctx.dialect
|
|
3259
3351
|
});
|
|
3260
3352
|
const joins = whereResult.joins.join(" ");
|
|
3261
|
-
const
|
|
3262
|
-
return {
|
|
3353
|
+
const hasClause = isValidWhereClause(whereResult.clause);
|
|
3354
|
+
return {
|
|
3355
|
+
joins,
|
|
3356
|
+
whereClause: hasClause ? ` ${SQL_TEMPLATES.AND} ${whereResult.clause}` : "",
|
|
3357
|
+
rawClause: hasClause ? whereResult.clause : ""
|
|
3358
|
+
};
|
|
3263
3359
|
}
|
|
3264
3360
|
function limitOneSql(sql, params, skipVal, scope) {
|
|
3265
3361
|
if (isNotNullish(skipVal)) {
|
|
@@ -3304,8 +3400,10 @@ function buildListIncludeSpec(args) {
|
|
|
3304
3400
|
const rowExpr = jsonBuildObject(args.relSelect, args.ctx.dialect);
|
|
3305
3401
|
const noTake = !isNotNullish(args.takeVal);
|
|
3306
3402
|
const noSkip = !isNotNullish(args.skipVal);
|
|
3403
|
+
const emptyJson = args.ctx.dialect === "postgres" ? `'[]'::json` : `json('[]')`;
|
|
3307
3404
|
if (args.ctx.dialect === "postgres" && noTake && noSkip) {
|
|
3308
|
-
const
|
|
3405
|
+
const rawAgg = args.orderBySql ? `json_agg(${rowExpr} ORDER BY ${args.orderBySql})` : `json_agg(${rowExpr})`;
|
|
3406
|
+
const selectExpr2 = `COALESCE(${rawAgg}, ${emptyJson})`;
|
|
3309
3407
|
const sql2 = buildBaseSql({
|
|
3310
3408
|
selectExpr: selectExpr2,
|
|
3311
3409
|
relTable: args.relTable,
|
|
@@ -3335,10 +3433,154 @@ function buildListIncludeSpec(args) {
|
|
|
3335
3433
|
args.skipVal,
|
|
3336
3434
|
scopeBase
|
|
3337
3435
|
);
|
|
3338
|
-
const
|
|
3436
|
+
const agg = jsonAgg("row", args.ctx.dialect);
|
|
3437
|
+
const selectExpr = `COALESCE(${agg}, ${emptyJson})`;
|
|
3339
3438
|
const sql = `${SQL_TEMPLATES.SELECT} ${selectExpr} ${SQL_TEMPLATES.FROM} (${base}) ${SQL_TEMPLATES.AS} ${rowAlias}`;
|
|
3340
3439
|
return Object.freeze({ name: args.relName, sql, isOneToOne: false });
|
|
3341
3440
|
}
|
|
3441
|
+
function resolveIncludeKeyPairs(field) {
|
|
3442
|
+
const fkFields = normalizeKeyList(field.foreignKey);
|
|
3443
|
+
if (fkFields.length === 0) {
|
|
3444
|
+
throw new Error(
|
|
3445
|
+
`Relation '${field.name}' is missing foreignKey for join-based include`
|
|
3446
|
+
);
|
|
3447
|
+
}
|
|
3448
|
+
const refs = normalizeKeyList(field.references);
|
|
3449
|
+
const refFields = refs.length > 0 ? refs : fkFields.length === 1 ? ["id"] : [];
|
|
3450
|
+
if (refFields.length !== fkFields.length) {
|
|
3451
|
+
throw new Error(
|
|
3452
|
+
`Relation '${field.name}' references count doesn't match foreignKey count`
|
|
3453
|
+
);
|
|
3454
|
+
}
|
|
3455
|
+
return {
|
|
3456
|
+
relKeyFields: field.isForeignKeyLocal ? refFields : fkFields,
|
|
3457
|
+
parentKeyFields: field.isForeignKeyLocal ? fkFields : refFields
|
|
3458
|
+
};
|
|
3459
|
+
}
|
|
3460
|
+
function buildFkSelectList(relAlias, relModel, relKeyFields) {
|
|
3461
|
+
return relKeyFields.map((f, i) => `${relAlias}.${quoteColumn(relModel, f)} AS "__fk${i}"`).join(SQL_SEPARATORS.FIELD_LIST);
|
|
3462
|
+
}
|
|
3463
|
+
function buildFkGroupByUnqualified(relKeyFields) {
|
|
3464
|
+
return relKeyFields.map((_, i) => `"__fk${i}"`).join(SQL_SEPARATORS.FIELD_LIST);
|
|
3465
|
+
}
|
|
3466
|
+
function buildJoinOnCondition(joinAlias, parentAlias, parentModel, parentKeyFields) {
|
|
3467
|
+
const parts = parentKeyFields.map(
|
|
3468
|
+
(f, i) => `${joinAlias}."__fk${i}" = ${parentAlias}.${quoteColumn(parentModel, f)}`
|
|
3469
|
+
);
|
|
3470
|
+
return parts.length === 1 ? parts[0] : `(${parts.join(" AND ")})`;
|
|
3471
|
+
}
|
|
3472
|
+
function buildPartitionBy(relAlias, relModel, relKeyFields) {
|
|
3473
|
+
return relKeyFields.map((f) => `${relAlias}.${quoteColumn(relModel, f)}`).join(SQL_SEPARATORS.FIELD_LIST);
|
|
3474
|
+
}
|
|
3475
|
+
function hasNestedRelationInArgs(relArgs, relModel) {
|
|
3476
|
+
if (!isPlainObject(relArgs)) return false;
|
|
3477
|
+
const relationSet = getRelationFieldSet(relModel);
|
|
3478
|
+
const checkSource = (src) => {
|
|
3479
|
+
if (!isPlainObject(src)) return false;
|
|
3480
|
+
for (const k of Object.keys(src)) {
|
|
3481
|
+
if (relationSet.has(k) && src[k] !== false)
|
|
3482
|
+
return true;
|
|
3483
|
+
}
|
|
3484
|
+
return false;
|
|
3485
|
+
};
|
|
3486
|
+
if (checkSource(relArgs.include)) return true;
|
|
3487
|
+
if (checkSource(relArgs.select)) return true;
|
|
3488
|
+
return false;
|
|
3489
|
+
}
|
|
3490
|
+
function canUseJoinInclude(dialect, isList, takeVal, skipVal, depth, outerHasLimit, hasNestedIncludes2) {
|
|
3491
|
+
if (dialect !== "postgres") return false;
|
|
3492
|
+
if (!isList) return false;
|
|
3493
|
+
if (depth > 0) return false;
|
|
3494
|
+
if (outerHasLimit) return false;
|
|
3495
|
+
if (hasNestedIncludes2) return false;
|
|
3496
|
+
if (schemaParser.isDynamicParameter(takeVal) || schemaParser.isDynamicParameter(skipVal)) return false;
|
|
3497
|
+
return true;
|
|
3498
|
+
}
|
|
3499
|
+
function buildJoinBasedNonPaginated(args) {
|
|
3500
|
+
const { relKeyFields, parentKeyFields } = resolveIncludeKeyPairs(args.field);
|
|
3501
|
+
const joinAlias = args.ctx.aliasGen.next(`__inc_${args.relName}`);
|
|
3502
|
+
const fkSelect = buildFkSelectList(args.relAlias, args.relModel, relKeyFields);
|
|
3503
|
+
const fkGroupBy = buildPartitionBy(args.relAlias, args.relModel, relKeyFields);
|
|
3504
|
+
const rowExpr = jsonBuildObject(args.relSelect, args.ctx.dialect);
|
|
3505
|
+
const aggExpr = args.orderBySql ? `json_agg(${rowExpr} ORDER BY ${args.orderBySql})` : `json_agg(${rowExpr})`;
|
|
3506
|
+
const joinsPart = args.whereJoins ? ` ${args.whereJoins}` : "";
|
|
3507
|
+
const wherePart = args.rawWhereClause ? ` ${SQL_TEMPLATES.WHERE} ${args.rawWhereClause}` : "";
|
|
3508
|
+
const subquery = `SELECT ${fkSelect}${SQL_SEPARATORS.FIELD_LIST}${aggExpr} AS __agg FROM ${args.relTable} ${args.relAlias}${joinsPart}${wherePart} GROUP BY ${fkGroupBy}`;
|
|
3509
|
+
const onCondition = buildJoinOnCondition(
|
|
3510
|
+
joinAlias,
|
|
3511
|
+
args.ctx.parentAlias,
|
|
3512
|
+
args.ctx.model,
|
|
3513
|
+
parentKeyFields
|
|
3514
|
+
);
|
|
3515
|
+
const joinSql = `LEFT JOIN (${subquery}) ${joinAlias} ON ${onCondition}`;
|
|
3516
|
+
const selectExpr = `COALESCE(${joinAlias}.__agg, '[]'::json) AS ${quote(args.relName)}`;
|
|
3517
|
+
return Object.freeze({
|
|
3518
|
+
name: args.relName,
|
|
3519
|
+
sql: "",
|
|
3520
|
+
isOneToOne: false,
|
|
3521
|
+
joinSql,
|
|
3522
|
+
selectExpr
|
|
3523
|
+
});
|
|
3524
|
+
}
|
|
3525
|
+
function buildJoinBasedPaginated(args) {
|
|
3526
|
+
const { relKeyFields, parentKeyFields } = resolveIncludeKeyPairs(args.field);
|
|
3527
|
+
const joinAlias = args.ctx.aliasGen.next(`__inc_${args.relName}`);
|
|
3528
|
+
const rankedAlias = args.ctx.aliasGen.next(`__ranked_${args.relName}`);
|
|
3529
|
+
const fkSelect = buildFkSelectList(args.relAlias, args.relModel, relKeyFields);
|
|
3530
|
+
const partitionBy = buildPartitionBy(
|
|
3531
|
+
args.relAlias,
|
|
3532
|
+
args.relModel,
|
|
3533
|
+
relKeyFields
|
|
3534
|
+
);
|
|
3535
|
+
const rowExpr = jsonBuildObject(args.relSelect, args.ctx.dialect);
|
|
3536
|
+
const orderExpr = args.orderBySql || `${args.relAlias}.${quoteColumn(args.relModel, "id")} ASC`;
|
|
3537
|
+
const joinsPart = args.whereJoins ? ` ${args.whereJoins}` : "";
|
|
3538
|
+
const wherePart = args.rawWhereClause ? ` ${SQL_TEMPLATES.WHERE} ${args.rawWhereClause}` : "";
|
|
3539
|
+
const innerSql = `SELECT ${fkSelect}${SQL_SEPARATORS.FIELD_LIST}${rowExpr} AS __row${SQL_SEPARATORS.FIELD_LIST}ROW_NUMBER() OVER (PARTITION BY ${partitionBy} ORDER BY ${orderExpr}) AS __rn FROM ${args.relTable} ${args.relAlias}${joinsPart}${wherePart}`;
|
|
3540
|
+
const scopeBase = buildIncludeScope(args.ctx.includePath);
|
|
3541
|
+
const rnFilterParts = [];
|
|
3542
|
+
if (isNotNullish(args.skipVal) && args.skipVal > 0) {
|
|
3543
|
+
const skipPh = addAutoScoped(
|
|
3544
|
+
args.ctx.params,
|
|
3545
|
+
args.skipVal,
|
|
3546
|
+
`${scopeBase}.skip`
|
|
3547
|
+
);
|
|
3548
|
+
rnFilterParts.push(`__rn > ${skipPh}`);
|
|
3549
|
+
if (isNotNullish(args.takeVal)) {
|
|
3550
|
+
const takePh = addAutoScoped(
|
|
3551
|
+
args.ctx.params,
|
|
3552
|
+
args.takeVal,
|
|
3553
|
+
`${scopeBase}.take`
|
|
3554
|
+
);
|
|
3555
|
+
rnFilterParts.push(`__rn <= (${skipPh} + ${takePh})`);
|
|
3556
|
+
}
|
|
3557
|
+
} else if (isNotNullish(args.takeVal)) {
|
|
3558
|
+
const takePh = addAutoScoped(
|
|
3559
|
+
args.ctx.params,
|
|
3560
|
+
args.takeVal,
|
|
3561
|
+
`${scopeBase}.take`
|
|
3562
|
+
);
|
|
3563
|
+
rnFilterParts.push(`__rn <= ${takePh}`);
|
|
3564
|
+
}
|
|
3565
|
+
const rnFilter = rnFilterParts.length > 0 ? ` ${SQL_TEMPLATES.WHERE} ${rnFilterParts.join(SQL_SEPARATORS.CONDITION_AND)}` : "";
|
|
3566
|
+
const fkGroupByOuter = buildFkGroupByUnqualified(relKeyFields);
|
|
3567
|
+
const outerSql = `SELECT ${fkGroupByOuter}${SQL_SEPARATORS.FIELD_LIST}json_agg(__row ORDER BY __rn) AS __agg FROM (${innerSql}) ${rankedAlias}${rnFilter} GROUP BY ${fkGroupByOuter}`;
|
|
3568
|
+
const onCondition = buildJoinOnCondition(
|
|
3569
|
+
joinAlias,
|
|
3570
|
+
args.ctx.parentAlias,
|
|
3571
|
+
args.ctx.model,
|
|
3572
|
+
parentKeyFields
|
|
3573
|
+
);
|
|
3574
|
+
const joinSql = `LEFT JOIN (${outerSql}) ${joinAlias} ON ${onCondition}`;
|
|
3575
|
+
const selectExpr = `COALESCE(${joinAlias}.__agg, '[]'::json) AS ${quote(args.relName)}`;
|
|
3576
|
+
return Object.freeze({
|
|
3577
|
+
name: args.relName,
|
|
3578
|
+
sql: "",
|
|
3579
|
+
isOneToOne: false,
|
|
3580
|
+
joinSql,
|
|
3581
|
+
selectExpr
|
|
3582
|
+
});
|
|
3583
|
+
}
|
|
3342
3584
|
function buildSingleInclude(relName, relArgs, field, relModel, ctx) {
|
|
3343
3585
|
const relTable = getRelationTableReference(relModel, ctx.dialect);
|
|
3344
3586
|
const relAlias = ctx.aliasGen.next(relName);
|
|
@@ -3395,6 +3637,48 @@ function buildSingleInclude(relName, relArgs, field, relModel, ctx) {
|
|
|
3395
3637
|
});
|
|
3396
3638
|
return Object.freeze({ name: relName, sql, isOneToOne: true });
|
|
3397
3639
|
}
|
|
3640
|
+
const depth = ctx.depth || 0;
|
|
3641
|
+
const outerHasLimit = ctx.outerHasLimit === true;
|
|
3642
|
+
const nestedIncludes = hasNestedRelationInArgs(relArgs, relModel);
|
|
3643
|
+
if (canUseJoinInclude(
|
|
3644
|
+
ctx.dialect,
|
|
3645
|
+
isList,
|
|
3646
|
+
adjusted.takeVal,
|
|
3647
|
+
paginationConfig.skipVal,
|
|
3648
|
+
depth,
|
|
3649
|
+
outerHasLimit,
|
|
3650
|
+
nestedIncludes
|
|
3651
|
+
)) {
|
|
3652
|
+
const hasTakeOrSkip = isNotNullish(adjusted.takeVal) || isNotNullish(paginationConfig.skipVal);
|
|
3653
|
+
if (!hasTakeOrSkip) {
|
|
3654
|
+
return buildJoinBasedNonPaginated({
|
|
3655
|
+
relName,
|
|
3656
|
+
relTable,
|
|
3657
|
+
relAlias,
|
|
3658
|
+
relModel,
|
|
3659
|
+
field,
|
|
3660
|
+
whereJoins: whereParts.joins,
|
|
3661
|
+
rawWhereClause: whereParts.rawClause,
|
|
3662
|
+
orderBySql,
|
|
3663
|
+
relSelect,
|
|
3664
|
+
ctx
|
|
3665
|
+
});
|
|
3666
|
+
}
|
|
3667
|
+
return buildJoinBasedPaginated({
|
|
3668
|
+
relName,
|
|
3669
|
+
relTable,
|
|
3670
|
+
relAlias,
|
|
3671
|
+
relModel,
|
|
3672
|
+
field,
|
|
3673
|
+
whereJoins: whereParts.joins,
|
|
3674
|
+
rawWhereClause: whereParts.rawClause,
|
|
3675
|
+
orderBySql,
|
|
3676
|
+
relSelect,
|
|
3677
|
+
takeVal: adjusted.takeVal,
|
|
3678
|
+
skipVal: paginationConfig.skipVal,
|
|
3679
|
+
ctx
|
|
3680
|
+
});
|
|
3681
|
+
}
|
|
3398
3682
|
return buildListIncludeSpec({
|
|
3399
3683
|
relName,
|
|
3400
3684
|
relTable,
|
|
@@ -3464,14 +3748,14 @@ function buildIncludeSqlInternal(args, ctx) {
|
|
|
3464
3748
|
buildSingleInclude(relName, relArgs, resolved.field, resolved.relModel, __spreadProps(__spreadValues({}, ctx), {
|
|
3465
3749
|
includePath: nextIncludePath,
|
|
3466
3750
|
visitPath: currentPath,
|
|
3467
|
-
depth
|
|
3751
|
+
depth,
|
|
3468
3752
|
stats
|
|
3469
3753
|
}))
|
|
3470
3754
|
);
|
|
3471
3755
|
}
|
|
3472
3756
|
return includes;
|
|
3473
3757
|
}
|
|
3474
|
-
function buildIncludeSql(args, model, schemas, parentAlias, params, dialect) {
|
|
3758
|
+
function buildIncludeSql(args, model, schemas, parentAlias, params, dialect, outerHasLimit = true) {
|
|
3475
3759
|
const aliasGen = createAliasGenerator();
|
|
3476
3760
|
const stats = {
|
|
3477
3761
|
totalIncludes: 0,
|
|
@@ -3491,7 +3775,8 @@ function buildIncludeSql(args, model, schemas, parentAlias, params, dialect) {
|
|
|
3491
3775
|
includePath: [],
|
|
3492
3776
|
visitPath: [],
|
|
3493
3777
|
depth: 0,
|
|
3494
|
-
stats
|
|
3778
|
+
stats,
|
|
3779
|
+
outerHasLimit
|
|
3495
3780
|
});
|
|
3496
3781
|
}
|
|
3497
3782
|
function resolveCountRelationOrThrow(relName, model, schemaByName) {
|
|
@@ -3529,7 +3814,7 @@ function resolveCountRelationOrThrow(relName, model, schemaByName) {
|
|
|
3529
3814
|
function defaultReferencesForCount(fkCount) {
|
|
3530
3815
|
if (fkCount === 1) return ["id"];
|
|
3531
3816
|
throw new Error(
|
|
3532
|
-
"Relation count for composite keys requires explicit references matching
|
|
3817
|
+
"Relation count for composite keys requires explicit references matching..."
|
|
3533
3818
|
);
|
|
3534
3819
|
}
|
|
3535
3820
|
function resolveCountKeyPairs(field) {
|
|
@@ -3638,6 +3923,307 @@ function joinNonEmpty(parts, sep) {
|
|
|
3638
3923
|
}
|
|
3639
3924
|
return result;
|
|
3640
3925
|
}
|
|
3926
|
+
|
|
3927
|
+
// src/builder/select/flat-join.ts
|
|
3928
|
+
function getPrimaryKeyField(model) {
|
|
3929
|
+
const scalarSet = getScalarFieldSet(model);
|
|
3930
|
+
for (const f of model.fields) {
|
|
3931
|
+
if (f.isId && !f.isRelation && scalarSet.has(f.name)) {
|
|
3932
|
+
return f.name;
|
|
3933
|
+
}
|
|
3934
|
+
}
|
|
3935
|
+
if (scalarSet.has("id")) return "id";
|
|
3936
|
+
throw new Error(
|
|
3937
|
+
`Model ${model.name} has no primary key field. Models must have either a field with isId=true or a field named 'id'.`
|
|
3938
|
+
);
|
|
3939
|
+
}
|
|
3940
|
+
function findPrimaryKeyFields(model) {
|
|
3941
|
+
const pkFields = model.fields.filter((f) => f.isId && !f.isRelation);
|
|
3942
|
+
if (pkFields.length > 0) return pkFields.map((f) => f.name);
|
|
3943
|
+
const idField = model.fields.find((f) => f.name === "id" && !f.isRelation);
|
|
3944
|
+
if (idField) return ["id"];
|
|
3945
|
+
throw new Error(`Model ${model.name} has no primary key field`);
|
|
3946
|
+
}
|
|
3947
|
+
function getRelationModel(parentModel, relationName, schemas) {
|
|
3948
|
+
const field = parentModel.fields.find((f) => f.name === relationName);
|
|
3949
|
+
if (!(field == null ? void 0 : field.isRelation) || !field.relatedModel) {
|
|
3950
|
+
throw new Error(`Invalid relation ${relationName} on ${parentModel.name}`);
|
|
3951
|
+
}
|
|
3952
|
+
const relModel = schemas.find((m) => m.name === field.relatedModel);
|
|
3953
|
+
if (!relModel) {
|
|
3954
|
+
throw new Error(`Related model ${field.relatedModel} not found`);
|
|
3955
|
+
}
|
|
3956
|
+
return relModel;
|
|
3957
|
+
}
|
|
3958
|
+
function extractIncludeSpecFromArgs(args, model) {
|
|
3959
|
+
const includeSpec = {};
|
|
3960
|
+
const relationSet = getRelationFieldSet(model);
|
|
3961
|
+
if (args.include && isPlainObject(args.include)) {
|
|
3962
|
+
for (const [key, value] of Object.entries(args.include)) {
|
|
3963
|
+
if (value !== false) includeSpec[key] = value;
|
|
3964
|
+
}
|
|
3965
|
+
}
|
|
3966
|
+
if (args.select && isPlainObject(args.select)) {
|
|
3967
|
+
for (const [key, value] of Object.entries(args.select)) {
|
|
3968
|
+
if (!relationSet.has(key)) continue;
|
|
3969
|
+
if (value === false) continue;
|
|
3970
|
+
if (value === true) {
|
|
3971
|
+
includeSpec[key] = true;
|
|
3972
|
+
continue;
|
|
3973
|
+
}
|
|
3974
|
+
if (isPlainObject(value)) {
|
|
3975
|
+
const v = value;
|
|
3976
|
+
if (isPlainObject(v.include) || isPlainObject(v.select)) {
|
|
3977
|
+
includeSpec[key] = value;
|
|
3978
|
+
}
|
|
3979
|
+
}
|
|
3980
|
+
}
|
|
3981
|
+
}
|
|
3982
|
+
return includeSpec;
|
|
3983
|
+
}
|
|
3984
|
+
function hasChildPagination(relArgs) {
|
|
3985
|
+
if (!isPlainObject(relArgs)) return false;
|
|
3986
|
+
const args = relArgs;
|
|
3987
|
+
if (args.take !== void 0 && args.take !== null) return true;
|
|
3988
|
+
if (args.skip !== void 0 && args.skip !== null) return true;
|
|
3989
|
+
return false;
|
|
3990
|
+
}
|
|
3991
|
+
function extractNestedIncludeSpec(relArgs, relModel) {
|
|
3992
|
+
const relationSet = getRelationFieldSet(relModel);
|
|
3993
|
+
const out = {};
|
|
3994
|
+
if (!isPlainObject(relArgs)) return out;
|
|
3995
|
+
const obj = relArgs;
|
|
3996
|
+
if (isPlainObject(obj.include)) {
|
|
3997
|
+
for (const [k, v] of Object.entries(
|
|
3998
|
+
obj.include
|
|
3999
|
+
)) {
|
|
4000
|
+
if (!relationSet.has(k)) continue;
|
|
4001
|
+
if (v === false) continue;
|
|
4002
|
+
out[k] = v;
|
|
4003
|
+
}
|
|
4004
|
+
}
|
|
4005
|
+
if (isPlainObject(obj.select)) {
|
|
4006
|
+
for (const [k, v] of Object.entries(
|
|
4007
|
+
obj.select
|
|
4008
|
+
)) {
|
|
4009
|
+
if (!relationSet.has(k)) continue;
|
|
4010
|
+
if (v === false) continue;
|
|
4011
|
+
if (v === true) {
|
|
4012
|
+
out[k] = true;
|
|
4013
|
+
continue;
|
|
4014
|
+
}
|
|
4015
|
+
if (isPlainObject(v)) {
|
|
4016
|
+
const vv = v;
|
|
4017
|
+
if (isPlainObject(vv.include) || isPlainObject(vv.select)) {
|
|
4018
|
+
out[k] = v;
|
|
4019
|
+
}
|
|
4020
|
+
}
|
|
4021
|
+
}
|
|
4022
|
+
}
|
|
4023
|
+
return out;
|
|
4024
|
+
}
|
|
4025
|
+
function extractSelectedScalarFields(relArgs, relModel) {
|
|
4026
|
+
const scalarFields = relModel.fields.filter((f) => !f.isRelation).map((f) => f.name);
|
|
4027
|
+
const scalarSet = new Set(scalarFields);
|
|
4028
|
+
if (relArgs === true || !isPlainObject(relArgs)) {
|
|
4029
|
+
return { includeAllScalars: true, selected: scalarFields };
|
|
4030
|
+
}
|
|
4031
|
+
const obj = relArgs;
|
|
4032
|
+
if (!isPlainObject(obj.select)) {
|
|
4033
|
+
return { includeAllScalars: true, selected: scalarFields };
|
|
4034
|
+
}
|
|
4035
|
+
const sel = obj.select;
|
|
4036
|
+
const selected = [];
|
|
4037
|
+
for (const [k, v] of Object.entries(sel)) {
|
|
4038
|
+
if (!scalarSet.has(k)) continue;
|
|
4039
|
+
if (v === true) selected.push(k);
|
|
4040
|
+
}
|
|
4041
|
+
return { includeAllScalars: false, selected };
|
|
4042
|
+
}
|
|
4043
|
+
function uniqPreserveOrder(items) {
|
|
4044
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4045
|
+
const out = [];
|
|
4046
|
+
for (const it of items) {
|
|
4047
|
+
if (seen.has(it)) continue;
|
|
4048
|
+
seen.add(it);
|
|
4049
|
+
out.push(it);
|
|
4050
|
+
}
|
|
4051
|
+
return out;
|
|
4052
|
+
}
|
|
4053
|
+
function buildChildColumns(args) {
|
|
4054
|
+
const { relModel, relationName, childAlias, prefix, relArgs } = args;
|
|
4055
|
+
const fullPrefix = prefix ? `${prefix}.${relationName}` : relationName;
|
|
4056
|
+
const pkFields = findPrimaryKeyFields(relModel);
|
|
4057
|
+
const scalarSelection = extractSelectedScalarFields(relArgs, relModel);
|
|
4058
|
+
const selectedScalar = scalarSelection.selected;
|
|
4059
|
+
const required = uniqPreserveOrder([...pkFields, ...selectedScalar]);
|
|
4060
|
+
const columns = [];
|
|
4061
|
+
for (const fieldName of required) {
|
|
4062
|
+
const field = relModel.fields.find(
|
|
4063
|
+
(f) => f.name === fieldName && !f.isRelation
|
|
4064
|
+
);
|
|
4065
|
+
if (!field) continue;
|
|
4066
|
+
const colName = field.dbName || field.name;
|
|
4067
|
+
const quotedCol = quote(colName);
|
|
4068
|
+
columns.push(`${childAlias}.${quotedCol} AS "${fullPrefix}.${field.name}"`);
|
|
4069
|
+
}
|
|
4070
|
+
return columns;
|
|
4071
|
+
}
|
|
4072
|
+
function canUseNestedFlatJoin(relArgs, depth) {
|
|
4073
|
+
if (depth > 10) return false;
|
|
4074
|
+
if (!isPlainObject(relArgs)) return true;
|
|
4075
|
+
if (hasChildPagination(relArgs)) return false;
|
|
4076
|
+
const obj = relArgs;
|
|
4077
|
+
if (obj.include && isPlainObject(obj.include)) {
|
|
4078
|
+
for (const childValue of Object.values(
|
|
4079
|
+
obj.include
|
|
4080
|
+
)) {
|
|
4081
|
+
if (childValue !== false && !canUseNestedFlatJoin(childValue, depth + 1))
|
|
4082
|
+
return false;
|
|
4083
|
+
}
|
|
4084
|
+
}
|
|
4085
|
+
if (obj.select && isPlainObject(obj.select)) {
|
|
4086
|
+
for (const childValue of Object.values(
|
|
4087
|
+
obj.select
|
|
4088
|
+
)) {
|
|
4089
|
+
if (childValue !== false && !canUseNestedFlatJoin(childValue, depth + 1))
|
|
4090
|
+
return false;
|
|
4091
|
+
}
|
|
4092
|
+
}
|
|
4093
|
+
return true;
|
|
4094
|
+
}
|
|
4095
|
+
function canUseFlatJoinForAll(includeSpec) {
|
|
4096
|
+
for (const value of Object.values(includeSpec)) {
|
|
4097
|
+
if (value === false) continue;
|
|
4098
|
+
if (!canUseNestedFlatJoin(value, 0)) return false;
|
|
4099
|
+
}
|
|
4100
|
+
return true;
|
|
4101
|
+
}
|
|
4102
|
+
function buildNestedJoins(parentModel, parentAlias, includeSpec, schemas, dialect, prefix, aliasCounter, depth = 0) {
|
|
4103
|
+
if (depth > 10) {
|
|
4104
|
+
throw new Error(
|
|
4105
|
+
`Nested joins exceeded maximum depth of 10 at prefix '${prefix}'`
|
|
4106
|
+
);
|
|
4107
|
+
}
|
|
4108
|
+
const joins = [];
|
|
4109
|
+
const selects = [];
|
|
4110
|
+
const orderBy = [];
|
|
4111
|
+
for (const [relName, relValue] of Object.entries(includeSpec)) {
|
|
4112
|
+
if (relValue === false) continue;
|
|
4113
|
+
const field = parentModel.fields.find((f) => f.name === relName);
|
|
4114
|
+
if (!isValidRelationField(field)) continue;
|
|
4115
|
+
const relModel = getRelationModel(parentModel, relName, schemas);
|
|
4116
|
+
const relTable = buildTableReference(
|
|
4117
|
+
SQL_TEMPLATES.PUBLIC_SCHEMA,
|
|
4118
|
+
relModel.tableName,
|
|
4119
|
+
dialect
|
|
4120
|
+
);
|
|
4121
|
+
const childAlias = `fj_${aliasCounter.count++}`;
|
|
4122
|
+
const joinCond = joinCondition(
|
|
4123
|
+
field,
|
|
4124
|
+
parentModel,
|
|
4125
|
+
relModel,
|
|
4126
|
+
parentAlias,
|
|
4127
|
+
childAlias
|
|
4128
|
+
);
|
|
4129
|
+
joins.push(`LEFT JOIN ${relTable} ${childAlias} ON ${joinCond}`);
|
|
4130
|
+
selects.push(
|
|
4131
|
+
...buildChildColumns({
|
|
4132
|
+
relModel,
|
|
4133
|
+
relationName: relName,
|
|
4134
|
+
childAlias,
|
|
4135
|
+
prefix,
|
|
4136
|
+
relArgs: relValue
|
|
4137
|
+
})
|
|
4138
|
+
);
|
|
4139
|
+
const childPkFields = findPrimaryKeyFields(relModel);
|
|
4140
|
+
for (const pkField of childPkFields) {
|
|
4141
|
+
orderBy.push(
|
|
4142
|
+
`${childAlias}.${quoteColumn(relModel, pkField)} ASC NULLS LAST`
|
|
4143
|
+
);
|
|
4144
|
+
}
|
|
4145
|
+
const nested = extractNestedIncludeSpec(relValue, relModel);
|
|
4146
|
+
if (Object.keys(nested).length > 0) {
|
|
4147
|
+
const nestedPrefix = prefix ? `${prefix}.${relName}` : relName;
|
|
4148
|
+
const deeper = buildNestedJoins(
|
|
4149
|
+
relModel,
|
|
4150
|
+
childAlias,
|
|
4151
|
+
nested,
|
|
4152
|
+
schemas,
|
|
4153
|
+
dialect,
|
|
4154
|
+
nestedPrefix,
|
|
4155
|
+
aliasCounter,
|
|
4156
|
+
depth + 1
|
|
4157
|
+
);
|
|
4158
|
+
joins.push(...deeper.joins);
|
|
4159
|
+
selects.push(...deeper.selects);
|
|
4160
|
+
orderBy.push(...deeper.orderBy);
|
|
4161
|
+
}
|
|
4162
|
+
}
|
|
4163
|
+
return { joins, selects, orderBy };
|
|
4164
|
+
}
|
|
4165
|
+
function buildFlatJoinSql(spec) {
|
|
4166
|
+
const {
|
|
4167
|
+
select,
|
|
4168
|
+
from,
|
|
4169
|
+
whereClause,
|
|
4170
|
+
whereJoins,
|
|
4171
|
+
orderBy,
|
|
4172
|
+
dialect,
|
|
4173
|
+
model,
|
|
4174
|
+
schemas,
|
|
4175
|
+
args
|
|
4176
|
+
} = spec;
|
|
4177
|
+
const includeSpec = extractIncludeSpecFromArgs(args, model);
|
|
4178
|
+
if (Object.keys(includeSpec).length === 0) {
|
|
4179
|
+
return { sql: "", requiresReduction: false, includeSpec: {} };
|
|
4180
|
+
}
|
|
4181
|
+
if (!canUseFlatJoinForAll(includeSpec)) {
|
|
4182
|
+
return { sql: "", requiresReduction: false, includeSpec: {} };
|
|
4183
|
+
}
|
|
4184
|
+
const baseJoins = whereJoins.length > 0 ? whereJoins.join(" ") : "";
|
|
4185
|
+
const baseWhere = whereClause && whereClause !== "1=1" ? `WHERE ${whereClause}` : "";
|
|
4186
|
+
const baseOrderBy = orderBy ? `ORDER BY ${orderBy}` : "";
|
|
4187
|
+
let baseSubquery = `
|
|
4188
|
+
SELECT * FROM ${from.table} ${from.alias}
|
|
4189
|
+
${baseJoins}
|
|
4190
|
+
${baseWhere}
|
|
4191
|
+
${baseOrderBy}
|
|
4192
|
+
`.trim();
|
|
4193
|
+
baseSubquery = appendPagination(baseSubquery, spec);
|
|
4194
|
+
const aliasCounter = { count: 0 };
|
|
4195
|
+
const built = buildNestedJoins(
|
|
4196
|
+
model,
|
|
4197
|
+
from.alias,
|
|
4198
|
+
includeSpec,
|
|
4199
|
+
schemas,
|
|
4200
|
+
dialect,
|
|
4201
|
+
"",
|
|
4202
|
+
aliasCounter,
|
|
4203
|
+
0
|
|
4204
|
+
);
|
|
4205
|
+
if (built.joins.length === 0) {
|
|
4206
|
+
return { sql: "", requiresReduction: false, includeSpec: {} };
|
|
4207
|
+
}
|
|
4208
|
+
const baseSelect = (select != null ? select : "").trim();
|
|
4209
|
+
const allSelects = [baseSelect, ...built.selects].filter((s) => s && s.trim().length > 0).join(SQL_SEPARATORS.FIELD_LIST);
|
|
4210
|
+
if (!allSelects) {
|
|
4211
|
+
throw new Error("Flat-join SELECT requires at least one selected field");
|
|
4212
|
+
}
|
|
4213
|
+
const pkField = getPrimaryKeyField(model);
|
|
4214
|
+
const orderByParts = [];
|
|
4215
|
+
if (orderBy) orderByParts.push(orderBy);
|
|
4216
|
+
orderByParts.push(`${from.alias}.${quoteColumn(model, pkField)} ASC`);
|
|
4217
|
+
orderByParts.push(...built.orderBy);
|
|
4218
|
+
const finalOrderBy = orderByParts.join(", ");
|
|
4219
|
+
const sql = `
|
|
4220
|
+
SELECT ${allSelects}
|
|
4221
|
+
FROM (${baseSubquery}) ${from.alias}
|
|
4222
|
+
${built.joins.join(" ")}
|
|
4223
|
+
ORDER BY ${finalOrderBy}
|
|
4224
|
+
`.trim();
|
|
4225
|
+
return { sql, requiresReduction: true, includeSpec };
|
|
4226
|
+
}
|
|
3641
4227
|
function buildWhereSql(conditions) {
|
|
3642
4228
|
if (!isNonEmptyArray(conditions)) return "";
|
|
3643
4229
|
return " " + SQL_TEMPLATES.WHERE + " " + conditions.join(SQL_SEPARATORS.CONDITION_AND);
|
|
@@ -3672,9 +4258,6 @@ function finalizeSql(sql, params, dialect) {
|
|
|
3672
4258
|
paramMappings: snapshot.mappings
|
|
3673
4259
|
};
|
|
3674
4260
|
}
|
|
3675
|
-
function isIdent(s) {
|
|
3676
|
-
return /^[A-Za-z_]\w*$/.test(s);
|
|
3677
|
-
}
|
|
3678
4261
|
function skipSpaces(s, i) {
|
|
3679
4262
|
while (i < s.length) {
|
|
3680
4263
|
const c = s.charCodeAt(i);
|
|
@@ -3768,11 +4351,6 @@ function parseSelectField(p, fromAlias) {
|
|
|
3768
4351
|
i = skipSpaces(p, i);
|
|
3769
4352
|
const a = readIdentOrQuoted(p, i);
|
|
3770
4353
|
const actualAlias = a.text.toLowerCase();
|
|
3771
|
-
if (!isIdent(a.text)) {
|
|
3772
|
-
throw new Error(
|
|
3773
|
-
`sqlite distinct emulation requires scalar select fields to be simple columns (alias.column). Got: ${p}`
|
|
3774
|
-
);
|
|
3775
|
-
}
|
|
3776
4354
|
if (actualAlias !== fromLower) {
|
|
3777
4355
|
throw new Error(`Expected alias '${fromAlias}', got '${a.text}' in: ${p}`);
|
|
3778
4356
|
}
|
|
@@ -3841,10 +4419,10 @@ function extractDistinctOrderEntries(spec) {
|
|
|
3841
4419
|
const value = item[field];
|
|
3842
4420
|
if (typeof value === "string") {
|
|
3843
4421
|
entries.push({ field, direction: value });
|
|
3844
|
-
|
|
3845
|
-
const obj = value;
|
|
3846
|
-
entries.push({ field, direction: obj.sort, nulls: obj.nulls });
|
|
4422
|
+
continue;
|
|
3847
4423
|
}
|
|
4424
|
+
const obj = value;
|
|
4425
|
+
entries.push({ field, direction: obj.direction, nulls: obj.nulls });
|
|
3848
4426
|
}
|
|
3849
4427
|
}
|
|
3850
4428
|
if (entries.length > 0) return entries;
|
|
@@ -3978,19 +4556,43 @@ function buildIncludeColumns(spec) {
|
|
|
3978
4556
|
const hasIncludes = isNonEmptyArray(includes);
|
|
3979
4557
|
const hasCountCols = isNonEmptyString(countCols);
|
|
3980
4558
|
if (!hasIncludes && !hasCountCols) {
|
|
3981
|
-
return {
|
|
4559
|
+
return {
|
|
4560
|
+
includeCols: "",
|
|
4561
|
+
selectWithIncludes: baseSelect,
|
|
4562
|
+
countJoins: [],
|
|
4563
|
+
includeJoins: []
|
|
4564
|
+
};
|
|
3982
4565
|
}
|
|
3983
4566
|
const emptyJson = dialect === "postgres" ? `'[]'::json` : `json('[]')`;
|
|
3984
|
-
const
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
4567
|
+
const correlatedParts = [];
|
|
4568
|
+
const joinIncludeJoins = [];
|
|
4569
|
+
const joinIncludeSelects = [];
|
|
4570
|
+
if (hasIncludes) {
|
|
4571
|
+
for (const inc of includes) {
|
|
4572
|
+
if (inc.joinSql && inc.selectExpr) {
|
|
4573
|
+
joinIncludeJoins.push(inc.joinSql);
|
|
4574
|
+
joinIncludeSelects.push(inc.selectExpr);
|
|
4575
|
+
} else {
|
|
4576
|
+
const expr = inc.isOneToOne ? "(" + inc.sql + ")" : "COALESCE((" + inc.sql + "), " + emptyJson + ")";
|
|
4577
|
+
correlatedParts.push(
|
|
4578
|
+
expr + " " + SQL_TEMPLATES.AS + " " + quote(inc.name)
|
|
4579
|
+
);
|
|
4580
|
+
}
|
|
4581
|
+
}
|
|
4582
|
+
}
|
|
4583
|
+
const correlatedCols = correlatedParts.join(SQL_SEPARATORS.FIELD_LIST);
|
|
4584
|
+
const joinSelectCols = joinIncludeSelects.join(SQL_SEPARATORS.FIELD_LIST);
|
|
4585
|
+
const allIncludeCols = joinNonEmpty(
|
|
4586
|
+
[correlatedCols, joinSelectCols, countCols],
|
|
3990
4587
|
SQL_SEPARATORS.FIELD_LIST
|
|
3991
4588
|
);
|
|
3992
|
-
const selectWithIncludes = buildSelectList(baseSelect,
|
|
3993
|
-
return {
|
|
4589
|
+
const selectWithIncludes = buildSelectList(baseSelect, allIncludeCols);
|
|
4590
|
+
return {
|
|
4591
|
+
includeCols: allIncludeCols,
|
|
4592
|
+
selectWithIncludes,
|
|
4593
|
+
countJoins,
|
|
4594
|
+
includeJoins: joinIncludeJoins
|
|
4595
|
+
};
|
|
3994
4596
|
}
|
|
3995
4597
|
function appendPagination(sql, spec) {
|
|
3996
4598
|
const { method, pagination, params } = spec;
|
|
@@ -4028,9 +4630,13 @@ function appendPagination(sql, spec) {
|
|
|
4028
4630
|
return parts.join(" ");
|
|
4029
4631
|
}
|
|
4030
4632
|
function hasWindowDistinct(spec) {
|
|
4633
|
+
if (spec.dialect !== "sqlite") return false;
|
|
4031
4634
|
const d = spec.distinct;
|
|
4032
4635
|
return isNotNullish(d) && isNonEmptyArray(d);
|
|
4033
4636
|
}
|
|
4637
|
+
function hasAnyDistinct(spec) {
|
|
4638
|
+
return isNotNullish(spec.distinct) && isNonEmptyArray(spec.distinct);
|
|
4639
|
+
}
|
|
4034
4640
|
function assertDistinctAllowed(method, enabled) {
|
|
4035
4641
|
if (enabled && method !== "findMany") {
|
|
4036
4642
|
throw new Error(
|
|
@@ -4038,11 +4644,6 @@ function assertDistinctAllowed(method, enabled) {
|
|
|
4038
4644
|
);
|
|
4039
4645
|
}
|
|
4040
4646
|
}
|
|
4041
|
-
function assertHasSelectFields(baseSelect, includeCols) {
|
|
4042
|
-
if (!isNonEmptyString(baseSelect) && !isNonEmptyString(includeCols)) {
|
|
4043
|
-
throw new Error("SELECT requires at least one selected field or include");
|
|
4044
|
-
}
|
|
4045
|
-
}
|
|
4046
4647
|
function withCountJoins(spec, countJoins, whereJoins) {
|
|
4047
4648
|
return __spreadProps(__spreadValues({}, spec), {
|
|
4048
4649
|
whereJoins: [...whereJoins || [], ...countJoins || []]
|
|
@@ -4069,6 +4670,63 @@ function pushWhere(parts, conditions) {
|
|
|
4069
4670
|
if (!isNonEmptyArray(conditions)) return;
|
|
4070
4671
|
parts.push(SQL_TEMPLATES.WHERE, conditions.join(SQL_SEPARATORS.CONDITION_AND));
|
|
4071
4672
|
}
|
|
4673
|
+
function extractIncludeSpec(args) {
|
|
4674
|
+
const includeSpec = {};
|
|
4675
|
+
if (args.include && isPlainObject(args.include)) {
|
|
4676
|
+
for (const [key, value] of Object.entries(args.include)) {
|
|
4677
|
+
if (value !== false) {
|
|
4678
|
+
includeSpec[key] = value;
|
|
4679
|
+
}
|
|
4680
|
+
}
|
|
4681
|
+
}
|
|
4682
|
+
if (args.select && isPlainObject(args.select)) {
|
|
4683
|
+
for (const [key, value] of Object.entries(args.select)) {
|
|
4684
|
+
if (value !== false && value !== true && isPlainObject(value)) {
|
|
4685
|
+
const selectVal = value;
|
|
4686
|
+
if (selectVal.include || selectVal.select) {
|
|
4687
|
+
includeSpec[key] = value;
|
|
4688
|
+
}
|
|
4689
|
+
}
|
|
4690
|
+
}
|
|
4691
|
+
}
|
|
4692
|
+
return includeSpec;
|
|
4693
|
+
}
|
|
4694
|
+
function hasNestedIncludes(includeSpec) {
|
|
4695
|
+
return Object.keys(includeSpec).length > 0;
|
|
4696
|
+
}
|
|
4697
|
+
function splitOrderByTerms(orderBy) {
|
|
4698
|
+
const raw = orderBy.trim();
|
|
4699
|
+
if (raw.length === 0) return [];
|
|
4700
|
+
return raw.split(SQL_SEPARATORS.ORDER_BY).map((s) => s.trim()).filter((s) => s.length > 0);
|
|
4701
|
+
}
|
|
4702
|
+
function hasIdInOrderBy(orderBy, fromAlias) {
|
|
4703
|
+
const aliasLower = String(fromAlias).toLowerCase();
|
|
4704
|
+
const terms = splitOrderByTerms(orderBy).map((t) => t.toLowerCase());
|
|
4705
|
+
return terms.some(
|
|
4706
|
+
(t) => t.startsWith(aliasLower + ".id ") || t.startsWith(aliasLower + '."id" ') || t === aliasLower + ".id" || t === aliasLower + '."id"'
|
|
4707
|
+
);
|
|
4708
|
+
}
|
|
4709
|
+
function ensureIdTiebreakerOrderBy(orderBy, fromAlias, model) {
|
|
4710
|
+
var _a, _b;
|
|
4711
|
+
const idField = (_b = (_a = model == null ? void 0 : model.fields) == null ? void 0 : _a.find) == null ? void 0 : _b.call(
|
|
4712
|
+
_a,
|
|
4713
|
+
(f) => f.name === "id" && !f.isRelation
|
|
4714
|
+
);
|
|
4715
|
+
if (!idField) return orderBy;
|
|
4716
|
+
if (hasIdInOrderBy(orderBy, fromAlias)) return orderBy;
|
|
4717
|
+
const t = col(fromAlias, "id", model) + " ASC";
|
|
4718
|
+
return isNonEmptyString(orderBy) ? orderBy + ", " + t : t;
|
|
4719
|
+
}
|
|
4720
|
+
function ensurePostgresDistinctOrderBy(args) {
|
|
4721
|
+
const { orderBy, distinct, fromAlias, model } = args;
|
|
4722
|
+
const distinctTerms = distinct.map((f) => col(fromAlias, f, model) + " ASC");
|
|
4723
|
+
const existing = splitOrderByTerms(orderBy);
|
|
4724
|
+
const canKeepAsIs = existing.length >= distinctTerms.length && distinctTerms.every(
|
|
4725
|
+
(term, i) => existing[i].toLowerCase().startsWith(term.split(" ASC")[0].toLowerCase())
|
|
4726
|
+
);
|
|
4727
|
+
const merged = canKeepAsIs ? orderBy : [...distinctTerms, ...existing].join(SQL_SEPARATORS.ORDER_BY);
|
|
4728
|
+
return ensureIdTiebreakerOrderBy(merged, fromAlias, model);
|
|
4729
|
+
}
|
|
4072
4730
|
function constructFinalSql(spec) {
|
|
4073
4731
|
const {
|
|
4074
4732
|
select,
|
|
@@ -4082,15 +4740,36 @@ function constructFinalSql(spec) {
|
|
|
4082
4740
|
cursorClause,
|
|
4083
4741
|
params,
|
|
4084
4742
|
dialect,
|
|
4085
|
-
model
|
|
4743
|
+
model,
|
|
4744
|
+
includes,
|
|
4745
|
+
schemas,
|
|
4746
|
+
pagination,
|
|
4747
|
+
args
|
|
4086
4748
|
} = spec;
|
|
4087
4749
|
const useWindowDistinct = hasWindowDistinct(spec);
|
|
4088
4750
|
assertDistinctAllowed(method, useWindowDistinct);
|
|
4089
|
-
const
|
|
4751
|
+
const hasDistinct = hasAnyDistinct(spec);
|
|
4752
|
+
assertDistinctAllowed(method, hasDistinct);
|
|
4753
|
+
const includeSpec = extractIncludeSpec(args);
|
|
4754
|
+
const hasIncludes = hasNestedIncludes(includeSpec);
|
|
4755
|
+
const shouldUseFlatJoin = dialect === "postgres" && hasIncludes && canUseFlatJoinForAll(includeSpec);
|
|
4756
|
+
if (shouldUseFlatJoin) {
|
|
4757
|
+
const flatResult = buildFlatJoinSql(spec);
|
|
4758
|
+
if (flatResult.sql) {
|
|
4759
|
+
const baseSqlResult = finalizeSql(flatResult.sql, params, dialect);
|
|
4760
|
+
return {
|
|
4761
|
+
sql: baseSqlResult.sql,
|
|
4762
|
+
params: baseSqlResult.params,
|
|
4763
|
+
paramMappings: baseSqlResult.paramMappings,
|
|
4764
|
+
requiresReduction: true,
|
|
4765
|
+
includeSpec: flatResult.includeSpec
|
|
4766
|
+
};
|
|
4767
|
+
}
|
|
4768
|
+
}
|
|
4769
|
+
const { includeCols, selectWithIncludes, countJoins, includeJoins } = buildIncludeColumns(spec);
|
|
4090
4770
|
if (useWindowDistinct) {
|
|
4091
|
-
const
|
|
4092
|
-
|
|
4093
|
-
const spec2 = withCountJoins(spec, countJoins, whereJoins);
|
|
4771
|
+
const allExtraJoins = [...countJoins, ...includeJoins];
|
|
4772
|
+
const spec2 = withCountJoins(spec, allExtraJoins, whereJoins);
|
|
4094
4773
|
let sql2 = buildSqliteDistinctQuery(spec2, selectWithIncludes).trim();
|
|
4095
4774
|
sql2 = appendPagination(sql2, spec);
|
|
4096
4775
|
return finalizeSql(sql2, params, dialect);
|
|
@@ -4112,9 +4791,22 @@ function constructFinalSql(spec) {
|
|
|
4112
4791
|
parts.push("CROSS JOIN", cteName);
|
|
4113
4792
|
}
|
|
4114
4793
|
pushJoinGroups(parts, whereJoins, countJoins);
|
|
4794
|
+
if (isNonEmptyArray(includeJoins)) {
|
|
4795
|
+
parts.push(includeJoins.join(" "));
|
|
4796
|
+
}
|
|
4115
4797
|
const conditions = buildConditions(whereClause, cursorClause);
|
|
4116
4798
|
pushWhere(parts, conditions);
|
|
4117
|
-
|
|
4799
|
+
let finalOrderBy = orderBy;
|
|
4800
|
+
if (dialect === "postgres" && isNonEmptyArray(distinct)) {
|
|
4801
|
+
finalOrderBy = ensurePostgresDistinctOrderBy({
|
|
4802
|
+
orderBy: orderBy || "",
|
|
4803
|
+
distinct: [...distinct],
|
|
4804
|
+
fromAlias: from.alias,
|
|
4805
|
+
model
|
|
4806
|
+
});
|
|
4807
|
+
}
|
|
4808
|
+
if (isNonEmptyString(finalOrderBy))
|
|
4809
|
+
parts.push(SQL_TEMPLATES.ORDER_BY, finalOrderBy);
|
|
4118
4810
|
let sql = parts.join(" ").trim();
|
|
4119
4811
|
sql = appendPagination(sql, spec);
|
|
4120
4812
|
return finalizeSql(sql, params, dialect);
|
|
@@ -4268,13 +4960,15 @@ function buildSelectSpec(input) {
|
|
|
4268
4960
|
whereResult.paramMappings,
|
|
4269
4961
|
whereResult.nextParamIndex
|
|
4270
4962
|
);
|
|
4963
|
+
const outerHasLimit = isNotNullish(take);
|
|
4271
4964
|
const includes = buildIncludeSql(
|
|
4272
4965
|
normalizedArgs,
|
|
4273
4966
|
model,
|
|
4274
4967
|
schemas,
|
|
4275
4968
|
alias,
|
|
4276
4969
|
params,
|
|
4277
|
-
dialect
|
|
4970
|
+
dialect,
|
|
4971
|
+
outerHasLimit
|
|
4278
4972
|
);
|
|
4279
4973
|
const cursorResult = buildCursorClauseIfAny({
|
|
4280
4974
|
cursor,
|
|
@@ -4454,6 +5148,14 @@ function buildInComparison(expr, op, val, params, dialect) {
|
|
|
4454
5148
|
if (val.length === 0) {
|
|
4455
5149
|
return op === Ops.IN ? "0=1" : "1=1";
|
|
4456
5150
|
}
|
|
5151
|
+
if (dialect === "sqlite" && val.length <= 30) {
|
|
5152
|
+
const placeholders = [];
|
|
5153
|
+
for (const item of val) {
|
|
5154
|
+
placeholders.push(params.add(item));
|
|
5155
|
+
}
|
|
5156
|
+
const list = placeholders.join(", ");
|
|
5157
|
+
return op === Ops.IN ? `${expr} IN (${list})` : `${expr} NOT IN (${list})`;
|
|
5158
|
+
}
|
|
4457
5159
|
const paramValue = prepareArrayParam(val, dialect);
|
|
4458
5160
|
const placeholder = params.add(paramValue);
|
|
4459
5161
|
return op === Ops.IN ? inArray(expr, placeholder, dialect) : notInArray(expr, placeholder, dialect);
|
|
@@ -4704,6 +5406,7 @@ function buildAggregateSql(args, whereResult, tableName, alias, model) {
|
|
|
4704
5406
|
throw new Error("buildAggregateSql requires at least one aggregate field");
|
|
4705
5407
|
}
|
|
4706
5408
|
const selectClause = aggFields.join(SQL_SEPARATORS.FIELD_LIST);
|
|
5409
|
+
const joinsPart = whereResult.joins && whereResult.joins.length > 0 ? whereResult.joins.join(" ") : "";
|
|
4707
5410
|
const whereClause = isValidWhereClause(whereResult.clause) ? SQL_TEMPLATES.WHERE + " " + whereResult.clause : "";
|
|
4708
5411
|
const parts = [
|
|
4709
5412
|
SQL_TEMPLATES.SELECT,
|
|
@@ -4712,6 +5415,7 @@ function buildAggregateSql(args, whereResult, tableName, alias, model) {
|
|
|
4712
5415
|
tableName,
|
|
4713
5416
|
alias
|
|
4714
5417
|
];
|
|
5418
|
+
if (joinsPart) parts.push(joinsPart);
|
|
4715
5419
|
if (whereClause) parts.push(whereClause);
|
|
4716
5420
|
const sql = parts.join(" ").trim();
|
|
4717
5421
|
validateSelectQuery(sql);
|
|
@@ -4764,7 +5468,9 @@ function buildGroupBySql(args, whereResult, tableName, alias, model, dialect) {
|
|
|
4764
5468
|
byFields
|
|
4765
5469
|
);
|
|
4766
5470
|
const havingClause = buildGroupByHaving(args, alias, params, model, d);
|
|
5471
|
+
const joinsPart = whereResult.joins && whereResult.joins.length > 0 ? whereResult.joins.join(" ") : "";
|
|
4767
5472
|
const whereClause = isValidWhereClause(whereResult.clause) ? SQL_TEMPLATES.WHERE + " " + whereResult.clause : "";
|
|
5473
|
+
const orderBySql = isNotNullish(args.orderBy) ? buildOrderBy(args.orderBy, alias, d, model) : "";
|
|
4768
5474
|
const parts = [
|
|
4769
5475
|
SQL_TEMPLATES.SELECT,
|
|
4770
5476
|
selectFields,
|
|
@@ -4772,9 +5478,21 @@ function buildGroupBySql(args, whereResult, tableName, alias, model, dialect) {
|
|
|
4772
5478
|
tableName,
|
|
4773
5479
|
alias
|
|
4774
5480
|
];
|
|
5481
|
+
if (joinsPart) parts.push(joinsPart);
|
|
4775
5482
|
if (whereClause) parts.push(whereClause);
|
|
4776
5483
|
parts.push(SQL_TEMPLATES.GROUP_BY, groupFields);
|
|
4777
5484
|
if (havingClause) parts.push(havingClause);
|
|
5485
|
+
if (orderBySql) {
|
|
5486
|
+
parts.push(SQL_TEMPLATES.ORDER_BY, orderBySql);
|
|
5487
|
+
}
|
|
5488
|
+
if (isNotNullish(args.take)) {
|
|
5489
|
+
const ph = addAutoScoped(params, args.take, "groupBy.take");
|
|
5490
|
+
parts.push(SQL_TEMPLATES.LIMIT, ph);
|
|
5491
|
+
}
|
|
5492
|
+
if (isNotNullish(args.skip)) {
|
|
5493
|
+
const ph = addAutoScoped(params, args.skip, "groupBy.skip");
|
|
5494
|
+
parts.push(SQL_TEMPLATES.OFFSET, ph);
|
|
5495
|
+
}
|
|
4778
5496
|
const sql = parts.join(" ").trim();
|
|
4779
5497
|
const snapshot = params.snapshot();
|
|
4780
5498
|
const allParams = [...whereResult.params, ...snapshot.params];
|
|
@@ -4787,32 +5505,25 @@ function buildGroupBySql(args, whereResult, tableName, alias, model, dialect) {
|
|
|
4787
5505
|
paramMappings: allMappings
|
|
4788
5506
|
};
|
|
4789
5507
|
}
|
|
4790
|
-
function
|
|
4791
|
-
|
|
5508
|
+
function findPrimaryKeyFields2(model) {
|
|
5509
|
+
const pkFields = model.fields.filter((f) => f.isId && !f.isRelation);
|
|
5510
|
+
if (pkFields.length > 0) return pkFields.map((f) => f.name);
|
|
5511
|
+
const defaultId = model.fields.find((f) => f.name === "id" && !f.isRelation);
|
|
5512
|
+
if (defaultId) return ["id"];
|
|
5513
|
+
return [];
|
|
4792
5514
|
}
|
|
4793
|
-
function
|
|
4794
|
-
|
|
5515
|
+
function normalizeCountArgs(argsOrSkip) {
|
|
5516
|
+
if (isPlainObject(argsOrSkip)) return argsOrSkip;
|
|
5517
|
+
if (argsOrSkip === void 0 || argsOrSkip === null) return {};
|
|
5518
|
+
return { skip: argsOrSkip };
|
|
4795
5519
|
}
|
|
4796
|
-
function
|
|
4797
|
-
if (
|
|
4798
|
-
|
|
4799
|
-
}
|
|
4800
|
-
if (schemaParser.isDynamicParameter(skip)) {
|
|
4801
|
-
throw new Error(
|
|
4802
|
-
"count() with skip is not supported because it produces nondeterministic results. Dynamic skip cannot be validated at build time. Use findMany().length or add explicit orderBy + cursor/skip logic in a deterministic query."
|
|
4803
|
-
);
|
|
4804
|
-
}
|
|
4805
|
-
const skipValue = parseSkipValue(skip);
|
|
4806
|
-
if (isPositiveInteger(skipValue)) {
|
|
4807
|
-
throw new Error(
|
|
4808
|
-
"count() with skip is not supported because it produces nondeterministic results. Use findMany().length or add explicit orderBy to ensure deterministic behavior."
|
|
4809
|
-
);
|
|
5520
|
+
function assertNoNegativeTake(args) {
|
|
5521
|
+
if (typeof args.take === "number" && args.take < 0) {
|
|
5522
|
+
throw new Error("Negative take is not supported for count()");
|
|
4810
5523
|
}
|
|
4811
5524
|
}
|
|
4812
|
-
function
|
|
4813
|
-
|
|
4814
|
-
assertSafeTableRef(tableName);
|
|
4815
|
-
validateSkipParameter(skip);
|
|
5525
|
+
function buildSimpleCountSql(whereResult, tableName, alias) {
|
|
5526
|
+
const joinsPart = whereResult.joins && whereResult.joins.length > 0 ? whereResult.joins.join(" ") : "";
|
|
4816
5527
|
const whereClause = isValidWhereClause(whereResult.clause) ? SQL_TEMPLATES.WHERE + " " + whereResult.clause : "";
|
|
4817
5528
|
const parts = [
|
|
4818
5529
|
SQL_TEMPLATES.SELECT,
|
|
@@ -4823,6 +5534,7 @@ function buildCountSql(whereResult, tableName, alias, skip, _dialect) {
|
|
|
4823
5534
|
tableName,
|
|
4824
5535
|
alias
|
|
4825
5536
|
];
|
|
5537
|
+
if (joinsPart) parts.push(joinsPart);
|
|
4826
5538
|
if (whereClause) parts.push(whereClause);
|
|
4827
5539
|
const sql = parts.join(" ").trim();
|
|
4828
5540
|
validateSelectQuery(sql);
|
|
@@ -4833,6 +5545,49 @@ function buildCountSql(whereResult, tableName, alias, skip, _dialect) {
|
|
|
4833
5545
|
paramMappings: whereResult.paramMappings
|
|
4834
5546
|
};
|
|
4835
5547
|
}
|
|
5548
|
+
function buildCountSql(whereResult, tableName, alias, argsOrSkip, dialect, model, schemas) {
|
|
5549
|
+
assertSafeAlias(alias);
|
|
5550
|
+
assertSafeTableRef(tableName);
|
|
5551
|
+
const args = normalizeCountArgs(argsOrSkip);
|
|
5552
|
+
assertNoNegativeTake(args);
|
|
5553
|
+
if (!model) {
|
|
5554
|
+
return buildSimpleCountSql(whereResult, tableName, alias);
|
|
5555
|
+
}
|
|
5556
|
+
const pkFields = findPrimaryKeyFields2(model);
|
|
5557
|
+
const distinctFields = isNonEmptyArray(args.distinct) ? args.distinct.map((x) => String(x)).filter((x) => x) : [];
|
|
5558
|
+
const selectFields = distinctFields.length > 0 ? distinctFields : pkFields;
|
|
5559
|
+
if (selectFields.length === 0) {
|
|
5560
|
+
return buildSimpleCountSql(whereResult, tableName, alias);
|
|
5561
|
+
}
|
|
5562
|
+
const select = {};
|
|
5563
|
+
for (const f of selectFields) select[f] = true;
|
|
5564
|
+
const subArgs = __spreadProps(__spreadValues({}, args), {
|
|
5565
|
+
include: void 0,
|
|
5566
|
+
select
|
|
5567
|
+
});
|
|
5568
|
+
const d = dialect != null ? dialect : getGlobalDialect();
|
|
5569
|
+
const subSchemas = Array.isArray(schemas) && schemas.length > 0 ? schemas : [model];
|
|
5570
|
+
const sub = buildSelectSql({
|
|
5571
|
+
method: "findMany",
|
|
5572
|
+
args: subArgs,
|
|
5573
|
+
model,
|
|
5574
|
+
schemas: subSchemas,
|
|
5575
|
+
from: { tableName, alias },
|
|
5576
|
+
whereResult,
|
|
5577
|
+
dialect: d
|
|
5578
|
+
});
|
|
5579
|
+
const countAlias = quote("__count_sub");
|
|
5580
|
+
const sql = `${SQL_TEMPLATES.SELECT} ${SQL_TEMPLATES.COUNT_ALL} ${SQL_TEMPLATES.AS} ${quote(
|
|
5581
|
+
"_count._all"
|
|
5582
|
+
)} ${SQL_TEMPLATES.FROM} (${sub.sql}) ${SQL_TEMPLATES.AS} ${countAlias}`;
|
|
5583
|
+
validateSelectQuery(sql);
|
|
5584
|
+
validateParamConsistency(sql, sub.params);
|
|
5585
|
+
return {
|
|
5586
|
+
sql,
|
|
5587
|
+
params: sub.params,
|
|
5588
|
+
paramMappings: sub.paramMappings
|
|
5589
|
+
};
|
|
5590
|
+
}
|
|
4836
5591
|
function safeAlias(input) {
|
|
4837
5592
|
const raw = String(input).toLowerCase();
|
|
4838
5593
|
const cleaned = raw.replace(/[^a-z0-9_]/g, "_");
|
|
@@ -4844,6 +5599,17 @@ function safeAlias(input) {
|
|
|
4844
5599
|
}
|
|
4845
5600
|
return base;
|
|
4846
5601
|
}
|
|
5602
|
+
function isPrismaMethod(v) {
|
|
5603
|
+
return v === "findMany" || v === "findFirst" || v === "findUnique" || v === "aggregate" || v === "groupBy" || v === "count";
|
|
5604
|
+
}
|
|
5605
|
+
function resolveMethod(directive) {
|
|
5606
|
+
var _a, _b;
|
|
5607
|
+
const m = directive == null ? void 0 : directive.method;
|
|
5608
|
+
if (isPrismaMethod(m)) return m;
|
|
5609
|
+
const pm = (_b = (_a = directive == null ? void 0 : directive.query) == null ? void 0 : _a.processed) == null ? void 0 : _b.method;
|
|
5610
|
+
if (isPrismaMethod(pm)) return pm;
|
|
5611
|
+
return "findMany";
|
|
5612
|
+
}
|
|
4847
5613
|
function buildSqlResult(args) {
|
|
4848
5614
|
const {
|
|
4849
5615
|
method,
|
|
@@ -4873,7 +5639,11 @@ function buildSqlResult(args) {
|
|
|
4873
5639
|
whereResult,
|
|
4874
5640
|
tableName,
|
|
4875
5641
|
alias,
|
|
4876
|
-
processed
|
|
5642
|
+
processed,
|
|
5643
|
+
dialect,
|
|
5644
|
+
modelDef,
|
|
5645
|
+
schemaModels
|
|
5646
|
+
);
|
|
4877
5647
|
}
|
|
4878
5648
|
return buildSelectSql({
|
|
4879
5649
|
method,
|
|
@@ -4961,7 +5731,43 @@ function buildMainWhere(args) {
|
|
|
4961
5731
|
dialect
|
|
4962
5732
|
});
|
|
4963
5733
|
}
|
|
5734
|
+
function extractIncludeSpec2(processed, modelDef) {
|
|
5735
|
+
const includeSpec = {};
|
|
5736
|
+
const relationSet = new Set(
|
|
5737
|
+
Array.isArray(modelDef == null ? void 0 : modelDef.fields) ? modelDef.fields.filter((f) => f && f.isRelation && typeof f.name === "string").map((f) => f.name) : []
|
|
5738
|
+
);
|
|
5739
|
+
if (processed.include && isPlainObject(processed.include)) {
|
|
5740
|
+
for (const [key, value] of Object.entries(processed.include)) {
|
|
5741
|
+
if (!relationSet.has(key)) continue;
|
|
5742
|
+
if (value !== false) {
|
|
5743
|
+
includeSpec[key] = value;
|
|
5744
|
+
}
|
|
5745
|
+
}
|
|
5746
|
+
}
|
|
5747
|
+
if (processed.select && isPlainObject(processed.select)) {
|
|
5748
|
+
for (const [key, value] of Object.entries(processed.select)) {
|
|
5749
|
+
if (!relationSet.has(key)) continue;
|
|
5750
|
+
if (value === false) continue;
|
|
5751
|
+
if (value === true) {
|
|
5752
|
+
includeSpec[key] = true;
|
|
5753
|
+
continue;
|
|
5754
|
+
}
|
|
5755
|
+
if (isPlainObject(value)) {
|
|
5756
|
+
const selectVal = value;
|
|
5757
|
+
if (selectVal.include || selectVal.select) {
|
|
5758
|
+
includeSpec[key] = value;
|
|
5759
|
+
} else {
|
|
5760
|
+
includeSpec[key] = true;
|
|
5761
|
+
}
|
|
5762
|
+
} else {
|
|
5763
|
+
includeSpec[key] = true;
|
|
5764
|
+
}
|
|
5765
|
+
}
|
|
5766
|
+
}
|
|
5767
|
+
return includeSpec;
|
|
5768
|
+
}
|
|
4964
5769
|
function buildAndNormalizeSql(args) {
|
|
5770
|
+
var _a;
|
|
4965
5771
|
const {
|
|
4966
5772
|
method,
|
|
4967
5773
|
processed,
|
|
@@ -4982,14 +5788,30 @@ function buildAndNormalizeSql(args) {
|
|
|
4982
5788
|
schemaModels,
|
|
4983
5789
|
dialect
|
|
4984
5790
|
});
|
|
4985
|
-
|
|
5791
|
+
const normalized = normalizeSqlAndMappingsForDialect(
|
|
4986
5792
|
sqlResult.sql,
|
|
4987
5793
|
sqlResult.paramMappings,
|
|
4988
5794
|
dialect
|
|
4989
5795
|
);
|
|
5796
|
+
const includeSpec = (_a = sqlResult.includeSpec && isPlainObject(sqlResult.includeSpec) ? sqlResult.includeSpec : null) != null ? _a : extractIncludeSpec2(processed, modelDef);
|
|
5797
|
+
const requiresReduction = sqlResult.requiresReduction === true;
|
|
5798
|
+
return {
|
|
5799
|
+
sql: normalized.sql,
|
|
5800
|
+
paramMappings: normalized.paramMappings,
|
|
5801
|
+
requiresReduction,
|
|
5802
|
+
includeSpec
|
|
5803
|
+
};
|
|
4990
5804
|
}
|
|
4991
5805
|
function finalizeDirective(args) {
|
|
4992
|
-
const {
|
|
5806
|
+
const {
|
|
5807
|
+
directive,
|
|
5808
|
+
method,
|
|
5809
|
+
normalizedSql,
|
|
5810
|
+
normalizedMappings,
|
|
5811
|
+
dialect,
|
|
5812
|
+
requiresReduction,
|
|
5813
|
+
includeSpec
|
|
5814
|
+
} = args;
|
|
4993
5815
|
const params = normalizedMappings.map((m) => {
|
|
4994
5816
|
var _a;
|
|
4995
5817
|
return (_a = m.value) != null ? _a : void 0;
|
|
@@ -4997,12 +5819,14 @@ function finalizeDirective(args) {
|
|
|
4997
5819
|
validateParamConsistencyByDialect(normalizedSql, params, dialect);
|
|
4998
5820
|
const { staticParams, dynamicKeys, paramOrder } = buildParamsFromMappings(normalizedMappings);
|
|
4999
5821
|
return {
|
|
5000
|
-
method
|
|
5822
|
+
method,
|
|
5001
5823
|
sql: normalizedSql,
|
|
5002
5824
|
staticParams,
|
|
5003
5825
|
dynamicKeys,
|
|
5004
5826
|
paramOrder,
|
|
5005
5827
|
paramMappings: normalizedMappings,
|
|
5828
|
+
requiresReduction,
|
|
5829
|
+
includeSpec,
|
|
5006
5830
|
originalDirective: directive
|
|
5007
5831
|
};
|
|
5008
5832
|
}
|
|
@@ -5018,8 +5842,8 @@ function generateSQL(directive) {
|
|
|
5018
5842
|
modelDef,
|
|
5019
5843
|
dialect
|
|
5020
5844
|
});
|
|
5021
|
-
const method = directive
|
|
5022
|
-
const
|
|
5845
|
+
const method = resolveMethod(directive);
|
|
5846
|
+
const built = buildAndNormalizeSql({
|
|
5023
5847
|
method,
|
|
5024
5848
|
processed: query.processed,
|
|
5025
5849
|
whereResult,
|
|
@@ -5031,9 +5855,12 @@ function generateSQL(directive) {
|
|
|
5031
5855
|
});
|
|
5032
5856
|
return finalizeDirective({
|
|
5033
5857
|
directive,
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
5858
|
+
method,
|
|
5859
|
+
normalizedSql: built.sql,
|
|
5860
|
+
normalizedMappings: built.paramMappings,
|
|
5861
|
+
dialect,
|
|
5862
|
+
requiresReduction: built.requiresReduction,
|
|
5863
|
+
includeSpec: built.includeSpec
|
|
5037
5864
|
});
|
|
5038
5865
|
}
|
|
5039
5866
|
|
|
@@ -5331,7 +6158,9 @@ function processModelDirectives(modelName, result, config) {
|
|
|
5331
6158
|
sql: sqlDirective.sql,
|
|
5332
6159
|
params: sqlDirective.staticParams,
|
|
5333
6160
|
dynamicKeys: sqlDirective.dynamicKeys,
|
|
5334
|
-
paramMappings: sqlDirective.paramMappings
|
|
6161
|
+
paramMappings: sqlDirective.paramMappings,
|
|
6162
|
+
requiresReduction: sqlDirective.requiresReduction || false,
|
|
6163
|
+
includeSpec: sqlDirective.includeSpec || {}
|
|
5335
6164
|
});
|
|
5336
6165
|
} catch (error) {
|
|
5337
6166
|
if (!config.skipInvalid) throw error;
|
|
@@ -5368,7 +6197,9 @@ function countTotalQueries(queries) {
|
|
|
5368
6197
|
}
|
|
5369
6198
|
function generateClient(options) {
|
|
5370
6199
|
return __async(this, null, function* () {
|
|
6200
|
+
var _a;
|
|
5371
6201
|
const { datamodel, outputDir, config } = options;
|
|
6202
|
+
const runtimeImportPath = (_a = options.runtimeImportPath) != null ? _a : "prisma-sql";
|
|
5372
6203
|
setGlobalDialect(config.dialect);
|
|
5373
6204
|
const models = schemaParser.convertDMMFToModels(datamodel);
|
|
5374
6205
|
const directiveResults = schemaParser.processAllDirectives(
|
|
@@ -5384,7 +6215,13 @@ function generateClient(options) {
|
|
|
5384
6215
|
);
|
|
5385
6216
|
const absoluteOutputDir = path.resolve(process.cwd(), outputDir);
|
|
5386
6217
|
yield promises.mkdir(absoluteOutputDir, { recursive: true });
|
|
5387
|
-
const code = generateCode(
|
|
6218
|
+
const code = generateCode(
|
|
6219
|
+
models,
|
|
6220
|
+
queries,
|
|
6221
|
+
config.dialect,
|
|
6222
|
+
datamodel,
|
|
6223
|
+
runtimeImportPath
|
|
6224
|
+
);
|
|
5388
6225
|
const outputPath = path.join(absoluteOutputDir, "index.ts");
|
|
5389
6226
|
yield promises.writeFile(outputPath, code);
|
|
5390
6227
|
const totalQueries = countTotalQueries(queries);
|
|
@@ -5409,9 +6246,11 @@ function createQueryKey(processedQuery) {
|
|
|
5409
6246
|
return value;
|
|
5410
6247
|
});
|
|
5411
6248
|
}
|
|
5412
|
-
function generateImports() {
|
|
6249
|
+
function generateImports(runtimeImportPath) {
|
|
5413
6250
|
return `// Generated by @prisma-sql/generator - DO NOT EDIT
|
|
5414
|
-
import { buildSQL, buildBatchSql, parseBatchResults, buildBatchCountSql, parseBatchCountResults, createTransactionExecutor, transformQueryResults, normalizeValue, type PrismaMethod, type Model, type BatchQuery, type BatchCountQuery, type TransactionQuery, type TransactionOptions } from
|
|
6251
|
+
import { buildSQL, buildBatchSql, parseBatchResults, buildBatchCountSql, parseBatchCountResults, createTransactionExecutor, transformQueryResults, normalizeValue, type PrismaMethod, type Model, type BatchQuery, type BatchCountQuery, type TransactionQuery, type TransactionOptions } from ${JSON.stringify(
|
|
6252
|
+
runtimeImportPath
|
|
6253
|
+
)}`;
|
|
5415
6254
|
}
|
|
5416
6255
|
function generateCoreTypes() {
|
|
5417
6256
|
return `class DeferredQuery {
|
|
@@ -5572,6 +6411,8 @@ const QUERIES: Record<string, Record<string, Record<string, {
|
|
|
5572
6411
|
params: unknown[]
|
|
5573
6412
|
dynamicKeys: string[]
|
|
5574
6413
|
paramMappings: any[]
|
|
6414
|
+
requiresReduction: boolean
|
|
6415
|
+
includeSpec: Record<string, any>
|
|
5575
6416
|
}>>> = ${formatQueries(queries)}
|
|
5576
6417
|
|
|
5577
6418
|
const DIALECT = ${JSON.stringify(dialect)}
|
|
@@ -5725,7 +6566,7 @@ function normalizeQuery(args: any): string {
|
|
|
5725
6566
|
})
|
|
5726
6567
|
}`;
|
|
5727
6568
|
}
|
|
5728
|
-
function generateExtension() {
|
|
6569
|
+
function generateExtension(runtimeImportPath) {
|
|
5729
6570
|
return `export function speedExtension(config: {
|
|
5730
6571
|
postgres?: any
|
|
5731
6572
|
sqlite?: any
|
|
@@ -5758,7 +6599,7 @@ function generateExtension() {
|
|
|
5758
6599
|
postgresClient: postgres,
|
|
5759
6600
|
})
|
|
5760
6601
|
|
|
5761
|
-
|
|
6602
|
+
async function handleMethod(this: any, method: PrismaMethod, args: any) {
|
|
5762
6603
|
const modelName = this?.name || this?.$name
|
|
5763
6604
|
const startTime = Date.now()
|
|
5764
6605
|
|
|
@@ -5769,11 +6610,15 @@ function generateExtension() {
|
|
|
5769
6610
|
let sql: string
|
|
5770
6611
|
let params: unknown[]
|
|
5771
6612
|
let prebaked = false
|
|
6613
|
+
let requiresReduction = false
|
|
6614
|
+
let includeSpec: Record<string, any> | undefined
|
|
5772
6615
|
|
|
5773
6616
|
if (prebakedQuery) {
|
|
5774
6617
|
sql = prebakedQuery.sql
|
|
5775
6618
|
params = resolveParamsFromMappings(transformedArgs, prebakedQuery.paramMappings)
|
|
5776
6619
|
prebaked = true
|
|
6620
|
+
requiresReduction = prebakedQuery.requiresReduction || false
|
|
6621
|
+
includeSpec = prebakedQuery.includeSpec
|
|
5777
6622
|
} else {
|
|
5778
6623
|
const model = MODELS.find((m) => m.name === modelName)
|
|
5779
6624
|
if (!model) {
|
|
@@ -5782,15 +6627,20 @@ function generateExtension() {
|
|
|
5782
6627
|
const result = buildSQL(model, MODELS, method, transformedArgs, DIALECT)
|
|
5783
6628
|
sql = result.sql
|
|
5784
6629
|
params = result.params as unknown[]
|
|
6630
|
+
requiresReduction = result.requiresReduction || false
|
|
6631
|
+
includeSpec = result.includeSpec
|
|
5785
6632
|
}
|
|
5786
6633
|
|
|
5787
6634
|
if (debug) {
|
|
5788
6635
|
console.log(\`[\${DIALECT}] \${modelName}.\${method} \${prebaked ? '\u26A1 PREBAKED' : '\u{1F528} RUNTIME'}\`)
|
|
5789
6636
|
console.log('SQL:', sql)
|
|
5790
6637
|
console.log('Params:', params)
|
|
6638
|
+
if (requiresReduction) {
|
|
6639
|
+
console.log('Requires reduction: true')
|
|
6640
|
+
}
|
|
5791
6641
|
}
|
|
5792
6642
|
|
|
5793
|
-
|
|
6643
|
+
let results = await executeQuery(client, method, sql, params)
|
|
5794
6644
|
const duration = Date.now() - startTime
|
|
5795
6645
|
|
|
5796
6646
|
onQuery?.({
|
|
@@ -5802,6 +6652,34 @@ function generateExtension() {
|
|
|
5802
6652
|
prebaked,
|
|
5803
6653
|
})
|
|
5804
6654
|
|
|
6655
|
+
if (requiresReduction && includeSpec) {
|
|
6656
|
+
if (results.length > 10000) {
|
|
6657
|
+
console.warn(
|
|
6658
|
+
\`[prisma-sql] \${modelName}.\${method} returned \${results.length} rows before reduction, falling back to Prisma for safety\`
|
|
6659
|
+
)
|
|
6660
|
+
return this.$parent[modelName][method](args)
|
|
6661
|
+
}
|
|
6662
|
+
|
|
6663
|
+
const model = MODEL_MAP.get(modelName)
|
|
6664
|
+
if (model) {
|
|
6665
|
+
const { buildReducerConfig, reduceFlatRows } = await import(${JSON.stringify(
|
|
6666
|
+
runtimeImportPath
|
|
6667
|
+
)})
|
|
6668
|
+
const config = buildReducerConfig(model, includeSpec, MODELS)
|
|
6669
|
+
results = reduceFlatRows(results, config)
|
|
6670
|
+
|
|
6671
|
+
if (method === 'findUnique' && results.length > 1) {
|
|
6672
|
+
throw new Error(
|
|
6673
|
+
\`findUnique returned \${results.length} records after reduction\`
|
|
6674
|
+
)
|
|
6675
|
+
}
|
|
6676
|
+
|
|
6677
|
+
if (method === 'findFirst' && results.length > 1) {
|
|
6678
|
+
results = [results[0]]
|
|
6679
|
+
}
|
|
6680
|
+
}
|
|
6681
|
+
}
|
|
6682
|
+
|
|
5805
6683
|
return transformQueryResults(method, results)
|
|
5806
6684
|
} catch (error) {
|
|
5807
6685
|
const msg = error instanceof Error ? error.message : String(error)
|
|
@@ -5972,18 +6850,18 @@ export type SpeedClient<T> = T & {
|
|
|
5972
6850
|
|
|
5973
6851
|
export type { BatchCountQuery, TransactionQuery, TransactionOptions }`;
|
|
5974
6852
|
}
|
|
5975
|
-
function generateCode(models, queries, dialect, datamodel) {
|
|
6853
|
+
function generateCode(models, queries, dialect, datamodel, runtimeImportPath) {
|
|
5976
6854
|
const cleanModels = models.map((model) => __spreadProps(__spreadValues({}, model), {
|
|
5977
6855
|
fields: model.fields.filter((f) => f !== void 0 && f !== null)
|
|
5978
6856
|
}));
|
|
5979
6857
|
const { mappings, fieldTypes } = extractEnumMappings(datamodel);
|
|
5980
6858
|
return [
|
|
5981
|
-
generateImports(),
|
|
6859
|
+
generateImports(runtimeImportPath),
|
|
5982
6860
|
generateCoreTypes(),
|
|
5983
6861
|
generateHelpers(),
|
|
5984
6862
|
generateDataConstants(cleanModels, mappings, fieldTypes, queries, dialect),
|
|
5985
6863
|
generateTransformLogic(),
|
|
5986
|
-
generateExtension(),
|
|
6864
|
+
generateExtension(runtimeImportPath),
|
|
5987
6865
|
generateTypeExports()
|
|
5988
6866
|
].join("\n\n");
|
|
5989
6867
|
}
|
|
@@ -6002,6 +6880,8 @@ function formatQueries(queries) {
|
|
|
6002
6880
|
params: ${JSON.stringify(query.params)},
|
|
6003
6881
|
dynamicKeys: ${JSON.stringify(query.dynamicKeys)},
|
|
6004
6882
|
paramMappings: ${JSON.stringify(query.paramMappings)},
|
|
6883
|
+
requiresReduction: ${query.requiresReduction || false},
|
|
6884
|
+
includeSpec: ${JSON.stringify(query.includeSpec || {})},
|
|
6005
6885
|
}`);
|
|
6006
6886
|
}
|
|
6007
6887
|
methodEntries.push(` ${JSON.stringify(method)}: {
|