befly 3.21.2 → 3.22.1
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/apis/admin/list.js +1 -1
- package/apis/admin/upd.js +2 -2
- package/apis/dict/upd.js +1 -1
- package/apis/dictType/upd.js +1 -1
- package/apis/role/all.js +1 -1
- package/apis/role/list.js +1 -1
- package/apis/role/upd.js +1 -1
- package/apis/source/imageList.js +27 -0
- package/apis/upload/file.js +105 -0
- package/checks/config.js +2 -0
- package/configs/beflyConfig.json +2 -1
- package/configs/beflyMenus.json +12 -0
- package/index.js +5 -5
- package/lib/dbHelper.js +170 -751
- package/lib/dbParse.js +1045 -0
- package/lib/dbUtil.js +52 -508
- package/lib/sqlBuilder.js +78 -294
- package/package.json +2 -2
- package/paths.js +2 -2
- package/router/static.js +2 -1
- package/sql/befly.sql +20 -0
- package/tables/file.json +81 -0
- package/utils/datetime.js +29 -32
package/lib/dbHelper.js
CHANGED
|
@@ -2,8 +2,9 @@ import { fieldClear } from "../utils/fieldClear.js";
|
|
|
2
2
|
import { isNonEmptyString, isNullable, isNumber, isPlainObject, isString } from "../utils/is.js";
|
|
3
3
|
import { arrayKeysToCamel, canConvertToNumber, keysToCamel, keysToSnake, snakeCase } from "../utils/util.js";
|
|
4
4
|
import { Logger } from "./logger.js";
|
|
5
|
+
import { DbParse } from "./dbParse.js";
|
|
5
6
|
import { SqlBuilder } from "./sqlBuilder.js";
|
|
6
|
-
import {
|
|
7
|
+
import { deserializeArrayFields, parseTableRef, serializeArrayFields } from "./dbUtil.js";
|
|
7
8
|
|
|
8
9
|
function quoteIdentMySql(identifier) {
|
|
9
10
|
if (!isString(identifier)) {
|
|
@@ -24,26 +25,6 @@ function quoteIdentMySql(identifier) {
|
|
|
24
25
|
return `\`${trimmed}\``;
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
function normalizeTableRef(tableRef) {
|
|
28
|
-
const parsed = parseTableRef(tableRef);
|
|
29
|
-
const schemaPart = parsed.schema ? snakeCase(parsed.schema) : null;
|
|
30
|
-
const tablePart = snakeCase(parsed.table);
|
|
31
|
-
const aliasPart = parsed.alias ? snakeCase(parsed.alias) : null;
|
|
32
|
-
let result = schemaPart ? `${schemaPart}.${tablePart}` : tablePart;
|
|
33
|
-
if (aliasPart) {
|
|
34
|
-
result = `${result} ${aliasPart}`;
|
|
35
|
-
}
|
|
36
|
-
return result;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function getJoinMainQualifier(tableRef) {
|
|
40
|
-
const parsed = parseTableRef(tableRef);
|
|
41
|
-
if (parsed.alias) {
|
|
42
|
-
return snakeCase(parsed.alias);
|
|
43
|
-
}
|
|
44
|
-
return snakeCase(parsed.table);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
28
|
function normalizeSqlMetaNumber(value) {
|
|
48
29
|
if (isNullable(value)) {
|
|
49
30
|
return 0;
|
|
@@ -147,503 +128,129 @@ function getExecuteErrorMessage(error) {
|
|
|
147
128
|
return detailText;
|
|
148
129
|
}
|
|
149
130
|
|
|
150
|
-
function
|
|
151
|
-
if (
|
|
152
|
-
throw new Error(
|
|
131
|
+
function assertGeneratedBatchId(id, table, index) {
|
|
132
|
+
if (typeof id !== "number") {
|
|
133
|
+
throw new Error(`批量插入生成 ID 失败:ids[${index}] 不是 number (table: ${table})`, {
|
|
153
134
|
cause: null,
|
|
154
|
-
code: "
|
|
135
|
+
code: "runtime"
|
|
155
136
|
});
|
|
156
137
|
}
|
|
157
138
|
}
|
|
158
139
|
|
|
159
|
-
function
|
|
160
|
-
if (!
|
|
161
|
-
throw new Error(
|
|
140
|
+
function assertTimeIdValue(id) {
|
|
141
|
+
if (!Number.isFinite(id) || id <= 0) {
|
|
142
|
+
throw new Error(`buildInsertRow(timeId) 失败:id 必须为 > 0 的有限 number (id: ${String(id)})`, {
|
|
162
143
|
cause: null,
|
|
163
144
|
code: "validation"
|
|
164
145
|
});
|
|
165
146
|
}
|
|
166
147
|
}
|
|
167
148
|
|
|
168
|
-
function
|
|
169
|
-
if (
|
|
170
|
-
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
if (!isPlainObject(excludeRules)) {
|
|
174
|
-
throw new Error(`${label} 必须是对象`, {
|
|
149
|
+
function assertInsertBatchSize(count, maxBatchSize) {
|
|
150
|
+
if (count > maxBatchSize) {
|
|
151
|
+
throw new Error(`批量插入数量 ${count} 超过最大限制 ${maxBatchSize}`, {
|
|
175
152
|
cause: null,
|
|
176
153
|
code: "validation"
|
|
177
154
|
});
|
|
178
155
|
}
|
|
179
|
-
|
|
180
|
-
for (const [key, value] of Object.entries(excludeRules)) {
|
|
181
|
-
assertNonEmptyString(key, `${label} 的字段名`);
|
|
182
|
-
if (!Array.isArray(value)) {
|
|
183
|
-
throw new Error(`${label}.${key} 必须是数组`, {
|
|
184
|
-
cause: null,
|
|
185
|
-
code: "validation"
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
156
|
}
|
|
190
157
|
|
|
191
|
-
function
|
|
192
|
-
if (
|
|
158
|
+
function assertWriteDataHasFields(data, message, table) {
|
|
159
|
+
if (Object.keys(data).length > 0) {
|
|
193
160
|
return;
|
|
194
161
|
}
|
|
195
162
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
if ((key === "$or" || key === "$and") && Array.isArray(value)) {
|
|
203
|
-
for (let i = 0; i < value.length; i++) {
|
|
204
|
-
if (isPlainObject(value[i])) {
|
|
205
|
-
validateWhereMeta(value[i], `${label}.${key}[${i}]`);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
continue;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
if (isPlainObject(value)) {
|
|
212
|
-
validateWhereMeta(value, `${label}.${key}`);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
163
|
+
throw new Error(`${message} (table: ${table})`, {
|
|
164
|
+
cause: null,
|
|
165
|
+
code: "validation"
|
|
166
|
+
});
|
|
215
167
|
}
|
|
216
168
|
|
|
217
|
-
function
|
|
218
|
-
|
|
219
|
-
if (
|
|
220
|
-
throw new Error(`${label}
|
|
169
|
+
function assertNoUndefinedInRecord(row, label) {
|
|
170
|
+
for (const [key, value] of Object.entries(row)) {
|
|
171
|
+
if (value === undefined) {
|
|
172
|
+
throw new Error(`${label} 存在 undefined 字段值 (field: ${key})`, {
|
|
221
173
|
cause: null,
|
|
222
174
|
code: "validation"
|
|
223
175
|
});
|
|
224
176
|
}
|
|
225
|
-
return;
|
|
226
177
|
}
|
|
227
|
-
|
|
228
|
-
if (!isPlainObject(where)) {
|
|
229
|
-
throw new Error(`${label} 必须是对象`, {
|
|
230
|
-
cause: null,
|
|
231
|
-
code: "validation"
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
validateWhereMeta(where, label);
|
|
236
178
|
}
|
|
237
179
|
|
|
238
|
-
function
|
|
239
|
-
if (!Array.isArray(
|
|
240
|
-
throw new Error(
|
|
180
|
+
function assertBatchInsertRowsConsistent(rows, options) {
|
|
181
|
+
if (!Array.isArray(rows)) {
|
|
182
|
+
throw new Error("批量插入 rows 必须是数组", {
|
|
241
183
|
cause: null,
|
|
242
184
|
code: "validation"
|
|
243
185
|
});
|
|
244
186
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
if (!isString(item) || !item.includes("#")) {
|
|
248
|
-
throw new Error(`${label} 字段必须是 "字段#方向" 格式的字符串`, {
|
|
249
|
-
cause: null,
|
|
250
|
-
code: "validation"
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
const parts = item.split("#");
|
|
255
|
-
if (parts.length !== 2) {
|
|
256
|
-
throw new Error(`${label} 字段必须是 "字段#方向" 格式的字符串`, {
|
|
257
|
-
cause: null,
|
|
258
|
-
code: "validation"
|
|
259
|
-
});
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
const field = isString(parts[0]) ? parts[0].trim() : "";
|
|
263
|
-
const direction = isString(parts[1]) ? parts[1].trim().toUpperCase() : "";
|
|
264
|
-
if (!field) {
|
|
265
|
-
throw new Error(`${label} 中字段名不能为空`, {
|
|
266
|
-
cause: null,
|
|
267
|
-
code: "validation"
|
|
268
|
-
});
|
|
269
|
-
}
|
|
270
|
-
if (direction !== "ASC" && direction !== "DESC") {
|
|
271
|
-
throw new Error(`${label} 方向必须是 ASC 或 DESC`, {
|
|
272
|
-
cause: null,
|
|
273
|
-
code: "validation"
|
|
274
|
-
});
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
function validateJoinFieldRef(value, label) {
|
|
280
|
-
if (!isNonEmptyString(value)) {
|
|
281
|
-
throw new Error(`${label} 不能为空`, {
|
|
187
|
+
if (rows.length === 0) {
|
|
188
|
+
throw new Error(`插入数据不能为空 (table: ${options.table})`, {
|
|
282
189
|
cause: null,
|
|
283
190
|
code: "validation"
|
|
284
191
|
});
|
|
285
192
|
}
|
|
286
193
|
|
|
287
|
-
const
|
|
288
|
-
if (
|
|
289
|
-
throw new Error(
|
|
194
|
+
const first = rows[0];
|
|
195
|
+
if (!first || typeof first !== "object" || Array.isArray(first)) {
|
|
196
|
+
throw new Error(`批量插入的每一行必须是对象 (table: ${options.table}, rowIndex: 0)`, {
|
|
290
197
|
cause: null,
|
|
291
198
|
code: "validation"
|
|
292
199
|
});
|
|
293
200
|
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
function parseLeftJoinEquality(joinItem) {
|
|
297
|
-
if (!isNonEmptyString(joinItem)) {
|
|
298
|
-
return null;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
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_]*)$/);
|
|
302
|
-
if (!match) {
|
|
303
|
-
return null;
|
|
304
|
-
}
|
|
305
201
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
};
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
function validateLeftJoinItems(leftJoin, label) {
|
|
313
|
-
if (!Array.isArray(leftJoin)) {
|
|
314
|
-
throw new Error(`${label} 必须是数组`, {
|
|
202
|
+
const fields = Object.keys(first);
|
|
203
|
+
if (fields.length === 0) {
|
|
204
|
+
throw new Error(`插入数据必须至少有一个字段 (table: ${options.table})`, {
|
|
315
205
|
cause: null,
|
|
316
206
|
code: "validation"
|
|
317
207
|
});
|
|
318
208
|
}
|
|
319
209
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
210
|
+
const fieldSet = new Set(fields);
|
|
211
|
+
for (let i = 0; i < rows.length; i++) {
|
|
212
|
+
const row = rows[i];
|
|
213
|
+
if (!row || typeof row !== "object" || Array.isArray(row)) {
|
|
214
|
+
throw new Error(`批量插入的每一行必须是对象 (table: ${options.table}, rowIndex: ${i})`, {
|
|
324
215
|
cause: null,
|
|
325
216
|
code: "validation"
|
|
326
217
|
});
|
|
327
218
|
}
|
|
328
219
|
|
|
329
|
-
const
|
|
330
|
-
if (
|
|
331
|
-
throw new Error(
|
|
220
|
+
const rowKeys = Object.keys(row);
|
|
221
|
+
if (rowKeys.length !== fields.length) {
|
|
222
|
+
throw new Error(`批量插入每行字段必须一致 (table: ${options.table}, rowIndex: ${i})`, {
|
|
332
223
|
cause: null,
|
|
333
224
|
code: "validation"
|
|
334
225
|
});
|
|
335
226
|
}
|
|
336
227
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
function validateLegacyJoinOptions(options, label) {
|
|
343
|
-
const legacyJoinKeys = ["joins", "innerJoin", "rightJoin"];
|
|
344
|
-
for (const key of legacyJoinKeys) {
|
|
345
|
-
if (!isNullable(options[key])) {
|
|
346
|
-
throw new Error(`${label} 仅支持 leftJoin,${label}.${key} 已废弃`, {
|
|
347
|
-
cause: null,
|
|
348
|
-
code: "validation"
|
|
349
|
-
});
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
function validateQueryTableOption(table, leftJoin, label) {
|
|
355
|
-
if (Array.isArray(table)) {
|
|
356
|
-
if (table.length === 0) {
|
|
357
|
-
throw new Error(`${label}.table 不能为空数组`, {
|
|
358
|
-
cause: null,
|
|
359
|
-
code: "validation"
|
|
360
|
-
});
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
for (let i = 0; i < table.length; i++) {
|
|
364
|
-
assertNonEmptyString(table[i], `${label}.table[${i}]`);
|
|
365
|
-
parseTableRef(table[i]);
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
if (Array.isArray(leftJoin) && leftJoin.length > 0) {
|
|
369
|
-
if (table.length !== leftJoin.length + 1) {
|
|
370
|
-
throw new Error(`${label}.table 与 ${label}.leftJoin 数量不匹配,要求 table.length = leftJoin.length + 1`, {
|
|
228
|
+
for (const key of rowKeys) {
|
|
229
|
+
if (!fieldSet.has(key)) {
|
|
230
|
+
throw new Error(`批量插入每行字段必须一致 (table: ${options.table}, rowIndex: ${i}, extraField: ${key})`, {
|
|
371
231
|
cause: null,
|
|
372
232
|
code: "validation"
|
|
373
233
|
});
|
|
374
234
|
}
|
|
375
|
-
return;
|
|
376
235
|
}
|
|
377
236
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
throw new Error(`${label}.leftJoin 启用时,${label}.table 必须是数组`, {
|
|
392
|
-
cause: null,
|
|
393
|
-
code: "validation"
|
|
394
|
-
});
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
function validateQueryOptions(options, label) {
|
|
399
|
-
if (!isPlainObject(options)) {
|
|
400
|
-
throw new Error(`${label} 必须是对象`, {
|
|
401
|
-
cause: null,
|
|
402
|
-
code: "validation"
|
|
403
|
-
});
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
validateQueryTableOption(options.table, options.leftJoin, label);
|
|
407
|
-
|
|
408
|
-
if (!isNullable(options.where)) {
|
|
409
|
-
validateWhereObject(options.where, `${label}.where`);
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
if (!isNullable(options.fields) && !Array.isArray(options.fields)) {
|
|
413
|
-
throw new Error(`${label}.fields 必须是数组`, {
|
|
414
|
-
cause: null,
|
|
415
|
-
code: "validation"
|
|
416
|
-
});
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
if (!isNullable(options.orderBy)) {
|
|
420
|
-
validateOrderByItems(options.orderBy, `${label}.orderBy`);
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
validateLegacyJoinOptions(options, label);
|
|
424
|
-
|
|
425
|
-
if (!isNullable(options.leftJoin)) {
|
|
426
|
-
validateLeftJoinItems(options.leftJoin, `${label}.leftJoin`);
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
if (!isNullable(options.page) && (!Number.isFinite(options.page) || Math.floor(options.page) !== options.page)) {
|
|
430
|
-
throw new Error(`${label}.page 必须是整数`, {
|
|
431
|
-
cause: null,
|
|
432
|
-
code: "validation"
|
|
433
|
-
});
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
if (!isNullable(options.limit) && (!Number.isFinite(options.limit) || Math.floor(options.limit) !== options.limit)) {
|
|
437
|
-
throw new Error(`${label}.limit 必须是整数`, {
|
|
438
|
-
cause: null,
|
|
439
|
-
code: "validation"
|
|
440
|
-
});
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
function validateTableName(table, label) {
|
|
445
|
-
assertNonEmptyString(table, label);
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
function validateBatchDataList(dataList, label) {
|
|
449
|
-
if (!Array.isArray(dataList)) {
|
|
450
|
-
throw new Error(`${label} 必须是数组`, {
|
|
451
|
-
cause: null,
|
|
452
|
-
code: "validation"
|
|
453
|
-
});
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
for (let i = 0; i < dataList.length; i++) {
|
|
457
|
-
if (!isPlainObject(dataList[i])) {
|
|
458
|
-
throw new Error(`${label}[${i}] 必须是对象`, {
|
|
459
|
-
cause: null,
|
|
460
|
-
code: "validation"
|
|
461
|
-
});
|
|
237
|
+
for (const field of fields) {
|
|
238
|
+
if (!(field in row)) {
|
|
239
|
+
throw new Error(`批量插入缺少字段 (table: ${options.table}, rowIndex: ${i}, field: ${field})`, {
|
|
240
|
+
cause: null,
|
|
241
|
+
code: "validation"
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
if (row[field] === undefined) {
|
|
245
|
+
throw new Error(`批量插入字段值不能为 undefined (table: ${options.table}, rowIndex: ${i}, field: ${field})`, {
|
|
246
|
+
cause: null,
|
|
247
|
+
code: "validation"
|
|
248
|
+
});
|
|
249
|
+
}
|
|
462
250
|
}
|
|
463
251
|
}
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
function validateTableWhereOptions(options, label, required = false) {
|
|
467
|
-
validateTableName(options.table, `${label}.table`);
|
|
468
|
-
validateWhereObject(options.where, `${label}.where`, required);
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
function validateTableDataOptions(options, label) {
|
|
472
|
-
validateTableName(options.table, `${label}.table`);
|
|
473
|
-
assertPlainObjectValue(options.data, `${label}.data`);
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
function validateTableBatchDataOptions(table, dataList, label) {
|
|
477
|
-
validateTableName(table, `${label}.table`);
|
|
478
|
-
validateBatchDataList(dataList, `${label}.dataList`);
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
function validateSimpleTableName(rawTable, label) {
|
|
482
|
-
if (Array.isArray(rawTable)) {
|
|
483
|
-
throw new Error(`${label}.table 不支持数组`, {
|
|
484
|
-
cause: null,
|
|
485
|
-
code: "validation"
|
|
486
|
-
});
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
const table = isString(rawTable) ? rawTable.trim() : "";
|
|
490
|
-
if (!table) {
|
|
491
|
-
throw new Error(`${label}.table 不能为空`, {
|
|
492
|
-
cause: null,
|
|
493
|
-
code: "validation"
|
|
494
|
-
});
|
|
495
|
-
}
|
|
496
|
-
if (table.includes(" ")) {
|
|
497
|
-
throw new Error(`${label} 不支持别名表写法(table: ${table})`, {
|
|
498
|
-
cause: null,
|
|
499
|
-
code: "validation"
|
|
500
|
-
});
|
|
501
|
-
}
|
|
502
|
-
if (table.includes(".")) {
|
|
503
|
-
throw new Error(`${label} 不支持 schema.table 写法(table: ${table})`, {
|
|
504
|
-
cause: null,
|
|
505
|
-
code: "validation"
|
|
506
|
-
});
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
function validateNoLeftJoin(leftJoin, message) {
|
|
511
|
-
if (Array.isArray(leftJoin) && leftJoin.length > 0) {
|
|
512
|
-
throw new Error(message, {
|
|
513
|
-
cause: null,
|
|
514
|
-
code: "validation"
|
|
515
|
-
});
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
function validateNoLeftJoinReadOptions(options, label, joinErrorMessage) {
|
|
520
|
-
validateTableName(options.table, `${label}.table`);
|
|
521
|
-
validateWhereObject(options.where, `${label}.where`, false);
|
|
522
|
-
validateNoLeftJoin(options.leftJoin, joinErrorMessage);
|
|
523
|
-
validateLegacyJoinOptions(options, label);
|
|
524
|
-
validateSimpleTableName(options.table, label);
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
function validateSafeFieldName(field) {
|
|
528
|
-
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(field)) {
|
|
529
|
-
throw new Error(`无效的字段名: ${field},只允许字母、数字和下划线`, {
|
|
530
|
-
cause: null,
|
|
531
|
-
code: "validation"
|
|
532
|
-
});
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
function validatePageLimitRange(prepared, rawTable) {
|
|
537
|
-
if (prepared.page < 1 || prepared.page > 10000) {
|
|
538
|
-
throw new Error(`页码必须在 1 到 10000 之间 (table: ${rawTable}, page: ${prepared.page}, limit: ${prepared.limit})`, {
|
|
539
|
-
cause: null,
|
|
540
|
-
code: "validation"
|
|
541
|
-
});
|
|
542
|
-
}
|
|
543
|
-
if (prepared.limit < 1 || prepared.limit > 100000) {
|
|
544
|
-
throw new Error(`每页数量必须在 1 到 100000 之间 (table: ${rawTable}, page: ${prepared.page}, limit: ${prepared.limit})`, {
|
|
545
|
-
cause: null,
|
|
546
|
-
code: "validation"
|
|
547
|
-
});
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
function validateGeneratedBatchId(id, table, index) {
|
|
552
|
-
if (typeof id !== "number") {
|
|
553
|
-
throw new Error(`批量插入生成 ID 失败:ids[${index}] 不是 number (table: ${table})`, {
|
|
554
|
-
cause: null,
|
|
555
|
-
code: "runtime"
|
|
556
|
-
});
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
function validateTimeIdValue(id) {
|
|
561
|
-
if (!Number.isFinite(id) || id <= 0) {
|
|
562
|
-
throw new Error(`buildInsertRow(timeId) 失败:id 必须为 > 0 的有限 number (id: ${String(id)})`, {
|
|
563
|
-
cause: null,
|
|
564
|
-
code: "validation"
|
|
565
|
-
});
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
function validateInsertBatchSize(count, maxBatchSize) {
|
|
570
|
-
if (count > maxBatchSize) {
|
|
571
|
-
throw new Error(`批量插入数量 ${count} 超过最大限制 ${maxBatchSize}`, {
|
|
572
|
-
cause: null,
|
|
573
|
-
code: "validation"
|
|
574
|
-
});
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
function validateIncrementValue(table, field, value) {
|
|
579
|
-
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
580
|
-
throw new Error(`自增值必须是有效的数字 (table: ${table}, field: ${field}, value: ${value})`, {
|
|
581
|
-
cause: null,
|
|
582
|
-
code: "validation"
|
|
583
|
-
});
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
function validateIncrementOptions(table, field, where, value, label) {
|
|
588
|
-
validateTableName(table, `${label}.table`);
|
|
589
|
-
validateWhereObject(where, `${label}.where`, true);
|
|
590
|
-
validateSafeFieldName(snakeCase(table));
|
|
591
|
-
validateSafeFieldName(snakeCase(field));
|
|
592
|
-
validateIncrementValue(table, field, value);
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
function validateIdList(ids, label) {
|
|
596
|
-
if (!Array.isArray(ids)) {
|
|
597
|
-
throw new Error(`${label} 必须是数组`, {
|
|
598
|
-
cause: null,
|
|
599
|
-
code: "validation"
|
|
600
|
-
});
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
function validateEffectiveWhere(snakeWhere, label) {
|
|
605
|
-
if (!snakeWhere || Object.keys(snakeWhere).length === 0) {
|
|
606
|
-
throw new Error(`${label} 需要有效的 WHERE 条件`, {
|
|
607
|
-
cause: null,
|
|
608
|
-
code: "validation"
|
|
609
|
-
});
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
function validateDataReadOptions(options, label) {
|
|
614
|
-
validateQueryOptions(options, label);
|
|
615
|
-
|
|
616
|
-
const hasLeftJoin = Array.isArray(options.leftJoin) && options.leftJoin.length > 0;
|
|
617
|
-
const classifiedFields = hasLeftJoin ? validateExplicitLeftJoinFields(options.fields, options.table[0]) : validateExplicitReadFields(options.fields);
|
|
618
|
-
|
|
619
|
-
return {
|
|
620
|
-
hasLeftJoin: hasLeftJoin,
|
|
621
|
-
classifiedFields: classifiedFields
|
|
622
|
-
};
|
|
623
|
-
}
|
|
624
252
|
|
|
625
|
-
|
|
626
|
-
validateQueryOptions(options, label);
|
|
627
|
-
|
|
628
|
-
return {
|
|
629
|
-
hasLeftJoin: Array.isArray(options.leftJoin) && options.leftJoin.length > 0
|
|
630
|
-
};
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
function validateGetCountOptions(options) {
|
|
634
|
-
return validateCountReadOptions(options, "getCount.options");
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
function validateGetOneOptions(options) {
|
|
638
|
-
return validateDataReadOptions(options, "getOne.options");
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
function validateGetListOptions(options) {
|
|
642
|
-
return validateDataReadOptions(options, "getList.options");
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
function validateGetAllOptions(options) {
|
|
646
|
-
return validateDataReadOptions(options, "getAll.options");
|
|
253
|
+
return fields;
|
|
647
254
|
}
|
|
648
255
|
|
|
649
256
|
class DbHelper {
|
|
@@ -788,20 +395,16 @@ class DbHelper {
|
|
|
788
395
|
return new SqlBuilder({ quoteIdent: quoteIdentMySql });
|
|
789
396
|
}
|
|
790
397
|
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
}
|
|
797
|
-
if (prepared.orderBy && prepared.orderBy.length > 0) {
|
|
798
|
-
builder.orderBy(prepared.orderBy);
|
|
799
|
-
}
|
|
800
|
-
return builder;
|
|
398
|
+
createDbParse() {
|
|
399
|
+
return new DbParse({
|
|
400
|
+
tables: this.tables,
|
|
401
|
+
beflyMode: this.beflyMode,
|
|
402
|
+
getTableColumns: this.getTableColumns.bind(this)
|
|
403
|
+
});
|
|
801
404
|
}
|
|
802
405
|
|
|
803
|
-
async fetchCount(prepared,
|
|
804
|
-
const builder = this.createSqlBuilder().selectRaw(alias).from(prepared.table).where(
|
|
406
|
+
async fetchCount(prepared, alias) {
|
|
407
|
+
const builder = this.createSqlBuilder().selectRaw(alias).from(prepared.table).where(prepared.where);
|
|
805
408
|
this.applyLeftJoins(builder, prepared.leftJoins);
|
|
806
409
|
const result = builder.toSelectSql();
|
|
807
410
|
const executeRes = await this.execute(result.sql, result.params);
|
|
@@ -839,55 +442,11 @@ class DbHelper {
|
|
|
839
442
|
return normalizeBigIntValues(deserializedList);
|
|
840
443
|
}
|
|
841
444
|
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
return [];
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
const processedFields = [];
|
|
848
|
-
const seenFields = new Set();
|
|
849
|
-
const pushField = (field) => {
|
|
850
|
-
if (seenFields.has(field)) {
|
|
851
|
-
return;
|
|
852
|
-
}
|
|
853
|
-
seenFields.add(field);
|
|
854
|
-
processedFields.push(field);
|
|
855
|
-
};
|
|
856
|
-
|
|
857
|
-
if (classifiedFields.mainAllIncluded) {
|
|
858
|
-
const mainColumns = await this.getTableColumns(mainTableRef);
|
|
859
|
-
const excludeFieldSet = new Set(classifiedFields.mainExcludeFields.map((field) => snakeCase(field)));
|
|
860
|
-
|
|
861
|
-
for (const column of mainColumns) {
|
|
862
|
-
if (excludeFieldSet.has(column)) {
|
|
863
|
-
continue;
|
|
864
|
-
}
|
|
865
|
-
pushField(`${classifiedFields.mainQualifier}.${column}`);
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
for (const field of classifiedFields.includeFields) {
|
|
870
|
-
pushField(processJoinField(field));
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
if (processedFields.length === 0) {
|
|
874
|
-
throw new Error(`排除字段后没有剩余字段可查询。表: ${mainTableRef}, 排除: ${classifiedFields.mainExcludeFields.join(", ")}`, {
|
|
875
|
-
cause: null,
|
|
876
|
-
code: "validation"
|
|
877
|
-
});
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
return processedFields;
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
_cleanAndSnakeAndSerializeWriteData(data, excludeValues = [null, undefined]) {
|
|
884
|
-
const cleanData = fieldClear(data, { excludeValues: excludeValues });
|
|
885
|
-
return serializeArrayFields(keysToSnake(cleanData));
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
_stripSystemFieldsForWrite(data, allowState) {
|
|
445
|
+
_prepareWriteUserData(data, allowState) {
|
|
446
|
+
const cleanData = fieldClear(data, { excludeValues: [null, undefined] });
|
|
889
447
|
const result = {};
|
|
890
|
-
|
|
448
|
+
|
|
449
|
+
for (const [key, value] of Object.entries(serializeArrayFields(keysToSnake(cleanData)))) {
|
|
891
450
|
if (key === "id") continue;
|
|
892
451
|
if (key === "created_at") continue;
|
|
893
452
|
if (key === "updated_at") continue;
|
|
@@ -895,18 +454,15 @@ class DbHelper {
|
|
|
895
454
|
if (!allowState && key === "state") continue;
|
|
896
455
|
result[key] = value;
|
|
897
456
|
}
|
|
898
|
-
return result;
|
|
899
|
-
}
|
|
900
457
|
|
|
901
|
-
|
|
902
|
-
return this._stripSystemFieldsForWrite(this._cleanAndSnakeAndSerializeWriteData(data), allowState);
|
|
458
|
+
return result;
|
|
903
459
|
}
|
|
904
460
|
|
|
905
461
|
_buildInsertRow(options) {
|
|
906
462
|
const result = { ...this._prepareWriteUserData(options.data, false) };
|
|
907
463
|
|
|
908
464
|
if (options.beflyMode === "auto") {
|
|
909
|
-
|
|
465
|
+
assertTimeIdValue(options.id);
|
|
910
466
|
result.id = options.id;
|
|
911
467
|
}
|
|
912
468
|
|
|
@@ -927,102 +483,6 @@ class DbHelper {
|
|
|
927
483
|
return result;
|
|
928
484
|
}
|
|
929
485
|
|
|
930
|
-
_buildPartialUpdateData(options) {
|
|
931
|
-
return this._prepareWriteUserData(options.data, options.allowState);
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
async normalizeLeftJoinOptions(options, cleanWhere, classifiedFields) {
|
|
935
|
-
const mainTableRef = options.table[0];
|
|
936
|
-
const processedFields = await this._joinFieldsToSnake(mainTableRef, classifiedFields);
|
|
937
|
-
const joinTableRefs = options.table.slice(1);
|
|
938
|
-
const normalizedTableRef = normalizeTableRef(mainTableRef);
|
|
939
|
-
const mainQualifier = getJoinMainQualifier(mainTableRef);
|
|
940
|
-
const leftJoins = options.leftJoin.map((joinItem, index) => parseLeftJoinItem(joinTableRefs[index], joinItem));
|
|
941
|
-
|
|
942
|
-
return {
|
|
943
|
-
table: normalizedTableRef,
|
|
944
|
-
tableQualifier: mainQualifier,
|
|
945
|
-
fields: processedFields,
|
|
946
|
-
where: processJoinWhere(cleanWhere),
|
|
947
|
-
leftJoins: leftJoins,
|
|
948
|
-
orderBy: processJoinOrderBy(options.orderBy || []),
|
|
949
|
-
page: options.page || 1,
|
|
950
|
-
limit: options.limit || 10
|
|
951
|
-
};
|
|
952
|
-
}
|
|
953
|
-
|
|
954
|
-
normalizeLeftJoinCountOptions(options, cleanWhere) {
|
|
955
|
-
const mainTableRef = options.table[0];
|
|
956
|
-
const joinTableRefs = options.table.slice(1);
|
|
957
|
-
const normalizedTableRef = normalizeTableRef(mainTableRef);
|
|
958
|
-
const mainQualifier = getJoinMainQualifier(mainTableRef);
|
|
959
|
-
const leftJoins = options.leftJoin.map((joinItem, index) => parseLeftJoinItem(joinTableRefs[index], joinItem));
|
|
960
|
-
|
|
961
|
-
return {
|
|
962
|
-
table: normalizedTableRef,
|
|
963
|
-
tableQualifier: mainQualifier,
|
|
964
|
-
fields: [],
|
|
965
|
-
where: processJoinWhere(cleanWhere),
|
|
966
|
-
leftJoins: leftJoins,
|
|
967
|
-
orderBy: [],
|
|
968
|
-
page: options.page || 1,
|
|
969
|
-
limit: options.limit || 10
|
|
970
|
-
};
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
async normalizeSingleTableOptions(options, cleanWhere, classifiedFields) {
|
|
974
|
-
const tableRef = Array.isArray(options.table) ? options.table[0] : options.table;
|
|
975
|
-
const snakeTable = snakeCase(tableRef);
|
|
976
|
-
const processedFields = await fieldsToSnake(tableRef, classifiedFields, this.getTableColumns.bind(this));
|
|
977
|
-
|
|
978
|
-
return {
|
|
979
|
-
table: snakeTable,
|
|
980
|
-
tableQualifier: snakeTable,
|
|
981
|
-
fields: processedFields,
|
|
982
|
-
where: whereKeysToSnake(cleanWhere),
|
|
983
|
-
leftJoins: [],
|
|
984
|
-
orderBy: orderByToSnake(options.orderBy || []),
|
|
985
|
-
page: options.page || 1,
|
|
986
|
-
limit: options.limit || 10
|
|
987
|
-
};
|
|
988
|
-
}
|
|
989
|
-
|
|
990
|
-
buildQueryOptions(options, defaults) {
|
|
991
|
-
const output = {
|
|
992
|
-
table: options.table,
|
|
993
|
-
page: defaults.page,
|
|
994
|
-
limit: defaults.limit
|
|
995
|
-
};
|
|
996
|
-
|
|
997
|
-
if (options.fields !== undefined) output.fields = options.fields;
|
|
998
|
-
if (options.where !== undefined) output.where = options.where;
|
|
999
|
-
if (options.leftJoin !== undefined) output.leftJoin = options.leftJoin;
|
|
1000
|
-
if (options.orderBy !== undefined) output.orderBy = options.orderBy;
|
|
1001
|
-
return output;
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
resolveFieldValue(record, field) {
|
|
1005
|
-
if (!isPlainObject(record)) {
|
|
1006
|
-
return null;
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
|
-
if (Object.hasOwn(record, field)) {
|
|
1010
|
-
return record[field];
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
|
-
const camelField = field.replace(/_([a-z])/g, (_match, letter) => letter.toUpperCase());
|
|
1014
|
-
if (camelField !== field && Object.hasOwn(record, camelField)) {
|
|
1015
|
-
return record[camelField];
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
const snakeField = field.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
|
|
1019
|
-
if (snakeField !== field && Object.hasOwn(record, snakeField)) {
|
|
1020
|
-
return record[snakeField];
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
|
-
return null;
|
|
1024
|
-
}
|
|
1025
|
-
|
|
1026
486
|
async getTableColumns(table) {
|
|
1027
487
|
const tableInfo = this.getTableDef(table);
|
|
1028
488
|
const fieldNames = Object.keys(tableInfo);
|
|
@@ -1047,71 +507,6 @@ class DbHelper {
|
|
|
1047
507
|
}
|
|
1048
508
|
}
|
|
1049
509
|
|
|
1050
|
-
async prepareBaseReadContext(options, validation, buildPrepared) {
|
|
1051
|
-
const cleanWhere = clearDeep(options.where || {});
|
|
1052
|
-
const prepared = await buildPrepared.call(this, options, cleanWhere, validation);
|
|
1053
|
-
|
|
1054
|
-
return {
|
|
1055
|
-
prepared: prepared,
|
|
1056
|
-
whereFiltered: addDefaultStateFilter(prepared.where, prepared.tableQualifier, prepared.leftJoins.length > 0, this.beflyMode)
|
|
1057
|
-
};
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
async buildPreparedReadOptions(options, cleanWhere, validation) {
|
|
1061
|
-
if (validation.hasLeftJoin) {
|
|
1062
|
-
return await this.normalizeLeftJoinOptions(options, cleanWhere, validation.classifiedFields);
|
|
1063
|
-
}
|
|
1064
|
-
|
|
1065
|
-
return await this.normalizeSingleTableOptions(options, cleanWhere, validation.classifiedFields);
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
buildPreparedCountOptions(options, cleanWhere, validation) {
|
|
1069
|
-
if (validation.hasLeftJoin) {
|
|
1070
|
-
return this.normalizeLeftJoinCountOptions(options, cleanWhere);
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
|
-
const snakeTable = snakeCase(Array.isArray(options.table) ? options.table[0] : options.table);
|
|
1074
|
-
return {
|
|
1075
|
-
table: snakeTable,
|
|
1076
|
-
tableQualifier: snakeTable,
|
|
1077
|
-
fields: [],
|
|
1078
|
-
where: whereKeysToSnake(cleanWhere),
|
|
1079
|
-
leftJoins: [],
|
|
1080
|
-
orderBy: [],
|
|
1081
|
-
page: options.page || 1,
|
|
1082
|
-
limit: options.limit || 10
|
|
1083
|
-
};
|
|
1084
|
-
}
|
|
1085
|
-
|
|
1086
|
-
async prepareReadContext(options, validation) {
|
|
1087
|
-
return await this.prepareBaseReadContext(options, validation, this.buildPreparedReadOptions);
|
|
1088
|
-
}
|
|
1089
|
-
|
|
1090
|
-
async prepareCountContext(options, validation) {
|
|
1091
|
-
return await this.prepareBaseReadContext(options, validation, this.buildPreparedCountOptions);
|
|
1092
|
-
}
|
|
1093
|
-
|
|
1094
|
-
prepareSingleTableWhere(table, where, useDefaultStateFilter = true) {
|
|
1095
|
-
const snakeTable = snakeCase(table);
|
|
1096
|
-
const snakeWhere = whereKeysToSnake(clearDeep(where || {}));
|
|
1097
|
-
|
|
1098
|
-
return {
|
|
1099
|
-
snakeTable: snakeTable,
|
|
1100
|
-
whereFiltered: useDefaultStateFilter ? addDefaultStateFilter(snakeWhere, snakeTable, false, this.beflyMode) : snakeWhere
|
|
1101
|
-
};
|
|
1102
|
-
}
|
|
1103
|
-
|
|
1104
|
-
prepareRequiredSingleTableWhere(table, where, useDefaultStateFilter, label) {
|
|
1105
|
-
const snakeTable = snakeCase(table);
|
|
1106
|
-
const snakeWhere = whereKeysToSnake(clearDeep(where || {}));
|
|
1107
|
-
validateEffectiveWhere(snakeWhere, label);
|
|
1108
|
-
|
|
1109
|
-
return {
|
|
1110
|
-
snakeTable: snakeTable,
|
|
1111
|
-
whereFiltered: useDefaultStateFilter ? addDefaultStateFilter(snakeWhere, snakeTable, false, this.beflyMode) : snakeWhere
|
|
1112
|
-
};
|
|
1113
|
-
}
|
|
1114
|
-
|
|
1115
510
|
async createInsertRows(table, snakeTable, dataList, now) {
|
|
1116
511
|
if (this.beflyMode === "manual") {
|
|
1117
512
|
return {
|
|
@@ -1147,7 +542,7 @@ class DbHelper {
|
|
|
1147
542
|
const processedList = dataList.map((data, index) => {
|
|
1148
543
|
const id = ids[index];
|
|
1149
544
|
if (dataList.length > 1) {
|
|
1150
|
-
|
|
545
|
+
assertGeneratedBatchId(id, snakeTable, index);
|
|
1151
546
|
}
|
|
1152
547
|
|
|
1153
548
|
return this._buildInsertRow({
|
|
@@ -1195,9 +590,8 @@ class DbHelper {
|
|
|
1195
590
|
}
|
|
1196
591
|
|
|
1197
592
|
async getCount(options) {
|
|
1198
|
-
const
|
|
1199
|
-
const
|
|
1200
|
-
const result = await this.fetchCount(prepared, whereFiltered, "COUNT(*) as count");
|
|
593
|
+
const prepared = this.createDbParse().parseCount(options);
|
|
594
|
+
const result = await this.fetchCount(prepared, "COUNT(*) as count");
|
|
1201
595
|
|
|
1202
596
|
return {
|
|
1203
597
|
data: result.total,
|
|
@@ -1206,9 +600,8 @@ class DbHelper {
|
|
|
1206
600
|
}
|
|
1207
601
|
|
|
1208
602
|
async getOne(options) {
|
|
1209
|
-
const
|
|
1210
|
-
const
|
|
1211
|
-
const builder = this.createSqlBuilder().select(prepared.fields).from(prepared.table).where(whereFiltered);
|
|
603
|
+
const prepared = await this.createDbParse().parseRead(options, "one");
|
|
604
|
+
const builder = this.createSqlBuilder().select(prepared.fields).from(prepared.table).where(prepared.where);
|
|
1212
605
|
this.applyLeftJoins(builder, prepared.leftJoins);
|
|
1213
606
|
|
|
1214
607
|
const { sql, params } = builder.toSelectSql();
|
|
@@ -1223,14 +616,16 @@ class DbHelper {
|
|
|
1223
616
|
}
|
|
1224
617
|
|
|
1225
618
|
async getList(options) {
|
|
1226
|
-
const
|
|
1227
|
-
const
|
|
1228
|
-
validatePageLimitRange(prepared, options.table);
|
|
1229
|
-
const countResult = await this.fetchCount(prepared, whereFiltered, "COUNT(*) as total");
|
|
619
|
+
const prepared = await this.createDbParse().parseRead(options, "list");
|
|
620
|
+
const countResult = await this.fetchCount(prepared, "COUNT(*) as total");
|
|
1230
621
|
const total = countResult.total;
|
|
1231
622
|
|
|
1232
623
|
const offset = (prepared.page - 1) * prepared.limit;
|
|
1233
|
-
const dataBuilder = this.
|
|
624
|
+
const dataBuilder = this.createSqlBuilder().select(prepared.fields).from(prepared.table).where(prepared.where).limit(prepared.limit).offset(offset);
|
|
625
|
+
this.applyLeftJoins(dataBuilder, prepared.leftJoins);
|
|
626
|
+
if (prepared.orderBy && prepared.orderBy.length > 0) {
|
|
627
|
+
dataBuilder.orderBy(prepared.orderBy);
|
|
628
|
+
}
|
|
1234
629
|
const { sql: dataSql, params: dataParams } = dataBuilder.toSelectSql();
|
|
1235
630
|
|
|
1236
631
|
if (total === 0) {
|
|
@@ -1273,10 +668,19 @@ class DbHelper {
|
|
|
1273
668
|
async getAll(options) {
|
|
1274
669
|
const MAX_LIMIT = 100000;
|
|
1275
670
|
const WARNING_LIMIT = 10000;
|
|
1276
|
-
const prepareOptions =
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
671
|
+
const prepareOptions = {
|
|
672
|
+
table: options.table,
|
|
673
|
+
page: 1,
|
|
674
|
+
limit: 10
|
|
675
|
+
};
|
|
676
|
+
|
|
677
|
+
if (options.fields !== undefined) prepareOptions.fields = options.fields;
|
|
678
|
+
if (options.where !== undefined) prepareOptions.where = options.where;
|
|
679
|
+
if (options.leftJoin !== undefined) prepareOptions.leftJoin = options.leftJoin;
|
|
680
|
+
if (options.orderBy !== undefined) prepareOptions.orderBy = options.orderBy;
|
|
681
|
+
|
|
682
|
+
const prepared = await this.createDbParse().parseRead(prepareOptions, "all");
|
|
683
|
+
const countResult = await this.fetchCount(prepared, "COUNT(*) as total");
|
|
1280
684
|
const total = countResult.total;
|
|
1281
685
|
|
|
1282
686
|
if (total === 0) {
|
|
@@ -1291,7 +695,11 @@ class DbHelper {
|
|
|
1291
695
|
};
|
|
1292
696
|
}
|
|
1293
697
|
|
|
1294
|
-
const dataBuilder = this.
|
|
698
|
+
const dataBuilder = this.createSqlBuilder().select(prepared.fields).from(prepared.table).where(prepared.where).limit(MAX_LIMIT);
|
|
699
|
+
this.applyLeftJoins(dataBuilder, prepared.leftJoins);
|
|
700
|
+
if (prepared.orderBy && prepared.orderBy.length > 0) {
|
|
701
|
+
dataBuilder.orderBy(prepared.orderBy);
|
|
702
|
+
}
|
|
1295
703
|
|
|
1296
704
|
const { sql: dataSql, params: dataParams } = dataBuilder.toSelectSql();
|
|
1297
705
|
const listExecuteRes = await this.execute(dataSql, dataParams);
|
|
@@ -1320,10 +728,9 @@ class DbHelper {
|
|
|
1320
728
|
}
|
|
1321
729
|
|
|
1322
730
|
async exists(options) {
|
|
1323
|
-
|
|
1324
|
-
const prepared = this.prepareSingleTableWhere(options.table, options.where, true);
|
|
731
|
+
const prepared = this.createDbParse().parseExists(options);
|
|
1325
732
|
|
|
1326
|
-
const builder = this.createSqlBuilder().selectRaw("COUNT(1) as cnt").from(prepared.snakeTable).where(prepared.
|
|
733
|
+
const builder = this.createSqlBuilder().selectRaw("COUNT(1) as cnt").from(prepared.snakeTable).where(prepared.where).limit(1);
|
|
1327
734
|
const { sql, params } = builder.toSelectSql();
|
|
1328
735
|
const executeRes = await this.execute(sql, params);
|
|
1329
736
|
const exists = (executeRes.data?.[0]?.cnt || 0) > 0;
|
|
@@ -1331,38 +738,55 @@ class DbHelper {
|
|
|
1331
738
|
}
|
|
1332
739
|
|
|
1333
740
|
async getFieldValue(options) {
|
|
1334
|
-
|
|
1335
|
-
const
|
|
1336
|
-
|
|
741
|
+
const parsed = await this.createDbParse().parseFieldValue(options);
|
|
742
|
+
const builder = this.createSqlBuilder().select(parsed.prepared.fields).from(parsed.prepared.table).where(parsed.prepared.where);
|
|
743
|
+
this.applyLeftJoins(builder, parsed.prepared.leftJoins);
|
|
744
|
+
if (parsed.prepared.orderBy && parsed.prepared.orderBy.length > 0) {
|
|
745
|
+
builder.orderBy(parsed.prepared.orderBy);
|
|
746
|
+
}
|
|
747
|
+
const { sql, params } = builder.toSelectSql();
|
|
748
|
+
const executeRes = await this.execute(sql, params);
|
|
749
|
+
const result = this.normalizeRowData(executeRes.data?.[0] || null);
|
|
750
|
+
let value = null;
|
|
751
|
+
let hasValue = false;
|
|
752
|
+
|
|
753
|
+
if (isPlainObject(result)) {
|
|
754
|
+
if (Object.hasOwn(result, parsed.field)) {
|
|
755
|
+
value = result[parsed.field];
|
|
756
|
+
hasValue = true;
|
|
757
|
+
}
|
|
1337
758
|
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
if (options.limit !== undefined) oneOptions.limit = options.limit;
|
|
1346
|
-
oneOptions.fields = [field];
|
|
759
|
+
if (!hasValue) {
|
|
760
|
+
const camelField = parsed.field.replace(/_([a-z])/g, (_match, letter) => letter.toUpperCase());
|
|
761
|
+
if (camelField !== parsed.field && Object.hasOwn(result, camelField)) {
|
|
762
|
+
value = result[camelField];
|
|
763
|
+
hasValue = true;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
1347
766
|
|
|
1348
|
-
|
|
767
|
+
if (!hasValue) {
|
|
768
|
+
const snakeField = parsed.field.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
|
|
769
|
+
if (snakeField !== parsed.field && Object.hasOwn(result, snakeField)) {
|
|
770
|
+
value = result[snakeField];
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
}
|
|
1349
774
|
|
|
1350
|
-
const result = oneRes.data;
|
|
1351
|
-
const value = this.resolveFieldValue(result, field);
|
|
1352
775
|
return {
|
|
1353
776
|
data: value,
|
|
1354
|
-
sql:
|
|
777
|
+
sql: executeRes.sql
|
|
1355
778
|
};
|
|
1356
779
|
}
|
|
1357
780
|
|
|
1358
781
|
async insData(options) {
|
|
1359
|
-
|
|
1360
|
-
const { table, data } =
|
|
1361
|
-
const snakeTable = snakeCase(table);
|
|
782
|
+
const parsed = this.createDbParse().parseInsert(options);
|
|
783
|
+
const { table, snakeTable, data } = parsed;
|
|
1362
784
|
const now = Date.now();
|
|
1363
785
|
const insertRows = await this.createInsertRows(table, snakeTable, [data], now);
|
|
1364
786
|
const processed = insertRows.processedList[0];
|
|
1365
787
|
|
|
788
|
+
assertWriteDataHasFields(processed, "插入数据必须至少有一个字段", snakeTable);
|
|
789
|
+
|
|
1366
790
|
assertNoUndefinedInRecord(processed, `insData 插入数据 (table: ${snakeTable})`);
|
|
1367
791
|
|
|
1368
792
|
const builder = this.createSqlBuilder();
|
|
@@ -1376,7 +800,9 @@ class DbHelper {
|
|
|
1376
800
|
}
|
|
1377
801
|
|
|
1378
802
|
async insBatch(table, dataList) {
|
|
1379
|
-
|
|
803
|
+
const parsed = this.createDbParse().parseInsertBatch(table, dataList);
|
|
804
|
+
table = parsed.table;
|
|
805
|
+
dataList = parsed.dataList;
|
|
1380
806
|
if (dataList.length === 0) {
|
|
1381
807
|
return {
|
|
1382
808
|
data: [],
|
|
@@ -1385,9 +811,9 @@ class DbHelper {
|
|
|
1385
811
|
}
|
|
1386
812
|
|
|
1387
813
|
const MAX_BATCH_SIZE = 1000;
|
|
1388
|
-
|
|
814
|
+
assertInsertBatchSize(dataList.length, MAX_BATCH_SIZE);
|
|
1389
815
|
|
|
1390
|
-
const snakeTable =
|
|
816
|
+
const snakeTable = parsed.snakeTable;
|
|
1391
817
|
const now = Date.now();
|
|
1392
818
|
const insertRows = await this.createInsertRows(table, snakeTable, dataList, now);
|
|
1393
819
|
const processedList = insertRows.processedList;
|
|
@@ -1423,8 +849,9 @@ class DbHelper {
|
|
|
1423
849
|
}
|
|
1424
850
|
|
|
1425
851
|
async delForceBatch(table, ids) {
|
|
1426
|
-
|
|
1427
|
-
|
|
852
|
+
const parsed = this.createDbParse().parseDelForceBatch(table, ids);
|
|
853
|
+
table = parsed.table;
|
|
854
|
+
ids = parsed.ids;
|
|
1428
855
|
if (ids.length === 0) {
|
|
1429
856
|
return {
|
|
1430
857
|
data: 0,
|
|
@@ -1432,7 +859,7 @@ class DbHelper {
|
|
|
1432
859
|
};
|
|
1433
860
|
}
|
|
1434
861
|
|
|
1435
|
-
const snakeTable =
|
|
862
|
+
const snakeTable = parsed.snakeTable;
|
|
1436
863
|
const query = SqlBuilder.toDeleteInSql({
|
|
1437
864
|
table: snakeTable,
|
|
1438
865
|
idField: "id",
|
|
@@ -1448,7 +875,9 @@ class DbHelper {
|
|
|
1448
875
|
}
|
|
1449
876
|
|
|
1450
877
|
async updBatch(table, dataList) {
|
|
1451
|
-
|
|
878
|
+
const parsed = this.createDbParse().parseUpdateBatch(table, dataList);
|
|
879
|
+
table = parsed.table;
|
|
880
|
+
dataList = parsed.dataList;
|
|
1452
881
|
if (dataList.length === 0) {
|
|
1453
882
|
return {
|
|
1454
883
|
data: 0,
|
|
@@ -1456,14 +885,14 @@ class DbHelper {
|
|
|
1456
885
|
};
|
|
1457
886
|
}
|
|
1458
887
|
|
|
1459
|
-
const snakeTable =
|
|
888
|
+
const snakeTable = parsed.snakeTable;
|
|
1460
889
|
const now = Date.now();
|
|
1461
890
|
|
|
1462
891
|
const processedList = [];
|
|
1463
892
|
const fieldSet = new Set();
|
|
1464
893
|
|
|
1465
894
|
for (const item of dataList) {
|
|
1466
|
-
const userData = this.
|
|
895
|
+
const userData = this._prepareWriteUserData(item.data, true);
|
|
1467
896
|
|
|
1468
897
|
for (const key of Object.keys(userData)) {
|
|
1469
898
|
fieldSet.add(key);
|
|
@@ -1501,14 +930,12 @@ class DbHelper {
|
|
|
1501
930
|
}
|
|
1502
931
|
|
|
1503
932
|
async updData(options) {
|
|
1504
|
-
|
|
1505
|
-
validateTableWhereOptions(options, "updData", true);
|
|
1506
|
-
const { table, data, where } = options;
|
|
1507
|
-
const prepared = this.prepareRequiredSingleTableWhere(table, where, true, "updData");
|
|
933
|
+
const parsed = this.createDbParse().parseUpdate(options);
|
|
1508
934
|
|
|
1509
|
-
const processed = this._buildUpdateRow({ data: data, now: Date.now(), allowState: true, beflyMode: this.beflyMode });
|
|
1510
|
-
|
|
1511
|
-
const
|
|
935
|
+
const processed = this._buildUpdateRow({ data: parsed.data, now: Date.now(), allowState: true, beflyMode: this.beflyMode });
|
|
936
|
+
assertWriteDataHasFields(processed, "更新数据必须至少有一个字段", parsed.snakeTable);
|
|
937
|
+
const builder = this.createSqlBuilder().where(parsed.where);
|
|
938
|
+
const { sql, params } = builder.toUpdateSql(parsed.snakeTable, processed);
|
|
1512
939
|
|
|
1513
940
|
const executeRes = await this.execute(sql, params);
|
|
1514
941
|
const changes = normalizeSqlMetaNumber(executeRes.data?.affectedRows);
|
|
@@ -1519,9 +946,7 @@ class DbHelper {
|
|
|
1519
946
|
}
|
|
1520
947
|
|
|
1521
948
|
async delData(options) {
|
|
1522
|
-
|
|
1523
|
-
const { table, where } = options;
|
|
1524
|
-
const prepared = this.prepareRequiredSingleTableWhere(table, where, true, "delData");
|
|
949
|
+
const parsed = this.createDbParse().parseDelete(options, false);
|
|
1525
950
|
const now = Date.now();
|
|
1526
951
|
const processed = {
|
|
1527
952
|
state: 0,
|
|
@@ -1532,8 +957,8 @@ class DbHelper {
|
|
|
1532
957
|
processed.updated_at = now;
|
|
1533
958
|
}
|
|
1534
959
|
|
|
1535
|
-
const builder = this.createSqlBuilder().where(
|
|
1536
|
-
const { sql, params } = builder.toUpdateSql(
|
|
960
|
+
const builder = this.createSqlBuilder().where(parsed.where);
|
|
961
|
+
const { sql, params } = builder.toUpdateSql(parsed.snakeTable, processed);
|
|
1537
962
|
const executeRes = await this.execute(sql, params);
|
|
1538
963
|
const changes = normalizeSqlMetaNumber(executeRes.data?.affectedRows);
|
|
1539
964
|
|
|
@@ -1544,12 +969,10 @@ class DbHelper {
|
|
|
1544
969
|
}
|
|
1545
970
|
|
|
1546
971
|
async delForce(options) {
|
|
1547
|
-
|
|
1548
|
-
const { table, where } = options;
|
|
1549
|
-
const prepared = this.prepareRequiredSingleTableWhere(table, where, false, "delForce");
|
|
972
|
+
const parsed = this.createDbParse().parseDelete(options, true);
|
|
1550
973
|
|
|
1551
|
-
const builder = this.createSqlBuilder().where(
|
|
1552
|
-
const { sql, params } = builder.toDeleteSql(
|
|
974
|
+
const builder = this.createSqlBuilder().where(parsed.where);
|
|
975
|
+
const { sql, params } = builder.toDeleteSql(parsed.snakeTable);
|
|
1553
976
|
|
|
1554
977
|
const executeRes = await this.execute(sql, params);
|
|
1555
978
|
const changes = normalizeSqlMetaNumber(executeRes.data?.affectedRows);
|
|
@@ -1560,7 +983,6 @@ class DbHelper {
|
|
|
1560
983
|
}
|
|
1561
984
|
|
|
1562
985
|
async disableData(options) {
|
|
1563
|
-
validateTableWhereOptions(options, "disableData", true);
|
|
1564
986
|
const { table, where } = options;
|
|
1565
987
|
|
|
1566
988
|
return await this.updData({
|
|
@@ -1573,7 +995,6 @@ class DbHelper {
|
|
|
1573
995
|
}
|
|
1574
996
|
|
|
1575
997
|
async enableData(options) {
|
|
1576
|
-
validateTableWhereOptions(options, "enableData", true);
|
|
1577
998
|
const { table, where } = options;
|
|
1578
999
|
|
|
1579
1000
|
return await this.updData({
|
|
@@ -1586,18 +1007,16 @@ class DbHelper {
|
|
|
1586
1007
|
}
|
|
1587
1008
|
|
|
1588
1009
|
async increment(table, field, where, value = 1) {
|
|
1589
|
-
|
|
1590
|
-
const prepared = this.prepareRequiredSingleTableWhere(table, where, true, "increment");
|
|
1591
|
-
const snakeField = snakeCase(field);
|
|
1010
|
+
const parsed = this.createDbParse().parseIncrement(table, field, where, value, "increment");
|
|
1592
1011
|
|
|
1593
|
-
const builder = this.createSqlBuilder().where(
|
|
1012
|
+
const builder = this.createSqlBuilder().where(parsed.where);
|
|
1594
1013
|
const { sql: whereClause, params: whereParams } = builder.getWhereConditions();
|
|
1595
1014
|
|
|
1596
|
-
const quotedTable = quoteIdentMySql(
|
|
1597
|
-
const quotedField = quoteIdentMySql(snakeField);
|
|
1015
|
+
const quotedTable = quoteIdentMySql(parsed.snakeTable);
|
|
1016
|
+
const quotedField = quoteIdentMySql(parsed.snakeField);
|
|
1598
1017
|
const sql = whereClause ? `UPDATE ${quotedTable} SET ${quotedField} = ${quotedField} + ? WHERE ${whereClause}` : `UPDATE ${quotedTable} SET ${quotedField} = ${quotedField} + ?`;
|
|
1599
1018
|
|
|
1600
|
-
const params = [value];
|
|
1019
|
+
const params = [parsed.value];
|
|
1601
1020
|
for (const param of whereParams) {
|
|
1602
1021
|
params.push(param);
|
|
1603
1022
|
}
|