befly 3.21.1 → 3.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/apis/admin/list.js +1 -1
- package/apis/admin/upd.js +2 -2
- package/apis/dict/all.js +1 -1
- package/apis/dict/detail.js +1 -1
- package/apis/dict/list.js +1 -1
- 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/checks/config.js +1 -3
- package/checks/table.js +2 -15
- package/configs/beflyConfig.json +1 -3
- package/index.js +5 -10
- package/lib/dbHelper.js +201 -736
- package/lib/dbParse.js +1045 -0
- package/lib/dbUtil.js +83 -438
- package/lib/logger.js +21 -45
- package/lib/sqlBuilder.js +78 -294
- package/package.json +2 -2
- package/plugins/mysql.js +2 -1
- package/scripts/syncDb/context.js +62 -47
- package/scripts/syncDb/diff.js +78 -15
- package/scripts/syncDb/index.js +16 -46
- package/scripts/syncDb/report.js +97 -98
- package/tables/admin.json +24 -0
- package/tables/api.json +24 -0
- package/tables/dict.json +24 -0
- package/tables/dictType.json +24 -0
- package/tables/emailLog.json +24 -0
- package/tables/errorReport.json +140 -0
- package/tables/infoReport.json +123 -0
- package/tables/loginLog.json +24 -0
- package/tables/menu.json +24 -0
- package/tables/operateLog.json +24 -0
- package/tables/role.json +24 -0
- package/tables/sysConfig.json +24 -0
- package/utils/loggerUtils.js +9 -14
- package/utils/scanSources.js +3 -3
- package/scripts/syncDb/query.js +0 -26
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) : validateExplicitReadFields(options.fields);
|
|
618
|
-
|
|
619
|
-
return {
|
|
620
|
-
hasLeftJoin: hasLeftJoin,
|
|
621
|
-
classifiedFields: classifiedFields
|
|
622
|
-
};
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
function validateCountReadOptions(options, label) {
|
|
626
|
-
validateQueryOptions(options, label);
|
|
627
252
|
|
|
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 {
|
|
@@ -661,6 +268,30 @@ class DbHelper {
|
|
|
661
268
|
this.sql = options.sql || null;
|
|
662
269
|
this.isTransaction = options.isTransaction === true;
|
|
663
270
|
this.beflyMode = options.beflyMode === "manual" ? "manual" : "auto";
|
|
271
|
+
this.tables = isPlainObject(options.tables) ? options.tables : {};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
getTableDef(table) {
|
|
275
|
+
const tableName = parseTableRef(table).table;
|
|
276
|
+
|
|
277
|
+
if (!/^_?[a-z][a-z0-9]*(?:[A-Z][a-z0-9]*)*$/.test(tableName)) {
|
|
278
|
+
throw new Error(`表名必须使用小驼峰写法 (table: ${tableName})`, {
|
|
279
|
+
cause: null,
|
|
280
|
+
code: "validation"
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const tableInfo = this.tables[tableName];
|
|
285
|
+
|
|
286
|
+
if (!isPlainObject(tableInfo)) {
|
|
287
|
+
const sourceLabel = tableName.startsWith("befly") ? "packages/core/tables" : "app tables";
|
|
288
|
+
throw new Error(`表 ${tableName} 缺少本地 tables 定义,请检查 ${sourceLabel}`, {
|
|
289
|
+
cause: null,
|
|
290
|
+
code: "runtime"
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return tableInfo;
|
|
664
295
|
}
|
|
665
296
|
|
|
666
297
|
async execute(sql, params) {
|
|
@@ -764,20 +395,16 @@ class DbHelper {
|
|
|
764
395
|
return new SqlBuilder({ quoteIdent: quoteIdentMySql });
|
|
765
396
|
}
|
|
766
397
|
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
}
|
|
773
|
-
if (prepared.orderBy && prepared.orderBy.length > 0) {
|
|
774
|
-
builder.orderBy(prepared.orderBy);
|
|
775
|
-
}
|
|
776
|
-
return builder;
|
|
398
|
+
createDbParse() {
|
|
399
|
+
return new DbParse({
|
|
400
|
+
tables: this.tables,
|
|
401
|
+
beflyMode: this.beflyMode,
|
|
402
|
+
getTableColumns: this.getTableColumns.bind(this)
|
|
403
|
+
});
|
|
777
404
|
}
|
|
778
405
|
|
|
779
|
-
async fetchCount(prepared,
|
|
780
|
-
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);
|
|
781
408
|
this.applyLeftJoins(builder, prepared.leftJoins);
|
|
782
409
|
const result = builder.toSelectSql();
|
|
783
410
|
const executeRes = await this.execute(result.sql, result.params);
|
|
@@ -815,22 +442,11 @@ class DbHelper {
|
|
|
815
442
|
return normalizeBigIntValues(deserializedList);
|
|
816
443
|
}
|
|
817
444
|
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
return classifiedFields.fields.map((field) => processJoinField(field));
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
return [];
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
_cleanAndSnakeAndSerializeWriteData(data, excludeValues = [null, undefined]) {
|
|
827
|
-
const cleanData = fieldClear(data, { excludeValues: excludeValues });
|
|
828
|
-
return serializeArrayFields(keysToSnake(cleanData));
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
_stripSystemFieldsForWrite(data, allowState) {
|
|
445
|
+
_prepareWriteUserData(data, allowState) {
|
|
446
|
+
const cleanData = fieldClear(data, { excludeValues: [null, undefined] });
|
|
832
447
|
const result = {};
|
|
833
|
-
|
|
448
|
+
|
|
449
|
+
for (const [key, value] of Object.entries(serializeArrayFields(keysToSnake(cleanData)))) {
|
|
834
450
|
if (key === "id") continue;
|
|
835
451
|
if (key === "created_at") continue;
|
|
836
452
|
if (key === "updated_at") continue;
|
|
@@ -838,18 +454,15 @@ class DbHelper {
|
|
|
838
454
|
if (!allowState && key === "state") continue;
|
|
839
455
|
result[key] = value;
|
|
840
456
|
}
|
|
841
|
-
return result;
|
|
842
|
-
}
|
|
843
457
|
|
|
844
|
-
|
|
845
|
-
return this._stripSystemFieldsForWrite(this._cleanAndSnakeAndSerializeWriteData(data), allowState);
|
|
458
|
+
return result;
|
|
846
459
|
}
|
|
847
460
|
|
|
848
461
|
_buildInsertRow(options) {
|
|
849
462
|
const result = { ...this._prepareWriteUserData(options.data, false) };
|
|
850
463
|
|
|
851
464
|
if (options.beflyMode === "auto") {
|
|
852
|
-
|
|
465
|
+
assertTimeIdValue(options.id);
|
|
853
466
|
result.id = options.id;
|
|
854
467
|
}
|
|
855
468
|
|
|
@@ -870,126 +483,18 @@ class DbHelper {
|
|
|
870
483
|
return result;
|
|
871
484
|
}
|
|
872
485
|
|
|
873
|
-
_buildPartialUpdateData(options) {
|
|
874
|
-
return this._prepareWriteUserData(options.data, options.allowState);
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
normalizeLeftJoinOptions(options, cleanWhere, classifiedFields) {
|
|
878
|
-
const processedFields = this._joinFieldsToSnake(classifiedFields);
|
|
879
|
-
const mainTableRef = options.table[0];
|
|
880
|
-
const joinTableRefs = options.table.slice(1);
|
|
881
|
-
const normalizedTableRef = normalizeTableRef(mainTableRef);
|
|
882
|
-
const mainQualifier = getJoinMainQualifier(mainTableRef);
|
|
883
|
-
const leftJoins = options.leftJoin.map((joinItem, index) => parseLeftJoinItem(joinTableRefs[index], joinItem));
|
|
884
|
-
|
|
885
|
-
return {
|
|
886
|
-
table: normalizedTableRef,
|
|
887
|
-
tableQualifier: mainQualifier,
|
|
888
|
-
fields: processedFields,
|
|
889
|
-
where: processJoinWhere(cleanWhere),
|
|
890
|
-
leftJoins: leftJoins,
|
|
891
|
-
orderBy: processJoinOrderBy(options.orderBy || []),
|
|
892
|
-
page: options.page || 1,
|
|
893
|
-
limit: options.limit || 10
|
|
894
|
-
};
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
normalizeLeftJoinCountOptions(options, cleanWhere) {
|
|
898
|
-
const mainTableRef = options.table[0];
|
|
899
|
-
const joinTableRefs = options.table.slice(1);
|
|
900
|
-
const normalizedTableRef = normalizeTableRef(mainTableRef);
|
|
901
|
-
const mainQualifier = getJoinMainQualifier(mainTableRef);
|
|
902
|
-
const leftJoins = options.leftJoin.map((joinItem, index) => parseLeftJoinItem(joinTableRefs[index], joinItem));
|
|
903
|
-
|
|
904
|
-
return {
|
|
905
|
-
table: normalizedTableRef,
|
|
906
|
-
tableQualifier: mainQualifier,
|
|
907
|
-
fields: [],
|
|
908
|
-
where: processJoinWhere(cleanWhere),
|
|
909
|
-
leftJoins: leftJoins,
|
|
910
|
-
orderBy: [],
|
|
911
|
-
page: options.page || 1,
|
|
912
|
-
limit: options.limit || 10
|
|
913
|
-
};
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
async normalizeSingleTableOptions(options, cleanWhere, classifiedFields) {
|
|
917
|
-
const tableRef = Array.isArray(options.table) ? options.table[0] : options.table;
|
|
918
|
-
const snakeTable = snakeCase(tableRef);
|
|
919
|
-
const processedFields = await fieldsToSnake(snakeTable, classifiedFields, this.getTableColumns.bind(this));
|
|
920
|
-
|
|
921
|
-
return {
|
|
922
|
-
table: snakeTable,
|
|
923
|
-
tableQualifier: snakeTable,
|
|
924
|
-
fields: processedFields,
|
|
925
|
-
where: whereKeysToSnake(cleanWhere),
|
|
926
|
-
leftJoins: [],
|
|
927
|
-
orderBy: orderByToSnake(options.orderBy || []),
|
|
928
|
-
page: options.page || 1,
|
|
929
|
-
limit: options.limit || 10
|
|
930
|
-
};
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
buildQueryOptions(options, defaults) {
|
|
934
|
-
const output = {
|
|
935
|
-
table: options.table,
|
|
936
|
-
page: defaults.page,
|
|
937
|
-
limit: defaults.limit
|
|
938
|
-
};
|
|
939
|
-
|
|
940
|
-
if (options.fields !== undefined) output.fields = options.fields;
|
|
941
|
-
if (options.where !== undefined) output.where = options.where;
|
|
942
|
-
if (options.leftJoin !== undefined) output.leftJoin = options.leftJoin;
|
|
943
|
-
if (options.orderBy !== undefined) output.orderBy = options.orderBy;
|
|
944
|
-
return output;
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
resolveFieldValue(record, field) {
|
|
948
|
-
if (!isPlainObject(record)) {
|
|
949
|
-
return null;
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
if (Object.hasOwn(record, field)) {
|
|
953
|
-
return record[field];
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
const camelField = field.replace(/_([a-z])/g, (_match, letter) => letter.toUpperCase());
|
|
957
|
-
if (camelField !== field && Object.hasOwn(record, camelField)) {
|
|
958
|
-
return record[camelField];
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
const snakeField = field.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
|
|
962
|
-
if (snakeField !== field && Object.hasOwn(record, snakeField)) {
|
|
963
|
-
return record[snakeField];
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
return null;
|
|
967
|
-
}
|
|
968
|
-
|
|
969
486
|
async getTableColumns(table) {
|
|
970
|
-
const
|
|
971
|
-
const
|
|
972
|
-
const quotedTable = quoteIdentMySql(snakeCase(parsed.table));
|
|
973
|
-
const executeRes = await this.execute(`SHOW COLUMNS FROM ${schemaPart}${quotedTable}`, []);
|
|
974
|
-
const result = executeRes.data;
|
|
487
|
+
const tableInfo = this.getTableDef(table);
|
|
488
|
+
const fieldNames = Object.keys(tableInfo);
|
|
975
489
|
|
|
976
|
-
if (
|
|
977
|
-
throw new Error(`表 ${table}
|
|
490
|
+
if (fieldNames.length === 0) {
|
|
491
|
+
throw new Error(`表 ${table} 的本地 tables 定义为空或没有字段`, {
|
|
978
492
|
cause: null,
|
|
979
493
|
code: "runtime"
|
|
980
494
|
});
|
|
981
495
|
}
|
|
982
496
|
|
|
983
|
-
|
|
984
|
-
for (const row of result) {
|
|
985
|
-
const record = row;
|
|
986
|
-
const name = record.Field;
|
|
987
|
-
if (isNonEmptyString(name)) {
|
|
988
|
-
columnNames.push(name);
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
return columnNames;
|
|
497
|
+
return fieldNames.map((fieldName) => snakeCase(fieldName));
|
|
993
498
|
}
|
|
994
499
|
|
|
995
500
|
applyLeftJoins(builder, leftJoins) {
|
|
@@ -1002,71 +507,6 @@ class DbHelper {
|
|
|
1002
507
|
}
|
|
1003
508
|
}
|
|
1004
509
|
|
|
1005
|
-
async prepareBaseReadContext(options, validation, buildPrepared) {
|
|
1006
|
-
const cleanWhere = clearDeep(options.where || {});
|
|
1007
|
-
const prepared = await buildPrepared.call(this, options, cleanWhere, validation);
|
|
1008
|
-
|
|
1009
|
-
return {
|
|
1010
|
-
prepared: prepared,
|
|
1011
|
-
whereFiltered: addDefaultStateFilter(prepared.where, prepared.tableQualifier, prepared.leftJoins.length > 0, this.beflyMode)
|
|
1012
|
-
};
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
async buildPreparedReadOptions(options, cleanWhere, validation) {
|
|
1016
|
-
if (validation.hasLeftJoin) {
|
|
1017
|
-
return this.normalizeLeftJoinOptions(options, cleanWhere, validation.classifiedFields);
|
|
1018
|
-
}
|
|
1019
|
-
|
|
1020
|
-
return await this.normalizeSingleTableOptions(options, cleanWhere, validation.classifiedFields);
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
|
-
buildPreparedCountOptions(options, cleanWhere, validation) {
|
|
1024
|
-
if (validation.hasLeftJoin) {
|
|
1025
|
-
return this.normalizeLeftJoinCountOptions(options, cleanWhere);
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
const snakeTable = snakeCase(Array.isArray(options.table) ? options.table[0] : options.table);
|
|
1029
|
-
return {
|
|
1030
|
-
table: snakeTable,
|
|
1031
|
-
tableQualifier: snakeTable,
|
|
1032
|
-
fields: [],
|
|
1033
|
-
where: whereKeysToSnake(cleanWhere),
|
|
1034
|
-
leftJoins: [],
|
|
1035
|
-
orderBy: [],
|
|
1036
|
-
page: options.page || 1,
|
|
1037
|
-
limit: options.limit || 10
|
|
1038
|
-
};
|
|
1039
|
-
}
|
|
1040
|
-
|
|
1041
|
-
async prepareReadContext(options, validation) {
|
|
1042
|
-
return await this.prepareBaseReadContext(options, validation, this.buildPreparedReadOptions);
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
|
-
async prepareCountContext(options, validation) {
|
|
1046
|
-
return await this.prepareBaseReadContext(options, validation, this.buildPreparedCountOptions);
|
|
1047
|
-
}
|
|
1048
|
-
|
|
1049
|
-
prepareSingleTableWhere(table, where, useDefaultStateFilter = true) {
|
|
1050
|
-
const snakeTable = snakeCase(table);
|
|
1051
|
-
const snakeWhere = whereKeysToSnake(clearDeep(where || {}));
|
|
1052
|
-
|
|
1053
|
-
return {
|
|
1054
|
-
snakeTable: snakeTable,
|
|
1055
|
-
whereFiltered: useDefaultStateFilter ? addDefaultStateFilter(snakeWhere, snakeTable, false, this.beflyMode) : snakeWhere
|
|
1056
|
-
};
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
prepareRequiredSingleTableWhere(table, where, useDefaultStateFilter, label) {
|
|
1060
|
-
const snakeTable = snakeCase(table);
|
|
1061
|
-
const snakeWhere = whereKeysToSnake(clearDeep(where || {}));
|
|
1062
|
-
validateEffectiveWhere(snakeWhere, label);
|
|
1063
|
-
|
|
1064
|
-
return {
|
|
1065
|
-
snakeTable: snakeTable,
|
|
1066
|
-
whereFiltered: useDefaultStateFilter ? addDefaultStateFilter(snakeWhere, snakeTable, false, this.beflyMode) : snakeWhere
|
|
1067
|
-
};
|
|
1068
|
-
}
|
|
1069
|
-
|
|
1070
510
|
async createInsertRows(table, snakeTable, dataList, now) {
|
|
1071
511
|
if (this.beflyMode === "manual") {
|
|
1072
512
|
return {
|
|
@@ -1102,7 +542,7 @@ class DbHelper {
|
|
|
1102
542
|
const processedList = dataList.map((data, index) => {
|
|
1103
543
|
const id = ids[index];
|
|
1104
544
|
if (dataList.length > 1) {
|
|
1105
|
-
|
|
545
|
+
assertGeneratedBatchId(id, snakeTable, index);
|
|
1106
546
|
}
|
|
1107
547
|
|
|
1108
548
|
return this._buildInsertRow({
|
|
@@ -1150,9 +590,8 @@ class DbHelper {
|
|
|
1150
590
|
}
|
|
1151
591
|
|
|
1152
592
|
async getCount(options) {
|
|
1153
|
-
const
|
|
1154
|
-
const
|
|
1155
|
-
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");
|
|
1156
595
|
|
|
1157
596
|
return {
|
|
1158
597
|
data: result.total,
|
|
@@ -1161,9 +600,8 @@ class DbHelper {
|
|
|
1161
600
|
}
|
|
1162
601
|
|
|
1163
602
|
async getOne(options) {
|
|
1164
|
-
const
|
|
1165
|
-
const
|
|
1166
|
-
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);
|
|
1167
605
|
this.applyLeftJoins(builder, prepared.leftJoins);
|
|
1168
606
|
|
|
1169
607
|
const { sql, params } = builder.toSelectSql();
|
|
@@ -1178,14 +616,16 @@ class DbHelper {
|
|
|
1178
616
|
}
|
|
1179
617
|
|
|
1180
618
|
async getList(options) {
|
|
1181
|
-
const
|
|
1182
|
-
const
|
|
1183
|
-
validatePageLimitRange(prepared, options.table);
|
|
1184
|
-
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");
|
|
1185
621
|
const total = countResult.total;
|
|
1186
622
|
|
|
1187
623
|
const offset = (prepared.page - 1) * prepared.limit;
|
|
1188
|
-
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
|
+
}
|
|
1189
629
|
const { sql: dataSql, params: dataParams } = dataBuilder.toSelectSql();
|
|
1190
630
|
|
|
1191
631
|
if (total === 0) {
|
|
@@ -1228,10 +668,19 @@ class DbHelper {
|
|
|
1228
668
|
async getAll(options) {
|
|
1229
669
|
const MAX_LIMIT = 100000;
|
|
1230
670
|
const WARNING_LIMIT = 10000;
|
|
1231
|
-
const prepareOptions =
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
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");
|
|
1235
684
|
const total = countResult.total;
|
|
1236
685
|
|
|
1237
686
|
if (total === 0) {
|
|
@@ -1246,7 +695,11 @@ class DbHelper {
|
|
|
1246
695
|
};
|
|
1247
696
|
}
|
|
1248
697
|
|
|
1249
|
-
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
|
+
}
|
|
1250
703
|
|
|
1251
704
|
const { sql: dataSql, params: dataParams } = dataBuilder.toSelectSql();
|
|
1252
705
|
const listExecuteRes = await this.execute(dataSql, dataParams);
|
|
@@ -1275,10 +728,9 @@ class DbHelper {
|
|
|
1275
728
|
}
|
|
1276
729
|
|
|
1277
730
|
async exists(options) {
|
|
1278
|
-
|
|
1279
|
-
const prepared = this.prepareSingleTableWhere(options.table, options.where, true);
|
|
731
|
+
const prepared = this.createDbParse().parseExists(options);
|
|
1280
732
|
|
|
1281
|
-
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);
|
|
1282
734
|
const { sql, params } = builder.toSelectSql();
|
|
1283
735
|
const executeRes = await this.execute(sql, params);
|
|
1284
736
|
const exists = (executeRes.data?.[0]?.cnt || 0) > 0;
|
|
@@ -1286,38 +738,55 @@ class DbHelper {
|
|
|
1286
738
|
}
|
|
1287
739
|
|
|
1288
740
|
async getFieldValue(options) {
|
|
1289
|
-
|
|
1290
|
-
const
|
|
1291
|
-
|
|
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
|
+
}
|
|
1292
758
|
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
if (options.limit !== undefined) oneOptions.limit = options.limit;
|
|
1301
|
-
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
|
+
}
|
|
1302
766
|
|
|
1303
|
-
|
|
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
|
+
}
|
|
1304
774
|
|
|
1305
|
-
const result = oneRes.data;
|
|
1306
|
-
const value = this.resolveFieldValue(result, field);
|
|
1307
775
|
return {
|
|
1308
776
|
data: value,
|
|
1309
|
-
sql:
|
|
777
|
+
sql: executeRes.sql
|
|
1310
778
|
};
|
|
1311
779
|
}
|
|
1312
780
|
|
|
1313
781
|
async insData(options) {
|
|
1314
|
-
|
|
1315
|
-
const { table, data } =
|
|
1316
|
-
const snakeTable = snakeCase(table);
|
|
782
|
+
const parsed = this.createDbParse().parseInsert(options);
|
|
783
|
+
const { table, snakeTable, data } = parsed;
|
|
1317
784
|
const now = Date.now();
|
|
1318
785
|
const insertRows = await this.createInsertRows(table, snakeTable, [data], now);
|
|
1319
786
|
const processed = insertRows.processedList[0];
|
|
1320
787
|
|
|
788
|
+
assertWriteDataHasFields(processed, "插入数据必须至少有一个字段", snakeTable);
|
|
789
|
+
|
|
1321
790
|
assertNoUndefinedInRecord(processed, `insData 插入数据 (table: ${snakeTable})`);
|
|
1322
791
|
|
|
1323
792
|
const builder = this.createSqlBuilder();
|
|
@@ -1331,7 +800,9 @@ class DbHelper {
|
|
|
1331
800
|
}
|
|
1332
801
|
|
|
1333
802
|
async insBatch(table, dataList) {
|
|
1334
|
-
|
|
803
|
+
const parsed = this.createDbParse().parseInsertBatch(table, dataList);
|
|
804
|
+
table = parsed.table;
|
|
805
|
+
dataList = parsed.dataList;
|
|
1335
806
|
if (dataList.length === 0) {
|
|
1336
807
|
return {
|
|
1337
808
|
data: [],
|
|
@@ -1340,9 +811,9 @@ class DbHelper {
|
|
|
1340
811
|
}
|
|
1341
812
|
|
|
1342
813
|
const MAX_BATCH_SIZE = 1000;
|
|
1343
|
-
|
|
814
|
+
assertInsertBatchSize(dataList.length, MAX_BATCH_SIZE);
|
|
1344
815
|
|
|
1345
|
-
const snakeTable =
|
|
816
|
+
const snakeTable = parsed.snakeTable;
|
|
1346
817
|
const now = Date.now();
|
|
1347
818
|
const insertRows = await this.createInsertRows(table, snakeTable, dataList, now);
|
|
1348
819
|
const processedList = insertRows.processedList;
|
|
@@ -1378,8 +849,9 @@ class DbHelper {
|
|
|
1378
849
|
}
|
|
1379
850
|
|
|
1380
851
|
async delForceBatch(table, ids) {
|
|
1381
|
-
|
|
1382
|
-
|
|
852
|
+
const parsed = this.createDbParse().parseDelForceBatch(table, ids);
|
|
853
|
+
table = parsed.table;
|
|
854
|
+
ids = parsed.ids;
|
|
1383
855
|
if (ids.length === 0) {
|
|
1384
856
|
return {
|
|
1385
857
|
data: 0,
|
|
@@ -1387,7 +859,7 @@ class DbHelper {
|
|
|
1387
859
|
};
|
|
1388
860
|
}
|
|
1389
861
|
|
|
1390
|
-
const snakeTable =
|
|
862
|
+
const snakeTable = parsed.snakeTable;
|
|
1391
863
|
const query = SqlBuilder.toDeleteInSql({
|
|
1392
864
|
table: snakeTable,
|
|
1393
865
|
idField: "id",
|
|
@@ -1403,7 +875,9 @@ class DbHelper {
|
|
|
1403
875
|
}
|
|
1404
876
|
|
|
1405
877
|
async updBatch(table, dataList) {
|
|
1406
|
-
|
|
878
|
+
const parsed = this.createDbParse().parseUpdateBatch(table, dataList);
|
|
879
|
+
table = parsed.table;
|
|
880
|
+
dataList = parsed.dataList;
|
|
1407
881
|
if (dataList.length === 0) {
|
|
1408
882
|
return {
|
|
1409
883
|
data: 0,
|
|
@@ -1411,14 +885,14 @@ class DbHelper {
|
|
|
1411
885
|
};
|
|
1412
886
|
}
|
|
1413
887
|
|
|
1414
|
-
const snakeTable =
|
|
888
|
+
const snakeTable = parsed.snakeTable;
|
|
1415
889
|
const now = Date.now();
|
|
1416
890
|
|
|
1417
891
|
const processedList = [];
|
|
1418
892
|
const fieldSet = new Set();
|
|
1419
893
|
|
|
1420
894
|
for (const item of dataList) {
|
|
1421
|
-
const userData = this.
|
|
895
|
+
const userData = this._prepareWriteUserData(item.data, true);
|
|
1422
896
|
|
|
1423
897
|
for (const key of Object.keys(userData)) {
|
|
1424
898
|
fieldSet.add(key);
|
|
@@ -1456,14 +930,12 @@ class DbHelper {
|
|
|
1456
930
|
}
|
|
1457
931
|
|
|
1458
932
|
async updData(options) {
|
|
1459
|
-
|
|
1460
|
-
validateTableWhereOptions(options, "updData", true);
|
|
1461
|
-
const { table, data, where } = options;
|
|
1462
|
-
const prepared = this.prepareRequiredSingleTableWhere(table, where, true, "updData");
|
|
933
|
+
const parsed = this.createDbParse().parseUpdate(options);
|
|
1463
934
|
|
|
1464
|
-
const processed = this._buildUpdateRow({ data: data, now: Date.now(), allowState: true, beflyMode: this.beflyMode });
|
|
1465
|
-
|
|
1466
|
-
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);
|
|
1467
939
|
|
|
1468
940
|
const executeRes = await this.execute(sql, params);
|
|
1469
941
|
const changes = normalizeSqlMetaNumber(executeRes.data?.affectedRows);
|
|
@@ -1474,9 +946,7 @@ class DbHelper {
|
|
|
1474
946
|
}
|
|
1475
947
|
|
|
1476
948
|
async delData(options) {
|
|
1477
|
-
|
|
1478
|
-
const { table, where } = options;
|
|
1479
|
-
const prepared = this.prepareRequiredSingleTableWhere(table, where, true, "delData");
|
|
949
|
+
const parsed = this.createDbParse().parseDelete(options, false);
|
|
1480
950
|
const now = Date.now();
|
|
1481
951
|
const processed = {
|
|
1482
952
|
state: 0,
|
|
@@ -1487,8 +957,8 @@ class DbHelper {
|
|
|
1487
957
|
processed.updated_at = now;
|
|
1488
958
|
}
|
|
1489
959
|
|
|
1490
|
-
const builder = this.createSqlBuilder().where(
|
|
1491
|
-
const { sql, params } = builder.toUpdateSql(
|
|
960
|
+
const builder = this.createSqlBuilder().where(parsed.where);
|
|
961
|
+
const { sql, params } = builder.toUpdateSql(parsed.snakeTable, processed);
|
|
1492
962
|
const executeRes = await this.execute(sql, params);
|
|
1493
963
|
const changes = normalizeSqlMetaNumber(executeRes.data?.affectedRows);
|
|
1494
964
|
|
|
@@ -1499,12 +969,10 @@ class DbHelper {
|
|
|
1499
969
|
}
|
|
1500
970
|
|
|
1501
971
|
async delForce(options) {
|
|
1502
|
-
|
|
1503
|
-
const { table, where } = options;
|
|
1504
|
-
const prepared = this.prepareRequiredSingleTableWhere(table, where, false, "delForce");
|
|
972
|
+
const parsed = this.createDbParse().parseDelete(options, true);
|
|
1505
973
|
|
|
1506
|
-
const builder = this.createSqlBuilder().where(
|
|
1507
|
-
const { sql, params } = builder.toDeleteSql(
|
|
974
|
+
const builder = this.createSqlBuilder().where(parsed.where);
|
|
975
|
+
const { sql, params } = builder.toDeleteSql(parsed.snakeTable);
|
|
1508
976
|
|
|
1509
977
|
const executeRes = await this.execute(sql, params);
|
|
1510
978
|
const changes = normalizeSqlMetaNumber(executeRes.data?.affectedRows);
|
|
@@ -1515,7 +983,6 @@ class DbHelper {
|
|
|
1515
983
|
}
|
|
1516
984
|
|
|
1517
985
|
async disableData(options) {
|
|
1518
|
-
validateTableWhereOptions(options, "disableData", true);
|
|
1519
986
|
const { table, where } = options;
|
|
1520
987
|
|
|
1521
988
|
return await this.updData({
|
|
@@ -1528,7 +995,6 @@ class DbHelper {
|
|
|
1528
995
|
}
|
|
1529
996
|
|
|
1530
997
|
async enableData(options) {
|
|
1531
|
-
validateTableWhereOptions(options, "enableData", true);
|
|
1532
998
|
const { table, where } = options;
|
|
1533
999
|
|
|
1534
1000
|
return await this.updData({
|
|
@@ -1541,18 +1007,16 @@ class DbHelper {
|
|
|
1541
1007
|
}
|
|
1542
1008
|
|
|
1543
1009
|
async increment(table, field, where, value = 1) {
|
|
1544
|
-
|
|
1545
|
-
const prepared = this.prepareRequiredSingleTableWhere(table, where, true, "increment");
|
|
1546
|
-
const snakeField = snakeCase(field);
|
|
1010
|
+
const parsed = this.createDbParse().parseIncrement(table, field, where, value, "increment");
|
|
1547
1011
|
|
|
1548
|
-
const builder = this.createSqlBuilder().where(
|
|
1012
|
+
const builder = this.createSqlBuilder().where(parsed.where);
|
|
1549
1013
|
const { sql: whereClause, params: whereParams } = builder.getWhereConditions();
|
|
1550
1014
|
|
|
1551
|
-
const quotedTable = quoteIdentMySql(
|
|
1552
|
-
const quotedField = quoteIdentMySql(snakeField);
|
|
1015
|
+
const quotedTable = quoteIdentMySql(parsed.snakeTable);
|
|
1016
|
+
const quotedField = quoteIdentMySql(parsed.snakeField);
|
|
1553
1017
|
const sql = whereClause ? `UPDATE ${quotedTable} SET ${quotedField} = ${quotedField} + ? WHERE ${whereClause}` : `UPDATE ${quotedTable} SET ${quotedField} = ${quotedField} + ?`;
|
|
1554
1018
|
|
|
1555
|
-
const params = [value];
|
|
1019
|
+
const params = [parsed.value];
|
|
1556
1020
|
for (const param of whereParams) {
|
|
1557
1021
|
params.push(param);
|
|
1558
1022
|
}
|
|
@@ -1600,7 +1064,8 @@ class DbHelper {
|
|
|
1600
1064
|
dbName: this.dbName,
|
|
1601
1065
|
sql: tx,
|
|
1602
1066
|
isTransaction: true,
|
|
1603
|
-
beflyMode: this.beflyMode
|
|
1067
|
+
beflyMode: this.beflyMode,
|
|
1068
|
+
tables: this.tables
|
|
1604
1069
|
});
|
|
1605
1070
|
const result = await callback(trans);
|
|
1606
1071
|
if (result?.code !== undefined && result.code !== 0) {
|