befly 3.20.10 → 3.21.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/lib/dbHelper.js +1626 -0
- package/lib/dbUtil.js +988 -0
- package/lib/sqlBuilder.js +714 -0
- package/package.json +4 -4
- package/plugins/mysql.js +1 -1
- package/scripts/syncDb/context.js +1 -1
- package/lib/dbHelper/builders.js +0 -658
- package/lib/dbHelper/dataOps.js +0 -578
- package/lib/dbHelper/execute.js +0 -128
- package/lib/dbHelper/index.js +0 -24
- package/lib/dbHelper/util.js +0 -95
- package/lib/dbHelper/validate.js +0 -695
- package/lib/sqlBuilder/batch.js +0 -114
- package/lib/sqlBuilder/check.js +0 -82
- package/lib/sqlBuilder/compiler.js +0 -360
- package/lib/sqlBuilder/index.js +0 -200
- package/lib/sqlBuilder/parser.js +0 -305
- package/lib/sqlBuilder/util.js +0 -273
package/lib/dbHelper/validate.js
DELETED
|
@@ -1,695 +0,0 @@
|
|
|
1
|
-
import { isFiniteNumber, isNonEmptyString, isNullable, isPlainObject, isString } from "../../utils/is.js";
|
|
2
|
-
import { snakeCase } from "../../utils/util.js";
|
|
3
|
-
|
|
4
|
-
function validateJoinFieldRef(value, label) {
|
|
5
|
-
if (!isNonEmptyString(value)) {
|
|
6
|
-
throw new Error(`${label} 不能为空`, {
|
|
7
|
-
cause: null,
|
|
8
|
-
code: "validation"
|
|
9
|
-
});
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const parts = value.split(".").map((item) => item.trim());
|
|
13
|
-
if (parts.length !== 2 || !isNonEmptyString(parts[0]) || !isNonEmptyString(parts[1])) {
|
|
14
|
-
throw new Error(`${label} 必须是 alias.field 格式`, {
|
|
15
|
-
cause: null,
|
|
16
|
-
code: "validation"
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function parseLeftJoinEquality(joinItem) {
|
|
22
|
-
if (!isNonEmptyString(joinItem)) {
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const match = joinItem.match(/^([a-zA-Z_][a-zA-Z0-9_]*\.[a-zA-Z_][a-zA-Z0-9_]*) ([a-zA-Z_][a-zA-Z0-9_]*\.[a-zA-Z_][a-zA-Z0-9_]*)$/);
|
|
27
|
-
if (!match) {
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return {
|
|
32
|
-
left: match[1],
|
|
33
|
-
right: match[2]
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function assertNonEmptyString(value, label) {
|
|
38
|
-
if (!isNonEmptyString(value)) {
|
|
39
|
-
throw new Error(`${label} 必须是非空字符串`, {
|
|
40
|
-
cause: null,
|
|
41
|
-
code: "validation"
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function assertPlainObjectValue(value, label) {
|
|
47
|
-
if (!isPlainObject(value)) {
|
|
48
|
-
throw new Error(`${label} 必须是对象`, {
|
|
49
|
-
cause: null,
|
|
50
|
-
code: "validation"
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export function validateWhereObject(where, label, required = false) {
|
|
56
|
-
if (isNullable(where)) {
|
|
57
|
-
if (required) {
|
|
58
|
-
throw new Error(`${label} 必须是对象`, {
|
|
59
|
-
cause: null,
|
|
60
|
-
code: "validation"
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (!isPlainObject(where)) {
|
|
67
|
-
throw new Error(`${label} 必须是对象`, {
|
|
68
|
-
cause: null,
|
|
69
|
-
code: "validation"
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
validateWhereMeta(where, label);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function validateExcludeRules(excludeRules, label) {
|
|
77
|
-
if (isNullable(excludeRules)) {
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (!isPlainObject(excludeRules)) {
|
|
82
|
-
throw new Error(`${label} 必须是对象`, {
|
|
83
|
-
cause: null,
|
|
84
|
-
code: "validation"
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
for (const [key, value] of Object.entries(excludeRules)) {
|
|
89
|
-
assertNonEmptyString(key, `${label} 的字段名`);
|
|
90
|
-
if (!Array.isArray(value)) {
|
|
91
|
-
throw new Error(`${label}.${key} 必须是数组`, {
|
|
92
|
-
cause: null,
|
|
93
|
-
code: "validation"
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function validateWhereMeta(where, label) {
|
|
100
|
-
if (!isPlainObject(where)) {
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
for (const [key, value] of Object.entries(where)) {
|
|
105
|
-
if (key === "$exclude") {
|
|
106
|
-
validateExcludeRules(value, `${label}.$exclude`);
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if ((key === "$or" || key === "$and") && Array.isArray(value)) {
|
|
111
|
-
for (let i = 0; i < value.length; i++) {
|
|
112
|
-
if (isPlainObject(value[i])) {
|
|
113
|
-
validateWhereMeta(value[i], `${label}.${key}[${i}]`);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
continue;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (isPlainObject(value)) {
|
|
120
|
-
validateWhereMeta(value, `${label}.${key}`);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function validateOrderByItems(orderBy, label) {
|
|
126
|
-
if (!Array.isArray(orderBy)) {
|
|
127
|
-
throw new Error(`${label} 必须是数组`, {
|
|
128
|
-
cause: null,
|
|
129
|
-
code: "validation"
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
for (const item of orderBy) {
|
|
134
|
-
if (!isString(item) || !item.includes("#")) {
|
|
135
|
-
throw new Error(`${label} 字段必须是 "字段#方向" 格式的字符串`, {
|
|
136
|
-
cause: null,
|
|
137
|
-
code: "validation"
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const parts = item.split("#");
|
|
142
|
-
if (parts.length !== 2) {
|
|
143
|
-
throw new Error(`${label} 字段必须是 "字段#方向" 格式的字符串`, {
|
|
144
|
-
cause: null,
|
|
145
|
-
code: "validation"
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const field = isString(parts[0]) ? parts[0].trim() : "";
|
|
150
|
-
const direction = isString(parts[1]) ? parts[1].trim().toUpperCase() : "";
|
|
151
|
-
if (!field) {
|
|
152
|
-
throw new Error(`${label} 中字段名不能为空`, {
|
|
153
|
-
cause: null,
|
|
154
|
-
code: "validation"
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
if (direction !== "ASC" && direction !== "DESC") {
|
|
158
|
-
throw new Error(`${label} 方向必须是 ASC 或 DESC`, {
|
|
159
|
-
cause: null,
|
|
160
|
-
code: "validation"
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function validateLeftJoinItems(leftJoin, label) {
|
|
167
|
-
if (!Array.isArray(leftJoin)) {
|
|
168
|
-
throw new Error(`${label} 必须是数组`, {
|
|
169
|
-
cause: null,
|
|
170
|
-
code: "validation"
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
for (let i = 0; i < leftJoin.length; i++) {
|
|
175
|
-
const joinItem = leftJoin[i];
|
|
176
|
-
if (!isNonEmptyString(joinItem)) {
|
|
177
|
-
throw new Error(`${label}[${i}] 必须是非空字符串`, {
|
|
178
|
-
cause: null,
|
|
179
|
-
code: "validation"
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const parsed = parseLeftJoinEquality(joinItem);
|
|
184
|
-
if (!parsed) {
|
|
185
|
-
throw new Error(`${label}[${i}] 必须是 "left.field right.field" 格式(空格表示等于)`, {
|
|
186
|
-
cause: null,
|
|
187
|
-
code: "validation"
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
validateJoinFieldRef(parsed.left, `${label}[${i}] 左侧字段`);
|
|
192
|
-
validateJoinFieldRef(parsed.right, `${label}[${i}] 右侧字段`);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function validateQueryTableOption(table, leftJoin, label) {
|
|
197
|
-
if (Array.isArray(table)) {
|
|
198
|
-
if (table.length === 0) {
|
|
199
|
-
throw new Error(`${label}.table 不能为空数组`, {
|
|
200
|
-
cause: null,
|
|
201
|
-
code: "validation"
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
for (let i = 0; i < table.length; i++) {
|
|
206
|
-
assertNonEmptyString(table[i], `${label}.table[${i}]`);
|
|
207
|
-
parseTableRef(table[i]);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
if (Array.isArray(leftJoin) && leftJoin.length > 0) {
|
|
211
|
-
if (table.length !== leftJoin.length + 1) {
|
|
212
|
-
throw new Error(`${label}.table 与 ${label}.leftJoin 数量不匹配,要求 table.length = leftJoin.length + 1`, {
|
|
213
|
-
cause: null,
|
|
214
|
-
code: "validation"
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
if (table.length > 1) {
|
|
221
|
-
throw new Error(`${label}.table 为数组时必须配合 leftJoin 使用`, {
|
|
222
|
-
cause: null,
|
|
223
|
-
code: "validation"
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
assertNonEmptyString(table, `${label}.table`);
|
|
230
|
-
parseTableRef(table);
|
|
231
|
-
|
|
232
|
-
if (Array.isArray(leftJoin) && leftJoin.length > 0) {
|
|
233
|
-
throw new Error(`${label}.leftJoin 启用时,${label}.table 必须是数组`, {
|
|
234
|
-
cause: null,
|
|
235
|
-
code: "validation"
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
function validateLegacyJoinOptions(options, label) {
|
|
241
|
-
const legacyJoinKeys = ["joins", "innerJoin", "rightJoin"];
|
|
242
|
-
for (const key of legacyJoinKeys) {
|
|
243
|
-
if (!isNullable(options[key])) {
|
|
244
|
-
throw new Error(`${label} 仅支持 leftJoin,${label}.${key} 已废弃`, {
|
|
245
|
-
cause: null,
|
|
246
|
-
code: "validation"
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
export function validateQueryOptions(options, label) {
|
|
253
|
-
if (!isPlainObject(options)) {
|
|
254
|
-
throw new Error(`${label} 必须是对象`, {
|
|
255
|
-
cause: null,
|
|
256
|
-
code: "validation"
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
validateQueryTableOption(options.table, options.leftJoin, label);
|
|
261
|
-
|
|
262
|
-
if (!isNullable(options.where)) {
|
|
263
|
-
validateWhereObject(options.where, `${label}.where`);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
if (!isNullable(options.fields) && !Array.isArray(options.fields)) {
|
|
267
|
-
throw new Error(`${label}.fields 必须是数组`, {
|
|
268
|
-
cause: null,
|
|
269
|
-
code: "validation"
|
|
270
|
-
});
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
if (!isNullable(options.orderBy)) {
|
|
274
|
-
validateOrderByItems(options.orderBy, `${label}.orderBy`);
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
validateLegacyJoinOptions(options, label);
|
|
278
|
-
|
|
279
|
-
if (!isNullable(options.leftJoin)) {
|
|
280
|
-
validateLeftJoinItems(options.leftJoin, `${label}.leftJoin`);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
if (!isNullable(options.page) && (!isFiniteNumber(options.page) || Math.floor(options.page) !== options.page)) {
|
|
284
|
-
throw new Error(`${label}.page 必须是整数`, {
|
|
285
|
-
cause: null,
|
|
286
|
-
code: "validation"
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
if (!isNullable(options.limit) && (!isFiniteNumber(options.limit) || Math.floor(options.limit) !== options.limit)) {
|
|
291
|
-
throw new Error(`${label}.limit 必须是整数`, {
|
|
292
|
-
cause: null,
|
|
293
|
-
code: "validation"
|
|
294
|
-
});
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
function validateBatchDataList(dataList, label) {
|
|
299
|
-
if (!Array.isArray(dataList)) {
|
|
300
|
-
throw new Error(`${label} 必须是数组`, {
|
|
301
|
-
cause: null,
|
|
302
|
-
code: "validation"
|
|
303
|
-
});
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
for (let i = 0; i < dataList.length; i++) {
|
|
307
|
-
if (!isPlainObject(dataList[i])) {
|
|
308
|
-
throw new Error(`${label}[${i}] 必须是对象`, {
|
|
309
|
-
cause: null,
|
|
310
|
-
code: "validation"
|
|
311
|
-
});
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
export function validateTableName(table, label) {
|
|
317
|
-
assertNonEmptyString(table, label);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
export function validateTableWhereOptions(options, label, required = false) {
|
|
321
|
-
validateTableName(options.table, `${label}.table`);
|
|
322
|
-
validateWhereObject(options.where, `${label}.where`, required);
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
export function validateTableDataOptions(options, label) {
|
|
326
|
-
validateTableName(options.table, `${label}.table`);
|
|
327
|
-
assertPlainObjectValue(options.data, `${label}.data`);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
export function validateTableBatchDataOptions(table, dataList, label) {
|
|
331
|
-
validateTableName(table, `${label}.table`);
|
|
332
|
-
validateBatchDataList(dataList, `${label}.dataList`);
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
export function validateNoLeftJoinReadOptions(options, label, joinErrorMessage) {
|
|
336
|
-
validateTableName(options.table, `${label}.table`);
|
|
337
|
-
validateWhereObject(options.where, `${label}.where`, false);
|
|
338
|
-
validateNoLeftJoin(options.leftJoin, joinErrorMessage);
|
|
339
|
-
validateLegacyJoinOptions(options, label);
|
|
340
|
-
validateSimpleTableName(options.table, label);
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
export function validateIncrementOptions(table, field, where, value, label) {
|
|
344
|
-
validateTableName(table, `${label}.table`);
|
|
345
|
-
validateWhereObject(where, `${label}.where`, true);
|
|
346
|
-
validateSafeFieldName(snakeCase(table));
|
|
347
|
-
validateSafeFieldName(snakeCase(field));
|
|
348
|
-
validateIncrementValue(table, field, value);
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
export function validateSimpleTableName(rawTable, label) {
|
|
352
|
-
if (Array.isArray(rawTable)) {
|
|
353
|
-
throw new Error(`${label}.table 不支持数组`, {
|
|
354
|
-
cause: null,
|
|
355
|
-
code: "validation"
|
|
356
|
-
});
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
const table = isString(rawTable) ? rawTable.trim() : "";
|
|
360
|
-
if (!table) {
|
|
361
|
-
throw new Error(`${label}.table 不能为空`, {
|
|
362
|
-
cause: null,
|
|
363
|
-
code: "validation"
|
|
364
|
-
});
|
|
365
|
-
}
|
|
366
|
-
if (table.includes(" ")) {
|
|
367
|
-
throw new Error(`${label} 不支持别名表写法(table: ${table})`, {
|
|
368
|
-
cause: null,
|
|
369
|
-
code: "validation"
|
|
370
|
-
});
|
|
371
|
-
}
|
|
372
|
-
if (table.includes(".")) {
|
|
373
|
-
throw new Error(`${label} 不支持 schema.table 写法(table: ${table})`, {
|
|
374
|
-
cause: null,
|
|
375
|
-
code: "validation"
|
|
376
|
-
});
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
export function validateSafeFieldName(field) {
|
|
381
|
-
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(field)) {
|
|
382
|
-
throw new Error(`无效的字段名: ${field},只允许字母、数字和下划线`, {
|
|
383
|
-
cause: null,
|
|
384
|
-
code: "validation"
|
|
385
|
-
});
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
export function validatePageLimitRange(prepared, rawTable) {
|
|
390
|
-
if (prepared.page < 1 || prepared.page > 10000) {
|
|
391
|
-
throw new Error(`页码必须在 1 到 10000 之间 (table: ${rawTable}, page: ${prepared.page}, limit: ${prepared.limit})`, {
|
|
392
|
-
cause: null,
|
|
393
|
-
code: "validation"
|
|
394
|
-
});
|
|
395
|
-
}
|
|
396
|
-
if (prepared.limit < 1 || prepared.limit > 100000) {
|
|
397
|
-
throw new Error(`每页数量必须在 1 到 100000 之间 (table: ${rawTable}, page: ${prepared.page}, limit: ${prepared.limit})`, {
|
|
398
|
-
cause: null,
|
|
399
|
-
code: "validation"
|
|
400
|
-
});
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
export function validateNoLeftJoin(leftJoin, message) {
|
|
405
|
-
if (Array.isArray(leftJoin) && leftJoin.length > 0) {
|
|
406
|
-
throw new Error(message, {
|
|
407
|
-
cause: null,
|
|
408
|
-
code: "validation"
|
|
409
|
-
});
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
export function validateIncrementValue(table, field, value) {
|
|
414
|
-
if (typeof value !== "number" || isNaN(value)) {
|
|
415
|
-
throw new Error(`自增值必须是有效的数字 (table: ${table}, field: ${field}, value: ${value})`, {
|
|
416
|
-
cause: null,
|
|
417
|
-
code: "validation"
|
|
418
|
-
});
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
export function validateExecuteSql(sql) {
|
|
423
|
-
if (!isString(sql)) {
|
|
424
|
-
throw new Error(`execute 只接受字符串类型的 SQL,收到类型: ${typeof sql},值: ${JSON.stringify(sql)}`, {
|
|
425
|
-
cause: null,
|
|
426
|
-
code: "validation"
|
|
427
|
-
});
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
export function validateExcludeFieldsResult(resultFields, table, excludeSnakeFields) {
|
|
432
|
-
if (resultFields.length === 0) {
|
|
433
|
-
throw new Error(`排除字段后没有剩余字段可查询。表: ${table}, 排除: ${excludeSnakeFields.join(", ")}`, {
|
|
434
|
-
cause: null,
|
|
435
|
-
code: "validation"
|
|
436
|
-
});
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
export function validateTimeIdValue(id) {
|
|
441
|
-
if (!Number.isFinite(id) || id <= 0) {
|
|
442
|
-
throw new Error(`buildInsertRow(timeId) 失败:id 必须为 > 0 的有限 number (id: ${String(id)})`, {
|
|
443
|
-
cause: null,
|
|
444
|
-
code: "validation"
|
|
445
|
-
});
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
export function validateInsertBatchSize(count, maxBatchSize) {
|
|
450
|
-
if (count > maxBatchSize) {
|
|
451
|
-
throw new Error(`批量插入数量 ${count} 超过最大限制 ${maxBatchSize}`, {
|
|
452
|
-
cause: null,
|
|
453
|
-
code: "validation"
|
|
454
|
-
});
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
export function assertNoExprField(field) {
|
|
459
|
-
if (!isString(field)) {
|
|
460
|
-
return;
|
|
461
|
-
}
|
|
462
|
-
if (!isNonEmptyString(field)) {
|
|
463
|
-
return;
|
|
464
|
-
}
|
|
465
|
-
const trimmed = field.trim();
|
|
466
|
-
if (trimmed.includes("(") || trimmed.includes(")")) {
|
|
467
|
-
throw new Error(`字段包含函数/表达式,请使用 selectRaw/whereRaw (field: ${trimmed})`, {
|
|
468
|
-
cause: null,
|
|
469
|
-
code: "validation"
|
|
470
|
-
});
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
export function assertNoUndefinedInRecord(row, label) {
|
|
475
|
-
for (const [key, value] of Object.entries(row)) {
|
|
476
|
-
if (value === undefined) {
|
|
477
|
-
throw new Error(`${label} 存在 undefined 字段值 (field: ${key})`, {
|
|
478
|
-
cause: null,
|
|
479
|
-
code: "validation"
|
|
480
|
-
});
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
export function assertBatchInsertRowsConsistent(rows, options) {
|
|
486
|
-
if (!Array.isArray(rows)) {
|
|
487
|
-
throw new Error("批量插入 rows 必须是数组", {
|
|
488
|
-
cause: null,
|
|
489
|
-
code: "validation"
|
|
490
|
-
});
|
|
491
|
-
}
|
|
492
|
-
if (rows.length === 0) {
|
|
493
|
-
throw new Error(`插入数据不能为空 (table: ${options.table})`, {
|
|
494
|
-
cause: null,
|
|
495
|
-
code: "validation"
|
|
496
|
-
});
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
const first = rows[0];
|
|
500
|
-
if (!first || typeof first !== "object" || Array.isArray(first)) {
|
|
501
|
-
throw new Error(`批量插入的每一行必须是对象 (table: ${options.table}, rowIndex: 0)`, {
|
|
502
|
-
cause: null,
|
|
503
|
-
code: "validation"
|
|
504
|
-
});
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
const fields = Object.keys(first);
|
|
508
|
-
if (fields.length === 0) {
|
|
509
|
-
throw new Error(`插入数据必须至少有一个字段 (table: ${options.table})`, {
|
|
510
|
-
cause: null,
|
|
511
|
-
code: "validation"
|
|
512
|
-
});
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
const fieldSet = new Set(fields);
|
|
516
|
-
for (let i = 0; i < rows.length; i++) {
|
|
517
|
-
const row = rows[i];
|
|
518
|
-
if (!row || typeof row !== "object" || Array.isArray(row)) {
|
|
519
|
-
throw new Error(`批量插入的每一行必须是对象 (table: ${options.table}, rowIndex: ${i})`, {
|
|
520
|
-
cause: null,
|
|
521
|
-
code: "validation"
|
|
522
|
-
});
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
const rowKeys = Object.keys(row);
|
|
526
|
-
if (rowKeys.length !== fields.length) {
|
|
527
|
-
throw new Error(`批量插入每行字段必须一致 (table: ${options.table}, rowIndex: ${i})`, {
|
|
528
|
-
cause: null,
|
|
529
|
-
code: "validation"
|
|
530
|
-
});
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
for (const key of rowKeys) {
|
|
534
|
-
if (!fieldSet.has(key)) {
|
|
535
|
-
throw new Error(`批量插入每行字段必须一致 (table: ${options.table}, rowIndex: ${i}, extraField: ${key})`, {
|
|
536
|
-
cause: null,
|
|
537
|
-
code: "validation"
|
|
538
|
-
});
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
for (const field of fields) {
|
|
543
|
-
if (!(field in row)) {
|
|
544
|
-
throw new Error(`批量插入缺少字段 (table: ${options.table}, rowIndex: ${i}, field: ${field})`, {
|
|
545
|
-
cause: null,
|
|
546
|
-
code: "validation"
|
|
547
|
-
});
|
|
548
|
-
}
|
|
549
|
-
if (row[field] === undefined) {
|
|
550
|
-
throw new Error(`批量插入字段值不能为 undefined (table: ${options.table}, rowIndex: ${i}, field: ${field})`, {
|
|
551
|
-
cause: null,
|
|
552
|
-
code: "validation"
|
|
553
|
-
});
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
return fields;
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
export function parseTableRef(tableRef) {
|
|
562
|
-
if (!isString(tableRef)) {
|
|
563
|
-
throw new Error(`tableRef 必须是字符串 (tableRef: ${String(tableRef)})`, {
|
|
564
|
-
cause: null,
|
|
565
|
-
code: "validation"
|
|
566
|
-
});
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
const trimmed = tableRef.trim();
|
|
570
|
-
if (!isNonEmptyString(tableRef)) {
|
|
571
|
-
throw new Error("tableRef 不能为空", {
|
|
572
|
-
cause: null,
|
|
573
|
-
code: "validation"
|
|
574
|
-
});
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
const parts = trimmed.split(/\s+/).filter((p) => p.length > 0);
|
|
578
|
-
if (parts.length === 0) {
|
|
579
|
-
throw new Error("tableRef 不能为空", {
|
|
580
|
-
cause: null,
|
|
581
|
-
code: "validation"
|
|
582
|
-
});
|
|
583
|
-
}
|
|
584
|
-
if (parts.length > 2) {
|
|
585
|
-
throw new Error(`不支持的表引用格式(包含过多片段)。请使用最简形式:table 或 table alias 或 schema.table 或 schema.table alias (tableRef: ${trimmed})`, {
|
|
586
|
-
cause: null,
|
|
587
|
-
code: "validation"
|
|
588
|
-
});
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
const namePart = parts[0];
|
|
592
|
-
if (!isNonEmptyString(namePart)) {
|
|
593
|
-
throw new Error(`tableRef 解析失败:缺少表名 (tableRef: ${trimmed})`, {
|
|
594
|
-
cause: null,
|
|
595
|
-
code: "validation"
|
|
596
|
-
});
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
let aliasPart = null;
|
|
600
|
-
if (parts.length === 2) {
|
|
601
|
-
const alias = parts[1];
|
|
602
|
-
if (!isNonEmptyString(alias)) {
|
|
603
|
-
throw new Error(`tableRef 解析失败:缺少 alias (tableRef: ${trimmed})`, {
|
|
604
|
-
cause: null,
|
|
605
|
-
code: "validation"
|
|
606
|
-
});
|
|
607
|
-
}
|
|
608
|
-
aliasPart = alias;
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
const nameSegments = namePart.split(".");
|
|
612
|
-
if (nameSegments.length > 2) {
|
|
613
|
-
throw new Error(`不支持的表引用格式(schema 层级过深) (tableRef: ${trimmed})`, {
|
|
614
|
-
cause: null,
|
|
615
|
-
code: "validation"
|
|
616
|
-
});
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
if (nameSegments.length === 2) {
|
|
620
|
-
const schema = nameSegments[0];
|
|
621
|
-
const table = nameSegments[1];
|
|
622
|
-
if (!isNonEmptyString(schema)) {
|
|
623
|
-
throw new Error(`tableRef 解析失败:schema 为空 (tableRef: ${trimmed})`, {
|
|
624
|
-
cause: null,
|
|
625
|
-
code: "validation"
|
|
626
|
-
});
|
|
627
|
-
}
|
|
628
|
-
if (!isNonEmptyString(table)) {
|
|
629
|
-
throw new Error(`tableRef 解析失败:table 为空 (tableRef: ${trimmed})`, {
|
|
630
|
-
cause: null,
|
|
631
|
-
code: "validation"
|
|
632
|
-
});
|
|
633
|
-
}
|
|
634
|
-
return { schema: schema, table: table, alias: aliasPart };
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
const table = nameSegments[0];
|
|
638
|
-
if (!isNonEmptyString(table)) {
|
|
639
|
-
throw new Error(`tableRef 解析失败:table 为空 (tableRef: ${trimmed})`, {
|
|
640
|
-
cause: null,
|
|
641
|
-
code: "validation"
|
|
642
|
-
});
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
return { schema: null, table: table, alias: aliasPart };
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
export function validateGeneratedBatchId(id, table, index) {
|
|
649
|
-
if (typeof id !== "number") {
|
|
650
|
-
throw new Error(`批量插入生成 ID 失败:ids[${index}] 不是 number (table: ${table})`, {
|
|
651
|
-
cause: null,
|
|
652
|
-
code: "runtime"
|
|
653
|
-
});
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
export function validateAndClassifyFields(fields) {
|
|
658
|
-
if (!fields || fields.length === 0) {
|
|
659
|
-
return { type: "all", fields: [] };
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
if (fields.some((f) => f === "*")) {
|
|
663
|
-
throw new Error("fields 不支持 * 星号,请使用空数组 [] 或不传参数表示查询所有字段", {
|
|
664
|
-
cause: null,
|
|
665
|
-
code: "validation"
|
|
666
|
-
});
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
if (fields.some((f) => !isNonEmptyString(f))) {
|
|
670
|
-
throw new Error("fields 不能包含空字符串或无效值", {
|
|
671
|
-
cause: null,
|
|
672
|
-
code: "validation"
|
|
673
|
-
});
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
for (const rawField of fields) {
|
|
677
|
-
const checkField = rawField.startsWith("!") ? rawField.substring(1) : rawField;
|
|
678
|
-
assertNoExprField(checkField);
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
const includeFields = fields.filter((f) => !f.startsWith("!"));
|
|
682
|
-
const excludeFields = fields.filter((f) => f.startsWith("!"));
|
|
683
|
-
|
|
684
|
-
if (includeFields.length > 0 && excludeFields.length === 0) {
|
|
685
|
-
return { type: "include", fields: includeFields };
|
|
686
|
-
}
|
|
687
|
-
if (excludeFields.length > 0 && includeFields.length === 0) {
|
|
688
|
-
return { type: "exclude", fields: excludeFields.map((f) => f.substring(1)) };
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
throw new Error('fields 不能同时包含普通字段和排除字段(! 开头)。只能使用以下3种方式之一:\n1. 空数组 [] 或不传(查询所有)\n2. 全部指定字段 ["id", "name"]\n3. 全部排除字段 ["!password", "!token"]', {
|
|
692
|
-
cause: null,
|
|
693
|
-
code: "validation"
|
|
694
|
-
});
|
|
695
|
-
}
|