prisma-sql 1.71.0 → 1.73.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/index.cjs CHANGED
@@ -57,6 +57,265 @@ var __async = (__this, __arguments, generator) => {
57
57
  });
58
58
  };
59
59
 
60
+ // src/utils/normalize-value.ts
61
+ var globalDateMode = "iso";
62
+ function setNormalizeDateMode(mode) {
63
+ globalDateMode = mode;
64
+ }
65
+ function detectSqliteDateMode(client) {
66
+ try {
67
+ const tables = client.prepare(
68
+ "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '_prisma_%' LIMIT 50"
69
+ ).all();
70
+ for (const { name } of tables) {
71
+ const row = client.prepare(`SELECT typeof("createdAt") as t FROM "${name}" LIMIT 1`).get();
72
+ if (row) {
73
+ return row.t === "integer" ? "ms" : "iso";
74
+ }
75
+ }
76
+ } catch (e) {
77
+ }
78
+ return "iso";
79
+ }
80
+ var MAX_DEPTH = 20;
81
+ function normalizeValue(value, seen = /* @__PURE__ */ new WeakSet(), depth = 0) {
82
+ if (depth > MAX_DEPTH) {
83
+ throw new Error(`Max normalization depth exceeded (${MAX_DEPTH} levels)`);
84
+ }
85
+ if (value instanceof Date) {
86
+ return normalizeDateValue(value);
87
+ }
88
+ if (typeof value === "bigint") {
89
+ return value.toString();
90
+ }
91
+ if (Array.isArray(value)) {
92
+ return normalizeArrayValue(value, seen, depth);
93
+ }
94
+ if (value && typeof value === "object") {
95
+ return normalizeObjectValue(value, seen, depth);
96
+ }
97
+ return value;
98
+ }
99
+ function normalizeDateValue(date) {
100
+ const t = date.getTime();
101
+ if (!Number.isFinite(t)) {
102
+ throw new Error("Invalid Date value in SQL params");
103
+ }
104
+ if (globalDateMode === "ms") {
105
+ return t;
106
+ }
107
+ return date.toISOString();
108
+ }
109
+ function normalizeArrayValue(value, seen, depth) {
110
+ const arrRef = value;
111
+ if (seen.has(arrRef)) {
112
+ throw new Error("Circular reference in SQL params");
113
+ }
114
+ seen.add(arrRef);
115
+ const out = value.map((v) => normalizeValue(v, seen, depth + 1));
116
+ seen.delete(arrRef);
117
+ return out;
118
+ }
119
+ function normalizeObjectValue(value, seen, depth) {
120
+ if (value instanceof Uint8Array) return value;
121
+ if (typeof Buffer !== "undefined" && Buffer.isBuffer(value)) return value;
122
+ const proto = Object.getPrototypeOf(value);
123
+ const isPlain = proto === Object.prototype || proto === null;
124
+ if (!isPlain) return value;
125
+ const obj = value;
126
+ if (seen.has(obj)) {
127
+ throw new Error("Circular reference in SQL params");
128
+ }
129
+ seen.add(obj);
130
+ const out = {};
131
+ for (const [k, v] of Object.entries(obj)) {
132
+ out[k] = normalizeValue(v, seen, depth + 1);
133
+ }
134
+ seen.delete(obj);
135
+ return out;
136
+ }
137
+
138
+ // src/sql-builder-dialect.ts
139
+ var globalDialect = "postgres";
140
+ function setGlobalDialect(dialect) {
141
+ if (dialect !== "postgres" && dialect !== "sqlite") {
142
+ throw new Error(
143
+ `Invalid dialect: ${dialect}. Must be 'postgres' or 'sqlite'`
144
+ );
145
+ }
146
+ globalDialect = dialect;
147
+ }
148
+ function getGlobalDialect() {
149
+ return globalDialect;
150
+ }
151
+ function assertNonEmpty(value, name) {
152
+ if (!value || value.trim().length === 0) {
153
+ throw new Error(`${name} is required and cannot be empty`);
154
+ }
155
+ }
156
+ function arrayContains(column, value, arrayType, dialect) {
157
+ assertNonEmpty(column, "arrayContains column");
158
+ assertNonEmpty(value, "arrayContains value");
159
+ if (dialect === "postgres") {
160
+ return `${column} @> ARRAY[${value}]::${arrayType}`;
161
+ }
162
+ return `EXISTS (SELECT 1 FROM json_each(${column}) WHERE value = ${value})`;
163
+ }
164
+ function arrayOverlaps(column, value, arrayType, dialect) {
165
+ assertNonEmpty(column, "arrayOverlaps column");
166
+ assertNonEmpty(value, "arrayOverlaps value");
167
+ if (dialect === "postgres") {
168
+ return `${column} && ${value}::${arrayType}`;
169
+ }
170
+ return `EXISTS (
171
+ SELECT 1 FROM json_each(${column}) AS col
172
+ JOIN json_each(${value}) AS val
173
+ WHERE col.value = val.value
174
+ )`;
175
+ }
176
+ function arrayContainsAll(column, value, arrayType, dialect) {
177
+ assertNonEmpty(column, "arrayContainsAll column");
178
+ assertNonEmpty(value, "arrayContainsAll value");
179
+ if (dialect === "postgres") {
180
+ return `${column} @> ${value}::${arrayType}`;
181
+ }
182
+ return `NOT EXISTS (
183
+ SELECT 1 FROM json_each(${value}) AS val
184
+ WHERE NOT EXISTS (
185
+ SELECT 1 FROM json_each(${column}) AS col
186
+ WHERE col.value = val.value
187
+ )
188
+ )`;
189
+ }
190
+ function arrayIsEmpty(column, dialect) {
191
+ assertNonEmpty(column, "arrayIsEmpty column");
192
+ if (dialect === "postgres") {
193
+ return `(${column} IS NULL OR array_length(${column}, 1) IS NULL)`;
194
+ }
195
+ return `(${column} IS NULL OR json_array_length(${column}) = 0)`;
196
+ }
197
+ function arrayIsNotEmpty(column, dialect) {
198
+ assertNonEmpty(column, "arrayIsNotEmpty column");
199
+ if (dialect === "postgres") {
200
+ return `(${column} IS NOT NULL AND array_length(${column}, 1) IS NOT NULL)`;
201
+ }
202
+ return `(${column} IS NOT NULL AND json_array_length(${column}) > 0)`;
203
+ }
204
+ function arrayEquals(column, value, arrayType, dialect) {
205
+ assertNonEmpty(column, "arrayEquals column");
206
+ assertNonEmpty(value, "arrayEquals value");
207
+ if (dialect === "postgres") {
208
+ return `${column} = ${value}::${arrayType}`;
209
+ }
210
+ return `json(${column}) = json(${value})`;
211
+ }
212
+ function caseInsensitiveLike(column, pattern, dialect) {
213
+ assertNonEmpty(column, "caseInsensitiveLike column");
214
+ assertNonEmpty(pattern, "caseInsensitiveLike pattern");
215
+ if (dialect === "postgres") {
216
+ return `${column} ILIKE ${pattern}`;
217
+ }
218
+ return `LOWER(${column}) LIKE LOWER(${pattern})`;
219
+ }
220
+ function caseInsensitiveEquals(column, value, dialect) {
221
+ assertNonEmpty(column, "caseInsensitiveEquals column");
222
+ assertNonEmpty(value, "caseInsensitiveEquals value");
223
+ return `LOWER(${column}) = LOWER(${value})`;
224
+ }
225
+ function jsonExtractText(column, path, dialect) {
226
+ assertNonEmpty(column, "jsonExtractText column");
227
+ assertNonEmpty(path, "jsonExtractText path");
228
+ if (dialect === "postgres") {
229
+ const p = String(path).trim();
230
+ const pathExpr = /^\$\d+$/.test(p) ? `${p}::text[]` : p;
231
+ return `${column}#>>${pathExpr}`;
232
+ }
233
+ return `json_extract(${column}, ${path})`;
234
+ }
235
+ function jsonExtractNumeric(column, path, dialect) {
236
+ assertNonEmpty(column, "jsonExtractNumeric column");
237
+ assertNonEmpty(path, "jsonExtractNumeric path");
238
+ if (dialect === "postgres") {
239
+ const p = String(path).trim();
240
+ const pathExpr = /^\$\d+$/.test(p) ? `${p}::text[]` : p;
241
+ return `(${column}#>>${pathExpr})::numeric`;
242
+ }
243
+ return `CAST(json_extract(${column}, ${path}) AS REAL)`;
244
+ }
245
+ function jsonToText(column, dialect) {
246
+ assertNonEmpty(column, "jsonToText column");
247
+ if (dialect === "postgres") {
248
+ return `${column}::text`;
249
+ }
250
+ return column;
251
+ }
252
+ function inArray(column, value, dialect) {
253
+ assertNonEmpty(column, "inArray column");
254
+ assertNonEmpty(value, "inArray value");
255
+ if (dialect === "postgres") {
256
+ return `${column} = ANY(${value})`;
257
+ }
258
+ return `${column} IN (SELECT value FROM json_each(${value}))`;
259
+ }
260
+ function notInArray(column, value, dialect) {
261
+ assertNonEmpty(column, "notInArray column");
262
+ assertNonEmpty(value, "notInArray value");
263
+ if (dialect === "postgres") {
264
+ return `${column} != ALL(${value})`;
265
+ }
266
+ return `${column} NOT IN (SELECT value FROM json_each(${value}))`;
267
+ }
268
+ function getArrayType(prismaType, dialect) {
269
+ if (!prismaType || prismaType.length === 0) {
270
+ return dialect === "sqlite" ? "TEXT" : "text[]";
271
+ }
272
+ if (dialect === "sqlite") {
273
+ return "TEXT";
274
+ }
275
+ const baseType = prismaType.replace(/\[\]|\?/g, "");
276
+ switch (baseType) {
277
+ case "String":
278
+ return "text[]";
279
+ case "Int":
280
+ return "integer[]";
281
+ case "Float":
282
+ return "double precision[]";
283
+ case "Decimal":
284
+ return "numeric[]";
285
+ case "Boolean":
286
+ return "boolean[]";
287
+ case "BigInt":
288
+ return "bigint[]";
289
+ case "DateTime":
290
+ return "timestamptz[]";
291
+ default:
292
+ return `"${baseType}"[]`;
293
+ }
294
+ }
295
+ function jsonAgg(content, dialect) {
296
+ assertNonEmpty(content, "jsonAgg content");
297
+ if (dialect === "postgres") {
298
+ return `json_agg(${content})`;
299
+ }
300
+ return `json_group_array(${content})`;
301
+ }
302
+ function jsonBuildObject(pairs, dialect) {
303
+ const safePairs = (pairs != null ? pairs : "").trim();
304
+ if (dialect === "postgres") {
305
+ return safePairs.length > 0 ? `json_build_object(${safePairs})` : `json_build_object()`;
306
+ }
307
+ return safePairs.length > 0 ? `json_object(${safePairs})` : `json_object()`;
308
+ }
309
+ function prepareArrayParam(value, dialect) {
310
+ if (!Array.isArray(value)) {
311
+ throw new Error("prepareArrayParam requires array value");
312
+ }
313
+ if (dialect === "postgres") {
314
+ return value.map((v) => normalizeValue(v));
315
+ }
316
+ return JSON.stringify(value.map((v) => normalizeValue(v)));
317
+ }
318
+
60
319
  // src/builder/shared/constants.ts
61
320
  var IS_PRODUCTION = process.env.NODE_ENV === "production";
62
321
  var SQL_SEPARATORS = Object.freeze({
@@ -761,406 +1020,155 @@ function normalizeKeyList(input) {
761
1020
  }
762
1021
 
763
1022
  // src/builder/shared/model-field-cache.ts
764
- var FIELD_INDICES_CACHE = /* @__PURE__ */ new WeakMap();
765
- function normalizeField(field) {
766
- return field;
767
- }
768
- function getFieldIndices(model) {
769
- var _a3;
770
- let cached = FIELD_INDICES_CACHE.get(model);
771
- if (cached) return cached;
772
- const scalarFields = /* @__PURE__ */ new Map();
773
- const relationFields = /* @__PURE__ */ new Map();
774
- const allFieldsByName = /* @__PURE__ */ new Map();
775
- const scalarNames = [];
776
- const relationNames = [];
777
- const jsonFields = /* @__PURE__ */ new Set();
778
- const pkFields = [];
779
- const columnMap = /* @__PURE__ */ new Map();
780
- const quotedColumns = /* @__PURE__ */ new Map();
781
- for (const rawField of model.fields) {
782
- const field = normalizeField(rawField);
783
- allFieldsByName.set(field.name, field);
784
- if (field.isRelation) {
785
- relationFields.set(field.name, field);
786
- relationNames.push(field.name);
787
- } else {
788
- scalarFields.set(field.name, field);
789
- scalarNames.push(field.name);
790
- const fieldType = String((_a3 = field.type) != null ? _a3 : "").toLowerCase();
791
- if (fieldType === "json") {
792
- jsonFields.add(field.name);
793
- }
794
- if (field.isId || field.isPrimaryKey || field.primaryKey) {
795
- pkFields.push(field.name);
796
- }
797
- if (field.dbName && field.dbName !== field.name) {
798
- columnMap.set(field.name, field.dbName);
799
- }
800
- const columnName = field.dbName || field.name;
801
- quotedColumns.set(field.name, quote(columnName));
802
- }
803
- }
804
- cached = Object.freeze({
805
- scalarFields,
806
- relationFields,
807
- allFieldsByName,
808
- scalarNames,
809
- relationNames,
810
- jsonFields,
811
- pkFields,
812
- columnMap,
813
- quotedColumns
814
- });
815
- FIELD_INDICES_CACHE.set(model, cached);
816
- return cached;
817
- }
818
- function getRelationFieldSet(model) {
819
- return new Set(getFieldIndices(model).relationNames);
820
- }
821
- function getScalarFieldSet(model) {
822
- return new Set(getFieldIndices(model).scalarNames);
823
- }
824
- function getColumnMap(model) {
825
- return getFieldIndices(model).columnMap;
826
- }
827
- function getScalarFieldNames(model) {
828
- return [...getFieldIndices(model).scalarNames];
829
- }
830
- function getQuotedColumn(model, fieldName) {
831
- return getFieldIndices(model).quotedColumns.get(fieldName);
832
- }
833
- function getJsonFieldSet(model) {
834
- return getFieldIndices(model).jsonFields;
835
- }
836
- function parseJsonIfNeeded(isJson, value) {
837
- if (!isJson) return value;
838
- if (value == null) return value;
839
- if (typeof value !== "string") return value;
840
- try {
841
- return JSON.parse(value);
842
- } catch (e) {
843
- return value;
844
- }
845
- }
846
- function maybeParseJson(value, jsonSet, fieldName) {
847
- if (!jsonSet.has(fieldName)) return value;
848
- if (value == null) return value;
849
- if (typeof value !== "string") return value;
850
- try {
851
- return JSON.parse(value);
852
- } catch (e) {
853
- return value;
854
- }
855
- }
856
-
857
- // src/builder/joins.ts
858
- function isRelationField(fieldName, model) {
859
- return getRelationFieldSet(model).has(fieldName);
860
- }
861
- function isValidRelationField(field) {
862
- if (!isNotNullish(field)) return false;
863
- if (!field.isRelation) return false;
864
- if (!isNotNullish(field.relatedModel) || field.relatedModel.trim().length === 0)
865
- return false;
866
- const fk = normalizeKeyList(field.foreignKey);
867
- if (fk.length === 0) return false;
868
- const refs = normalizeKeyList(field.references);
869
- if (refs.length === 0) {
870
- return fk.length === 1;
871
- }
872
- if (refs.length !== fk.length) return false;
873
- return true;
874
- }
875
- function getReferenceFieldNames(field, foreignKeyCount) {
876
- const refs = normalizeKeyList(field.references);
877
- if (refs.length === 0) {
878
- if (foreignKeyCount === 1) return [SPECIAL_FIELDS.ID];
879
- return [];
880
- }
881
- if (refs.length !== foreignKeyCount) return [];
882
- return refs;
883
- }
884
- function joinCondition(field, parentModel, childModel, parentAlias, childAlias) {
885
- assertSafeAlias(parentAlias);
886
- assertSafeAlias(childAlias);
887
- const fkFields = normalizeKeyList(field.foreignKey);
888
- if (fkFields.length === 0) {
889
- throw createError(
890
- `Relation '${field.name}' is missing foreignKey. This indicates a schema parsing error. Relations must specify fields/references.`,
891
- { field: field.name }
892
- );
893
- }
894
- const refFields = getReferenceFieldNames(field, fkFields.length);
895
- if (refFields.length !== fkFields.length) {
896
- throw createError(
897
- `Relation '${field.name}' is missing references (or references count does not match foreignKey count). This is required to support non-id and composite keys.`,
898
- { field: field.name }
899
- );
900
- }
901
- const parts = [];
902
- for (let i = 0; i < fkFields.length; i++) {
903
- const fk = fkFields[i];
904
- const ref = refFields[i];
905
- const left = field.isForeignKeyLocal ? `${childAlias}.${quoteColumn(childModel, ref)}` : `${childAlias}.${quoteColumn(childModel, fk)}`;
906
- const right = field.isForeignKeyLocal ? `${parentAlias}.${quoteColumn(parentModel, fk)}` : `${parentAlias}.${quoteColumn(parentModel, ref)}`;
907
- parts.push(`${left} = ${right}`);
908
- }
909
- return parts.length === 1 ? parts[0] : `(${parts.join(" AND ")})`;
910
- }
911
- function getModelByName(schemas, name) {
912
- return schemas.find((m) => m.name === name);
913
- }
914
-
915
- // src/utils/normalize-value.ts
916
- var globalDateMode = "iso";
917
- function setNormalizeDateMode(mode) {
918
- globalDateMode = mode;
919
- }
920
- function detectSqliteDateMode(client) {
921
- try {
922
- const tables = client.prepare(
923
- "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '_prisma_%' LIMIT 50"
924
- ).all();
925
- for (const { name } of tables) {
926
- const row = client.prepare(`SELECT typeof("createdAt") as t FROM "${name}" LIMIT 1`).get();
927
- if (row) {
928
- return row.t === "integer" ? "ms" : "iso";
929
- }
930
- }
931
- } catch (e) {
932
- }
933
- return "iso";
934
- }
935
- var MAX_DEPTH = 20;
936
- function normalizeValue(value, seen = /* @__PURE__ */ new WeakSet(), depth = 0) {
937
- if (depth > MAX_DEPTH) {
938
- throw new Error(`Max normalization depth exceeded (${MAX_DEPTH} levels)`);
939
- }
940
- if (value instanceof Date) {
941
- return normalizeDateValue(value);
942
- }
943
- if (typeof value === "bigint") {
944
- return value.toString();
945
- }
946
- if (Array.isArray(value)) {
947
- return normalizeArrayValue(value, seen, depth);
948
- }
949
- if (value && typeof value === "object") {
950
- return normalizeObjectValue(value, seen, depth);
951
- }
952
- return value;
953
- }
954
- function normalizeDateValue(date) {
955
- const t = date.getTime();
956
- if (!Number.isFinite(t)) {
957
- throw new Error("Invalid Date value in SQL params");
958
- }
959
- if (globalDateMode === "ms") {
960
- return t;
961
- }
962
- return date.toISOString();
963
- }
964
- function normalizeArrayValue(value, seen, depth) {
965
- const arrRef = value;
966
- if (seen.has(arrRef)) {
967
- throw new Error("Circular reference in SQL params");
968
- }
969
- seen.add(arrRef);
970
- const out = value.map((v) => normalizeValue(v, seen, depth + 1));
971
- seen.delete(arrRef);
972
- return out;
973
- }
974
- function normalizeObjectValue(value, seen, depth) {
975
- if (value instanceof Uint8Array) return value;
976
- if (typeof Buffer !== "undefined" && Buffer.isBuffer(value)) return value;
977
- const proto = Object.getPrototypeOf(value);
978
- const isPlain = proto === Object.prototype || proto === null;
979
- if (!isPlain) return value;
980
- const obj = value;
981
- if (seen.has(obj)) {
982
- throw new Error("Circular reference in SQL params");
983
- }
984
- seen.add(obj);
985
- const out = {};
986
- for (const [k, v] of Object.entries(obj)) {
987
- out[k] = normalizeValue(v, seen, depth + 1);
988
- }
989
- seen.delete(obj);
990
- return out;
991
- }
992
-
993
- // src/sql-builder-dialect.ts
994
- var globalDialect = "postgres";
995
- function getGlobalDialect() {
996
- return globalDialect;
997
- }
998
- function assertNonEmpty(value, name) {
999
- if (!value || value.trim().length === 0) {
1000
- throw new Error(`${name} is required and cannot be empty`);
1001
- }
1002
- }
1003
- function arrayContains(column, value, arrayType, dialect) {
1004
- assertNonEmpty(column, "arrayContains column");
1005
- assertNonEmpty(value, "arrayContains value");
1006
- if (dialect === "postgres") {
1007
- return `${column} @> ARRAY[${value}]::${arrayType}`;
1008
- }
1009
- return `EXISTS (SELECT 1 FROM json_each(${column}) WHERE value = ${value})`;
1010
- }
1011
- function arrayOverlaps(column, value, arrayType, dialect) {
1012
- assertNonEmpty(column, "arrayOverlaps column");
1013
- assertNonEmpty(value, "arrayOverlaps value");
1014
- if (dialect === "postgres") {
1015
- return `${column} && ${value}::${arrayType}`;
1016
- }
1017
- return `EXISTS (
1018
- SELECT 1 FROM json_each(${column}) AS col
1019
- JOIN json_each(${value}) AS val
1020
- WHERE col.value = val.value
1021
- )`;
1022
- }
1023
- function arrayContainsAll(column, value, arrayType, dialect) {
1024
- assertNonEmpty(column, "arrayContainsAll column");
1025
- assertNonEmpty(value, "arrayContainsAll value");
1026
- if (dialect === "postgres") {
1027
- return `${column} @> ${value}::${arrayType}`;
1028
- }
1029
- return `NOT EXISTS (
1030
- SELECT 1 FROM json_each(${value}) AS val
1031
- WHERE NOT EXISTS (
1032
- SELECT 1 FROM json_each(${column}) AS col
1033
- WHERE col.value = val.value
1034
- )
1035
- )`;
1023
+ var FIELD_INDICES_CACHE = /* @__PURE__ */ new WeakMap();
1024
+ function normalizeField(field) {
1025
+ return field;
1036
1026
  }
1037
- function arrayIsEmpty(column, dialect) {
1038
- assertNonEmpty(column, "arrayIsEmpty column");
1039
- if (dialect === "postgres") {
1040
- return `(${column} IS NULL OR array_length(${column}, 1) IS NULL)`;
1027
+ function getFieldIndices(model) {
1028
+ var _a3;
1029
+ let cached = FIELD_INDICES_CACHE.get(model);
1030
+ if (cached) return cached;
1031
+ const scalarFields = /* @__PURE__ */ new Map();
1032
+ const relationFields = /* @__PURE__ */ new Map();
1033
+ const allFieldsByName = /* @__PURE__ */ new Map();
1034
+ const scalarNames = [];
1035
+ const relationNames = [];
1036
+ const jsonFields = /* @__PURE__ */ new Set();
1037
+ const pkFields = [];
1038
+ const columnMap = /* @__PURE__ */ new Map();
1039
+ const quotedColumns = /* @__PURE__ */ new Map();
1040
+ for (const rawField of model.fields) {
1041
+ const field = normalizeField(rawField);
1042
+ allFieldsByName.set(field.name, field);
1043
+ if (field.isRelation) {
1044
+ relationFields.set(field.name, field);
1045
+ relationNames.push(field.name);
1046
+ } else {
1047
+ scalarFields.set(field.name, field);
1048
+ scalarNames.push(field.name);
1049
+ const fieldType = String((_a3 = field.type) != null ? _a3 : "").toLowerCase();
1050
+ if (fieldType === "json") {
1051
+ jsonFields.add(field.name);
1052
+ }
1053
+ if (field.isId || field.isPrimaryKey || field.primaryKey) {
1054
+ pkFields.push(field.name);
1055
+ }
1056
+ if (field.dbName && field.dbName !== field.name) {
1057
+ columnMap.set(field.name, field.dbName);
1058
+ }
1059
+ const columnName = field.dbName || field.name;
1060
+ quotedColumns.set(field.name, quote(columnName));
1061
+ }
1041
1062
  }
1042
- return `(${column} IS NULL OR json_array_length(${column}) = 0)`;
1063
+ cached = Object.freeze({
1064
+ scalarFields,
1065
+ relationFields,
1066
+ allFieldsByName,
1067
+ scalarNames,
1068
+ relationNames,
1069
+ jsonFields,
1070
+ pkFields,
1071
+ columnMap,
1072
+ quotedColumns
1073
+ });
1074
+ FIELD_INDICES_CACHE.set(model, cached);
1075
+ return cached;
1043
1076
  }
1044
- function arrayIsNotEmpty(column, dialect) {
1045
- assertNonEmpty(column, "arrayIsNotEmpty column");
1046
- if (dialect === "postgres") {
1047
- return `(${column} IS NOT NULL AND array_length(${column}, 1) IS NOT NULL)`;
1048
- }
1049
- return `(${column} IS NOT NULL AND json_array_length(${column}) > 0)`;
1077
+ function getRelationFieldSet(model) {
1078
+ return new Set(getFieldIndices(model).relationNames);
1050
1079
  }
1051
- function arrayEquals(column, value, arrayType, dialect) {
1052
- assertNonEmpty(column, "arrayEquals column");
1053
- assertNonEmpty(value, "arrayEquals value");
1054
- if (dialect === "postgres") {
1055
- return `${column} = ${value}::${arrayType}`;
1056
- }
1057
- return `json(${column}) = json(${value})`;
1080
+ function getScalarFieldSet(model) {
1081
+ return new Set(getFieldIndices(model).scalarNames);
1058
1082
  }
1059
- function caseInsensitiveLike(column, pattern, dialect) {
1060
- assertNonEmpty(column, "caseInsensitiveLike column");
1061
- assertNonEmpty(pattern, "caseInsensitiveLike pattern");
1062
- if (dialect === "postgres") {
1063
- return `${column} ILIKE ${pattern}`;
1064
- }
1065
- return `LOWER(${column}) LIKE LOWER(${pattern})`;
1083
+ function getColumnMap(model) {
1084
+ return getFieldIndices(model).columnMap;
1066
1085
  }
1067
- function caseInsensitiveEquals(column, value, dialect) {
1068
- assertNonEmpty(column, "caseInsensitiveEquals column");
1069
- assertNonEmpty(value, "caseInsensitiveEquals value");
1070
- return `LOWER(${column}) = LOWER(${value})`;
1086
+ function getScalarFieldNames(model) {
1087
+ return [...getFieldIndices(model).scalarNames];
1071
1088
  }
1072
- function jsonExtractText(column, path, dialect) {
1073
- assertNonEmpty(column, "jsonExtractText column");
1074
- assertNonEmpty(path, "jsonExtractText path");
1075
- if (dialect === "postgres") {
1076
- const p = String(path).trim();
1077
- const pathExpr = /^\$\d+$/.test(p) ? `${p}::text[]` : p;
1078
- return `${column}#>>${pathExpr}`;
1079
- }
1080
- return `json_extract(${column}, ${path})`;
1089
+ function getQuotedColumn(model, fieldName) {
1090
+ return getFieldIndices(model).quotedColumns.get(fieldName);
1081
1091
  }
1082
- function jsonExtractNumeric(column, path, dialect) {
1083
- assertNonEmpty(column, "jsonExtractNumeric column");
1084
- assertNonEmpty(path, "jsonExtractNumeric path");
1085
- if (dialect === "postgres") {
1086
- const p = String(path).trim();
1087
- const pathExpr = /^\$\d+$/.test(p) ? `${p}::text[]` : p;
1088
- return `(${column}#>>${pathExpr})::numeric`;
1089
- }
1090
- return `CAST(json_extract(${column}, ${path}) AS REAL)`;
1092
+ function getJsonFieldSet(model) {
1093
+ return getFieldIndices(model).jsonFields;
1091
1094
  }
1092
- function jsonToText(column, dialect) {
1093
- assertNonEmpty(column, "jsonToText column");
1094
- if (dialect === "postgres") {
1095
- return `${column}::text`;
1095
+ function parseJsonIfNeeded(isJson, value) {
1096
+ if (!isJson) return value;
1097
+ if (value == null) return value;
1098
+ if (typeof value !== "string") return value;
1099
+ try {
1100
+ return JSON.parse(value);
1101
+ } catch (e) {
1102
+ return value;
1096
1103
  }
1097
- return column;
1098
1104
  }
1099
- function inArray(column, value, dialect) {
1100
- assertNonEmpty(column, "inArray column");
1101
- assertNonEmpty(value, "inArray value");
1102
- if (dialect === "postgres") {
1103
- return `${column} = ANY(${value})`;
1105
+ function maybeParseJson(value, jsonSet, fieldName) {
1106
+ if (!jsonSet.has(fieldName)) return value;
1107
+ if (value == null) return value;
1108
+ if (typeof value !== "string") return value;
1109
+ try {
1110
+ return JSON.parse(value);
1111
+ } catch (e) {
1112
+ return value;
1104
1113
  }
1105
- return `${column} IN (SELECT value FROM json_each(${value}))`;
1106
1114
  }
1107
- function notInArray(column, value, dialect) {
1108
- assertNonEmpty(column, "notInArray column");
1109
- assertNonEmpty(value, "notInArray value");
1110
- if (dialect === "postgres") {
1111
- return `${column} != ALL(${value})`;
1112
- }
1113
- return `${column} NOT IN (SELECT value FROM json_each(${value}))`;
1115
+
1116
+ // src/builder/joins.ts
1117
+ function isRelationField(fieldName, model) {
1118
+ return getRelationFieldSet(model).has(fieldName);
1114
1119
  }
1115
- function getArrayType(prismaType, dialect) {
1116
- if (!prismaType || prismaType.length === 0) {
1117
- return dialect === "sqlite" ? "TEXT" : "text[]";
1118
- }
1119
- if (dialect === "sqlite") {
1120
- return "TEXT";
1121
- }
1122
- const baseType = prismaType.replace(/\[\]|\?/g, "");
1123
- switch (baseType) {
1124
- case "String":
1125
- return "text[]";
1126
- case "Int":
1127
- return "integer[]";
1128
- case "Float":
1129
- return "double precision[]";
1130
- case "Decimal":
1131
- return "numeric[]";
1132
- case "Boolean":
1133
- return "boolean[]";
1134
- case "BigInt":
1135
- return "bigint[]";
1136
- case "DateTime":
1137
- return "timestamptz[]";
1138
- default:
1139
- return `"${baseType}"[]`;
1120
+ function isValidRelationField(field) {
1121
+ if (!isNotNullish(field)) return false;
1122
+ if (!field.isRelation) return false;
1123
+ if (!isNotNullish(field.relatedModel) || field.relatedModel.trim().length === 0)
1124
+ return false;
1125
+ const fk = normalizeKeyList(field.foreignKey);
1126
+ if (fk.length === 0) return false;
1127
+ const refs = normalizeKeyList(field.references);
1128
+ if (refs.length === 0) {
1129
+ return fk.length === 1;
1140
1130
  }
1131
+ if (refs.length !== fk.length) return false;
1132
+ return true;
1141
1133
  }
1142
- function jsonAgg(content, dialect) {
1143
- assertNonEmpty(content, "jsonAgg content");
1144
- if (dialect === "postgres") {
1145
- return `json_agg(${content})`;
1134
+ function getReferenceFieldNames(field, foreignKeyCount) {
1135
+ const refs = normalizeKeyList(field.references);
1136
+ if (refs.length === 0) {
1137
+ if (foreignKeyCount === 1) return [SPECIAL_FIELDS.ID];
1138
+ return [];
1146
1139
  }
1147
- return `json_group_array(${content})`;
1140
+ if (refs.length !== foreignKeyCount) return [];
1141
+ return refs;
1148
1142
  }
1149
- function jsonBuildObject(pairs, dialect) {
1150
- const safePairs = (pairs != null ? pairs : "").trim();
1151
- if (dialect === "postgres") {
1152
- return safePairs.length > 0 ? `json_build_object(${safePairs})` : `json_build_object()`;
1143
+ function joinCondition(field, parentModel, childModel, parentAlias, childAlias) {
1144
+ assertSafeAlias(parentAlias);
1145
+ assertSafeAlias(childAlias);
1146
+ const fkFields = normalizeKeyList(field.foreignKey);
1147
+ if (fkFields.length === 0) {
1148
+ throw createError(
1149
+ `Relation '${field.name}' is missing foreignKey. This indicates a schema parsing error. Relations must specify fields/references.`,
1150
+ { field: field.name }
1151
+ );
1153
1152
  }
1154
- return safePairs.length > 0 ? `json_object(${safePairs})` : `json_object()`;
1155
- }
1156
- function prepareArrayParam(value, dialect) {
1157
- if (!Array.isArray(value)) {
1158
- throw new Error("prepareArrayParam requires array value");
1153
+ const refFields = getReferenceFieldNames(field, fkFields.length);
1154
+ if (refFields.length !== fkFields.length) {
1155
+ throw createError(
1156
+ `Relation '${field.name}' is missing references (or references count does not match foreignKey count). This is required to support non-id and composite keys.`,
1157
+ { field: field.name }
1158
+ );
1159
1159
  }
1160
- if (dialect === "postgres") {
1161
- return value.map((v) => normalizeValue(v));
1160
+ const parts = [];
1161
+ for (let i = 0; i < fkFields.length; i++) {
1162
+ const fk = fkFields[i];
1163
+ const ref = refFields[i];
1164
+ const left = field.isForeignKeyLocal ? `${childAlias}.${quoteColumn(childModel, ref)}` : `${childAlias}.${quoteColumn(childModel, fk)}`;
1165
+ const right = field.isForeignKeyLocal ? `${parentAlias}.${quoteColumn(parentModel, fk)}` : `${parentAlias}.${quoteColumn(parentModel, ref)}`;
1166
+ parts.push(`${left} = ${right}`);
1162
1167
  }
1163
- return JSON.stringify(value.map((v) => normalizeValue(v)));
1168
+ return parts.length === 1 ? parts[0] : `(${parts.join(" AND ")})`;
1169
+ }
1170
+ function getModelByName(schemas, name) {
1171
+ return schemas.find((m) => m.name === name);
1164
1172
  }
1165
1173
  function normalizeIntLike(name, v, opts = {}) {
1166
1174
  var _a3, _b;
@@ -4868,7 +4876,6 @@ var DEFAULT_SELECT_CACHE = /* @__PURE__ */ new WeakMap();
4868
4876
  function toSelectEntries(select) {
4869
4877
  const out = [];
4870
4878
  for (const [k, v] of Object.entries(select)) {
4871
- if (k === "_count") continue;
4872
4879
  if (v !== false && v !== void 0) out.push([k, v]);
4873
4880
  }
4874
4881
  return out;
@@ -4961,7 +4968,9 @@ function validateFieldKeys(entries, scalarSet, relationSet, allowCount = false)
4961
4968
  if (!scalarSet.has(k) && !relationSet.has(k)) unknown.push(k);
4962
4969
  }
4963
4970
  if (unknown.length > 0) {
4964
- throw new Error(`Select contains unknown fields: ${unknown.join(", ")}`);
4971
+ throw new Error(
4972
+ `Unknown field '${unknown.join("', '")}' does not exist on this model`
4973
+ );
4965
4974
  }
4966
4975
  }
4967
4976
  function buildSelectedScalarParts(entries, scalarNames, alias, model) {
@@ -5142,23 +5151,56 @@ function buildNestedToOneJoins(relations, baseAlias, baseModel, aliasGen, dialec
5142
5151
  }
5143
5152
  return { joins, aliasMap };
5144
5153
  }
5145
- function buildNestedToOneSelects(relations, aliasMap) {
5154
+ function buildNestedToOneSelects(relations, aliasMap, dialect) {
5146
5155
  const selects = [];
5147
5156
  for (const rel of relations) {
5148
5157
  const relAlias = aliasMap.get(rel.name);
5149
5158
  if (!relAlias) continue;
5150
5159
  const relSelect = buildRelationSelect(rel.args, rel.model, relAlias);
5151
5160
  if (!relSelect || relSelect.trim().length === 0) continue;
5152
- selects.push(`${sqlStringLiteral(rel.name)}, ${relSelect}`);
5161
+ const jsonExpr = jsonBuildObject(relSelect, dialect);
5162
+ const pkField = getPrimaryKeyField(rel.model);
5163
+ const pkCol = `${relAlias}.${quoteColumn(rel.model, pkField)}`;
5164
+ const nullSafeExpr = `CASE WHEN ${pkCol} IS NOT NULL THEN ${jsonExpr} ELSE NULL END`;
5165
+ selects.push(`${sqlStringLiteral(rel.name)}, ${nullSafeExpr}`);
5153
5166
  }
5154
5167
  return selects;
5155
5168
  }
5169
+ function extractCountSelectFromRelArgs(relArgs) {
5170
+ if (!isPlainObject(relArgs)) return null;
5171
+ const obj = relArgs;
5172
+ if (!isPlainObject(obj.select)) return null;
5173
+ const sel = obj.select;
5174
+ const countRaw = sel["_count"];
5175
+ if (!countRaw) return null;
5176
+ if (isPlainObject(countRaw) && "select" in countRaw) {
5177
+ return countRaw.select;
5178
+ }
5179
+ return null;
5180
+ }
5156
5181
  function buildSelectWithNestedIncludes(relArgs, relModel, relAlias, ctx) {
5157
5182
  const nestedToOnes = extractNestedToOneRelations(
5158
5183
  relArgs,
5159
5184
  relModel,
5160
5185
  ctx.schemaByName
5161
5186
  );
5187
+ const countSelect = extractCountSelectFromRelArgs(relArgs);
5188
+ const countJoins = [];
5189
+ function appendCountToSelect(baseSelect2) {
5190
+ if (!countSelect || Object.keys(countSelect).length === 0) return baseSelect2;
5191
+ const countBuild = buildRelationCountSql(
5192
+ countSelect,
5193
+ relModel,
5194
+ ctx.schemas,
5195
+ relAlias,
5196
+ ctx.params,
5197
+ ctx.dialect
5198
+ );
5199
+ if (!countBuild.jsonPairs) return baseSelect2;
5200
+ countJoins.push(...countBuild.joins);
5201
+ const countExpr = `'_count', ${jsonBuildObject(countBuild.jsonPairs, ctx.dialect)}`;
5202
+ return baseSelect2 ? `${baseSelect2}${SQL_SEPARATORS.FIELD_LIST}${countExpr}` : countExpr;
5203
+ }
5162
5204
  if (nestedToOnes.length === 0) {
5163
5205
  let relSelect = buildRelationSelect(relArgs, relModel, relAlias);
5164
5206
  let nestedIncludes = [];
@@ -5187,7 +5229,8 @@ function buildSelectWithNestedIncludes(relArgs, relModel, relAlias, ctx) {
5187
5229
  ).join(SQL_SEPARATORS.FIELD_LIST);
5188
5230
  relSelect = isNotNullish(relSelect) && relSelect.trim().length > 0 ? `${relSelect}${SQL_SEPARATORS.FIELD_LIST}${nestedSelects2}` : nestedSelects2;
5189
5231
  }
5190
- return { select: relSelect, nestedJoins: [] };
5232
+ relSelect = appendCountToSelect(relSelect);
5233
+ return { select: relSelect, nestedJoins: countJoins };
5191
5234
  }
5192
5235
  const { joins, aliasMap } = buildNestedToOneJoins(
5193
5236
  nestedToOnes,
@@ -5197,7 +5240,11 @@ function buildSelectWithNestedIncludes(relArgs, relModel, relAlias, ctx) {
5197
5240
  ctx.dialect
5198
5241
  );
5199
5242
  const baseSelect = buildRelationSelect(relArgs, relModel, relAlias);
5200
- const nestedSelects = buildNestedToOneSelects(nestedToOnes, aliasMap);
5243
+ const nestedSelects = buildNestedToOneSelects(
5244
+ nestedToOnes,
5245
+ aliasMap,
5246
+ ctx.dialect
5247
+ );
5201
5248
  const allParts = [];
5202
5249
  if (baseSelect && baseSelect.trim().length > 0) {
5203
5250
  allParts.push(baseSelect);
@@ -5205,9 +5252,11 @@ function buildSelectWithNestedIncludes(relArgs, relModel, relAlias, ctx) {
5205
5252
  for (const ns of nestedSelects) {
5206
5253
  allParts.push(ns);
5207
5254
  }
5255
+ let finalSelect = allParts.join(SQL_SEPARATORS.FIELD_LIST);
5256
+ finalSelect = appendCountToSelect(finalSelect);
5208
5257
  return {
5209
- select: allParts.join(SQL_SEPARATORS.FIELD_LIST),
5210
- nestedJoins: joins
5258
+ select: finalSelect,
5259
+ nestedJoins: [...joins, ...countJoins]
5211
5260
  };
5212
5261
  }
5213
5262
  function buildWhereParts(whereInput, relModel, relAlias, ctx) {
@@ -5589,7 +5638,7 @@ function validateDistinct(model, distinct) {
5589
5638
  const relationSet = getRelationFieldSet(model);
5590
5639
  if (relationSet.has(f)) {
5591
5640
  throw new Error(
5592
- `distinct field '${f}' is a relation. Only scalar fields are allowed.
5641
+ `distinct field '${f}' is a relation field. Only scalar fields are allowed.
5593
5642
  Available scalar fields: ${[...scalarSet].join(", ")}`
5594
5643
  );
5595
5644
  }
@@ -5627,7 +5676,7 @@ function validateOrderBy(model, orderBy) {
5627
5676
  if (!scalarSet.has(f)) {
5628
5677
  if (relationSet.has(f)) {
5629
5678
  throw new Error(
5630
- `orderBy field '${f}' is a relation. Only scalar fields are allowed.
5679
+ `orderBy field '${f}' is a relation field. Only scalar fields are allowed.
5631
5680
  Available scalar fields: ${[...scalarSet].join(", ")}`
5632
5681
  );
5633
5682
  }
@@ -9367,6 +9416,26 @@ function executeRaw(client, sql, params, dialect) {
9367
9416
  function buildSQL(model, models, method, args, dialect) {
9368
9417
  return buildSQLWithCache(model, models, method, args, dialect);
9369
9418
  }
9419
+ function createToSQLFunction(models, dialect) {
9420
+ if (!models || !Array.isArray(models) || models.length === 0) {
9421
+ throw new Error("createToSQL requires non-empty models array");
9422
+ }
9423
+ const modelMap = new Map(models.map((m) => [m.name, m]));
9424
+ setGlobalDialect(dialect);
9425
+ return function toSQL(model, method, args = {}) {
9426
+ const m = modelMap.get(model);
9427
+ if (!m) {
9428
+ throw new Error(
9429
+ `Model '${model}' not found. Available: ${[...modelMap.keys()].join(", ")}`
9430
+ );
9431
+ }
9432
+ return buildSQL(m, models, method, args, dialect);
9433
+ };
9434
+ }
9435
+ function createToSQL(modelsOrDmmf, dialect) {
9436
+ const models = Array.isArray(modelsOrDmmf) ? modelsOrDmmf : schemaParser.convertDMMFToModels(modelsOrDmmf.datamodel);
9437
+ return createToSQLFunction(models, dialect);
9438
+ }
9370
9439
  function generateSQL2(directive) {
9371
9440
  return generateSQL(directive);
9372
9441
  }
@@ -9379,6 +9448,7 @@ exports.buildSQL = buildSQL;
9379
9448
  exports.countIncludeDepth = countIncludeDepth;
9380
9449
  exports.createProgressiveReducer = createProgressiveReducer;
9381
9450
  exports.createStreamingReducer = createStreamingReducer;
9451
+ exports.createToSQL = createToSQL;
9382
9452
  exports.createTransactionExecutor = createTransactionExecutor;
9383
9453
  exports.detectSqliteDateMode = detectSqliteDateMode;
9384
9454
  exports.executePostgresQuery = executePostgresQuery;