@an5/orm 1.0.8 → 1.1.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/README.md +174 -175
- package/dist/an5Metadata.js +64 -0
- package/dist/an5Orm.d.ts +92 -3
- package/dist/an5Orm.js +697 -206
- package/dist/config.d.ts +9 -1
- package/dist/config.js +13 -18
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +1 -1
- package/dist/generator/src/api.d.ts +35 -0
- package/dist/generator/src/api.js +58 -0
- package/dist/generator/src/code-generator.d.ts +14 -13
- package/dist/generator/src/code-generator.js +249 -225
- package/dist/generator/src/config.d.ts +162 -0
- package/dist/generator/src/config.js +354 -0
- package/dist/generator/src/dialect.d.ts +88 -0
- package/dist/generator/src/dialect.js +318 -0
- package/dist/generator/src/dotnet-generator.d.ts +21 -11
- package/dist/generator/src/dotnet-generator.js +1053 -546
- package/dist/generator/src/field-types.d.ts +96 -0
- package/dist/generator/src/field-types.js +433 -0
- package/dist/generator/src/golang-generator.d.ts +36 -0
- package/dist/generator/src/golang-generator.js +1229 -0
- package/dist/generator/src/index.d.ts +1 -1
- package/dist/generator/src/index.js +137 -88
- package/dist/generator/src/metadata-generator.d.ts +11 -8
- package/dist/generator/src/metadata-generator.js +105 -90
- package/dist/generator/src/parser.d.ts +37 -11
- package/dist/generator/src/parser.js +208 -206
- package/dist/generator/src/push-apply.d.ts +37 -0
- package/dist/generator/src/push-apply.js +109 -0
- package/dist/generator/src/push-schema.d.ts +93 -0
- package/dist/generator/src/push-schema.js +189 -0
- package/dist/generator/src/python-generator.d.ts +37 -9
- package/dist/generator/src/python-generator.js +428 -105
- package/dist/generator/src/rust-generator.d.ts +38 -0
- package/dist/generator/src/rust-generator.js +971 -0
- package/dist/generator/src/suggest.d.ts +17 -0
- package/dist/generator/src/suggest.js +45 -0
- package/dist/generator/src/type-kinds.d.ts +27 -0
- package/dist/generator/src/type-kinds.js +72 -0
- package/dist/generator/src/types.d.ts +60 -27
- package/dist/generator/src/types.js +41 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +19 -8
- package/dist/logger.js +1 -1
- package/dist/migration-core.d.ts +102 -0
- package/dist/migration-core.js +731 -0
- package/dist/orm.d.ts +147 -0
- package/dist/orm.js +1654 -0
- package/dist/provider-support.d.ts +27 -0
- package/dist/provider-support.js +50 -0
- package/dist/scripts/release-cli.d.ts +2 -2
- package/dist/scripts/release-cli.js +115 -115
- package/dist/sql-utils.d.ts +3 -1
- package/dist/sql-utils.js +94 -24
- package/generator/src/api.ts +45 -0
- package/generator/src/code-generator.ts +259 -230
- package/generator/src/config.ts +496 -0
- package/generator/src/dialect.ts +415 -0
- package/generator/src/dotnet-generator.ts +1071 -544
- package/generator/src/field-types.ts +473 -0
- package/generator/src/golang-generator.ts +1267 -0
- package/generator/src/index.ts +146 -116
- package/generator/src/metadata-generator.ts +116 -101
- package/generator/src/parser.ts +234 -202
- package/generator/src/push-apply.ts +148 -0
- package/generator/src/push-schema.ts +250 -0
- package/generator/src/python-generator.ts +480 -114
- package/generator/src/rust-generator.ts +974 -0
- package/generator/src/suggest.ts +41 -0
- package/generator/src/type-kinds.ts +78 -0
- package/generator/src/types.ts +77 -29
- package/package.json +32 -6
package/dist/an5Orm.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.an5Orm = exports.An5ORM = void 0;
|
|
3
|
+
exports.an5Orm = exports.An5ORM = exports.ViewClient = exports.TableClient = void 0;
|
|
4
4
|
const adapters_1 = require("@an5/adapters");
|
|
5
5
|
const crypto_1 = require("crypto");
|
|
6
6
|
const logger_1 = require("./logger");
|
|
@@ -8,7 +8,7 @@ const errors_1 = require("./errors");
|
|
|
8
8
|
const metadata_1 = require("./metadata");
|
|
9
9
|
const sql_utils_1 = require("./sql-utils");
|
|
10
10
|
function aggSelect(fn, key) {
|
|
11
|
-
return `${fn}(${(0, sql_utils_1.quoteIdentifier)(key)}) as ${aggAlias(fn, key)}`;
|
|
11
|
+
return `${fn}(${(0, sql_utils_1.quoteIdentifier)(key)}) as ${aggAlias(fn.toLowerCase(), key)}`;
|
|
12
12
|
}
|
|
13
13
|
function aggAlias(fn, key) {
|
|
14
14
|
return (0, sql_utils_1.sanitizeParamName)(`${fn}_${key}`);
|
|
@@ -21,9 +21,204 @@ async function getAdapter() {
|
|
|
21
21
|
}
|
|
22
22
|
return adapter;
|
|
23
23
|
}
|
|
24
|
-
|
|
24
|
+
const execQuery = Object.assign(async (queryText, params) => {
|
|
25
25
|
const a = await getAdapter();
|
|
26
26
|
return a.exec(queryText, params);
|
|
27
|
+
}, {
|
|
28
|
+
executeRaw: async (queryText, params) => {
|
|
29
|
+
const a = await getAdapter();
|
|
30
|
+
return a._executeRaw(queryText, params);
|
|
31
|
+
},
|
|
32
|
+
transaction: async (fn, options) => {
|
|
33
|
+
const a = await getAdapter();
|
|
34
|
+
return a.$transaction(async (tx) => fn(executorFromAdapterLike(tx)), options);
|
|
35
|
+
},
|
|
36
|
+
beginTransaction: async () => {
|
|
37
|
+
const a = await getAdapter();
|
|
38
|
+
if (typeof a.$begin !== "function") {
|
|
39
|
+
throw new Error("Interactive transactions require an adapter with $begin support");
|
|
40
|
+
}
|
|
41
|
+
const tx = await a.$begin();
|
|
42
|
+
return {
|
|
43
|
+
executor: executorFromAdapterLike(tx),
|
|
44
|
+
commit: () => tx.$commit(),
|
|
45
|
+
rollback: () => tx.$rollback(),
|
|
46
|
+
};
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
function normalizeAffectedCount(result) {
|
|
50
|
+
if (typeof result === "number")
|
|
51
|
+
return result;
|
|
52
|
+
if (!result)
|
|
53
|
+
return 0;
|
|
54
|
+
if (Array.isArray(result.rowsAffected))
|
|
55
|
+
return Number(result.rowsAffected[0] ?? 0);
|
|
56
|
+
if (typeof result.rowsAffected === "number")
|
|
57
|
+
return result.rowsAffected;
|
|
58
|
+
if (typeof result.count === "number")
|
|
59
|
+
return result.count;
|
|
60
|
+
if (Array.isArray(result) && result.length === 1)
|
|
61
|
+
return normalizeAffectedCount(result[0]);
|
|
62
|
+
return 0;
|
|
63
|
+
}
|
|
64
|
+
const FILTER_OPERATOR_KEYS = new Set(["in", "notIn", "contains", "startsWith", "endsWith", "not", "gte", "lte", "gt", "lt"]);
|
|
65
|
+
function executorFromAdapterLike(adapterLike) {
|
|
66
|
+
return Object.assign(async (queryText, params) => {
|
|
67
|
+
return adapterLike.exec(queryText, params);
|
|
68
|
+
}, {
|
|
69
|
+
executeRaw: async (queryText, params) => {
|
|
70
|
+
if (typeof adapterLike._executeRaw === "function") {
|
|
71
|
+
return adapterLike._executeRaw(queryText, params);
|
|
72
|
+
}
|
|
73
|
+
if (typeof adapterLike.executeRaw === "function") {
|
|
74
|
+
return adapterLike.executeRaw(queryText, params);
|
|
75
|
+
}
|
|
76
|
+
return normalizeAffectedCount(await adapterLike.exec(queryText, params));
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
function flattenSimpleEqualityWhere(where) {
|
|
81
|
+
if (!where || typeof where !== "object" || where instanceof Date || Array.isArray(where)) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
const flat = {};
|
|
85
|
+
for (const [key, value] of Object.entries(where)) {
|
|
86
|
+
if (value === null || value === undefined || Array.isArray(value))
|
|
87
|
+
return null;
|
|
88
|
+
if (value instanceof Date || typeof value !== "object") {
|
|
89
|
+
flat[key] = value;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (!key.includes("_"))
|
|
93
|
+
return null;
|
|
94
|
+
for (const [innerKey, innerValue] of Object.entries(value)) {
|
|
95
|
+
if (FILTER_OPERATOR_KEYS.has(innerKey))
|
|
96
|
+
return null;
|
|
97
|
+
if (innerValue === null || innerValue === undefined || (typeof innerValue === "object" && !(innerValue instanceof Date))) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
flat[innerKey] = innerValue;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return Object.keys(flat).length > 0 ? flat : null;
|
|
104
|
+
}
|
|
105
|
+
function asArray(value) {
|
|
106
|
+
if (value === undefined)
|
|
107
|
+
return [];
|
|
108
|
+
return Array.isArray(value) ? value : [value];
|
|
109
|
+
}
|
|
110
|
+
function hasOwn(obj, key) {
|
|
111
|
+
return !!obj && Object.prototype.hasOwnProperty.call(obj, key);
|
|
112
|
+
}
|
|
113
|
+
function appendUpdateSet(sets, params, key, val) {
|
|
114
|
+
const col = (0, sql_utils_1.quoteIdentifier)(key);
|
|
115
|
+
const safeKey = (0, sql_utils_1.sanitizeParamName)(key);
|
|
116
|
+
if (val && typeof val === "object" && !(val instanceof Date)) {
|
|
117
|
+
const ops = [
|
|
118
|
+
['increment', '+'],
|
|
119
|
+
['decrement', '-'],
|
|
120
|
+
['multiply', '*'],
|
|
121
|
+
['divide', '/'],
|
|
122
|
+
];
|
|
123
|
+
for (const [opKey, symbol] of ops) {
|
|
124
|
+
if (val[opKey] !== undefined) {
|
|
125
|
+
const paramName = `${safeKey}_${opKey.slice(0, 3)}`;
|
|
126
|
+
sets.push(`${col} = ${col} ${symbol} @${paramName}`);
|
|
127
|
+
params[paramName] = val[opKey];
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (val.set !== undefined) {
|
|
132
|
+
const paramName = `${safeKey}_set`;
|
|
133
|
+
sets.push(`${col} = @${paramName}`);
|
|
134
|
+
params[paramName] = val.set;
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
sets.push(`${col} = @${safeKey}`);
|
|
139
|
+
params[safeKey] = val;
|
|
140
|
+
}
|
|
141
|
+
function normalizeByFields(by) {
|
|
142
|
+
if (typeof by === "string")
|
|
143
|
+
return [by];
|
|
144
|
+
return Array.isArray(by) ? by.filter((field) => typeof field === "string" && field.length > 0) : [];
|
|
145
|
+
}
|
|
146
|
+
function selectedAggregateFields(fields) {
|
|
147
|
+
if (!fields || typeof fields !== "object")
|
|
148
|
+
return [];
|
|
149
|
+
return Object.keys(fields).filter((key) => fields[key]);
|
|
150
|
+
}
|
|
151
|
+
function aggregateExpression(kind, field) {
|
|
152
|
+
const upper = kind.toUpperCase();
|
|
153
|
+
if (upper === "COUNT" && field === "_all")
|
|
154
|
+
return "COUNT(*)";
|
|
155
|
+
return `${upper}(${(0, sql_utils_1.quoteIdentifier)(field)})`;
|
|
156
|
+
}
|
|
157
|
+
function addHavingPredicate(conditions, params, expr, paramBase, filter) {
|
|
158
|
+
if (filter && typeof filter === "object" && !(filter instanceof Date)) {
|
|
159
|
+
for (const [op, value] of Object.entries(filter)) {
|
|
160
|
+
const p = (0, sql_utils_1.sanitizeParamName)(`having_${paramBase}_${op}`);
|
|
161
|
+
if (op === "equals") {
|
|
162
|
+
conditions.push(`${expr} = @${p}`);
|
|
163
|
+
params[p] = value;
|
|
164
|
+
}
|
|
165
|
+
else if (op === "gt" || op === "gte" || op === "lt" || op === "lte") {
|
|
166
|
+
const symbol = op === "gt" ? ">" : op === "gte" ? ">=" : op === "lt" ? "<" : "<=";
|
|
167
|
+
conditions.push(`${expr} ${symbol} @${p}`);
|
|
168
|
+
params[p] = value;
|
|
169
|
+
}
|
|
170
|
+
else if (op === "not") {
|
|
171
|
+
if (value && typeof value === "object" && !(value instanceof Date)) {
|
|
172
|
+
const nested = [];
|
|
173
|
+
addHavingPredicate(nested, params, expr, `${paramBase}_not`, value);
|
|
174
|
+
if (nested.length > 0)
|
|
175
|
+
conditions.push(`NOT (${nested.join(" AND ")})`);
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
conditions.push(`${expr} <> @${p}`);
|
|
179
|
+
params[p] = value;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
else if ((op === "in" || op === "notIn") && Array.isArray(value)) {
|
|
183
|
+
if (value.length === 0) {
|
|
184
|
+
conditions.push(op === "in" ? "1 = 0" : "1 = 1");
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
const placeholders = value.map((item, idx) => {
|
|
188
|
+
const itemParam = (0, sql_utils_1.sanitizeParamName)(`${p}_${idx}`);
|
|
189
|
+
params[itemParam] = item;
|
|
190
|
+
return `@${itemParam}`;
|
|
191
|
+
});
|
|
192
|
+
conditions.push(`${expr} ${op === "in" ? "IN" : "NOT IN"} (${placeholders.join(", ")})`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const p = (0, sql_utils_1.sanitizeParamName)(`having_${paramBase}`);
|
|
199
|
+
conditions.push(`${expr} = @${p}`);
|
|
200
|
+
params[p] = filter;
|
|
201
|
+
}
|
|
202
|
+
function buildHavingSql(having, params) {
|
|
203
|
+
if (!having || typeof having !== "object")
|
|
204
|
+
return "";
|
|
205
|
+
const conditions = [];
|
|
206
|
+
const aggregateKeys = {
|
|
207
|
+
_count: "COUNT",
|
|
208
|
+
_sum: "SUM",
|
|
209
|
+
_avg: "AVG",
|
|
210
|
+
_min: "MIN",
|
|
211
|
+
_max: "MAX",
|
|
212
|
+
};
|
|
213
|
+
for (const [key, sqlFn] of Object.entries(aggregateKeys)) {
|
|
214
|
+
const fields = having[key];
|
|
215
|
+
if (!fields || typeof fields !== "object")
|
|
216
|
+
continue;
|
|
217
|
+
for (const [field, filter] of Object.entries(fields)) {
|
|
218
|
+
addHavingPredicate(conditions, params, aggregateExpression(sqlFn, field), `${key}_${field}`, filter);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return conditions.length > 0 ? conditions.join(" AND ") : "";
|
|
27
222
|
}
|
|
28
223
|
function projectFields(row, select) {
|
|
29
224
|
if (!row || !select)
|
|
@@ -39,6 +234,52 @@ function projectFields(row, select) {
|
|
|
39
234
|
}
|
|
40
235
|
return projected;
|
|
41
236
|
}
|
|
237
|
+
function collectRelationSelections(modelName, select, metadata) {
|
|
238
|
+
if (!select || typeof select !== "object")
|
|
239
|
+
return undefined;
|
|
240
|
+
const modelRelations = metadata.relationMap[modelName] || {};
|
|
241
|
+
const relationSelections = {};
|
|
242
|
+
for (const [key, value] of Object.entries(select)) {
|
|
243
|
+
if (!value)
|
|
244
|
+
continue;
|
|
245
|
+
if (key === "_count" || modelRelations[key]) {
|
|
246
|
+
relationSelections[key] = value;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return Object.keys(relationSelections).length > 0 ? relationSelections : undefined;
|
|
250
|
+
}
|
|
251
|
+
function mergeIncludes(primary, secondary) {
|
|
252
|
+
if (!primary)
|
|
253
|
+
return secondary;
|
|
254
|
+
if (!secondary)
|
|
255
|
+
return primary;
|
|
256
|
+
return { ...secondary, ...primary };
|
|
257
|
+
}
|
|
258
|
+
function relationOrderClause(orderBy, fallbackKey) {
|
|
259
|
+
const sql = (0, sql_utils_1.buildOrderBy)(orderBy);
|
|
260
|
+
return sql ? sql.replace(/^\s*ORDER\s+BY\s+/i, "") : `${(0, sql_utils_1.quoteIdentifier)(fallbackKey)} ASC`;
|
|
261
|
+
}
|
|
262
|
+
function requiredRelationKeys(modelName, include, metadata) {
|
|
263
|
+
if (!include || typeof include !== "object")
|
|
264
|
+
return [];
|
|
265
|
+
const modelRelations = metadata.relationMap[modelName] || {};
|
|
266
|
+
const keys = new Set();
|
|
267
|
+
for (const [key, value] of Object.entries(include)) {
|
|
268
|
+
if (!value)
|
|
269
|
+
continue;
|
|
270
|
+
const relation = modelRelations[key];
|
|
271
|
+
if (!relation) {
|
|
272
|
+
if (key === "_count") {
|
|
273
|
+
for (const rel of Object.values(modelRelations)) {
|
|
274
|
+
keys.add(rel.localKey);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
keys.add(relation.relationType === "one" ? relation.foreignKey : relation.localKey);
|
|
280
|
+
}
|
|
281
|
+
return Array.from(keys);
|
|
282
|
+
}
|
|
42
283
|
// Helper to batch-query and resolve relationships
|
|
43
284
|
async function resolveIncludes(modelName, rows, include, executor, metadata) {
|
|
44
285
|
if (!rows || rows.length === 0 || !include)
|
|
@@ -51,22 +292,24 @@ async function resolveIncludes(modelName, rows, include, executor, metadata) {
|
|
|
51
292
|
continue;
|
|
52
293
|
const relation = modelRelations[key];
|
|
53
294
|
if (!relation) {
|
|
54
|
-
if (key === "_count" && value
|
|
55
|
-
const countFields =
|
|
295
|
+
if (key === "_count" && value) {
|
|
296
|
+
const countFields = value === true
|
|
297
|
+
? Object.keys(modelRelations)
|
|
298
|
+
: Object.keys(value.select || {}).filter((field) => value.select[field]);
|
|
56
299
|
for (const countField of countFields) {
|
|
57
300
|
const rel = modelRelations[countField];
|
|
58
301
|
if (rel) {
|
|
59
|
-
const relTable = metadata.modelToTable[rel.modelName];
|
|
302
|
+
const relTable = (0, sql_utils_1.quoteTableIdentifier)(metadata.modelToTable[rel.modelName] || rel.modelName);
|
|
60
303
|
const localKeys = rows.map(r => r[rel.localKey]).filter(Boolean);
|
|
61
304
|
if (localKeys.length === 0) {
|
|
62
305
|
rows.forEach(r => { r._count = { ...r._count, [countField]: 0 }; });
|
|
63
306
|
continue;
|
|
64
307
|
}
|
|
65
|
-
const sqlText = `
|
|
66
|
-
SELECT ${(0, sql_utils_1.quoteIdentifier)(rel.foreignKey)} as parentId, COUNT(*) as count
|
|
67
|
-
FROM ${relTable} WITH (NOLOCK)
|
|
68
|
-
WHERE ${(0, sql_utils_1.quoteIdentifier)(rel.foreignKey)} IN (${localKeys.map((_, i) => `@lk_${i}`).join(", ")})
|
|
69
|
-
GROUP BY ${(0, sql_utils_1.quoteIdentifier)(rel.foreignKey)}
|
|
308
|
+
const sqlText = `
|
|
309
|
+
SELECT ${(0, sql_utils_1.quoteIdentifier)(rel.foreignKey)} as parentId, COUNT(*) as count
|
|
310
|
+
FROM ${relTable} WITH (NOLOCK)
|
|
311
|
+
WHERE ${(0, sql_utils_1.quoteIdentifier)(rel.foreignKey)} IN (${localKeys.map((_, i) => `@lk_${i}`).join(", ")})
|
|
312
|
+
GROUP BY ${(0, sql_utils_1.quoteIdentifier)(rel.foreignKey)}
|
|
70
313
|
`;
|
|
71
314
|
const countParams = {};
|
|
72
315
|
localKeys.forEach((lk, i) => { countParams[`lk_${i}`] = lk; });
|
|
@@ -82,7 +325,7 @@ async function resolveIncludes(modelName, rows, include, executor, metadata) {
|
|
|
82
325
|
}
|
|
83
326
|
continue;
|
|
84
327
|
}
|
|
85
|
-
const relTable = metadata.modelToTable[relation.modelName];
|
|
328
|
+
const relTable = (0, sql_utils_1.quoteTableIdentifier)(metadata.modelToTable[relation.modelName] || relation.modelName);
|
|
86
329
|
const isMany = relation.relationType === "many";
|
|
87
330
|
const matchKey = relation.relationType === "one" ? relation.foreignKey : relation.localKey;
|
|
88
331
|
const searchKey = relation.relationType === "one" ? relation.localKey : relation.foreignKey;
|
|
@@ -93,6 +336,9 @@ async function resolveIncludes(modelName, rows, include, executor, metadata) {
|
|
|
93
336
|
}
|
|
94
337
|
const uniqueKeys = Array.from(new Set(keys));
|
|
95
338
|
let relCols = "*";
|
|
339
|
+
const nestedInclude = value && typeof value === "object"
|
|
340
|
+
? mergeIncludes(value.include, collectRelationSelections(relation.modelName, value.select, metadata))
|
|
341
|
+
: undefined;
|
|
96
342
|
if (value && typeof value === "object" && value.select) {
|
|
97
343
|
const subSelect = value.select;
|
|
98
344
|
const subRelations = metadata.relationMap[relation.modelName] || {};
|
|
@@ -100,37 +346,67 @@ async function resolveIncludes(modelName, rows, include, executor, metadata) {
|
|
|
100
346
|
.filter(k => subSelect[k] && !subRelations[k])
|
|
101
347
|
.map(k => (0, sql_utils_1.quoteIdentifier)(k));
|
|
102
348
|
if (selectedSubCols.length > 0) {
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
selectedSubCols.
|
|
349
|
+
for (const requiredKey of [searchKey, ...requiredRelationKeys(relation.modelName, nestedInclude, metadata)]) {
|
|
350
|
+
const quotedRequiredKey = (0, sql_utils_1.quoteIdentifier)(requiredKey);
|
|
351
|
+
if (!selectedSubCols.includes(quotedRequiredKey)) {
|
|
352
|
+
selectedSubCols.push(quotedRequiredKey);
|
|
353
|
+
}
|
|
106
354
|
}
|
|
107
355
|
relCols = selectedSubCols.join(", ");
|
|
108
356
|
}
|
|
109
357
|
}
|
|
110
|
-
|
|
358
|
+
const filters = [`${(0, sql_utils_1.quoteIdentifier)(searchKey)} IN (${uniqueKeys.map((_, i) => `@k_${i}`).join(", ")})`];
|
|
359
|
+
let sqlText = "";
|
|
111
360
|
const subParams = {};
|
|
112
361
|
uniqueKeys.forEach((k, i) => { subParams[`k_${i}`] = k; });
|
|
362
|
+
let subArgs = undefined;
|
|
113
363
|
if (value && typeof value === "object") {
|
|
114
|
-
|
|
115
|
-
|
|
364
|
+
subArgs = value;
|
|
365
|
+
const subWhereSql = (0, sql_utils_1.parseWhere)(relation.modelName, subArgs.where, subParams, `rel_${key}_`, {
|
|
366
|
+
relationMap: metadata.relationMap,
|
|
367
|
+
modelToTable: metadata.modelToTable,
|
|
368
|
+
});
|
|
369
|
+
if (subWhereSql) {
|
|
370
|
+
filters.push(subWhereSql);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
const hasRelationPagination = isMany && subArgs && (subArgs.take !== undefined || subArgs.skip !== undefined);
|
|
374
|
+
if (hasRelationPagination) {
|
|
375
|
+
const rn = (0, sql_utils_1.quoteIdentifier)("__an5_rn");
|
|
376
|
+
const skip = (0, sql_utils_1.toNonNegativeInt)(subArgs.skip);
|
|
377
|
+
const take = subArgs.take !== undefined ? (0, sql_utils_1.toNonNegativeInt)(subArgs.take, 1) : undefined;
|
|
378
|
+
const whereSql = filters.join(" AND ");
|
|
379
|
+
sqlText = `SELECT * FROM (
|
|
380
|
+
SELECT ${relCols}, ROW_NUMBER() OVER (PARTITION BY ${(0, sql_utils_1.quoteIdentifier)(searchKey)} ORDER BY ${relationOrderClause(subArgs.orderBy, searchKey)}) AS ${rn}
|
|
381
|
+
FROM ${relTable} WITH (NOLOCK)
|
|
382
|
+
WHERE ${whereSql}
|
|
383
|
+
) AS [an5_rel]
|
|
384
|
+
WHERE ${rn} > ${skip}`;
|
|
385
|
+
if (take !== undefined) {
|
|
386
|
+
sqlText += ` AND ${rn} <= ${skip + take}`;
|
|
387
|
+
}
|
|
388
|
+
sqlText += ` ORDER BY ${(0, sql_utils_1.quoteIdentifier)(searchKey)}, ${rn}`;
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
391
|
+
sqlText = `SELECT ${relCols} FROM ${relTable} WITH (NOLOCK) WHERE ${filters.join(" AND ")}`;
|
|
392
|
+
if (subArgs?.orderBy) {
|
|
116
393
|
sqlText += (0, sql_utils_1.buildOrderBy)(subArgs.orderBy);
|
|
117
394
|
}
|
|
118
395
|
}
|
|
119
396
|
const relatedRows = await executor(sqlText, subParams);
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
if (value && typeof value === "object" && value.include) {
|
|
126
|
-
await resolveIncludes(relation.modelName, relatedRows, value.include, executor, metadata);
|
|
397
|
+
relatedRows.forEach((r) => { delete r.__an5_rn; });
|
|
398
|
+
if (nestedInclude) {
|
|
399
|
+
await resolveIncludes(relation.modelName, relatedRows, nestedInclude, executor, metadata);
|
|
127
400
|
}
|
|
401
|
+
const outputRows = value && typeof value === "object" && value.select
|
|
402
|
+
? relatedRows.map((r) => projectFields(r, value.select))
|
|
403
|
+
: relatedRows;
|
|
128
404
|
const groupMap = new Map();
|
|
129
|
-
relatedRows.forEach((r) => {
|
|
405
|
+
relatedRows.forEach((r, idx) => {
|
|
130
406
|
const k = r[searchKey];
|
|
131
407
|
if (!groupMap.has(k))
|
|
132
408
|
groupMap.set(k, []);
|
|
133
|
-
groupMap.get(k).push(
|
|
409
|
+
groupMap.get(k).push(outputRows[idx]);
|
|
134
410
|
});
|
|
135
411
|
rows.forEach(r => {
|
|
136
412
|
const k = r[matchKey];
|
|
@@ -148,20 +424,33 @@ async function resolveIncludes(modelName, rows, include, executor, metadata) {
|
|
|
148
424
|
class TableClient {
|
|
149
425
|
constructor(modelName, tableName, executor, orm) {
|
|
150
426
|
this.modelName = modelName;
|
|
151
|
-
this.tableName = tableName;
|
|
152
427
|
this.executor = executor;
|
|
153
428
|
this.orm = orm;
|
|
429
|
+
this.tableName = (0, sql_utils_1.quoteTableIdentifier)(tableName);
|
|
430
|
+
}
|
|
431
|
+
async executeRaw(queryText, params) {
|
|
432
|
+
if (this.executor.executeRaw) {
|
|
433
|
+
return this.executor.executeRaw(queryText, params);
|
|
434
|
+
}
|
|
435
|
+
return normalizeAffectedCount(await this.executor(queryText, params));
|
|
154
436
|
}
|
|
155
437
|
async findMany(args) {
|
|
156
438
|
return this.orm._executeMiddleware({ model: this.modelName, action: 'findMany', args }, async (params) => {
|
|
157
439
|
const { args: finalArgs } = params;
|
|
158
440
|
const hasSkip = finalArgs?.skip !== undefined && finalArgs?.skip !== null;
|
|
441
|
+
const selectedRelations = collectRelationSelections(this.modelName, finalArgs?.select, this.orm.metadata);
|
|
442
|
+
const include = mergeIncludes(finalArgs?.include, selectedRelations);
|
|
159
443
|
let cols = "*";
|
|
160
444
|
if (finalArgs?.select) {
|
|
161
445
|
const modelRelations = this.orm.metadata.relationMap[this.modelName] || {};
|
|
162
|
-
const
|
|
163
|
-
.filter(k => finalArgs.select[k] && !modelRelations[k])
|
|
164
|
-
|
|
446
|
+
const selectedColNames = Object.keys(finalArgs.select)
|
|
447
|
+
.filter(k => finalArgs.select[k] && !modelRelations[k] && k !== "_count");
|
|
448
|
+
for (const key of requiredRelationKeys(this.modelName, include, this.orm.metadata)) {
|
|
449
|
+
if (!selectedColNames.includes(key)) {
|
|
450
|
+
selectedColNames.push(key);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
const selectedCols = selectedColNames.map(k => (0, sql_utils_1.quoteIdentifier)(k));
|
|
165
454
|
if (selectedCols.length > 0) {
|
|
166
455
|
cols = selectedCols.join(", ");
|
|
167
456
|
}
|
|
@@ -190,14 +479,14 @@ class TableClient {
|
|
|
190
479
|
}
|
|
191
480
|
}
|
|
192
481
|
const rows = await this.executor(sqlText, p);
|
|
482
|
+
if (include) {
|
|
483
|
+
await resolveIncludes(this.modelName, rows, include, this.executor, this.orm.metadata);
|
|
484
|
+
}
|
|
193
485
|
if (finalArgs?.select) {
|
|
194
486
|
rows.forEach((r, idx) => {
|
|
195
487
|
rows[idx] = projectFields(r, finalArgs.select);
|
|
196
488
|
});
|
|
197
489
|
}
|
|
198
|
-
if (finalArgs?.include) {
|
|
199
|
-
await resolveIncludes(this.modelName, rows, finalArgs.include, this.executor, this.orm.metadata);
|
|
200
|
-
}
|
|
201
490
|
return rows;
|
|
202
491
|
});
|
|
203
492
|
}
|
|
@@ -225,6 +514,12 @@ class TableClient {
|
|
|
225
514
|
return result[0]?.count || 0;
|
|
226
515
|
});
|
|
227
516
|
}
|
|
517
|
+
scopedRelationWhere(relation, parentId, where) {
|
|
518
|
+
const parentWhere = { [relation.foreignKey]: parentId };
|
|
519
|
+
if (!where || Object.keys(where).length === 0)
|
|
520
|
+
return parentWhere;
|
|
521
|
+
return { AND: [where, parentWhere] };
|
|
522
|
+
}
|
|
228
523
|
async handleNestedWrites(data, parentId) {
|
|
229
524
|
const modelRelations = this.orm.metadata.relationMap[this.modelName] || {};
|
|
230
525
|
for (const [key, value] of Object.entries(data)) {
|
|
@@ -235,32 +530,72 @@ class TableClient {
|
|
|
235
530
|
if (!relTableClient)
|
|
236
531
|
continue;
|
|
237
532
|
const nestedOps = value;
|
|
533
|
+
if (relation.relationType !== "many")
|
|
534
|
+
continue;
|
|
535
|
+
if (hasOwn(nestedOps, "set")) {
|
|
536
|
+
await relTableClient.updateMany({
|
|
537
|
+
where: { [relation.foreignKey]: parentId },
|
|
538
|
+
data: { [relation.foreignKey]: null },
|
|
539
|
+
});
|
|
540
|
+
for (const item of asArray(nestedOps.set)) {
|
|
541
|
+
await relTableClient.update({
|
|
542
|
+
where: item,
|
|
543
|
+
data: { [relation.foreignKey]: parentId }
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
}
|
|
238
547
|
// Handle deleteMany (an5Orm-style)
|
|
239
|
-
if (nestedOps
|
|
548
|
+
if (hasOwn(nestedOps, "deleteMany")) {
|
|
240
549
|
const deleteWhere = Array.isArray(nestedOps.deleteMany) ? { OR: nestedOps.deleteMany } : nestedOps.deleteMany;
|
|
241
550
|
// Scope deletion to parent
|
|
242
|
-
const scopedWhere =
|
|
551
|
+
const scopedWhere = this.scopedRelationWhere(relation, parentId, deleteWhere);
|
|
243
552
|
await relTableClient.deleteMany({ where: scopedWhere });
|
|
244
553
|
}
|
|
554
|
+
if (hasOwn(nestedOps, "delete")) {
|
|
555
|
+
const deleteWhere = Array.isArray(nestedOps.delete) ? { OR: nestedOps.delete } : nestedOps.delete;
|
|
556
|
+
await relTableClient.deleteMany({
|
|
557
|
+
where: this.scopedRelationWhere(relation, parentId, deleteWhere),
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
if (hasOwn(nestedOps, "disconnect")) {
|
|
561
|
+
const disconnectWhere = Array.isArray(nestedOps.disconnect) ? { OR: nestedOps.disconnect } : nestedOps.disconnect;
|
|
562
|
+
await relTableClient.updateMany({
|
|
563
|
+
where: this.scopedRelationWhere(relation, parentId, disconnectWhere),
|
|
564
|
+
data: { [relation.foreignKey]: null },
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
if (hasOwn(nestedOps, "update")) {
|
|
568
|
+
for (const item of asArray(nestedOps.update)) {
|
|
569
|
+
await relTableClient.update({
|
|
570
|
+
where: this.scopedRelationWhere(relation, parentId, item.where),
|
|
571
|
+
data: item.data,
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
if (hasOwn(nestedOps, "upsert")) {
|
|
576
|
+
for (const item of asArray(nestedOps.upsert)) {
|
|
577
|
+
await relTableClient.upsert({
|
|
578
|
+
where: this.scopedRelationWhere(relation, parentId, item.where),
|
|
579
|
+
create: { ...item.create, [relation.foreignKey]: parentId },
|
|
580
|
+
update: item.update,
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
}
|
|
245
584
|
// Handle create
|
|
246
|
-
if (nestedOps
|
|
247
|
-
const
|
|
248
|
-
for (const item of createItems) {
|
|
585
|
+
if (hasOwn(nestedOps, "create")) {
|
|
586
|
+
for (const item of asArray(nestedOps.create)) {
|
|
249
587
|
// Inject parent ID
|
|
250
588
|
const itemData = { ...item, [relation.foreignKey]: parentId };
|
|
251
589
|
await relTableClient.create({ data: itemData });
|
|
252
590
|
}
|
|
253
591
|
}
|
|
254
592
|
// Handle connect
|
|
255
|
-
if (nestedOps
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
data: { [relation.foreignKey]: parentId }
|
|
262
|
-
});
|
|
263
|
-
}
|
|
593
|
+
if (hasOwn(nestedOps, "connect")) {
|
|
594
|
+
for (const item of asArray(nestedOps.connect)) {
|
|
595
|
+
await relTableClient.update({
|
|
596
|
+
where: item,
|
|
597
|
+
data: { [relation.foreignKey]: parentId }
|
|
598
|
+
});
|
|
264
599
|
}
|
|
265
600
|
}
|
|
266
601
|
}
|
|
@@ -290,9 +625,10 @@ class TableClient {
|
|
|
290
625
|
// Handle one-relation connect where we hold the FK
|
|
291
626
|
for (const [key, value] of Object.entries(nestedData)) {
|
|
292
627
|
const rel = modelRelations[key];
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
const
|
|
628
|
+
const nestedValue = value && typeof value === "object" ? value : {};
|
|
629
|
+
if (rel && rel.relationType === "one" && (nestedValue.connect || nestedValue.set)) {
|
|
630
|
+
const connectObj = nestedValue.connect || nestedValue.set;
|
|
631
|
+
const targetId = connectObj[rel.localKey] || connectObj.id || Object.values(connectObj)[0];
|
|
296
632
|
data[rel.foreignKey] = targetId;
|
|
297
633
|
}
|
|
298
634
|
}
|
|
@@ -369,48 +705,35 @@ class TableClient {
|
|
|
369
705
|
// Handle one-relation connect where we hold the FK
|
|
370
706
|
for (const [key, value] of Object.entries(nestedData)) {
|
|
371
707
|
const rel = modelRelations[key];
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
708
|
+
const nestedValue = value && typeof value === "object" ? value : {};
|
|
709
|
+
if (rel && rel.relationType === "one") {
|
|
710
|
+
if (nestedValue.connect || nestedValue.set) {
|
|
711
|
+
const connectObj = nestedValue.connect || nestedValue.set;
|
|
712
|
+
const targetId = connectObj[rel.localKey] || connectObj.id || Object.values(connectObj)[0];
|
|
713
|
+
data[rel.foreignKey] = targetId;
|
|
714
|
+
}
|
|
715
|
+
else if (hasOwn(nestedValue, "disconnect") || value === null) {
|
|
716
|
+
data[rel.foreignKey] = null;
|
|
717
|
+
}
|
|
376
718
|
}
|
|
377
719
|
}
|
|
378
720
|
const sets = [];
|
|
379
721
|
const p = {};
|
|
380
722
|
for (const key of Object.keys(data)) {
|
|
381
723
|
const val = data[key];
|
|
382
|
-
|
|
383
|
-
const safeKey = (0, sql_utils_1.sanitizeParamName)(key);
|
|
384
|
-
if (val && typeof val === "object" && !(val instanceof Date)) {
|
|
385
|
-
if (val.increment !== undefined) {
|
|
386
|
-
sets.push(`${col} = ${col} + @${safeKey}_inc`);
|
|
387
|
-
p[`${safeKey}_inc`] = val.increment;
|
|
388
|
-
continue;
|
|
389
|
-
}
|
|
390
|
-
else if (val.decrement !== undefined) {
|
|
391
|
-
sets.push(`${col} = ${col} - @${safeKey}_dec`);
|
|
392
|
-
p[`${safeKey}_dec`] = val.decrement;
|
|
393
|
-
continue;
|
|
394
|
-
}
|
|
395
|
-
else if (val.set !== undefined) {
|
|
396
|
-
sets.push(`${col} = @${safeKey}_set`);
|
|
397
|
-
p[`${safeKey}_set`] = val.set;
|
|
398
|
-
continue;
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
sets.push(`${col} = @${safeKey}`);
|
|
402
|
-
p[safeKey] = val;
|
|
724
|
+
appendUpdateSet(sets, p, key, val);
|
|
403
725
|
}
|
|
404
726
|
const whereParams = {};
|
|
405
727
|
const whereSql = this.orm.parseWhere(this.modelName, finalArgs.where, whereParams, "w_");
|
|
406
728
|
Object.assign(p, whereParams);
|
|
407
|
-
const sqlText = `UPDATE ${this.tableName} SET ${sets.join(", ")} WHERE ${whereSql}`;
|
|
408
|
-
await this.executor(sqlText, p);
|
|
409
|
-
// Get parent ID for nested writes
|
|
410
729
|
const existing = await this.findUnique({ where: finalArgs.where });
|
|
411
730
|
if (!existing)
|
|
412
731
|
throw new Error("Record not found to update");
|
|
413
732
|
const parentId = existing.id;
|
|
733
|
+
if (sets.length > 0) {
|
|
734
|
+
const sqlText = `UPDATE ${this.tableName} SET ${sets.join(", ")} WHERE ${whereSql}`;
|
|
735
|
+
await this.executor(sqlText, p);
|
|
736
|
+
}
|
|
414
737
|
// Process nested writes
|
|
415
738
|
await this.handleNestedWrites(nestedData, parentId);
|
|
416
739
|
const updated = await this.findUnique({ where: finalArgs.where, include: finalArgs.include });
|
|
@@ -434,33 +757,16 @@ class TableClient {
|
|
|
434
757
|
const val = data[key];
|
|
435
758
|
if (this.orm.metadata.relationMap[this.modelName]?.[key])
|
|
436
759
|
continue;
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
sets.push(`${col} = ${col} + @${safeKey}_inc`);
|
|
442
|
-
p[`${safeKey}_inc`] = val.increment;
|
|
443
|
-
continue;
|
|
444
|
-
}
|
|
445
|
-
else if (val.decrement !== undefined) {
|
|
446
|
-
sets.push(`${col} = ${col} - @${safeKey}_dec`);
|
|
447
|
-
p[`${safeKey}_dec`] = val.decrement;
|
|
448
|
-
continue;
|
|
449
|
-
}
|
|
450
|
-
else if (val.set !== undefined) {
|
|
451
|
-
sets.push(`${col} = @${safeKey}_set`);
|
|
452
|
-
p[`${safeKey}_set`] = val.set;
|
|
453
|
-
continue;
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
sets.push(`${col} = @${safeKey}`);
|
|
457
|
-
p[safeKey] = val;
|
|
760
|
+
appendUpdateSet(sets, p, key, val);
|
|
761
|
+
}
|
|
762
|
+
if (sets.length === 0) {
|
|
763
|
+
return { count: 0 };
|
|
458
764
|
}
|
|
459
765
|
const whereParams = {};
|
|
460
766
|
const whereSql = this.orm.parseWhere(this.modelName, finalArgs.where, whereParams, "w_");
|
|
461
767
|
Object.assign(p, whereParams);
|
|
462
768
|
const sqlText = `UPDATE ${this.tableName} SET ${sets.join(", ")} ${whereSql ? `WHERE ${whereSql}` : ""}`;
|
|
463
|
-
const count = await
|
|
769
|
+
const count = await this.executeRaw(sqlText, p);
|
|
464
770
|
return { count };
|
|
465
771
|
});
|
|
466
772
|
}
|
|
@@ -483,7 +789,7 @@ class TableClient {
|
|
|
483
789
|
const p = {};
|
|
484
790
|
const whereSql = this.orm.parseWhere(this.modelName, finalArgs?.where, p);
|
|
485
791
|
const sqlText = `DELETE FROM ${this.tableName} ${whereSql ? `WHERE ${whereSql}` : ""}`;
|
|
486
|
-
const count = await
|
|
792
|
+
const count = await this.executeRaw(sqlText, p);
|
|
487
793
|
return { count };
|
|
488
794
|
});
|
|
489
795
|
}
|
|
@@ -524,7 +830,7 @@ class TableClient {
|
|
|
524
830
|
return rows;
|
|
525
831
|
}
|
|
526
832
|
catch (err) {
|
|
527
|
-
const msg = String(err
|
|
833
|
+
const msg = (String(err?.message || "") + " " + String(err?.originalError?.message || "")).toLowerCase();
|
|
528
834
|
// Handle specific float16 to float32 conversion error by retrying with float16
|
|
529
835
|
if (msg.includes("float16") && msg.includes("float32") && msg.includes("conversion") && !finalArgs.vectorElementType) {
|
|
530
836
|
logger_1.logger.info(`Detected float16 vector storage. Retrying vectorSearch with float16 element type.`);
|
|
@@ -532,8 +838,17 @@ class TableClient {
|
|
|
532
838
|
}
|
|
533
839
|
const isUnsupported = msg.includes("vector_distance") ||
|
|
534
840
|
msg.includes("type vector") ||
|
|
841
|
+
msg.includes("type \"vector\"") ||
|
|
842
|
+
msg.includes("data type vector") ||
|
|
843
|
+
msg.includes("incorrect syntax") ||
|
|
844
|
+
msg.includes("syntax near") ||
|
|
845
|
+
msg.includes("not a recognized built-in function") ||
|
|
846
|
+
msg.includes("not a defined system type") ||
|
|
535
847
|
msg.includes("limit of 1998") ||
|
|
536
|
-
err?.number === 195
|
|
848
|
+
err?.number === 195 ||
|
|
849
|
+
err?.number === 102 ||
|
|
850
|
+
err?.number === 319 ||
|
|
851
|
+
err?.originalError?.number === 319;
|
|
537
852
|
if (!isUnsupported) {
|
|
538
853
|
throw err;
|
|
539
854
|
}
|
|
@@ -618,7 +933,7 @@ class TableClient {
|
|
|
618
933
|
rowPlaceholders.push(`(${vals.join(", ")})`);
|
|
619
934
|
});
|
|
620
935
|
const sqlText = `INSERT INTO ${this.tableName} (${cols.map(sql_utils_1.quoteIdentifier).join(", ")}) VALUES ${rowPlaceholders.join(", ")}`;
|
|
621
|
-
await
|
|
936
|
+
await this.executeRaw(sqlText, params);
|
|
622
937
|
return { count: rows.length };
|
|
623
938
|
}
|
|
624
939
|
catch (err) {
|
|
@@ -646,25 +961,25 @@ class TableClient {
|
|
|
646
961
|
const resultObj = {};
|
|
647
962
|
if (finalArgs._sum) {
|
|
648
963
|
resultObj._sum = {};
|
|
649
|
-
for (const key of
|
|
964
|
+
for (const key of selectedAggregateFields(finalArgs._sum)) {
|
|
650
965
|
selects.push(aggSelect("SUM", key));
|
|
651
966
|
}
|
|
652
967
|
}
|
|
653
968
|
if (finalArgs._avg) {
|
|
654
969
|
resultObj._avg = {};
|
|
655
|
-
for (const key of
|
|
970
|
+
for (const key of selectedAggregateFields(finalArgs._avg)) {
|
|
656
971
|
selects.push(aggSelect("AVG", key));
|
|
657
972
|
}
|
|
658
973
|
}
|
|
659
974
|
if (finalArgs._min) {
|
|
660
975
|
resultObj._min = {};
|
|
661
|
-
for (const key of
|
|
976
|
+
for (const key of selectedAggregateFields(finalArgs._min)) {
|
|
662
977
|
selects.push(aggSelect("MIN", key));
|
|
663
978
|
}
|
|
664
979
|
}
|
|
665
980
|
if (finalArgs._max) {
|
|
666
981
|
resultObj._max = {};
|
|
667
|
-
for (const key of
|
|
982
|
+
for (const key of selectedAggregateFields(finalArgs._max)) {
|
|
668
983
|
selects.push(aggSelect("MAX", key));
|
|
669
984
|
}
|
|
670
985
|
}
|
|
@@ -674,7 +989,7 @@ class TableClient {
|
|
|
674
989
|
selects.push(`COUNT(*) as count_all`);
|
|
675
990
|
}
|
|
676
991
|
else {
|
|
677
|
-
for (const key of
|
|
992
|
+
for (const key of selectedAggregateFields(finalArgs._count)) {
|
|
678
993
|
selects.push(aggSelect("COUNT", key));
|
|
679
994
|
}
|
|
680
995
|
}
|
|
@@ -691,22 +1006,22 @@ class TableClient {
|
|
|
691
1006
|
const rows = await this.executor(sqlText, p);
|
|
692
1007
|
const row = rows[0] || {};
|
|
693
1008
|
if (finalArgs._sum) {
|
|
694
|
-
for (const key of
|
|
1009
|
+
for (const key of selectedAggregateFields(finalArgs._sum)) {
|
|
695
1010
|
resultObj._sum[key] = row[aggAlias("sum", key)] !== undefined ? row[aggAlias("sum", key)] : null;
|
|
696
1011
|
}
|
|
697
1012
|
}
|
|
698
1013
|
if (finalArgs._avg) {
|
|
699
|
-
for (const key of
|
|
1014
|
+
for (const key of selectedAggregateFields(finalArgs._avg)) {
|
|
700
1015
|
resultObj._avg[key] = row[aggAlias("avg", key)] !== undefined ? row[aggAlias("avg", key)] : null;
|
|
701
1016
|
}
|
|
702
1017
|
}
|
|
703
1018
|
if (finalArgs._min) {
|
|
704
|
-
for (const key of
|
|
1019
|
+
for (const key of selectedAggregateFields(finalArgs._min)) {
|
|
705
1020
|
resultObj._min[key] = row[aggAlias("min", key)] !== undefined ? row[aggAlias("min", key)] : null;
|
|
706
1021
|
}
|
|
707
1022
|
}
|
|
708
1023
|
if (finalArgs._max) {
|
|
709
|
-
for (const key of
|
|
1024
|
+
for (const key of selectedAggregateFields(finalArgs._max)) {
|
|
710
1025
|
resultObj._max[key] = row[aggAlias("max", key)] !== undefined ? row[aggAlias("max", key)] : null;
|
|
711
1026
|
}
|
|
712
1027
|
}
|
|
@@ -715,7 +1030,7 @@ class TableClient {
|
|
|
715
1030
|
resultObj._count._all = row[`count_all`] || 0;
|
|
716
1031
|
}
|
|
717
1032
|
else {
|
|
718
|
-
for (const key of
|
|
1033
|
+
for (const key of selectedAggregateFields(finalArgs._count)) {
|
|
719
1034
|
resultObj._count[key] = row[aggAlias("count", key)] || 0;
|
|
720
1035
|
}
|
|
721
1036
|
}
|
|
@@ -726,7 +1041,7 @@ class TableClient {
|
|
|
726
1041
|
async groupBy(args) {
|
|
727
1042
|
return this.orm._executeMiddleware({ model: this.modelName, action: 'groupBy', args }, async (params) => {
|
|
728
1043
|
const { args: finalArgs } = params;
|
|
729
|
-
const byFields = finalArgs.by
|
|
1044
|
+
const byFields = normalizeByFields(finalArgs.by);
|
|
730
1045
|
if (byFields.length === 0) {
|
|
731
1046
|
throw new Error("groupBy requires 'by' fields");
|
|
732
1047
|
}
|
|
@@ -736,28 +1051,28 @@ class TableClient {
|
|
|
736
1051
|
selects.push(`COUNT(*) as count_all`);
|
|
737
1052
|
}
|
|
738
1053
|
else {
|
|
739
|
-
for (const key of
|
|
1054
|
+
for (const key of selectedAggregateFields(finalArgs._count)) {
|
|
740
1055
|
selects.push(aggSelect("COUNT", key));
|
|
741
1056
|
}
|
|
742
1057
|
}
|
|
743
1058
|
}
|
|
744
1059
|
if (finalArgs._sum) {
|
|
745
|
-
for (const key of
|
|
1060
|
+
for (const key of selectedAggregateFields(finalArgs._sum)) {
|
|
746
1061
|
selects.push(aggSelect("SUM", key));
|
|
747
1062
|
}
|
|
748
1063
|
}
|
|
749
1064
|
if (finalArgs._avg) {
|
|
750
|
-
for (const key of
|
|
1065
|
+
for (const key of selectedAggregateFields(finalArgs._avg)) {
|
|
751
1066
|
selects.push(aggSelect("AVG", key));
|
|
752
1067
|
}
|
|
753
1068
|
}
|
|
754
1069
|
if (finalArgs._min) {
|
|
755
|
-
for (const key of
|
|
1070
|
+
for (const key of selectedAggregateFields(finalArgs._min)) {
|
|
756
1071
|
selects.push(aggSelect("MIN", key));
|
|
757
1072
|
}
|
|
758
1073
|
}
|
|
759
1074
|
if (finalArgs._max) {
|
|
760
|
-
for (const key of
|
|
1075
|
+
for (const key of selectedAggregateFields(finalArgs._max)) {
|
|
761
1076
|
selects.push(aggSelect("MAX", key));
|
|
762
1077
|
}
|
|
763
1078
|
}
|
|
@@ -768,6 +1083,24 @@ class TableClient {
|
|
|
768
1083
|
sqlText += ` WHERE ${whereSql}`;
|
|
769
1084
|
}
|
|
770
1085
|
sqlText += ` GROUP BY ${byFields.map((f) => (0, sql_utils_1.quoteIdentifier)(f)).join(", ")}`;
|
|
1086
|
+
const havingSql = buildHavingSql(finalArgs?.having, p);
|
|
1087
|
+
if (havingSql) {
|
|
1088
|
+
sqlText += ` HAVING ${havingSql}`;
|
|
1089
|
+
}
|
|
1090
|
+
const hasSkip = finalArgs?.skip !== undefined && finalArgs?.skip !== null;
|
|
1091
|
+
const hasTake = finalArgs?.take !== undefined && finalArgs?.take !== null;
|
|
1092
|
+
if (finalArgs?.orderBy) {
|
|
1093
|
+
sqlText += (0, sql_utils_1.buildOrderBy)(finalArgs.orderBy);
|
|
1094
|
+
}
|
|
1095
|
+
else if (hasSkip || hasTake) {
|
|
1096
|
+
sqlText += ` ORDER BY ${byFields.map((f) => (0, sql_utils_1.quoteIdentifier)(f)).join(", ")}`;
|
|
1097
|
+
}
|
|
1098
|
+
if (hasSkip || hasTake) {
|
|
1099
|
+
sqlText += ` OFFSET ${(0, sql_utils_1.toNonNegativeInt)(finalArgs.skip)} ROWS`;
|
|
1100
|
+
if (hasTake) {
|
|
1101
|
+
sqlText += ` FETCH NEXT ${(0, sql_utils_1.toNonNegativeInt)(finalArgs.take, 1)} ROWS ONLY`;
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
771
1104
|
const rows = await this.executor(sqlText, p);
|
|
772
1105
|
return rows.map((row) => {
|
|
773
1106
|
const item = {};
|
|
@@ -780,32 +1113,32 @@ class TableClient {
|
|
|
780
1113
|
item._count._all = row[`count_all`] || 0;
|
|
781
1114
|
}
|
|
782
1115
|
else {
|
|
783
|
-
for (const key of
|
|
1116
|
+
for (const key of selectedAggregateFields(finalArgs._count)) {
|
|
784
1117
|
item._count[key] = row[aggAlias("count", key)] || 0;
|
|
785
1118
|
}
|
|
786
1119
|
}
|
|
787
1120
|
}
|
|
788
1121
|
if (finalArgs._sum) {
|
|
789
1122
|
item._sum = {};
|
|
790
|
-
for (const key of
|
|
1123
|
+
for (const key of selectedAggregateFields(finalArgs._sum)) {
|
|
791
1124
|
item._sum[key] = row[aggAlias("sum", key)] !== undefined ? row[aggAlias("sum", key)] : null;
|
|
792
1125
|
}
|
|
793
1126
|
}
|
|
794
1127
|
if (finalArgs._avg) {
|
|
795
1128
|
item._avg = {};
|
|
796
|
-
for (const key of
|
|
1129
|
+
for (const key of selectedAggregateFields(finalArgs._avg)) {
|
|
797
1130
|
item._avg[key] = row[aggAlias("avg", key)] !== undefined ? row[aggAlias("avg", key)] : null;
|
|
798
1131
|
}
|
|
799
1132
|
}
|
|
800
1133
|
if (finalArgs._min) {
|
|
801
1134
|
item._min = {};
|
|
802
|
-
for (const key of
|
|
1135
|
+
for (const key of selectedAggregateFields(finalArgs._min)) {
|
|
803
1136
|
item._min[key] = row[aggAlias("min", key)] !== undefined ? row[aggAlias("min", key)] : null;
|
|
804
1137
|
}
|
|
805
1138
|
}
|
|
806
1139
|
if (finalArgs._max) {
|
|
807
1140
|
item._max = {};
|
|
808
|
-
for (const key of
|
|
1141
|
+
for (const key of selectedAggregateFields(finalArgs._max)) {
|
|
809
1142
|
item._max[key] = row[aggAlias("max", key)] !== undefined ? row[aggAlias("max", key)] : null;
|
|
810
1143
|
}
|
|
811
1144
|
}
|
|
@@ -813,6 +1146,13 @@ class TableClient {
|
|
|
813
1146
|
});
|
|
814
1147
|
});
|
|
815
1148
|
}
|
|
1149
|
+
async sequentialUpsert(finalArgs) {
|
|
1150
|
+
const existing = await this.findUnique({ where: finalArgs.where });
|
|
1151
|
+
if (existing) {
|
|
1152
|
+
return this.update({ where: finalArgs.where, data: finalArgs.update, include: finalArgs.include });
|
|
1153
|
+
}
|
|
1154
|
+
return this.create({ data: finalArgs.create, include: finalArgs.include });
|
|
1155
|
+
}
|
|
816
1156
|
async upsert(args) {
|
|
817
1157
|
return this.orm._executeMiddleware({ model: this.modelName, action: 'upsert', args }, async (params) => {
|
|
818
1158
|
const { args: finalArgs } = params;
|
|
@@ -838,8 +1178,11 @@ class TableClient {
|
|
|
838
1178
|
cleanCreate.updatedAt = now;
|
|
839
1179
|
if (!cleanUpdate.updatedAt && this.orm.metadata.modelFields[this.modelName]?.updatedAt)
|
|
840
1180
|
cleanUpdate.updatedAt = now;
|
|
1181
|
+
const atomicWhere = flattenSimpleEqualityWhere(where);
|
|
1182
|
+
if (!atomicWhere || Object.keys(cleanUpdate).length === 0) {
|
|
1183
|
+
return this.sequentialUpsert(finalArgs);
|
|
1184
|
+
}
|
|
841
1185
|
const p = {};
|
|
842
|
-
const whereSql = this.orm.parseWhere(this.modelName, where, p, "upw_");
|
|
843
1186
|
const allKeys = Array.from(new Set([...Object.keys(cleanCreate), ...Object.keys(cleanUpdate)]));
|
|
844
1187
|
const cParam = (k) => `c_${(0, sql_utils_1.sanitizeParamName)(k)}`;
|
|
845
1188
|
const uParam = (k) => `u_${(0, sql_utils_1.sanitizeParamName)(k)}`;
|
|
@@ -849,6 +1192,9 @@ class TableClient {
|
|
|
849
1192
|
if (cleanUpdate[k] !== undefined)
|
|
850
1193
|
p[uParam(k)] = cleanUpdate[k];
|
|
851
1194
|
}
|
|
1195
|
+
for (const [k, v] of Object.entries(atomicWhere)) {
|
|
1196
|
+
p[`upw_${(0, sql_utils_1.sanitizeParamName)(k)}`] = v;
|
|
1197
|
+
}
|
|
852
1198
|
const sourceSelect = allKeys.map(k => {
|
|
853
1199
|
const val = cleanCreate[k] !== undefined ? `@${cParam(k)}` : (cleanUpdate[k] !== undefined ? `@${uParam(k)}` : "NULL");
|
|
854
1200
|
return `${val} as ${(0, sql_utils_1.quoteIdentifier)(k)}`;
|
|
@@ -856,26 +1202,18 @@ class TableClient {
|
|
|
856
1202
|
const updateSets = Object.keys(cleanUpdate).map(k => `target.${(0, sql_utils_1.quoteIdentifier)(k)} = source.${(0, sql_utils_1.quoteIdentifier)(k)}`).join(", ");
|
|
857
1203
|
const insertCols = Object.keys(cleanCreate).map(k => (0, sql_utils_1.quoteIdentifier)(k)).join(", ");
|
|
858
1204
|
const insertVals = Object.keys(cleanCreate).map(k => `source.${(0, sql_utils_1.quoteIdentifier)(k)}`).join(", ");
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
const
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
MERGE INTO ${this.tableName} WITH (HOLDLOCK) AS target
|
|
872
|
-
USING (SELECT ${sourceSelect}) AS source
|
|
873
|
-
ON (${onClause})
|
|
874
|
-
WHEN MATCHED THEN
|
|
875
|
-
UPDATE SET ${updateSets}
|
|
876
|
-
WHEN NOT MATCHED THEN
|
|
877
|
-
INSERT (${insertCols}) VALUES (${insertVals})
|
|
878
|
-
OUTPUT inserted.*;
|
|
1205
|
+
const onClause = Object.keys(atomicWhere)
|
|
1206
|
+
.map(k => `target.${(0, sql_utils_1.quoteIdentifier)(k)} = @upw_${(0, sql_utils_1.sanitizeParamName)(k)}`)
|
|
1207
|
+
.join(" AND ");
|
|
1208
|
+
const sqlText = `
|
|
1209
|
+
MERGE INTO ${this.tableName} WITH (HOLDLOCK) AS target
|
|
1210
|
+
USING (SELECT ${sourceSelect}) AS source
|
|
1211
|
+
ON (${onClause})
|
|
1212
|
+
WHEN MATCHED THEN
|
|
1213
|
+
UPDATE SET ${updateSets}
|
|
1214
|
+
WHEN NOT MATCHED THEN
|
|
1215
|
+
INSERT (${insertCols}) VALUES (${insertVals})
|
|
1216
|
+
OUTPUT inserted.*;
|
|
879
1217
|
`;
|
|
880
1218
|
try {
|
|
881
1219
|
const rows = await this.executor(sqlText, p);
|
|
@@ -887,17 +1225,12 @@ class TableClient {
|
|
|
887
1225
|
}
|
|
888
1226
|
catch (err) {
|
|
889
1227
|
logger_1.logger.warn(`Atomic upsert failed, falling back to sequential: ${err.message}`);
|
|
890
|
-
|
|
891
|
-
if (existing) {
|
|
892
|
-
return this.update({ where: finalArgs.where, data: finalArgs.update, include: finalArgs.include });
|
|
893
|
-
}
|
|
894
|
-
else {
|
|
895
|
-
return this.create({ data: finalArgs.create, include: finalArgs.include });
|
|
896
|
-
}
|
|
1228
|
+
return this.sequentialUpsert(finalArgs);
|
|
897
1229
|
}
|
|
898
1230
|
});
|
|
899
1231
|
}
|
|
900
1232
|
}
|
|
1233
|
+
exports.TableClient = TableClient;
|
|
901
1234
|
function addNoLockToQuery(sql, metadata) {
|
|
902
1235
|
// If it's not a SELECT query, don't modify it
|
|
903
1236
|
if (!/^\s*SELECT/i.test(sql)) {
|
|
@@ -935,28 +1268,124 @@ function addNoLockToQuery(sql, metadata) {
|
|
|
935
1268
|
// Lazy-load generated schema metadata from the ORM's own generated copy so
|
|
936
1269
|
// `new An5ORM()` resolves schema models out of the box without the core ever
|
|
937
1270
|
// importing from the generated client package (client is generated FROM the ORM).
|
|
1271
|
+
class ViewClient {
|
|
1272
|
+
constructor(viewName, rawTableName, executor, orm) {
|
|
1273
|
+
this.viewName = viewName;
|
|
1274
|
+
this.rawTableName = rawTableName;
|
|
1275
|
+
this.executor = executor;
|
|
1276
|
+
this.orm = orm;
|
|
1277
|
+
this.tableClient = new TableClient(viewName, rawTableName, executor, orm);
|
|
1278
|
+
}
|
|
1279
|
+
async findMany(args) { return this.tableClient.findMany(args); }
|
|
1280
|
+
async findFirst(args) { return this.tableClient.findFirst(args); }
|
|
1281
|
+
async findUnique(args) { return this.tableClient.findUnique(args); }
|
|
1282
|
+
async count(args) { return this.tableClient.count(args); }
|
|
1283
|
+
async aggregate(args) { return this.tableClient.aggregate(args); }
|
|
1284
|
+
async groupBy(args) { return this.tableClient.groupBy(args); }
|
|
1285
|
+
async vectorSearch(args) { return this.tableClient.vectorSearch(args); }
|
|
1286
|
+
async create() {
|
|
1287
|
+
throw new errors_1.An5ClientKnownRequestError("Views are read-only and do not support create operations.", { code: "P2000", clientVersion: "1.0.8" });
|
|
1288
|
+
}
|
|
1289
|
+
async createMany() {
|
|
1290
|
+
throw new errors_1.An5ClientKnownRequestError("Views are read-only and do not support createMany operations.", { code: "P2000", clientVersion: "1.0.8" });
|
|
1291
|
+
}
|
|
1292
|
+
async update() {
|
|
1293
|
+
throw new errors_1.An5ClientKnownRequestError("Views are read-only and do not support update operations.", { code: "P2000", clientVersion: "1.0.8" });
|
|
1294
|
+
}
|
|
1295
|
+
async updateMany() {
|
|
1296
|
+
throw new errors_1.An5ClientKnownRequestError("Views are read-only and do not support updateMany operations.", { code: "P2000", clientVersion: "1.0.8" });
|
|
1297
|
+
}
|
|
1298
|
+
async delete() {
|
|
1299
|
+
throw new errors_1.An5ClientKnownRequestError("Views are read-only and do not support delete operations.", { code: "P2000", clientVersion: "1.0.8" });
|
|
1300
|
+
}
|
|
1301
|
+
async deleteMany() {
|
|
1302
|
+
throw new errors_1.An5ClientKnownRequestError("Views are read-only and do not support deleteMany operations.", { code: "P2000", clientVersion: "1.0.8" });
|
|
1303
|
+
}
|
|
1304
|
+
async upsert() {
|
|
1305
|
+
throw new errors_1.An5ClientKnownRequestError("Views are read-only and do not support upsert operations.", { code: "P2000", clientVersion: "1.0.8" });
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
exports.ViewClient = ViewClient;
|
|
1309
|
+
function buildProcCallSql(procName, params) {
|
|
1310
|
+
const p = {};
|
|
1311
|
+
const targetProc = (0, sql_utils_1.quoteTableIdentifier)(procName);
|
|
1312
|
+
if (!params) {
|
|
1313
|
+
return { sqlText: `EXEC ${targetProc};`, p };
|
|
1314
|
+
}
|
|
1315
|
+
if (Array.isArray(params)) {
|
|
1316
|
+
const argList = params.map((val, idx) => {
|
|
1317
|
+
const key = `p_${idx}`;
|
|
1318
|
+
p[key] = val;
|
|
1319
|
+
return `@${key}`;
|
|
1320
|
+
}).join(", ");
|
|
1321
|
+
return { sqlText: `EXEC ${targetProc} ${argList};`, p };
|
|
1322
|
+
}
|
|
1323
|
+
const argList = Object.entries(params).map(([key, val], idx) => {
|
|
1324
|
+
const paramKey = (0, sql_utils_1.sanitizeParamName)(`p_${key}_${idx}`);
|
|
1325
|
+
p[paramKey] = val;
|
|
1326
|
+
return `@${(0, sql_utils_1.sanitizeParamName)(key)} = @${paramKey}`;
|
|
1327
|
+
}).join(", ");
|
|
1328
|
+
return { sqlText: `EXEC ${targetProc} ${argList};`, p };
|
|
1329
|
+
}
|
|
1330
|
+
function buildFunctionCallSql(fnName, params) {
|
|
1331
|
+
const p = {};
|
|
1332
|
+
const targetFn = (0, sql_utils_1.quoteTableIdentifier)(fnName);
|
|
1333
|
+
if (!params) {
|
|
1334
|
+
return { sqlText: `SELECT * FROM ${targetFn}();`, p };
|
|
1335
|
+
}
|
|
1336
|
+
if (Array.isArray(params)) {
|
|
1337
|
+
const argList = params.map((val, idx) => {
|
|
1338
|
+
const key = `p_${idx}`;
|
|
1339
|
+
p[key] = val;
|
|
1340
|
+
return `@${key}`;
|
|
1341
|
+
}).join(", ");
|
|
1342
|
+
return { sqlText: `SELECT * FROM ${targetFn}(${argList});`, p };
|
|
1343
|
+
}
|
|
1344
|
+
const argList = Object.entries(params).map(([key, val], idx) => {
|
|
1345
|
+
const paramKey = (0, sql_utils_1.sanitizeParamName)(`p_${key}_${idx}`);
|
|
1346
|
+
p[paramKey] = val;
|
|
1347
|
+
return `@${paramKey}`;
|
|
1348
|
+
}).join(", ");
|
|
1349
|
+
return { sqlText: `SELECT * FROM ${targetFn}(${argList});`, p };
|
|
1350
|
+
}
|
|
938
1351
|
let autoMetadata = null;
|
|
939
1352
|
function loadAutoMetadata() {
|
|
940
1353
|
if (autoMetadata)
|
|
941
1354
|
return autoMetadata;
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
1355
|
+
const candidates = [
|
|
1356
|
+
"an5-client/typescript/an5Metadata",
|
|
1357
|
+
"../an5Client/typescript/an5Metadata",
|
|
1358
|
+
"./an5Metadata"
|
|
1359
|
+
];
|
|
1360
|
+
for (const cand of candidates) {
|
|
1361
|
+
try {
|
|
1362
|
+
const m = require(cand);
|
|
1363
|
+
if (m && m.modelToTable && Object.keys(m.modelToTable).length > 0) {
|
|
1364
|
+
autoMetadata = {
|
|
1365
|
+
modelToTable: m.modelToTable || {},
|
|
1366
|
+
relationMap: m.relationMap || {},
|
|
1367
|
+
modelFields: m.modelFields || {},
|
|
1368
|
+
};
|
|
1369
|
+
return autoMetadata;
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
catch { }
|
|
952
1373
|
}
|
|
1374
|
+
autoMetadata = metadata_1.DEFAULT_METADATA;
|
|
953
1375
|
return autoMetadata;
|
|
954
1376
|
}
|
|
955
1377
|
// Proxied AN5 ORM client class
|
|
956
1378
|
class An5ORM {
|
|
957
|
-
constructor(customExecutor, metadata) {
|
|
958
|
-
this.
|
|
1379
|
+
constructor(customExecutor, metadata, inTransaction = false, transactionControl) {
|
|
1380
|
+
this.inTransaction = inTransaction;
|
|
1381
|
+
this.transactionControl = transactionControl;
|
|
959
1382
|
this.middlewares = [];
|
|
1383
|
+
if (customExecutor && typeof customExecutor === "object" && typeof customExecutor.exec === "function") {
|
|
1384
|
+
this.customExecutor = executorFromAdapterLike(customExecutor);
|
|
1385
|
+
}
|
|
1386
|
+
else if (typeof customExecutor === "function") {
|
|
1387
|
+
this.customExecutor = customExecutor;
|
|
1388
|
+
}
|
|
960
1389
|
this.metadata = metadata ?? loadAutoMetadata();
|
|
961
1390
|
// Add default logging middleware
|
|
962
1391
|
this.$use(async (params, next) => {
|
|
@@ -976,43 +1405,68 @@ class An5ORM {
|
|
|
976
1405
|
}
|
|
977
1406
|
});
|
|
978
1407
|
return new Proxy(this, {
|
|
979
|
-
get(target, prop) {
|
|
980
|
-
if (prop === "
|
|
981
|
-
return target
|
|
982
|
-
}
|
|
983
|
-
if (prop === "$transaction") {
|
|
984
|
-
return target.$transaction.bind(target);
|
|
985
|
-
}
|
|
986
|
-
if (prop === "$connect") {
|
|
987
|
-
return target.$connect.bind(target);
|
|
988
|
-
}
|
|
989
|
-
if (prop === "$disconnect") {
|
|
990
|
-
return target.$disconnect.bind(target);
|
|
991
|
-
}
|
|
992
|
-
if (prop === "$queryRaw") {
|
|
993
|
-
return target.$queryRaw.bind(target);
|
|
994
|
-
}
|
|
995
|
-
if (prop === "$queryRawUnsafe") {
|
|
996
|
-
return target.$queryRawUnsafe.bind(target);
|
|
997
|
-
}
|
|
998
|
-
if (prop === "$executeRaw") {
|
|
999
|
-
return target.$executeRaw.bind(target);
|
|
1000
|
-
}
|
|
1001
|
-
if (prop === "$executeRawUnsafe") {
|
|
1002
|
-
return target.$executeRawUnsafe.bind(target);
|
|
1408
|
+
get(target, prop, receiver) {
|
|
1409
|
+
if (typeof prop === "string" && prop in target && typeof target[prop] === "function") {
|
|
1410
|
+
return target[prop].bind(target);
|
|
1003
1411
|
}
|
|
1004
1412
|
if (!(prop in target) && typeof prop === "string" && !prop.startsWith("_")) {
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
if (tableName) {
|
|
1008
|
-
|
|
1009
|
-
)
|
|
1413
|
+
let modelName = prop;
|
|
1414
|
+
let tableName = target.metadata.modelToTable[prop];
|
|
1415
|
+
if (!tableName) {
|
|
1416
|
+
const lowerProp = prop.toLowerCase();
|
|
1417
|
+
for (const [mName, tName] of Object.entries(target.metadata.modelToTable)) {
|
|
1418
|
+
const lowerModel = mName.toLowerCase();
|
|
1419
|
+
const lowerTable = tName.toLowerCase();
|
|
1420
|
+
if (lowerModel === lowerProp ||
|
|
1421
|
+
lowerTable === lowerProp ||
|
|
1422
|
+
lowerModel + "s" === lowerProp ||
|
|
1423
|
+
lowerModel + "es" === lowerProp ||
|
|
1424
|
+
lowerTable + "s" === lowerProp ||
|
|
1425
|
+
lowerTable + "es" === lowerProp) {
|
|
1426
|
+
modelName = mName;
|
|
1427
|
+
tableName = tName;
|
|
1428
|
+
break;
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
if (!tableName) {
|
|
1433
|
+
tableName = prop;
|
|
1010
1434
|
}
|
|
1435
|
+
target[prop] = new TableClient(modelName, tableName, target.customExecutor || execQuery, receiver);
|
|
1011
1436
|
}
|
|
1012
1437
|
return target[prop];
|
|
1013
1438
|
}
|
|
1014
1439
|
});
|
|
1015
1440
|
}
|
|
1441
|
+
table(name) {
|
|
1442
|
+
const rawTable = this.metadata.modelToTable[name] || name;
|
|
1443
|
+
return new TableClient(name, rawTable, this.customExecutor || execQuery, this);
|
|
1444
|
+
}
|
|
1445
|
+
view(name) {
|
|
1446
|
+
const rawTable = this.metadata.modelToTable[name] || name;
|
|
1447
|
+
return new ViewClient(name, rawTable, this.customExecutor || execQuery, this);
|
|
1448
|
+
}
|
|
1449
|
+
$view(name) {
|
|
1450
|
+
return this.view(name);
|
|
1451
|
+
}
|
|
1452
|
+
async $queryProc(procName, params) {
|
|
1453
|
+
const executor = this.customExecutor || execQuery;
|
|
1454
|
+
const { sqlText, p } = buildProcCallSql(procName, params);
|
|
1455
|
+
return executor(sqlText, p);
|
|
1456
|
+
}
|
|
1457
|
+
async $executeProc(procName, params) {
|
|
1458
|
+
const executor = this.customExecutor || execQuery;
|
|
1459
|
+
const { sqlText, p } = buildProcCallSql(procName, params);
|
|
1460
|
+
if (executor.executeRaw) {
|
|
1461
|
+
return executor.executeRaw(sqlText, p);
|
|
1462
|
+
}
|
|
1463
|
+
return normalizeAffectedCount(await executor(sqlText, p));
|
|
1464
|
+
}
|
|
1465
|
+
async $queryFunction(fnName, params) {
|
|
1466
|
+
const executor = this.customExecutor || execQuery;
|
|
1467
|
+
const { sqlText, p } = buildFunctionCallSql(fnName, params);
|
|
1468
|
+
return executor(sqlText, p);
|
|
1469
|
+
}
|
|
1016
1470
|
$use(middleware) {
|
|
1017
1471
|
this.middlewares.push(middleware);
|
|
1018
1472
|
}
|
|
@@ -1116,8 +1570,10 @@ class An5ORM {
|
|
|
1116
1570
|
throw new Error("Invalid query format for $executeRaw");
|
|
1117
1571
|
}
|
|
1118
1572
|
queryText = addNoLockToQuery(queryText, this.metadata);
|
|
1119
|
-
|
|
1120
|
-
|
|
1573
|
+
if (executor.executeRaw) {
|
|
1574
|
+
return executor.executeRaw(queryText, params);
|
|
1575
|
+
}
|
|
1576
|
+
return normalizeAffectedCount(await executor(queryText, params));
|
|
1121
1577
|
}
|
|
1122
1578
|
async $executeRawUnsafe(queryText, ...values) {
|
|
1123
1579
|
const executor = this.customExecutor || execQuery;
|
|
@@ -1129,23 +1585,58 @@ class An5ORM {
|
|
|
1129
1585
|
});
|
|
1130
1586
|
}
|
|
1131
1587
|
const modifiedQueryText = addNoLockToQuery(queryText, this.metadata);
|
|
1132
|
-
|
|
1133
|
-
|
|
1588
|
+
if (executor.executeRaw) {
|
|
1589
|
+
return executor.executeRaw(modifiedQueryText, params);
|
|
1590
|
+
}
|
|
1591
|
+
return normalizeAffectedCount(await executor(modifiedQueryText, params));
|
|
1134
1592
|
}
|
|
1135
1593
|
async $transaction(fn, options) {
|
|
1136
1594
|
if (Array.isArray(fn)) {
|
|
1137
1595
|
return Promise.all(fn);
|
|
1138
1596
|
}
|
|
1139
|
-
const
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
const
|
|
1143
|
-
return
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1597
|
+
const executor = this.customExecutor || execQuery;
|
|
1598
|
+
if (!executor.transaction) {
|
|
1599
|
+
if (this.inTransaction) {
|
|
1600
|
+
const txClient = new An5ORM(executor, this.metadata, true);
|
|
1601
|
+
return fn(txClient);
|
|
1602
|
+
}
|
|
1603
|
+
throw new Error("Transactions require an executor with transaction support");
|
|
1604
|
+
}
|
|
1605
|
+
return executor.transaction(async (txExecutor) => {
|
|
1606
|
+
const txClient = new An5ORM(txExecutor, this.metadata, true);
|
|
1146
1607
|
return fn(txClient);
|
|
1147
1608
|
}, options);
|
|
1148
1609
|
}
|
|
1610
|
+
async $begin() {
|
|
1611
|
+
if (this.inTransaction) {
|
|
1612
|
+
throw new Error("Interactive transactions cannot be nested");
|
|
1613
|
+
}
|
|
1614
|
+
const executor = this.customExecutor || execQuery;
|
|
1615
|
+
if (!executor.beginTransaction) {
|
|
1616
|
+
throw new Error("Interactive transactions require an executor with beginTransaction support");
|
|
1617
|
+
}
|
|
1618
|
+
const tx = await executor.beginTransaction();
|
|
1619
|
+
return new An5ORM(tx.executor, this.metadata, true, {
|
|
1620
|
+
commit: tx.commit,
|
|
1621
|
+
rollback: tx.rollback,
|
|
1622
|
+
});
|
|
1623
|
+
}
|
|
1624
|
+
async $commit() {
|
|
1625
|
+
if (!this.transactionControl) {
|
|
1626
|
+
throw new Error("$commit can only be called on an interactive transaction client");
|
|
1627
|
+
}
|
|
1628
|
+
const control = this.transactionControl;
|
|
1629
|
+
this.transactionControl = undefined;
|
|
1630
|
+
await control.commit();
|
|
1631
|
+
}
|
|
1632
|
+
async $rollback() {
|
|
1633
|
+
if (!this.transactionControl) {
|
|
1634
|
+
throw new Error("$rollback can only be called on an interactive transaction client");
|
|
1635
|
+
}
|
|
1636
|
+
const control = this.transactionControl;
|
|
1637
|
+
this.transactionControl = undefined;
|
|
1638
|
+
await control.rollback();
|
|
1639
|
+
}
|
|
1149
1640
|
}
|
|
1150
1641
|
exports.An5ORM = An5ORM;
|
|
1151
1642
|
exports.an5Orm = new An5ORM();
|