@an5/orm 1.0.7 → 1.0.9
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 +97 -83
- package/dist/an5Metadata.d.ts +10 -0
- package/dist/an5Metadata.js +93 -0
- package/dist/an5Orm.d.ts +95 -3
- package/dist/an5Orm.js +808 -512
- package/dist/config.d.ts +2 -0
- package/dist/config.js +24 -0
- package/dist/errors.d.ts +14 -0
- package/dist/errors.js +16 -0
- package/dist/generator/src/code-generator.d.ts +1 -0
- package/dist/generator/src/code-generator.js +44 -20
- package/dist/generator/src/dotnet-generator.d.ts +2 -0
- package/dist/generator/src/dotnet-generator.js +332 -6
- package/dist/generator/src/index.js +18 -2
- package/dist/generator/src/metadata-generator.d.ts +4 -1
- package/dist/generator/src/metadata-generator.js +22 -8
- package/dist/generator/src/python-generator.d.ts +10 -0
- package/dist/generator/src/python-generator.js +242 -2
- package/dist/index.d.ts +6 -2
- package/dist/index.js +23 -8
- package/dist/logger.d.ts +53 -0
- package/dist/logger.js +117 -0
- package/dist/metadata.d.ts +24 -0
- package/dist/metadata.js +15 -0
- package/dist/migration-core.d.ts +87 -0
- package/dist/migration-core.js +692 -0
- package/dist/sql-utils.d.ts +31 -0
- package/dist/sql-utils.js +290 -0
- package/generator/src/code-generator.ts +45 -20
- package/generator/src/dotnet-generator.ts +344 -6
- package/generator/src/golang-generator.ts +1242 -0
- package/generator/src/index.ts +8 -1
- package/generator/src/metadata-generator.ts +29 -12
- package/generator/src/python-generator.ts +314 -4
- package/package.json +12 -10
- package/dist/cleanup.d.ts +0 -2
- package/dist/cleanup.js +0 -120
- package/dist/cli.js +0 -64
- package/dist/migrate.d.ts +0 -11
- package/dist/migrate.js +0 -301
- package/dist/pull.d.ts +0 -2
- package/dist/pull.js +0 -339
- package/dist/push.d.ts +0 -2
- package/dist/push.js +0 -281
- package/dist/seed.d.ts +0 -2
- package/dist/seed.js +0 -401
package/dist/an5Orm.js
CHANGED
|
@@ -1,205 +1,224 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.an5Orm = exports.An5ORM = void 0;
|
|
7
|
-
const
|
|
8
|
-
const an5_adapters_1 = require("@an5/adapters");
|
|
3
|
+
exports.an5Orm = exports.An5ORM = exports.ViewClient = exports.TableClient = void 0;
|
|
4
|
+
const adapters_1 = require("@an5/adapters");
|
|
9
5
|
const crypto_1 = require("crypto");
|
|
10
|
-
const logger_1 = require("
|
|
11
|
-
const
|
|
12
|
-
const
|
|
6
|
+
const logger_1 = require("./logger");
|
|
7
|
+
const errors_1 = require("./errors");
|
|
8
|
+
const metadata_1 = require("./metadata");
|
|
9
|
+
const sql_utils_1 = require("./sql-utils");
|
|
10
|
+
function aggSelect(fn, key) {
|
|
11
|
+
return `${fn}(${(0, sql_utils_1.quoteIdentifier)(key)}) as ${aggAlias(fn.toLowerCase(), key)}`;
|
|
12
|
+
}
|
|
13
|
+
function aggAlias(fn, key) {
|
|
14
|
+
return (0, sql_utils_1.sanitizeParamName)(`${fn}_${key}`);
|
|
15
|
+
}
|
|
13
16
|
let adapter = null;
|
|
14
17
|
async function getAdapter() {
|
|
15
18
|
if (!adapter) {
|
|
16
|
-
adapter = new
|
|
19
|
+
adapter = new adapters_1.An5Adapter({ connectionString: process.env.DATABASE_URL });
|
|
17
20
|
await adapter.$connect();
|
|
18
21
|
}
|
|
19
22
|
return adapter;
|
|
20
23
|
}
|
|
21
|
-
|
|
24
|
+
const execQuery = Object.assign(async (queryText, params) => {
|
|
22
25
|
const a = await getAdapter();
|
|
23
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;
|
|
24
63
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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 = {};
|
|
30
85
|
for (const [key, value] of Object.entries(where)) {
|
|
31
|
-
if (
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
!value.contains &&
|
|
37
|
-
!value.not &&
|
|
38
|
-
!value.gte &&
|
|
39
|
-
!value.lte &&
|
|
40
|
-
!value.gt &&
|
|
41
|
-
!value.lt) {
|
|
42
|
-
Object.assign(cleanWhere, value);
|
|
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;
|
|
43
91
|
}
|
|
44
|
-
|
|
45
|
-
|
|
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;
|
|
46
101
|
}
|
|
47
102
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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;
|
|
54
129
|
}
|
|
55
130
|
}
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
131
|
+
if (val.set !== undefined) {
|
|
132
|
+
const paramName = `${safeKey}_set`;
|
|
133
|
+
sets.push(`${col} = @${paramName}`);
|
|
134
|
+
params[paramName] = val.set;
|
|
135
|
+
return;
|
|
62
136
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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 ")})`);
|
|
85
176
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (relation.relationType === "one") {
|
|
90
|
-
if (op === "none") {
|
|
91
|
-
conditions.push(`${relation.foreignKey} NOT IN (SELECT ${relation.localKey} FROM ${relationTable} WITH (NOLOCK) WHERE ${subWhereSql})`);
|
|
92
|
-
}
|
|
93
|
-
else {
|
|
94
|
-
conditions.push(`${relation.foreignKey} IN (SELECT ${relation.localKey} FROM ${relationTable} WITH (NOLOCK) WHERE ${subWhereSql})`);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
else {
|
|
98
|
-
if (op === "none") {
|
|
99
|
-
conditions.push(`${relation.localKey} NOT IN (SELECT ${relation.foreignKey} FROM ${relationTable} WITH (NOLOCK) WHERE ${subWhereSql})`);
|
|
100
|
-
}
|
|
101
|
-
else {
|
|
102
|
-
conditions.push(`${relation.localKey} IN (SELECT ${relation.foreignKey} FROM ${relationTable} WITH (NOLOCK) WHERE ${subWhereSql})`);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
177
|
+
else {
|
|
178
|
+
conditions.push(`${expr} <> @${p}`);
|
|
179
|
+
params[p] = value;
|
|
105
180
|
}
|
|
106
181
|
}
|
|
107
|
-
else {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const ops = Object.entries(value);
|
|
111
|
-
for (const [op, opVal] of ops) {
|
|
112
|
-
if (op === "in" && Array.isArray(opVal)) {
|
|
113
|
-
if (opVal.length === 0) {
|
|
114
|
-
conditions.push("1 = 0");
|
|
115
|
-
}
|
|
116
|
-
else {
|
|
117
|
-
const inParams = [];
|
|
118
|
-
opVal.forEach((item, idx) => {
|
|
119
|
-
const inParamName = `${paramName}_in_${idx}`;
|
|
120
|
-
inParams.push(`@${inParamName}`);
|
|
121
|
-
params[inParamName] = item;
|
|
122
|
-
});
|
|
123
|
-
conditions.push(`${key} IN (${inParams.join(", ")})`);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
else if (op === "notIn" && Array.isArray(opVal)) {
|
|
127
|
-
if (opVal.length === 0) {
|
|
128
|
-
// NOT IN empty list is always true, but let's be safe
|
|
129
|
-
conditions.push("1 = 1");
|
|
130
|
-
}
|
|
131
|
-
else {
|
|
132
|
-
const inParams = [];
|
|
133
|
-
opVal.forEach((item, idx) => {
|
|
134
|
-
const inParamName = `${paramName}_notin_${idx}`;
|
|
135
|
-
inParams.push(`@${inParamName}`);
|
|
136
|
-
params[inParamName] = item;
|
|
137
|
-
});
|
|
138
|
-
conditions.push(`${key} NOT IN (${inParams.join(", ")})`);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
else if (op === "contains") {
|
|
142
|
-
conditions.push(`${key} LIKE @${paramName}_contains`);
|
|
143
|
-
params[`${paramName}_contains`] = `%${opVal}%`;
|
|
144
|
-
}
|
|
145
|
-
else if (op === "startsWith") {
|
|
146
|
-
conditions.push(`${key} LIKE @${paramName}_startsWith`);
|
|
147
|
-
params[`${paramName}_startsWith`] = `${opVal}%`;
|
|
148
|
-
}
|
|
149
|
-
else if (op === "endsWith") {
|
|
150
|
-
conditions.push(`${key} LIKE @${paramName}_endsWith`);
|
|
151
|
-
params[`${paramName}_endsWith`] = `%${opVal}`;
|
|
152
|
-
}
|
|
153
|
-
else if (op === "not") {
|
|
154
|
-
conditions.push(`${key} <> @${paramName}_not`);
|
|
155
|
-
params[`${paramName}_not`] = opVal;
|
|
156
|
-
}
|
|
157
|
-
else if (op === "gte") {
|
|
158
|
-
conditions.push(`${key} >= @${paramName}_gte`);
|
|
159
|
-
params[`${paramName}_gte`] = opVal;
|
|
160
|
-
}
|
|
161
|
-
else if (op === "lte") {
|
|
162
|
-
conditions.push(`${key} <= @${paramName}_lte`);
|
|
163
|
-
params[`${paramName}_lte`] = opVal;
|
|
164
|
-
}
|
|
165
|
-
else if (op === "gt") {
|
|
166
|
-
conditions.push(`${key} > @${paramName}_gt`);
|
|
167
|
-
params[`${paramName}_gt`] = opVal;
|
|
168
|
-
}
|
|
169
|
-
else if (op === "lt") {
|
|
170
|
-
conditions.push(`${key} < @${paramName}_lt`);
|
|
171
|
-
params[`${paramName}_lt`] = opVal;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
182
|
+
else if ((op === "in" || op === "notIn") && Array.isArray(value)) {
|
|
183
|
+
if (value.length === 0) {
|
|
184
|
+
conditions.push(op === "in" ? "1 = 0" : "1 = 1");
|
|
174
185
|
}
|
|
175
186
|
else {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
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(", ")})`);
|
|
183
193
|
}
|
|
184
194
|
}
|
|
185
195
|
}
|
|
196
|
+
return;
|
|
186
197
|
}
|
|
187
|
-
|
|
198
|
+
const p = (0, sql_utils_1.sanitizeParamName)(`having_${paramBase}`);
|
|
199
|
+
conditions.push(`${expr} = @${p}`);
|
|
200
|
+
params[p] = filter;
|
|
188
201
|
}
|
|
189
|
-
function
|
|
190
|
-
if (!
|
|
202
|
+
function buildHavingSql(having, params) {
|
|
203
|
+
if (!having || typeof having !== "object")
|
|
191
204
|
return "";
|
|
192
|
-
const
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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);
|
|
200
219
|
}
|
|
201
220
|
}
|
|
202
|
-
return
|
|
221
|
+
return conditions.length > 0 ? conditions.join(" AND ") : "";
|
|
203
222
|
}
|
|
204
223
|
function projectFields(row, select) {
|
|
205
224
|
if (!row || !select)
|
|
@@ -215,11 +234,57 @@ function projectFields(row, select) {
|
|
|
215
234
|
}
|
|
216
235
|
return projected;
|
|
217
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
|
+
}
|
|
218
283
|
// Helper to batch-query and resolve relationships
|
|
219
|
-
async function resolveIncludes(modelName, rows, include, executor) {
|
|
284
|
+
async function resolveIncludes(modelName, rows, include, executor, metadata) {
|
|
220
285
|
if (!rows || rows.length === 0 || !include)
|
|
221
286
|
return;
|
|
222
|
-
const modelRelations =
|
|
287
|
+
const modelRelations = metadata.relationMap[modelName];
|
|
223
288
|
if (!modelRelations)
|
|
224
289
|
return;
|
|
225
290
|
for (const [key, value] of Object.entries(include)) {
|
|
@@ -227,22 +292,24 @@ async function resolveIncludes(modelName, rows, include, executor) {
|
|
|
227
292
|
continue;
|
|
228
293
|
const relation = modelRelations[key];
|
|
229
294
|
if (!relation) {
|
|
230
|
-
if (key === "_count" && value
|
|
231
|
-
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]);
|
|
232
299
|
for (const countField of countFields) {
|
|
233
300
|
const rel = modelRelations[countField];
|
|
234
301
|
if (rel) {
|
|
235
|
-
const relTable =
|
|
302
|
+
const relTable = (0, sql_utils_1.quoteTableIdentifier)(metadata.modelToTable[rel.modelName] || rel.modelName);
|
|
236
303
|
const localKeys = rows.map(r => r[rel.localKey]).filter(Boolean);
|
|
237
304
|
if (localKeys.length === 0) {
|
|
238
305
|
rows.forEach(r => { r._count = { ...r._count, [countField]: 0 }; });
|
|
239
306
|
continue;
|
|
240
307
|
}
|
|
241
308
|
const sqlText = `
|
|
242
|
-
SELECT ${rel.foreignKey} as parentId, COUNT(*) as count
|
|
309
|
+
SELECT ${(0, sql_utils_1.quoteIdentifier)(rel.foreignKey)} as parentId, COUNT(*) as count
|
|
243
310
|
FROM ${relTable} WITH (NOLOCK)
|
|
244
|
-
WHERE ${rel.foreignKey} IN (${localKeys.map((_, i) => `@lk_${i}`).join(", ")})
|
|
245
|
-
GROUP BY ${rel.foreignKey}
|
|
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)}
|
|
246
313
|
`;
|
|
247
314
|
const countParams = {};
|
|
248
315
|
localKeys.forEach((lk, i) => { countParams[`lk_${i}`] = lk; });
|
|
@@ -258,7 +325,7 @@ async function resolveIncludes(modelName, rows, include, executor) {
|
|
|
258
325
|
}
|
|
259
326
|
continue;
|
|
260
327
|
}
|
|
261
|
-
const relTable =
|
|
328
|
+
const relTable = (0, sql_utils_1.quoteTableIdentifier)(metadata.modelToTable[relation.modelName] || relation.modelName);
|
|
262
329
|
const isMany = relation.relationType === "many";
|
|
263
330
|
const matchKey = relation.relationType === "one" ? relation.foreignKey : relation.localKey;
|
|
264
331
|
const searchKey = relation.relationType === "one" ? relation.localKey : relation.foreignKey;
|
|
@@ -269,43 +336,77 @@ async function resolveIncludes(modelName, rows, include, executor) {
|
|
|
269
336
|
}
|
|
270
337
|
const uniqueKeys = Array.from(new Set(keys));
|
|
271
338
|
let relCols = "*";
|
|
339
|
+
const nestedInclude = value && typeof value === "object"
|
|
340
|
+
? mergeIncludes(value.include, collectRelationSelections(relation.modelName, value.select, metadata))
|
|
341
|
+
: undefined;
|
|
272
342
|
if (value && typeof value === "object" && value.select) {
|
|
273
343
|
const subSelect = value.select;
|
|
274
|
-
const subRelations =
|
|
344
|
+
const subRelations = metadata.relationMap[relation.modelName] || {};
|
|
275
345
|
const selectedSubCols = Object.keys(subSelect)
|
|
276
346
|
.filter(k => subSelect[k] && !subRelations[k])
|
|
277
|
-
.map(k =>
|
|
347
|
+
.map(k => (0, sql_utils_1.quoteIdentifier)(k));
|
|
278
348
|
if (selectedSubCols.length > 0) {
|
|
279
|
-
|
|
280
|
-
|
|
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
|
+
}
|
|
281
354
|
}
|
|
282
355
|
relCols = selectedSubCols.join(", ");
|
|
283
356
|
}
|
|
284
357
|
}
|
|
285
|
-
|
|
358
|
+
const filters = [`${(0, sql_utils_1.quoteIdentifier)(searchKey)} IN (${uniqueKeys.map((_, i) => `@k_${i}`).join(", ")})`];
|
|
359
|
+
let sqlText = "";
|
|
286
360
|
const subParams = {};
|
|
287
361
|
uniqueKeys.forEach((k, i) => { subParams[`k_${i}`] = k; });
|
|
362
|
+
let subArgs = undefined;
|
|
288
363
|
if (value && typeof value === "object") {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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);
|
|
292
371
|
}
|
|
293
372
|
}
|
|
294
|
-
const
|
|
295
|
-
if (
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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}`;
|
|
299
389
|
}
|
|
300
|
-
|
|
301
|
-
|
|
390
|
+
else {
|
|
391
|
+
sqlText = `SELECT ${relCols} FROM ${relTable} WITH (NOLOCK) WHERE ${filters.join(" AND ")}`;
|
|
392
|
+
if (subArgs?.orderBy) {
|
|
393
|
+
sqlText += (0, sql_utils_1.buildOrderBy)(subArgs.orderBy);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
const relatedRows = await executor(sqlText, subParams);
|
|
397
|
+
relatedRows.forEach((r) => { delete r.__an5_rn; });
|
|
398
|
+
if (nestedInclude) {
|
|
399
|
+
await resolveIncludes(relation.modelName, relatedRows, nestedInclude, executor, metadata);
|
|
302
400
|
}
|
|
401
|
+
const outputRows = value && typeof value === "object" && value.select
|
|
402
|
+
? relatedRows.map((r) => projectFields(r, value.select))
|
|
403
|
+
: relatedRows;
|
|
303
404
|
const groupMap = new Map();
|
|
304
|
-
relatedRows.forEach((r) => {
|
|
405
|
+
relatedRows.forEach((r, idx) => {
|
|
305
406
|
const k = r[searchKey];
|
|
306
407
|
if (!groupMap.has(k))
|
|
307
408
|
groupMap.set(k, []);
|
|
308
|
-
groupMap.get(k).push(
|
|
409
|
+
groupMap.get(k).push(outputRows[idx]);
|
|
309
410
|
});
|
|
310
411
|
rows.forEach(r => {
|
|
311
412
|
const k = r[matchKey];
|
|
@@ -323,56 +424,69 @@ async function resolveIncludes(modelName, rows, include, executor) {
|
|
|
323
424
|
class TableClient {
|
|
324
425
|
constructor(modelName, tableName, executor, orm) {
|
|
325
426
|
this.modelName = modelName;
|
|
326
|
-
this.tableName = tableName;
|
|
327
427
|
this.executor = executor;
|
|
328
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));
|
|
329
436
|
}
|
|
330
437
|
async findMany(args) {
|
|
331
438
|
return this.orm._executeMiddleware({ model: this.modelName, action: 'findMany', args }, async (params) => {
|
|
332
439
|
const { args: finalArgs } = params;
|
|
333
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);
|
|
334
443
|
let cols = "*";
|
|
335
444
|
if (finalArgs?.select) {
|
|
336
|
-
const modelRelations =
|
|
337
|
-
const
|
|
338
|
-
.filter(k => finalArgs.select[k] && !modelRelations[k])
|
|
339
|
-
|
|
445
|
+
const modelRelations = this.orm.metadata.relationMap[this.modelName] || {};
|
|
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));
|
|
340
454
|
if (selectedCols.length > 0) {
|
|
341
455
|
cols = selectedCols.join(", ");
|
|
342
456
|
}
|
|
343
457
|
}
|
|
344
458
|
let sqlText = "SELECT";
|
|
345
459
|
if (finalArgs?.take && !hasSkip) {
|
|
346
|
-
sqlText += ` TOP (${finalArgs.take})`;
|
|
460
|
+
sqlText += ` TOP (${(0, sql_utils_1.toNonNegativeInt)(finalArgs.take, 1)})`;
|
|
347
461
|
}
|
|
348
462
|
sqlText += ` ${cols} FROM ${this.tableName} WITH (NOLOCK)`;
|
|
349
463
|
const p = {};
|
|
350
|
-
const whereSql = parseWhere(this.modelName, finalArgs?.where, p);
|
|
464
|
+
const whereSql = this.orm.parseWhere(this.modelName, finalArgs?.where, p);
|
|
351
465
|
if (whereSql) {
|
|
352
466
|
sqlText += ` WHERE ${whereSql}`;
|
|
353
467
|
}
|
|
354
468
|
if (finalArgs?.orderBy) {
|
|
355
|
-
sqlText += buildOrderBy(finalArgs.orderBy);
|
|
469
|
+
sqlText += (0, sql_utils_1.buildOrderBy)(finalArgs.orderBy);
|
|
356
470
|
}
|
|
357
471
|
else if (hasSkip) {
|
|
358
472
|
// OFFSET requires an ORDER BY clause in SQL Server
|
|
359
473
|
sqlText += " ORDER BY (SELECT NULL)";
|
|
360
474
|
}
|
|
361
475
|
if (hasSkip) {
|
|
362
|
-
sqlText += ` OFFSET ${finalArgs.skip} ROWS`;
|
|
476
|
+
sqlText += ` OFFSET ${(0, sql_utils_1.toNonNegativeInt)(finalArgs.skip)} ROWS`;
|
|
363
477
|
if (finalArgs?.take) {
|
|
364
|
-
sqlText += ` FETCH NEXT ${finalArgs.take} ROWS ONLY`;
|
|
478
|
+
sqlText += ` FETCH NEXT ${(0, sql_utils_1.toNonNegativeInt)(finalArgs.take, 1)} ROWS ONLY`;
|
|
365
479
|
}
|
|
366
480
|
}
|
|
367
481
|
const rows = await this.executor(sqlText, p);
|
|
482
|
+
if (include) {
|
|
483
|
+
await resolveIncludes(this.modelName, rows, include, this.executor, this.orm.metadata);
|
|
484
|
+
}
|
|
368
485
|
if (finalArgs?.select) {
|
|
369
486
|
rows.forEach((r, idx) => {
|
|
370
487
|
rows[idx] = projectFields(r, finalArgs.select);
|
|
371
488
|
});
|
|
372
489
|
}
|
|
373
|
-
if (finalArgs?.include) {
|
|
374
|
-
await resolveIncludes(this.modelName, rows, finalArgs.include, this.executor);
|
|
375
|
-
}
|
|
376
490
|
return rows;
|
|
377
491
|
});
|
|
378
492
|
}
|
|
@@ -392,7 +506,7 @@ class TableClient {
|
|
|
392
506
|
const { args: finalArgs } = params;
|
|
393
507
|
let sqlText = `SELECT COUNT(*) as count FROM ${this.tableName} WITH (NOLOCK)`;
|
|
394
508
|
const p = {};
|
|
395
|
-
const whereSql = parseWhere(this.modelName, finalArgs?.where, p);
|
|
509
|
+
const whereSql = this.orm.parseWhere(this.modelName, finalArgs?.where, p);
|
|
396
510
|
if (whereSql) {
|
|
397
511
|
sqlText += ` WHERE ${whereSql}`;
|
|
398
512
|
}
|
|
@@ -400,8 +514,14 @@ class TableClient {
|
|
|
400
514
|
return result[0]?.count || 0;
|
|
401
515
|
});
|
|
402
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
|
+
}
|
|
403
523
|
async handleNestedWrites(data, parentId) {
|
|
404
|
-
const modelRelations =
|
|
524
|
+
const modelRelations = this.orm.metadata.relationMap[this.modelName] || {};
|
|
405
525
|
for (const [key, value] of Object.entries(data)) {
|
|
406
526
|
const relation = modelRelations[key];
|
|
407
527
|
if (!relation || !value || typeof value !== "object" || (value instanceof Date))
|
|
@@ -410,32 +530,72 @@ class TableClient {
|
|
|
410
530
|
if (!relTableClient)
|
|
411
531
|
continue;
|
|
412
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
|
+
}
|
|
413
547
|
// Handle deleteMany (an5Orm-style)
|
|
414
|
-
if (nestedOps
|
|
548
|
+
if (hasOwn(nestedOps, "deleteMany")) {
|
|
415
549
|
const deleteWhere = Array.isArray(nestedOps.deleteMany) ? { OR: nestedOps.deleteMany } : nestedOps.deleteMany;
|
|
416
550
|
// Scope deletion to parent
|
|
417
|
-
const scopedWhere =
|
|
551
|
+
const scopedWhere = this.scopedRelationWhere(relation, parentId, deleteWhere);
|
|
418
552
|
await relTableClient.deleteMany({ where: scopedWhere });
|
|
419
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
|
+
}
|
|
420
584
|
// Handle create
|
|
421
|
-
if (nestedOps
|
|
422
|
-
const
|
|
423
|
-
for (const item of createItems) {
|
|
585
|
+
if (hasOwn(nestedOps, "create")) {
|
|
586
|
+
for (const item of asArray(nestedOps.create)) {
|
|
424
587
|
// Inject parent ID
|
|
425
588
|
const itemData = { ...item, [relation.foreignKey]: parentId };
|
|
426
589
|
await relTableClient.create({ data: itemData });
|
|
427
590
|
}
|
|
428
591
|
}
|
|
429
592
|
// Handle connect
|
|
430
|
-
if (nestedOps
|
|
431
|
-
const
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
data: { [relation.foreignKey]: parentId }
|
|
437
|
-
});
|
|
438
|
-
}
|
|
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
|
+
});
|
|
439
599
|
}
|
|
440
600
|
}
|
|
441
601
|
}
|
|
@@ -447,35 +607,39 @@ class TableClient {
|
|
|
447
607
|
const data = { ...finalArgs.data };
|
|
448
608
|
// Extract nested writes
|
|
449
609
|
const nestedData = {};
|
|
450
|
-
const modelRelations =
|
|
610
|
+
const modelRelations = this.orm.metadata.relationMap[this.modelName] || {};
|
|
451
611
|
for (const key of Object.keys(data)) {
|
|
452
612
|
if (modelRelations[key]) {
|
|
453
613
|
nestedData[key] = data[key];
|
|
454
614
|
delete data[key];
|
|
455
615
|
}
|
|
456
616
|
}
|
|
457
|
-
if (!data.id &&
|
|
617
|
+
if (!data.id && this.orm.metadata.modelFields[this.modelName]?.id?.ts === "string") {
|
|
458
618
|
data.id = (0, crypto_1.randomUUID)();
|
|
459
619
|
}
|
|
460
620
|
const now = new Date();
|
|
461
|
-
if (!data.createdAt &&
|
|
621
|
+
if (!data.createdAt && this.orm.metadata.modelFields[this.modelName]?.createdAt)
|
|
462
622
|
data.createdAt = now;
|
|
463
|
-
if (!data.updatedAt &&
|
|
623
|
+
if (!data.updatedAt && this.orm.metadata.modelFields[this.modelName]?.updatedAt)
|
|
464
624
|
data.updatedAt = now;
|
|
465
625
|
// Handle one-relation connect where we hold the FK
|
|
466
626
|
for (const [key, value] of Object.entries(nestedData)) {
|
|
467
627
|
const rel = modelRelations[key];
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
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];
|
|
471
632
|
data[rel.foreignKey] = targetId;
|
|
472
633
|
}
|
|
473
634
|
}
|
|
474
635
|
const keys = Object.keys(data);
|
|
475
|
-
const columns = keys.map(k =>
|
|
476
|
-
const
|
|
636
|
+
const columns = keys.map(k => (0, sql_utils_1.quoteIdentifier)(k)).join(", ");
|
|
637
|
+
const safeKeys = keys.map(k => (0, sql_utils_1.sanitizeParamName)(k));
|
|
638
|
+
const placeholders = safeKeys.map(k => `@${k}`).join(", ");
|
|
639
|
+
const safeData = {};
|
|
640
|
+
keys.forEach((k, i) => { safeData[safeKeys[i]] = data[k]; });
|
|
477
641
|
const sqlText = `INSERT INTO ${this.tableName} (${columns}) OUTPUT inserted.* VALUES (${placeholders})`;
|
|
478
|
-
const rows = await this.executor(sqlText,
|
|
642
|
+
const rows = await this.executor(sqlText, safeData);
|
|
479
643
|
const createdRow = rows[0];
|
|
480
644
|
if (!createdRow) {
|
|
481
645
|
throw new Error("Failed to insert record and retrieve output");
|
|
@@ -500,19 +664,19 @@ class TableClient {
|
|
|
500
664
|
msg.includes('unique') ||
|
|
501
665
|
errNumber === 2627 ||
|
|
502
666
|
errNumber === 2601) {
|
|
503
|
-
throw new
|
|
667
|
+
throw new errors_1.An5ClientKnownRequestError("Unique constraint failed", {
|
|
504
668
|
code: "P2002",
|
|
505
669
|
clientVersion: "mock",
|
|
506
670
|
});
|
|
507
671
|
}
|
|
508
672
|
if (msg.includes('foreign key') || errNumber === 547) {
|
|
509
|
-
throw new
|
|
673
|
+
throw new errors_1.An5ClientKnownRequestError("Foreign key constraint failed", {
|
|
510
674
|
code: "P2003",
|
|
511
675
|
clientVersion: "mock",
|
|
512
676
|
});
|
|
513
677
|
}
|
|
514
678
|
if (msg.includes('not found') || errNumber === 404) {
|
|
515
|
-
throw new
|
|
679
|
+
throw new errors_1.An5ClientKnownRequestError("Record not found", {
|
|
516
680
|
code: "P2025",
|
|
517
681
|
clientVersion: "mock",
|
|
518
682
|
});
|
|
@@ -525,12 +689,12 @@ class TableClient {
|
|
|
525
689
|
return this.orm._executeMiddleware({ model: this.modelName, action: 'update', args }, async (params) => {
|
|
526
690
|
const { args: finalArgs } = params;
|
|
527
691
|
const data = { ...finalArgs.data };
|
|
528
|
-
if (
|
|
692
|
+
if (this.orm.metadata.modelFields[this.modelName]?.updatedAt) {
|
|
529
693
|
data.updatedAt = new Date();
|
|
530
694
|
}
|
|
531
695
|
// Extract nested writes
|
|
532
696
|
const nestedData = {};
|
|
533
|
-
const modelRelations =
|
|
697
|
+
const modelRelations = this.orm.metadata.relationMap[this.modelName] || {};
|
|
534
698
|
for (const key of Object.keys(data)) {
|
|
535
699
|
if (modelRelations[key]) {
|
|
536
700
|
nestedData[key] = data[key];
|
|
@@ -541,46 +705,35 @@ class TableClient {
|
|
|
541
705
|
// Handle one-relation connect where we hold the FK
|
|
542
706
|
for (const [key, value] of Object.entries(nestedData)) {
|
|
543
707
|
const rel = modelRelations[key];
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
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
|
+
}
|
|
548
718
|
}
|
|
549
719
|
}
|
|
550
720
|
const sets = [];
|
|
551
721
|
const p = {};
|
|
552
722
|
for (const key of Object.keys(data)) {
|
|
553
723
|
const val = data[key];
|
|
554
|
-
|
|
555
|
-
if (val.increment !== undefined) {
|
|
556
|
-
sets.push(`[${key}] = [${key}] + @${key}_inc`);
|
|
557
|
-
p[`${key}_inc`] = val.increment;
|
|
558
|
-
continue;
|
|
559
|
-
}
|
|
560
|
-
else if (val.decrement !== undefined) {
|
|
561
|
-
sets.push(`[${key}] = [${key}] - @${key}_dec`);
|
|
562
|
-
p[`${key}_dec`] = val.decrement;
|
|
563
|
-
continue;
|
|
564
|
-
}
|
|
565
|
-
else if (val.set !== undefined) {
|
|
566
|
-
sets.push(`[${key}] = @${key}_set`);
|
|
567
|
-
p[`${key}_set`] = val.set;
|
|
568
|
-
continue;
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
sets.push(`[${key}] = @${key}`);
|
|
572
|
-
p[key] = val;
|
|
724
|
+
appendUpdateSet(sets, p, key, val);
|
|
573
725
|
}
|
|
574
726
|
const whereParams = {};
|
|
575
|
-
const whereSql = parseWhere(this.modelName, finalArgs.where, whereParams, "w_");
|
|
727
|
+
const whereSql = this.orm.parseWhere(this.modelName, finalArgs.where, whereParams, "w_");
|
|
576
728
|
Object.assign(p, whereParams);
|
|
577
|
-
const sqlText = `UPDATE ${this.tableName} SET ${sets.join(", ")} WHERE ${whereSql}`;
|
|
578
|
-
await this.executor(sqlText, p);
|
|
579
|
-
// Get parent ID for nested writes
|
|
580
729
|
const existing = await this.findUnique({ where: finalArgs.where });
|
|
581
730
|
if (!existing)
|
|
582
731
|
throw new Error("Record not found to update");
|
|
583
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
|
+
}
|
|
584
737
|
// Process nested writes
|
|
585
738
|
await this.handleNestedWrites(nestedData, parentId);
|
|
586
739
|
const updated = await this.findUnique({ where: finalArgs.where, include: finalArgs.include });
|
|
@@ -594,7 +747,7 @@ class TableClient {
|
|
|
594
747
|
return this.orm._executeMiddleware({ model: this.modelName, action: 'updateMany', args }, async (params) => {
|
|
595
748
|
const { args: finalArgs } = params;
|
|
596
749
|
const data = { ...finalArgs.data };
|
|
597
|
-
if (
|
|
750
|
+
if (this.orm.metadata.modelFields[this.modelName]?.updatedAt) {
|
|
598
751
|
data.updatedAt = new Date();
|
|
599
752
|
}
|
|
600
753
|
delete data.id;
|
|
@@ -602,39 +755,19 @@ class TableClient {
|
|
|
602
755
|
const p = {};
|
|
603
756
|
for (const key of Object.keys(data)) {
|
|
604
757
|
const val = data[key];
|
|
605
|
-
if (
|
|
758
|
+
if (this.orm.metadata.relationMap[this.modelName]?.[key])
|
|
606
759
|
continue;
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
continue;
|
|
612
|
-
}
|
|
613
|
-
else if (val.decrement !== undefined) {
|
|
614
|
-
sets.push(`[${key}] = [${key}] - @${key}_dec`);
|
|
615
|
-
p[`${key}_dec`] = val.decrement;
|
|
616
|
-
continue;
|
|
617
|
-
}
|
|
618
|
-
else if (val.set !== undefined) {
|
|
619
|
-
sets.push(`[${key}] = @${key}_set`);
|
|
620
|
-
p[`${key}_set`] = val.set;
|
|
621
|
-
continue;
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
sets.push(`[${key}] = @${key}`);
|
|
625
|
-
p[key] = val;
|
|
760
|
+
appendUpdateSet(sets, p, key, val);
|
|
761
|
+
}
|
|
762
|
+
if (sets.length === 0) {
|
|
763
|
+
return { count: 0 };
|
|
626
764
|
}
|
|
627
765
|
const whereParams = {};
|
|
628
|
-
const whereSql = parseWhere(this.modelName, finalArgs.where, whereParams, "w_");
|
|
766
|
+
const whereSql = this.orm.parseWhere(this.modelName, finalArgs.where, whereParams, "w_");
|
|
629
767
|
Object.assign(p, whereParams);
|
|
630
768
|
const sqlText = `UPDATE ${this.tableName} SET ${sets.join(", ")} ${whereSql ? `WHERE ${whereSql}` : ""}`;
|
|
631
|
-
const
|
|
632
|
-
|
|
633
|
-
for (const [key, value] of Object.entries(p)) {
|
|
634
|
-
request.input(key, value);
|
|
635
|
-
}
|
|
636
|
-
const result = await request.query(sqlText);
|
|
637
|
-
return { count: result.rowsAffected?.[0] || 0 };
|
|
769
|
+
const count = await this.executeRaw(sqlText, p);
|
|
770
|
+
return { count };
|
|
638
771
|
});
|
|
639
772
|
}
|
|
640
773
|
async delete(args) {
|
|
@@ -644,7 +777,7 @@ class TableClient {
|
|
|
644
777
|
if (!existing)
|
|
645
778
|
throw new Error("Record not found to delete");
|
|
646
779
|
const p = {};
|
|
647
|
-
const whereSql = parseWhere(this.modelName, finalArgs.where, p);
|
|
780
|
+
const whereSql = this.orm.parseWhere(this.modelName, finalArgs.where, p);
|
|
648
781
|
const sqlText = `DELETE FROM ${this.tableName} WHERE ${whereSql}`;
|
|
649
782
|
await this.executor(sqlText, p);
|
|
650
783
|
return existing;
|
|
@@ -654,39 +787,37 @@ class TableClient {
|
|
|
654
787
|
return this.orm._executeMiddleware({ model: this.modelName, action: 'deleteMany', args }, async (params) => {
|
|
655
788
|
const { args: finalArgs } = params;
|
|
656
789
|
const p = {};
|
|
657
|
-
const whereSql = parseWhere(this.modelName, finalArgs?.where, p);
|
|
790
|
+
const whereSql = this.orm.parseWhere(this.modelName, finalArgs?.where, p);
|
|
658
791
|
const sqlText = `DELETE FROM ${this.tableName} ${whereSql ? `WHERE ${whereSql}` : ""}`;
|
|
659
|
-
const
|
|
660
|
-
|
|
661
|
-
for (const [key, value] of Object.entries(p)) {
|
|
662
|
-
request.input(key, value);
|
|
663
|
-
}
|
|
664
|
-
const result = await request.query(sqlText);
|
|
665
|
-
return { count: result.rowsAffected?.[0] || 0 };
|
|
792
|
+
const count = await this.executeRaw(sqlText, p);
|
|
793
|
+
return { count };
|
|
666
794
|
});
|
|
667
795
|
}
|
|
668
796
|
async vectorSearch(args) {
|
|
669
797
|
return this.orm._executeMiddleware({ model: this.modelName, action: 'vectorSearch', args }, async (params) => {
|
|
670
798
|
const { args: finalArgs } = params;
|
|
671
799
|
const field = finalArgs.vectorField || "embedding";
|
|
672
|
-
const
|
|
673
|
-
const
|
|
674
|
-
const
|
|
675
|
-
const
|
|
800
|
+
const METRICS = ["cosine", "euclidean", "dot"];
|
|
801
|
+
const ELEMENT_TYPES = ["float32", "float16", "uint8"];
|
|
802
|
+
const metric = METRICS.includes(finalArgs.distanceMetric) ? finalArgs.distanceMetric : "cosine";
|
|
803
|
+
const elementType = ELEMENT_TYPES.includes(finalArgs.vectorElementType) ? finalArgs.vectorElementType : "float32";
|
|
804
|
+
const take = (0, sql_utils_1.toNonNegativeInt)(finalArgs.take, 10);
|
|
805
|
+
const dim = Number(finalArgs.vector?.length) || 0;
|
|
676
806
|
const vectorJson = JSON.stringify(finalArgs.vector);
|
|
807
|
+
const col = (0, sql_utils_1.quoteIdentifier)(field);
|
|
677
808
|
try {
|
|
678
809
|
if (dim > 1998) {
|
|
679
810
|
throw new Error("Vector dimension exceeds SQL Server limit of 1998");
|
|
680
811
|
}
|
|
681
812
|
let sqlText = `SELECT TOP (${take}) *, `;
|
|
682
|
-
sqlText += `VECTOR_DISTANCE('${metric}', CAST(
|
|
813
|
+
sqlText += `VECTOR_DISTANCE('${metric}', CAST(${col} AS VECTOR(${dim}, ${elementType})), CAST(@query_vector AS VECTOR(${dim}, ${elementType}))) AS distance `;
|
|
683
814
|
sqlText += `FROM ${this.tableName} WITH (NOLOCK) `;
|
|
684
815
|
const p = {
|
|
685
816
|
query_vector: vectorJson
|
|
686
817
|
};
|
|
687
818
|
const whereClauses = [];
|
|
688
|
-
whereClauses.push(
|
|
689
|
-
const whereSql = parseWhere(this.modelName, finalArgs.where, p, "v_");
|
|
819
|
+
whereClauses.push(`${col} IS NOT NULL`);
|
|
820
|
+
const whereSql = this.orm.parseWhere(this.modelName, finalArgs.where, p, "v_");
|
|
690
821
|
if (whereSql) {
|
|
691
822
|
whereClauses.push(whereSql);
|
|
692
823
|
}
|
|
@@ -694,12 +825,12 @@ class TableClient {
|
|
|
694
825
|
sqlText += `ORDER BY distance ASC`;
|
|
695
826
|
const rows = await this.executor(sqlText, p);
|
|
696
827
|
if (finalArgs.include) {
|
|
697
|
-
await resolveIncludes(this.modelName, rows, finalArgs.include, this.executor);
|
|
828
|
+
await resolveIncludes(this.modelName, rows, finalArgs.include, this.executor, this.orm.metadata);
|
|
698
829
|
}
|
|
699
830
|
return rows;
|
|
700
831
|
}
|
|
701
832
|
catch (err) {
|
|
702
|
-
const msg = String(err
|
|
833
|
+
const msg = (String(err?.message || "") + " " + String(err?.originalError?.message || "")).toLowerCase();
|
|
703
834
|
// Handle specific float16 to float32 conversion error by retrying with float16
|
|
704
835
|
if (msg.includes("float16") && msg.includes("float32") && msg.includes("conversion") && !finalArgs.vectorElementType) {
|
|
705
836
|
logger_1.logger.info(`Detected float16 vector storage. Retrying vectorSearch with float16 element type.`);
|
|
@@ -707,8 +838,17 @@ class TableClient {
|
|
|
707
838
|
}
|
|
708
839
|
const isUnsupported = msg.includes("vector_distance") ||
|
|
709
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") ||
|
|
710
847
|
msg.includes("limit of 1998") ||
|
|
711
|
-
err?.number === 195
|
|
848
|
+
err?.number === 195 ||
|
|
849
|
+
err?.number === 102 ||
|
|
850
|
+
err?.number === 319 ||
|
|
851
|
+
err?.originalError?.number === 319;
|
|
712
852
|
if (!isUnsupported) {
|
|
713
853
|
throw err;
|
|
714
854
|
}
|
|
@@ -716,8 +856,8 @@ class TableClient {
|
|
|
716
856
|
let fallbackSql = `SELECT * FROM ${this.tableName} WITH (NOLOCK) `;
|
|
717
857
|
const fallbackParams = {};
|
|
718
858
|
const fallbackWhereClauses = [];
|
|
719
|
-
fallbackWhereClauses.push(
|
|
720
|
-
const fallbackWhereSql = parseWhere(this.modelName, finalArgs.where, fallbackParams, "vf_");
|
|
859
|
+
fallbackWhereClauses.push(`${col} IS NOT NULL`);
|
|
860
|
+
const fallbackWhereSql = this.orm.parseWhere(this.modelName, finalArgs.where, fallbackParams, "vf_");
|
|
721
861
|
if (fallbackWhereSql) {
|
|
722
862
|
fallbackWhereClauses.push(fallbackWhereSql);
|
|
723
863
|
}
|
|
@@ -754,7 +894,7 @@ class TableClient {
|
|
|
754
894
|
.sort((a, b) => a.distance - b.distance)
|
|
755
895
|
.slice(0, take);
|
|
756
896
|
if (finalArgs.include && results.length > 0) {
|
|
757
|
-
await resolveIncludes(this.modelName, results, finalArgs.include, this.executor);
|
|
897
|
+
await resolveIncludes(this.modelName, results, finalArgs.include, this.executor, this.orm.metadata);
|
|
758
898
|
}
|
|
759
899
|
return results;
|
|
760
900
|
}
|
|
@@ -766,42 +906,10 @@ class TableClient {
|
|
|
766
906
|
if (!finalArgs.data || finalArgs.data.length === 0)
|
|
767
907
|
return { count: 0 };
|
|
768
908
|
try {
|
|
769
|
-
const
|
|
770
|
-
const table = new mssql_1.default.Table(this.tableName);
|
|
771
|
-
const fields = an5Metadata_1.modelFields[this.modelName] || {};
|
|
909
|
+
const fields = this.orm.metadata.modelFields[this.modelName] || {};
|
|
772
910
|
const fieldNames = Object.keys(fields);
|
|
773
|
-
for (const col of fieldNames) {
|
|
774
|
-
const fieldDef = fields[col];
|
|
775
|
-
const rawType = fieldDef?.ts || 'string';
|
|
776
|
-
const sqlType = fieldDef?.sql || 'NVARCHAR(MAX)';
|
|
777
|
-
const isNullable = rawType.endsWith('?');
|
|
778
|
-
const tsType = isNullable ? rawType.slice(0, -1) : rawType;
|
|
779
|
-
// Map SQL Server type to mssql package type
|
|
780
|
-
const sqlBase = sqlType.toUpperCase().split('(')[0];
|
|
781
|
-
if (sqlBase === 'INT' || sqlBase === 'SMALLINT' || sqlBase === 'TINYINT') {
|
|
782
|
-
table.columns.add(col, mssql_1.default.Int, { nullable: isNullable });
|
|
783
|
-
}
|
|
784
|
-
else if (sqlBase === 'BIGINT') {
|
|
785
|
-
table.columns.add(col, mssql_1.default.BigInt, { nullable: isNullable });
|
|
786
|
-
}
|
|
787
|
-
else if (sqlBase === 'FLOAT' || sqlBase === 'REAL' || sqlBase === 'DECIMAL' || sqlBase === 'NUMERIC' || sqlBase === 'MONEY' || sqlBase === 'SMALLMONEY') {
|
|
788
|
-
table.columns.add(col, mssql_1.default.Float, { nullable: isNullable });
|
|
789
|
-
}
|
|
790
|
-
else if (sqlBase === 'BIT') {
|
|
791
|
-
table.columns.add(col, mssql_1.default.Bit, { nullable: isNullable });
|
|
792
|
-
}
|
|
793
|
-
else if (sqlBase === 'DATETIME' || sqlBase === 'DATETIME2' || sqlBase === 'SMALLDATETIME' || sqlBase === 'DATE' || sqlBase === 'TIME') {
|
|
794
|
-
table.columns.add(col, mssql_1.default.DateTime, { nullable: isNullable });
|
|
795
|
-
}
|
|
796
|
-
else if (sqlBase === 'UNIQUEIDENTIFIER') {
|
|
797
|
-
table.columns.add(col, mssql_1.default.UniqueIdentifier, { nullable: isNullable });
|
|
798
|
-
}
|
|
799
|
-
else {
|
|
800
|
-
table.columns.add(col, mssql_1.default.NVarChar(mssql_1.default.MAX), { nullable: isNullable });
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
911
|
const now = new Date();
|
|
804
|
-
|
|
912
|
+
const rows = finalArgs.data.map((item) => {
|
|
805
913
|
const rowData = { ...item };
|
|
806
914
|
if (!rowData.id && fields.id?.ts === "string")
|
|
807
915
|
rowData.id = (0, crypto_1.randomUUID)();
|
|
@@ -809,17 +917,24 @@ class TableClient {
|
|
|
809
917
|
rowData.createdAt = now;
|
|
810
918
|
if (!rowData.updatedAt && fields.updatedAt)
|
|
811
919
|
rowData.updatedAt = now;
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
920
|
+
return rowData;
|
|
921
|
+
});
|
|
922
|
+
const cols = fieldNames.filter(col => rows.some((r) => r[col] !== undefined));
|
|
923
|
+
if (cols.length === 0)
|
|
924
|
+
throw new Error("No insertable columns");
|
|
925
|
+
const params = {};
|
|
926
|
+
const rowPlaceholders = [];
|
|
927
|
+
rows.forEach((row, rowIdx) => {
|
|
928
|
+
const vals = cols.map(col => {
|
|
929
|
+
const p = (0, sql_utils_1.sanitizeParamName)(`r${rowIdx}_${col}`);
|
|
930
|
+
params[p] = row[col] ?? null;
|
|
931
|
+
return `@${p}`;
|
|
817
932
|
});
|
|
818
|
-
|
|
819
|
-
}
|
|
820
|
-
const
|
|
821
|
-
await
|
|
822
|
-
return { count:
|
|
933
|
+
rowPlaceholders.push(`(${vals.join(", ")})`);
|
|
934
|
+
});
|
|
935
|
+
const sqlText = `INSERT INTO ${this.tableName} (${cols.map(sql_utils_1.quoteIdentifier).join(", ")}) VALUES ${rowPlaceholders.join(", ")}`;
|
|
936
|
+
await this.executeRaw(sqlText, params);
|
|
937
|
+
return { count: rows.length };
|
|
823
938
|
}
|
|
824
939
|
catch (err) {
|
|
825
940
|
logger_1.logger.warn(`Bulk insert failed, falling back to sequential inserts: ${err.message}`);
|
|
@@ -846,26 +961,26 @@ class TableClient {
|
|
|
846
961
|
const resultObj = {};
|
|
847
962
|
if (finalArgs._sum) {
|
|
848
963
|
resultObj._sum = {};
|
|
849
|
-
for (const key of
|
|
850
|
-
selects.push(
|
|
964
|
+
for (const key of selectedAggregateFields(finalArgs._sum)) {
|
|
965
|
+
selects.push(aggSelect("SUM", key));
|
|
851
966
|
}
|
|
852
967
|
}
|
|
853
968
|
if (finalArgs._avg) {
|
|
854
969
|
resultObj._avg = {};
|
|
855
|
-
for (const key of
|
|
856
|
-
selects.push(
|
|
970
|
+
for (const key of selectedAggregateFields(finalArgs._avg)) {
|
|
971
|
+
selects.push(aggSelect("AVG", key));
|
|
857
972
|
}
|
|
858
973
|
}
|
|
859
974
|
if (finalArgs._min) {
|
|
860
975
|
resultObj._min = {};
|
|
861
|
-
for (const key of
|
|
862
|
-
selects.push(
|
|
976
|
+
for (const key of selectedAggregateFields(finalArgs._min)) {
|
|
977
|
+
selects.push(aggSelect("MIN", key));
|
|
863
978
|
}
|
|
864
979
|
}
|
|
865
980
|
if (finalArgs._max) {
|
|
866
981
|
resultObj._max = {};
|
|
867
|
-
for (const key of
|
|
868
|
-
selects.push(
|
|
982
|
+
for (const key of selectedAggregateFields(finalArgs._max)) {
|
|
983
|
+
selects.push(aggSelect("MAX", key));
|
|
869
984
|
}
|
|
870
985
|
}
|
|
871
986
|
if (finalArgs._count) {
|
|
@@ -874,8 +989,8 @@ class TableClient {
|
|
|
874
989
|
selects.push(`COUNT(*) as count_all`);
|
|
875
990
|
}
|
|
876
991
|
else {
|
|
877
|
-
for (const key of
|
|
878
|
-
selects.push(
|
|
992
|
+
for (const key of selectedAggregateFields(finalArgs._count)) {
|
|
993
|
+
selects.push(aggSelect("COUNT", key));
|
|
879
994
|
}
|
|
880
995
|
}
|
|
881
996
|
}
|
|
@@ -884,30 +999,30 @@ class TableClient {
|
|
|
884
999
|
}
|
|
885
1000
|
let sqlText = `SELECT ${selects.join(", ")} FROM ${this.tableName} WITH (NOLOCK)`;
|
|
886
1001
|
const p = {};
|
|
887
|
-
const whereSql = parseWhere(this.modelName, finalArgs?.where, p);
|
|
1002
|
+
const whereSql = this.orm.parseWhere(this.modelName, finalArgs?.where, p);
|
|
888
1003
|
if (whereSql) {
|
|
889
1004
|
sqlText += ` WHERE ${whereSql}`;
|
|
890
1005
|
}
|
|
891
1006
|
const rows = await this.executor(sqlText, p);
|
|
892
1007
|
const row = rows[0] || {};
|
|
893
1008
|
if (finalArgs._sum) {
|
|
894
|
-
for (const key of
|
|
895
|
-
resultObj._sum[key] = row[
|
|
1009
|
+
for (const key of selectedAggregateFields(finalArgs._sum)) {
|
|
1010
|
+
resultObj._sum[key] = row[aggAlias("sum", key)] !== undefined ? row[aggAlias("sum", key)] : null;
|
|
896
1011
|
}
|
|
897
1012
|
}
|
|
898
1013
|
if (finalArgs._avg) {
|
|
899
|
-
for (const key of
|
|
900
|
-
resultObj._avg[key] = row[
|
|
1014
|
+
for (const key of selectedAggregateFields(finalArgs._avg)) {
|
|
1015
|
+
resultObj._avg[key] = row[aggAlias("avg", key)] !== undefined ? row[aggAlias("avg", key)] : null;
|
|
901
1016
|
}
|
|
902
1017
|
}
|
|
903
1018
|
if (finalArgs._min) {
|
|
904
|
-
for (const key of
|
|
905
|
-
resultObj._min[key] = row[
|
|
1019
|
+
for (const key of selectedAggregateFields(finalArgs._min)) {
|
|
1020
|
+
resultObj._min[key] = row[aggAlias("min", key)] !== undefined ? row[aggAlias("min", key)] : null;
|
|
906
1021
|
}
|
|
907
1022
|
}
|
|
908
1023
|
if (finalArgs._max) {
|
|
909
|
-
for (const key of
|
|
910
|
-
resultObj._max[key] = row[
|
|
1024
|
+
for (const key of selectedAggregateFields(finalArgs._max)) {
|
|
1025
|
+
resultObj._max[key] = row[aggAlias("max", key)] !== undefined ? row[aggAlias("max", key)] : null;
|
|
911
1026
|
}
|
|
912
1027
|
}
|
|
913
1028
|
if (finalArgs._count) {
|
|
@@ -915,8 +1030,8 @@ class TableClient {
|
|
|
915
1030
|
resultObj._count._all = row[`count_all`] || 0;
|
|
916
1031
|
}
|
|
917
1032
|
else {
|
|
918
|
-
for (const key of
|
|
919
|
-
resultObj._count[key] = row[
|
|
1033
|
+
for (const key of selectedAggregateFields(finalArgs._count)) {
|
|
1034
|
+
resultObj._count[key] = row[aggAlias("count", key)] || 0;
|
|
920
1035
|
}
|
|
921
1036
|
}
|
|
922
1037
|
}
|
|
@@ -926,48 +1041,66 @@ class TableClient {
|
|
|
926
1041
|
async groupBy(args) {
|
|
927
1042
|
return this.orm._executeMiddleware({ model: this.modelName, action: 'groupBy', args }, async (params) => {
|
|
928
1043
|
const { args: finalArgs } = params;
|
|
929
|
-
const byFields = finalArgs.by
|
|
1044
|
+
const byFields = normalizeByFields(finalArgs.by);
|
|
930
1045
|
if (byFields.length === 0) {
|
|
931
1046
|
throw new Error("groupBy requires 'by' fields");
|
|
932
1047
|
}
|
|
933
|
-
const selects =
|
|
1048
|
+
const selects = byFields.map((f) => (0, sql_utils_1.quoteIdentifier)(f));
|
|
934
1049
|
if (finalArgs._count) {
|
|
935
1050
|
if (finalArgs._count === true || finalArgs._count._all) {
|
|
936
1051
|
selects.push(`COUNT(*) as count_all`);
|
|
937
1052
|
}
|
|
938
1053
|
else {
|
|
939
|
-
for (const key of
|
|
940
|
-
selects.push(
|
|
1054
|
+
for (const key of selectedAggregateFields(finalArgs._count)) {
|
|
1055
|
+
selects.push(aggSelect("COUNT", key));
|
|
941
1056
|
}
|
|
942
1057
|
}
|
|
943
1058
|
}
|
|
944
1059
|
if (finalArgs._sum) {
|
|
945
|
-
for (const key of
|
|
946
|
-
selects.push(
|
|
1060
|
+
for (const key of selectedAggregateFields(finalArgs._sum)) {
|
|
1061
|
+
selects.push(aggSelect("SUM", key));
|
|
947
1062
|
}
|
|
948
1063
|
}
|
|
949
1064
|
if (finalArgs._avg) {
|
|
950
|
-
for (const key of
|
|
951
|
-
selects.push(
|
|
1065
|
+
for (const key of selectedAggregateFields(finalArgs._avg)) {
|
|
1066
|
+
selects.push(aggSelect("AVG", key));
|
|
952
1067
|
}
|
|
953
1068
|
}
|
|
954
1069
|
if (finalArgs._min) {
|
|
955
|
-
for (const key of
|
|
956
|
-
selects.push(
|
|
1070
|
+
for (const key of selectedAggregateFields(finalArgs._min)) {
|
|
1071
|
+
selects.push(aggSelect("MIN", key));
|
|
957
1072
|
}
|
|
958
1073
|
}
|
|
959
1074
|
if (finalArgs._max) {
|
|
960
|
-
for (const key of
|
|
961
|
-
selects.push(
|
|
1075
|
+
for (const key of selectedAggregateFields(finalArgs._max)) {
|
|
1076
|
+
selects.push(aggSelect("MAX", key));
|
|
962
1077
|
}
|
|
963
1078
|
}
|
|
964
1079
|
let sqlText = `SELECT ${selects.join(", ")} FROM ${this.tableName} WITH (NOLOCK)`;
|
|
965
1080
|
const p = {};
|
|
966
|
-
const whereSql = parseWhere(this.modelName, finalArgs?.where, p);
|
|
1081
|
+
const whereSql = this.orm.parseWhere(this.modelName, finalArgs?.where, p);
|
|
967
1082
|
if (whereSql) {
|
|
968
1083
|
sqlText += ` WHERE ${whereSql}`;
|
|
969
1084
|
}
|
|
970
|
-
sqlText += ` GROUP BY ${byFields.join(", ")}`;
|
|
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
|
+
}
|
|
971
1104
|
const rows = await this.executor(sqlText, p);
|
|
972
1105
|
return rows.map((row) => {
|
|
973
1106
|
const item = {};
|
|
@@ -980,39 +1113,46 @@ class TableClient {
|
|
|
980
1113
|
item._count._all = row[`count_all`] || 0;
|
|
981
1114
|
}
|
|
982
1115
|
else {
|
|
983
|
-
for (const key of
|
|
984
|
-
item._count[key] = row[
|
|
1116
|
+
for (const key of selectedAggregateFields(finalArgs._count)) {
|
|
1117
|
+
item._count[key] = row[aggAlias("count", key)] || 0;
|
|
985
1118
|
}
|
|
986
1119
|
}
|
|
987
1120
|
}
|
|
988
1121
|
if (finalArgs._sum) {
|
|
989
1122
|
item._sum = {};
|
|
990
|
-
for (const key of
|
|
991
|
-
item._sum[key] = row[
|
|
1123
|
+
for (const key of selectedAggregateFields(finalArgs._sum)) {
|
|
1124
|
+
item._sum[key] = row[aggAlias("sum", key)] !== undefined ? row[aggAlias("sum", key)] : null;
|
|
992
1125
|
}
|
|
993
1126
|
}
|
|
994
1127
|
if (finalArgs._avg) {
|
|
995
1128
|
item._avg = {};
|
|
996
|
-
for (const key of
|
|
997
|
-
item._avg[key] = row[
|
|
1129
|
+
for (const key of selectedAggregateFields(finalArgs._avg)) {
|
|
1130
|
+
item._avg[key] = row[aggAlias("avg", key)] !== undefined ? row[aggAlias("avg", key)] : null;
|
|
998
1131
|
}
|
|
999
1132
|
}
|
|
1000
1133
|
if (finalArgs._min) {
|
|
1001
1134
|
item._min = {};
|
|
1002
|
-
for (const key of
|
|
1003
|
-
item._min[key] = row[
|
|
1135
|
+
for (const key of selectedAggregateFields(finalArgs._min)) {
|
|
1136
|
+
item._min[key] = row[aggAlias("min", key)] !== undefined ? row[aggAlias("min", key)] : null;
|
|
1004
1137
|
}
|
|
1005
1138
|
}
|
|
1006
1139
|
if (finalArgs._max) {
|
|
1007
1140
|
item._max = {};
|
|
1008
|
-
for (const key of
|
|
1009
|
-
item._max[key] = row[
|
|
1141
|
+
for (const key of selectedAggregateFields(finalArgs._max)) {
|
|
1142
|
+
item._max[key] = row[aggAlias("max", key)] !== undefined ? row[aggAlias("max", key)] : null;
|
|
1010
1143
|
}
|
|
1011
1144
|
}
|
|
1012
1145
|
return item;
|
|
1013
1146
|
});
|
|
1014
1147
|
});
|
|
1015
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
|
+
}
|
|
1016
1156
|
async upsert(args) {
|
|
1017
1157
|
return this.orm._executeMiddleware({ model: this.modelName, action: 'upsert', args }, async (params) => {
|
|
1018
1158
|
const { args: finalArgs } = params;
|
|
@@ -1021,50 +1161,50 @@ class TableClient {
|
|
|
1021
1161
|
const cleanCreate = { ...createData };
|
|
1022
1162
|
const cleanUpdate = { ...updateData };
|
|
1023
1163
|
for (const key of Object.keys(cleanCreate)) {
|
|
1024
|
-
if (
|
|
1164
|
+
if (this.orm.metadata.relationMap[this.modelName]?.[key])
|
|
1025
1165
|
delete cleanCreate[key];
|
|
1026
1166
|
}
|
|
1027
1167
|
for (const key of Object.keys(cleanUpdate)) {
|
|
1028
|
-
if (
|
|
1168
|
+
if (this.orm.metadata.relationMap[this.modelName]?.[key])
|
|
1029
1169
|
delete cleanUpdate[key];
|
|
1030
1170
|
}
|
|
1031
|
-
if (!cleanCreate.id &&
|
|
1171
|
+
if (!cleanCreate.id && this.orm.metadata.modelFields[this.modelName]?.id?.ts === "string") {
|
|
1032
1172
|
cleanCreate.id = (0, crypto_1.randomUUID)();
|
|
1033
1173
|
}
|
|
1034
1174
|
const now = new Date();
|
|
1035
|
-
if (!cleanCreate.createdAt &&
|
|
1175
|
+
if (!cleanCreate.createdAt && this.orm.metadata.modelFields[this.modelName]?.createdAt)
|
|
1036
1176
|
cleanCreate.createdAt = now;
|
|
1037
|
-
if (!cleanCreate.updatedAt &&
|
|
1177
|
+
if (!cleanCreate.updatedAt && this.orm.metadata.modelFields[this.modelName]?.updatedAt)
|
|
1038
1178
|
cleanCreate.updatedAt = now;
|
|
1039
|
-
if (!cleanUpdate.updatedAt &&
|
|
1179
|
+
if (!cleanUpdate.updatedAt && this.orm.metadata.modelFields[this.modelName]?.updatedAt)
|
|
1040
1180
|
cleanUpdate.updatedAt = now;
|
|
1181
|
+
const atomicWhere = flattenSimpleEqualityWhere(where);
|
|
1182
|
+
if (!atomicWhere || Object.keys(cleanUpdate).length === 0) {
|
|
1183
|
+
return this.sequentialUpsert(finalArgs);
|
|
1184
|
+
}
|
|
1041
1185
|
const p = {};
|
|
1042
|
-
const whereSql = parseWhere(this.modelName, where, p, "upw_");
|
|
1043
1186
|
const allKeys = Array.from(new Set([...Object.keys(cleanCreate), ...Object.keys(cleanUpdate)]));
|
|
1187
|
+
const cParam = (k) => `c_${(0, sql_utils_1.sanitizeParamName)(k)}`;
|
|
1188
|
+
const uParam = (k) => `u_${(0, sql_utils_1.sanitizeParamName)(k)}`;
|
|
1044
1189
|
for (const k of allKeys) {
|
|
1045
1190
|
if (cleanCreate[k] !== undefined)
|
|
1046
|
-
p[
|
|
1191
|
+
p[cParam(k)] = cleanCreate[k];
|
|
1047
1192
|
if (cleanUpdate[k] !== undefined)
|
|
1048
|
-
p[
|
|
1193
|
+
p[uParam(k)] = cleanUpdate[k];
|
|
1194
|
+
}
|
|
1195
|
+
for (const [k, v] of Object.entries(atomicWhere)) {
|
|
1196
|
+
p[`upw_${(0, sql_utils_1.sanitizeParamName)(k)}`] = v;
|
|
1049
1197
|
}
|
|
1050
1198
|
const sourceSelect = allKeys.map(k => {
|
|
1051
|
-
const val = cleanCreate[k] !== undefined ?
|
|
1052
|
-
return `${val} as
|
|
1199
|
+
const val = cleanCreate[k] !== undefined ? `@${cParam(k)}` : (cleanUpdate[k] !== undefined ? `@${uParam(k)}` : "NULL");
|
|
1200
|
+
return `${val} as ${(0, sql_utils_1.quoteIdentifier)(k)}`;
|
|
1053
1201
|
}).join(", ");
|
|
1054
|
-
const updateSets = Object.keys(cleanUpdate).map(k => `target.
|
|
1055
|
-
const insertCols = Object.keys(cleanCreate).map(k =>
|
|
1056
|
-
const insertVals = Object.keys(cleanCreate).map(k => `source.
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
const onClause = Object.keys(where).map(k => {
|
|
1061
|
-
if (typeof where[k] === 'object' && where[k] !== null && !(where[k] instanceof Date)) {
|
|
1062
|
-
// Flatten unique object if needed
|
|
1063
|
-
const inner = where[k];
|
|
1064
|
-
return Object.keys(inner).map(ik => `target.[${ik}] = @upw_${k}_${ik}`).join(" AND ");
|
|
1065
|
-
}
|
|
1066
|
-
return `target.[${k}] = @upw_${k}`;
|
|
1067
|
-
}).join(" AND ");
|
|
1202
|
+
const updateSets = Object.keys(cleanUpdate).map(k => `target.${(0, sql_utils_1.quoteIdentifier)(k)} = source.${(0, sql_utils_1.quoteIdentifier)(k)}`).join(", ");
|
|
1203
|
+
const insertCols = Object.keys(cleanCreate).map(k => (0, sql_utils_1.quoteIdentifier)(k)).join(", ");
|
|
1204
|
+
const insertVals = Object.keys(cleanCreate).map(k => `source.${(0, sql_utils_1.quoteIdentifier)(k)}`).join(", ");
|
|
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 ");
|
|
1068
1208
|
const sqlText = `
|
|
1069
1209
|
MERGE INTO ${this.tableName} WITH (HOLDLOCK) AS target
|
|
1070
1210
|
USING (SELECT ${sourceSelect}) AS source
|
|
@@ -1079,29 +1219,24 @@ class TableClient {
|
|
|
1079
1219
|
const rows = await this.executor(sqlText, p);
|
|
1080
1220
|
const result = rows[0];
|
|
1081
1221
|
if (include && result) {
|
|
1082
|
-
await resolveIncludes(this.modelName, [result], include, this.executor);
|
|
1222
|
+
await resolveIncludes(this.modelName, [result], include, this.executor, this.orm.metadata);
|
|
1083
1223
|
}
|
|
1084
1224
|
return result;
|
|
1085
1225
|
}
|
|
1086
1226
|
catch (err) {
|
|
1087
1227
|
logger_1.logger.warn(`Atomic upsert failed, falling back to sequential: ${err.message}`);
|
|
1088
|
-
|
|
1089
|
-
if (existing) {
|
|
1090
|
-
return this.update({ where: finalArgs.where, data: finalArgs.update, include: finalArgs.include });
|
|
1091
|
-
}
|
|
1092
|
-
else {
|
|
1093
|
-
return this.create({ data: finalArgs.create, include: finalArgs.include });
|
|
1094
|
-
}
|
|
1228
|
+
return this.sequentialUpsert(finalArgs);
|
|
1095
1229
|
}
|
|
1096
1230
|
});
|
|
1097
1231
|
}
|
|
1098
1232
|
}
|
|
1099
|
-
|
|
1233
|
+
exports.TableClient = TableClient;
|
|
1234
|
+
function addNoLockToQuery(sql, metadata) {
|
|
1100
1235
|
// If it's not a SELECT query, don't modify it
|
|
1101
1236
|
if (!/^\s*SELECT/i.test(sql)) {
|
|
1102
1237
|
return sql;
|
|
1103
1238
|
}
|
|
1104
|
-
const tableNames = Object.values(
|
|
1239
|
+
const tableNames = Object.values(metadata.modelToTable);
|
|
1105
1240
|
let modifiedSql = sql;
|
|
1106
1241
|
for (const table of tableNames) {
|
|
1107
1242
|
const escapedTable = table.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
@@ -1130,11 +1265,128 @@ function addNoLockToQuery(sql) {
|
|
|
1130
1265
|
}
|
|
1131
1266
|
return modifiedSql;
|
|
1132
1267
|
}
|
|
1268
|
+
// Lazy-load generated schema metadata from the ORM's own generated copy so
|
|
1269
|
+
// `new An5ORM()` resolves schema models out of the box without the core ever
|
|
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
|
+
}
|
|
1351
|
+
let autoMetadata = null;
|
|
1352
|
+
function loadAutoMetadata() {
|
|
1353
|
+
if (autoMetadata)
|
|
1354
|
+
return autoMetadata;
|
|
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 { }
|
|
1373
|
+
}
|
|
1374
|
+
autoMetadata = metadata_1.DEFAULT_METADATA;
|
|
1375
|
+
return autoMetadata;
|
|
1376
|
+
}
|
|
1133
1377
|
// Proxied AN5 ORM client class
|
|
1134
1378
|
class An5ORM {
|
|
1135
|
-
constructor(customExecutor) {
|
|
1136
|
-
this.
|
|
1379
|
+
constructor(customExecutor, metadata, inTransaction = false, transactionControl) {
|
|
1380
|
+
this.inTransaction = inTransaction;
|
|
1381
|
+
this.transactionControl = transactionControl;
|
|
1137
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
|
+
}
|
|
1389
|
+
this.metadata = metadata ?? loadAutoMetadata();
|
|
1138
1390
|
// Add default logging middleware
|
|
1139
1391
|
this.$use(async (params, next) => {
|
|
1140
1392
|
const start = Date.now();
|
|
@@ -1153,46 +1405,77 @@ class An5ORM {
|
|
|
1153
1405
|
}
|
|
1154
1406
|
});
|
|
1155
1407
|
return new Proxy(this, {
|
|
1156
|
-
get(target, prop) {
|
|
1157
|
-
if (prop === "
|
|
1158
|
-
return target
|
|
1159
|
-
}
|
|
1160
|
-
if (prop === "$transaction") {
|
|
1161
|
-
return target.$transaction.bind(target);
|
|
1162
|
-
}
|
|
1163
|
-
if (prop === "$connect") {
|
|
1164
|
-
return target.$connect.bind(target);
|
|
1165
|
-
}
|
|
1166
|
-
if (prop === "$disconnect") {
|
|
1167
|
-
return target.$disconnect.bind(target);
|
|
1168
|
-
}
|
|
1169
|
-
if (prop === "$queryRaw") {
|
|
1170
|
-
return target.$queryRaw.bind(target);
|
|
1171
|
-
}
|
|
1172
|
-
if (prop === "$queryRawUnsafe") {
|
|
1173
|
-
return target.$queryRawUnsafe.bind(target);
|
|
1174
|
-
}
|
|
1175
|
-
if (prop === "$executeRaw") {
|
|
1176
|
-
return target.$executeRaw.bind(target);
|
|
1177
|
-
}
|
|
1178
|
-
if (prop === "$executeRawUnsafe") {
|
|
1179
|
-
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);
|
|
1180
1411
|
}
|
|
1181
1412
|
if (!(prop in target) && typeof prop === "string" && !prop.startsWith("_")) {
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
if (tableName) {
|
|
1185
|
-
|
|
1186
|
-
)
|
|
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
|
+
}
|
|
1187
1431
|
}
|
|
1432
|
+
if (!tableName) {
|
|
1433
|
+
tableName = prop;
|
|
1434
|
+
}
|
|
1435
|
+
target[prop] = new TableClient(modelName, tableName, target.customExecutor || execQuery, receiver);
|
|
1188
1436
|
}
|
|
1189
1437
|
return target[prop];
|
|
1190
1438
|
}
|
|
1191
1439
|
});
|
|
1192
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
|
+
}
|
|
1193
1470
|
$use(middleware) {
|
|
1194
1471
|
this.middlewares.push(middleware);
|
|
1195
1472
|
}
|
|
1473
|
+
parseWhere(modelName, where, params, prefix = "") {
|
|
1474
|
+
return (0, sql_utils_1.parseWhere)(modelName, where, params, prefix, {
|
|
1475
|
+
relationMap: this.metadata.relationMap,
|
|
1476
|
+
modelToTable: this.metadata.modelToTable,
|
|
1477
|
+
});
|
|
1478
|
+
}
|
|
1196
1479
|
async _executeMiddleware(params, finalAction) {
|
|
1197
1480
|
let index = -1;
|
|
1198
1481
|
const dispatch = async (i, currentParams) => {
|
|
@@ -1243,7 +1526,7 @@ class An5ORM {
|
|
|
1243
1526
|
else {
|
|
1244
1527
|
throw new Error("Invalid query format for $queryRaw");
|
|
1245
1528
|
}
|
|
1246
|
-
queryText = addNoLockToQuery(queryText);
|
|
1529
|
+
queryText = addNoLockToQuery(queryText, this.metadata);
|
|
1247
1530
|
return executor(queryText, params);
|
|
1248
1531
|
}
|
|
1249
1532
|
async $queryRawUnsafe(queryText, ...values) {
|
|
@@ -1255,7 +1538,7 @@ class An5ORM {
|
|
|
1255
1538
|
params[paramName] = val;
|
|
1256
1539
|
});
|
|
1257
1540
|
}
|
|
1258
|
-
const modifiedQueryText = addNoLockToQuery(queryText);
|
|
1541
|
+
const modifiedQueryText = addNoLockToQuery(queryText, this.metadata);
|
|
1259
1542
|
const result = await executor(modifiedQueryText, params);
|
|
1260
1543
|
return result;
|
|
1261
1544
|
}
|
|
@@ -1286,9 +1569,11 @@ class An5ORM {
|
|
|
1286
1569
|
else {
|
|
1287
1570
|
throw new Error("Invalid query format for $executeRaw");
|
|
1288
1571
|
}
|
|
1289
|
-
queryText = addNoLockToQuery(queryText);
|
|
1290
|
-
|
|
1291
|
-
|
|
1572
|
+
queryText = addNoLockToQuery(queryText, this.metadata);
|
|
1573
|
+
if (executor.executeRaw) {
|
|
1574
|
+
return executor.executeRaw(queryText, params);
|
|
1575
|
+
}
|
|
1576
|
+
return normalizeAffectedCount(await executor(queryText, params));
|
|
1292
1577
|
}
|
|
1293
1578
|
async $executeRawUnsafe(queryText, ...values) {
|
|
1294
1579
|
const executor = this.customExecutor || execQuery;
|
|
@@ -1299,50 +1584,61 @@ class An5ORM {
|
|
|
1299
1584
|
params[paramName] = val;
|
|
1300
1585
|
});
|
|
1301
1586
|
}
|
|
1302
|
-
const modifiedQueryText = addNoLockToQuery(queryText);
|
|
1303
|
-
|
|
1304
|
-
|
|
1587
|
+
const modifiedQueryText = addNoLockToQuery(queryText, this.metadata);
|
|
1588
|
+
if (executor.executeRaw) {
|
|
1589
|
+
return executor.executeRaw(modifiedQueryText, params);
|
|
1590
|
+
}
|
|
1591
|
+
return normalizeAffectedCount(await executor(modifiedQueryText, params));
|
|
1305
1592
|
}
|
|
1306
1593
|
async $transaction(fn, options) {
|
|
1307
1594
|
if (Array.isArray(fn)) {
|
|
1308
1595
|
return Promise.all(fn);
|
|
1309
1596
|
}
|
|
1310
|
-
const
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
const request = new mssql_1.default.Request(transaction);
|
|
1316
|
-
if (params) {
|
|
1317
|
-
for (const [key, value] of Object.entries(params)) {
|
|
1318
|
-
request.input(key, value);
|
|
1319
|
-
}
|
|
1320
|
-
}
|
|
1321
|
-
const result = await request.query(queryText);
|
|
1322
|
-
const recordset = (result.recordset || []);
|
|
1323
|
-
recordset.rowsAffected = result.rowsAffected;
|
|
1324
|
-
return recordset;
|
|
1325
|
-
};
|
|
1326
|
-
const txClient = new An5ORM(txExecutor);
|
|
1327
|
-
try {
|
|
1328
|
-
const result = await fn(txClient);
|
|
1329
|
-
await transaction.commit();
|
|
1330
|
-
logger_1.logger.info("Database Transaction Committed Successfully");
|
|
1331
|
-
return result;
|
|
1332
|
-
}
|
|
1333
|
-
catch (err) {
|
|
1334
|
-
try {
|
|
1335
|
-
await transaction.rollback();
|
|
1336
|
-
logger_1.logger.warn("Database Transaction Rolled Back");
|
|
1337
|
-
}
|
|
1338
|
-
catch (rollbackErr) {
|
|
1339
|
-
logger_1.logger.error("Failed to rollback database transaction", rollbackErr);
|
|
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);
|
|
1340
1602
|
}
|
|
1341
|
-
throw
|
|
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);
|
|
1607
|
+
return fn(txClient);
|
|
1608
|
+
}, options);
|
|
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");
|
|
1342
1635
|
}
|
|
1636
|
+
const control = this.transactionControl;
|
|
1637
|
+
this.transactionControl = undefined;
|
|
1638
|
+
await control.rollback();
|
|
1343
1639
|
}
|
|
1344
1640
|
}
|
|
1345
1641
|
exports.An5ORM = An5ORM;
|
|
1346
1642
|
exports.an5Orm = new An5ORM();
|
|
1347
1643
|
exports.default = exports.an5Orm;
|
|
1348
|
-
//# sourceMappingURL=an5Orm.js.map
|
|
1644
|
+
//# sourceMappingURL=an5Orm.js.map
|