befly 3.21.2 → 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/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 { addDefaultStateFilter, assertBatchInsertRowsConsistent, assertNoUndefinedInRecord, clearDeep, deserializeArrayFields, fieldsToSnake, orderByToSnake, parseLeftJoinItem, parseTableRef, processJoinField, processJoinOrderBy, processJoinWhere, serializeArrayFields, validateExplicitLeftJoinFields, validateExplicitReadFields, whereKeysToSnake } from "./dbUtil.js";
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 assertNonEmptyString(value, label) {
151
- if (!isNonEmptyString(value)) {
152
- throw new Error(`${label} 必须是非空字符串`, {
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: "validation"
135
+ code: "runtime"
155
136
  });
156
137
  }
157
138
  }
158
139
 
159
- function assertPlainObjectValue(value, label) {
160
- if (!isPlainObject(value)) {
161
- throw new Error(`${label} 必须是对象`, {
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 validateExcludeRules(excludeRules, label) {
169
- if (isNullable(excludeRules)) {
170
- return;
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 validateWhereMeta(where, label) {
192
- if (!isPlainObject(where)) {
158
+ function assertWriteDataHasFields(data, message, table) {
159
+ if (Object.keys(data).length > 0) {
193
160
  return;
194
161
  }
195
162
 
196
- for (const [key, value] of Object.entries(where)) {
197
- if (key === "$exclude") {
198
- validateExcludeRules(value, `${label}.$exclude`);
199
- continue;
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 validateWhereObject(where, label, required = false) {
218
- if (isNullable(where)) {
219
- if (required) {
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 validateOrderByItems(orderBy, label) {
239
- if (!Array.isArray(orderBy)) {
240
- throw new Error(`${label} 必须是数组`, {
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
- for (const item of orderBy) {
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 parts = value.split(".").map((item) => item.trim());
288
- if (parts.length !== 2 || !isNonEmptyString(parts[0]) || !isNonEmptyString(parts[1])) {
289
- throw new Error(`${label} 必须是 alias.field 格式`, {
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
- return {
307
- left: match[1],
308
- right: match[2]
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
- for (let i = 0; i < leftJoin.length; i++) {
321
- const joinItem = leftJoin[i];
322
- if (!isNonEmptyString(joinItem)) {
323
- throw new Error(`${label}[${i}] 必须是非空字符串`, {
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 parsed = parseLeftJoinEquality(joinItem);
330
- if (!parsed) {
331
- throw new Error(`${label}[${i}] 必须是 "left.field right.field" 格式(空格表示等于)`, {
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
- validateJoinFieldRef(parsed.left, `${label}[${i}] 左侧字段`);
338
- validateJoinFieldRef(parsed.right, `${label}[${i}] 右侧字段`);
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
- if (table.length > 1) {
379
- throw new Error(`${label}.table 为数组时必须配合 leftJoin 使用`, {
380
- cause: null,
381
- code: "validation"
382
- });
383
- }
384
- return;
385
- }
386
-
387
- assertNonEmptyString(table, `${label}.table`);
388
- parseTableRef(table);
389
-
390
- if (Array.isArray(leftJoin) && leftJoin.length > 0) {
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
- function validateCountReadOptions(options, label) {
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
- createListBuilder(prepared, whereFiltered, limit, offset) {
792
- const builder = this.createSqlBuilder().select(prepared.fields).from(prepared.table).where(whereFiltered).limit(limit);
793
- this.applyLeftJoins(builder, prepared.leftJoins);
794
- if (!isNullable(offset)) {
795
- builder.offset(offset);
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, whereFiltered, alias) {
804
- const builder = this.createSqlBuilder().selectRaw(alias).from(prepared.table).where(whereFiltered);
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
- async _joinFieldsToSnake(mainTableRef, classifiedFields) {
843
- if (classifiedFields.type !== "join") {
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
- for (const [key, value] of Object.entries(data)) {
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
- _prepareWriteUserData(data, allowState) {
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
- validateTimeIdValue(options.id);
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
- validateGeneratedBatchId(id, snakeTable, index);
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 validation = validateGetCountOptions(options);
1199
- const { prepared, whereFiltered } = await this.prepareCountContext(options, validation);
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 validation = validateGetOneOptions(options);
1210
- const { prepared, whereFiltered } = await this.prepareReadContext(options, validation);
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 validation = validateGetListOptions(options);
1227
- const { prepared, whereFiltered } = await this.prepareReadContext(options, validation);
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.createListBuilder(prepared, whereFiltered, prepared.limit, offset);
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 = this.buildQueryOptions(options, { page: 1, limit: 10 });
1277
- const validation = validateGetAllOptions(prepareOptions);
1278
- const { prepared, whereFiltered } = await this.prepareReadContext(prepareOptions, validation);
1279
- const countResult = await this.fetchCount(prepared, whereFiltered, "COUNT(*) as total");
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.createListBuilder(prepared, whereFiltered, MAX_LIMIT, null);
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
- validateNoLeftJoinReadOptions(options, "exists", "exists 不支持 leftJoin(请使用显式 query 或拆分查询)");
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.whereFiltered).limit(1);
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
- validateNoLeftJoinReadOptions(options, "getFieldValue", "getFieldValue 不支持 leftJoin(请使用 getOne/getList 并自行取字段)");
1335
- const field = options.field;
1336
- validateSafeFieldName(field);
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
- const oneOptions = {
1339
- table: options.table
1340
- };
1341
- if (options.where !== undefined) oneOptions.where = options.where;
1342
- if (options.leftJoin !== undefined) oneOptions.leftJoin = options.leftJoin;
1343
- if (options.orderBy !== undefined) oneOptions.orderBy = options.orderBy;
1344
- if (options.page !== undefined) oneOptions.page = options.page;
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
- const oneRes = await this.getOne(oneOptions);
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: oneRes.sql
777
+ sql: executeRes.sql
1355
778
  };
1356
779
  }
1357
780
 
1358
781
  async insData(options) {
1359
- validateTableDataOptions(options, "insData");
1360
- const { table, data } = options;
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
- validateTableBatchDataOptions(table, dataList, "insBatch");
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
- validateInsertBatchSize(dataList.length, MAX_BATCH_SIZE);
814
+ assertInsertBatchSize(dataList.length, MAX_BATCH_SIZE);
1389
815
 
1390
- const snakeTable = snakeCase(table);
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
- validateTableName(table, "delForceBatch.table");
1427
- validateIdList(ids, "delForceBatch.ids");
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 = snakeCase(table);
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
- validateTableBatchDataOptions(table, dataList, "updBatch");
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 = snakeCase(table);
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._buildPartialUpdateData({ data: item.data, allowState: true });
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
- validateTableDataOptions(options, "updData");
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
- const builder = this.createSqlBuilder().where(prepared.whereFiltered);
1511
- const { sql, params } = builder.toUpdateSql(prepared.snakeTable, processed);
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
- validateTableWhereOptions(options, "delData", true);
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(prepared.whereFiltered);
1536
- const { sql, params } = builder.toUpdateSql(prepared.snakeTable, processed);
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
- validateTableWhereOptions(options, "delForce", true);
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(prepared.whereFiltered);
1552
- const { sql, params } = builder.toDeleteSql(prepared.snakeTable);
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
- validateIncrementOptions(table, field, where, value, "increment");
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(prepared.whereFiltered);
1012
+ const builder = this.createSqlBuilder().where(parsed.where);
1594
1013
  const { sql: whereClause, params: whereParams } = builder.getWhereConditions();
1595
1014
 
1596
- const quotedTable = quoteIdentMySql(prepared.snakeTable);
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
  }