prisma-sql 1.64.0 → 1.65.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/generator.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.64.0",
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 sortRaw = obj.sort;
1253
- const out = __spreadProps(__spreadValues({}, obj), { sort: getNextSort(sortRaw) });
1254
- const nullsRaw = obj.nulls;
1255
- if (typeof nullsRaw === "string") {
1256
- out.nulls = flipNulls(nullsRaw);
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 ? { sort: parsed.direction, nulls: parsed.nulls } : parsed.direction
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
- var NUMERIC_TYPES = /* @__PURE__ */ new Set(["Int", "Float", "Decimal", "BigInt"]);
1352
- function assertScalarField(model, fieldName, context) {
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
- `${context} references unknown field '${fieldName}' on model ${model.name}`,
1351
+ `Field '${fieldName}' does not exist on model ${model.name}`,
1357
1352
  {
1358
1353
  field: fieldName,
1359
1354
  modelName: model.name,
1360
- availableFields: model.fields.map((f) => f.name)
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 createError(
1366
- `${context} does not support relation field '${fieldName}'`,
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
- const field = assertScalarField(model, fieldName, context);
1374
- const baseType = field.type.replace(/\[\]|\?/g, "");
1375
- if (!NUMERIC_TYPES.has(baseType)) {
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
- `${context} requires numeric field, got '${field.type}'`,
1378
- { field: fieldName, modelName: model.name }
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) return "_t";
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
- return ALIAS_FORBIDDEN_KEYWORDS.has(lowered) ? `_${lowered}` : lowered;
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
- function formatPosition(position) {
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
- return buildDefaultScalarFields(model, alias).join(
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 whereClause = isValidWhereClause(whereResult.clause) ? ` ${SQL_TEMPLATES.AND} ${whereResult.clause}` : "";
3260
- return { joins, whereClause };
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 selectExpr2 = args.orderBySql ? `json_agg(${rowExpr} ORDER BY ${args.orderBySql})` : `json_agg(${rowExpr})`;
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 selectExpr = jsonAgg("row", args.ctx.dialect);
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: depth + 1,
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 foreignKey length"
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
- } else {
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 { includeCols: "", selectWithIncludes: baseSelect, countJoins: [] };
4557
+ return {
4558
+ includeCols: "",
4559
+ selectWithIncludes: baseSelect,
4560
+ countJoins: [],
4561
+ includeJoins: []
4562
+ };
3980
4563
  }
3981
4564
  const emptyJson = dialect === "postgres" ? `'[]'::json` : `json('[]')`;
3982
- const includeCols = hasIncludes ? includes.map((inc) => {
3983
- const expr = inc.isOneToOne ? "(" + inc.sql + ")" : "COALESCE((" + inc.sql + "), " + emptyJson + ")";
3984
- return expr + " " + SQL_TEMPLATES.AS + " " + quote(inc.name);
3985
- }).join(SQL_SEPARATORS.FIELD_LIST) : "";
3986
- const allCols = joinNonEmpty(
3987
- [includeCols, countCols],
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, allCols);
3991
- return { includeCols: allCols, selectWithIncludes, countJoins };
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 { includeCols, selectWithIncludes, countJoins } = buildIncludeColumns(spec);
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 baseSelect2 = (select != null ? select : "").trim();
4090
- assertHasSelectFields(baseSelect2, includeCols);
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
- if (isNonEmptyString(orderBy)) parts.push(SQL_TEMPLATES.ORDER_BY, orderBy);
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 isPositiveInteger(value) {
4789
- return Number.isFinite(value) && Number.isInteger(value) && value > 0;
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 parseSkipValue(skip) {
4792
- return typeof skip === "string" ? Number(skip.trim()) : skip;
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 validateSkipParameter(skip) {
4795
- if (skip === void 0 || skip === null) {
4796
- return;
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 buildCountSql(whereResult, tableName, alias, skip, _dialect) {
4811
- assertSafeAlias(alias);
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.skip);
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
- return normalizeSqlAndMappingsForDialect(
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 { directive, normalizedSql, normalizedMappings, dialect } = args;
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: directive.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.method;
5020
- const normalized = buildAndNormalizeSql({
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
- normalizedSql: normalized.sql,
5033
- normalizedMappings: normalized.paramMappings,
5034
- dialect
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(models, queries, config.dialect, datamodel);
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 'prisma-sql'`;
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
- const handleMethod = async function(this: any, method: PrismaMethod, args: any) {
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
- const results = await executeQuery(client, method, sql, params)
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)}: {