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.
@@ -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.63.0",
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 sortRaw = obj.sort;
1255
- const out = __spreadProps(__spreadValues({}, obj), { sort: getNextSort(sortRaw) });
1256
- const nullsRaw = obj.nulls;
1257
- if (typeof nullsRaw === "string") {
1258
- out.nulls = flipNulls(nullsRaw);
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 ? { sort: parsed.direction, nulls: parsed.nulls } : parsed.direction
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
- var NUMERIC_TYPES = /* @__PURE__ */ new Set(["Int", "Float", "Decimal", "BigInt"]);
1354
- function assertScalarField(model, fieldName, context) {
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
- `${context} references unknown field '${fieldName}' on model ${model.name}`,
1353
+ `Field '${fieldName}' does not exist on model ${model.name}`,
1359
1354
  {
1360
1355
  field: fieldName,
1361
1356
  modelName: model.name,
1362
- availableFields: model.fields.map((f) => f.name)
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 createError(
1368
- `${context} does not support relation field '${fieldName}'`,
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
- const field = assertScalarField(model, fieldName, context);
1376
- const baseType = field.type.replace(/\[\]|\?/g, "");
1377
- if (!NUMERIC_TYPES.has(baseType)) {
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
- `${context} requires numeric field, got '${field.type}'`,
1380
- { field: fieldName, modelName: model.name }
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) return "_t";
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
- return ALIAS_FORBIDDEN_KEYWORDS.has(lowered) ? `_${lowered}` : lowered;
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
- function formatPosition(position) {
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
- return buildDefaultScalarFields(model, alias).join(
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 whereClause = isValidWhereClause(whereResult.clause) ? ` ${SQL_TEMPLATES.AND} ${whereResult.clause}` : "";
3262
- return { joins, whereClause };
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 selectExpr2 = args.orderBySql ? `json_agg(${rowExpr} ORDER BY ${args.orderBySql})` : `json_agg(${rowExpr})`;
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 selectExpr = jsonAgg("row", args.ctx.dialect);
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: depth + 1,
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 foreignKey length"
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
- } else {
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 { includeCols: "", selectWithIncludes: baseSelect, countJoins: [] };
4559
+ return {
4560
+ includeCols: "",
4561
+ selectWithIncludes: baseSelect,
4562
+ countJoins: [],
4563
+ includeJoins: []
4564
+ };
3982
4565
  }
3983
4566
  const emptyJson = dialect === "postgres" ? `'[]'::json` : `json('[]')`;
3984
- const includeCols = hasIncludes ? includes.map((inc) => {
3985
- const expr = inc.isOneToOne ? "(" + inc.sql + ")" : "COALESCE((" + inc.sql + "), " + emptyJson + ")";
3986
- return expr + " " + SQL_TEMPLATES.AS + " " + quote(inc.name);
3987
- }).join(SQL_SEPARATORS.FIELD_LIST) : "";
3988
- const allCols = joinNonEmpty(
3989
- [includeCols, countCols],
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, allCols);
3993
- return { includeCols: allCols, selectWithIncludes, countJoins };
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 { includeCols, selectWithIncludes, countJoins } = buildIncludeColumns(spec);
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 baseSelect2 = (select != null ? select : "").trim();
4092
- assertHasSelectFields(baseSelect2, includeCols);
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
- if (isNonEmptyString(orderBy)) parts.push(SQL_TEMPLATES.ORDER_BY, orderBy);
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 isPositiveInteger(value) {
4791
- return Number.isFinite(value) && Number.isInteger(value) && value > 0;
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 parseSkipValue(skip) {
4794
- return typeof skip === "string" ? Number(skip.trim()) : skip;
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 validateSkipParameter(skip) {
4797
- if (skip === void 0 || skip === null) {
4798
- return;
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 buildCountSql(whereResult, tableName, alias, skip, _dialect) {
4813
- assertSafeAlias(alias);
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.skip);
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
- return normalizeSqlAndMappingsForDialect(
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 { directive, normalizedSql, normalizedMappings, dialect } = args;
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: directive.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.method;
5022
- const normalized = buildAndNormalizeSql({
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
- normalizedSql: normalized.sql,
5035
- normalizedMappings: normalized.paramMappings,
5036
- dialect
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(models, queries, config.dialect, datamodel);
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 'prisma-sql'`;
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
- const handleMethod = async function(this: any, method: PrismaMethod, args: any) {
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
- const results = await executeQuery(client, method, sql, params)
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)}: {