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