befly 3.20.8 → 3.20.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apis/dict/all.js +2 -2
- package/apis/dict/detail.js +2 -2
- package/apis/dict/list.js +2 -2
- package/hooks/permission.js +1 -2
- package/lib/cacheHelper.js +16 -17
- package/lib/dbHelper/builders.js +91 -207
- package/lib/dbHelper/dataOps.js +119 -123
- package/lib/dbHelper/validate.js +88 -20
- package/lib/sqlBuilder/batch.js +7 -8
- package/lib/sqlBuilder/check.js +19 -87
- package/lib/sqlBuilder/compiler.js +91 -90
- package/lib/sqlBuilder/parser.js +122 -103
- package/lib/sqlBuilder/util.js +66 -53
- package/package.json +2 -2
- package/lib/cacheKeys.js +0 -42
- package/lib/sqlBuilder/errors.js +0 -60
package/lib/dbHelper/dataOps.js
CHANGED
|
@@ -7,13 +7,102 @@ import { addDefaultStateFilter, buildInsertRow, buildPartialUpdateData, buildUpd
|
|
|
7
7
|
import { assertBatchInsertRowsConsistent, assertNoUndefinedInRecord, validateGeneratedBatchId, validateIncrementOptions, validateInsertBatchSize, validateNoLeftJoinReadOptions, validatePageLimitRange, validateSafeFieldName, validateTableBatchDataOptions, validateTableDataOptions, validateTableName, validateTableWhereOptions } from "./validate.js";
|
|
8
8
|
|
|
9
9
|
export const dataOpsMethods = {
|
|
10
|
+
prepareSingleTableWhere(table, where, useDefaultStateFilter = true) {
|
|
11
|
+
const snakeTable = snakeCase(table);
|
|
12
|
+
const snakeWhere = whereKeysToSnake(clearDeep(where || {}));
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
snakeTable: snakeTable,
|
|
16
|
+
whereFiltered: useDefaultStateFilter ? addDefaultStateFilter(snakeWhere, snakeTable, false, this.beflyMode) : snakeWhere
|
|
17
|
+
};
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
async createInsertRows(table, snakeTable, dataList, now) {
|
|
21
|
+
if (this.beflyMode === "manual") {
|
|
22
|
+
return {
|
|
23
|
+
ids: [],
|
|
24
|
+
processedList: dataList.map((data) =>
|
|
25
|
+
buildInsertRow({
|
|
26
|
+
data: data,
|
|
27
|
+
now: now,
|
|
28
|
+
beflyMode: this.beflyMode
|
|
29
|
+
})
|
|
30
|
+
)
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const ids = [];
|
|
35
|
+
try {
|
|
36
|
+
for (let i = 0; i < dataList.length; i++) {
|
|
37
|
+
ids.push(await this.redis.genTimeID());
|
|
38
|
+
}
|
|
39
|
+
} catch (error) {
|
|
40
|
+
if (dataList.length === 1) {
|
|
41
|
+
throw new Error(`生成 ID 失败,Redis 可能不可用 (table: ${table})`, {
|
|
42
|
+
cause: error,
|
|
43
|
+
code: "runtime",
|
|
44
|
+
subsystem: "db",
|
|
45
|
+
operation: "genTimeId",
|
|
46
|
+
table: table
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const processedList = dataList.map((data, index) => {
|
|
53
|
+
const id = ids[index];
|
|
54
|
+
if (dataList.length > 1) {
|
|
55
|
+
validateGeneratedBatchId(id, snakeTable, index);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return buildInsertRow({
|
|
59
|
+
data: data,
|
|
60
|
+
id: id,
|
|
61
|
+
now: now,
|
|
62
|
+
beflyMode: this.beflyMode
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
ids: ids,
|
|
68
|
+
processedList: processedList
|
|
69
|
+
};
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
resolveInsertedData(table, count, executeRes, generatedIds, operation) {
|
|
73
|
+
const lastInsertRowidNum = normalizeSqlMetaNumber(executeRes.data?.lastInsertRowid);
|
|
74
|
+
|
|
75
|
+
if (this.beflyMode === "manual") {
|
|
76
|
+
if (lastInsertRowidNum <= 0) {
|
|
77
|
+
throw new Error(operation === "insBatch" ? `批量插入失败:beflyMode=manual 时无法获取 lastInsertRowid (table: ${table})` : `插入失败:beflyMode=manual 时无法获取 lastInsertRowid (table: ${table})`, {
|
|
78
|
+
cause: null,
|
|
79
|
+
code: "runtime"
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (count === 1) {
|
|
84
|
+
return lastInsertRowidNum;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const outIds = [];
|
|
88
|
+
for (let i = 0; i < count; i++) {
|
|
89
|
+
outIds.push(lastInsertRowidNum + i);
|
|
90
|
+
}
|
|
91
|
+
return outIds;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (count === 1) {
|
|
95
|
+
const generatedId = generatedIds[0];
|
|
96
|
+
return (isNumber(generatedId) ? generatedId : 0) || lastInsertRowidNum || 0;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return generatedIds;
|
|
100
|
+
},
|
|
101
|
+
|
|
10
102
|
// 读取操作
|
|
11
103
|
async getCount(options) {
|
|
12
|
-
const {
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
const whereFiltered = addDefaultStateFilter(where, tableQualifier, hasLeftJoin, this.beflyMode);
|
|
16
|
-
const result = await this.fetchCount({ table: table, leftJoins: leftJoins }, whereFiltered, "COUNT(*) as count");
|
|
104
|
+
const { prepared, whereFiltered } = await this.prepareReadContext(options, "getCount.options");
|
|
105
|
+
const result = await this.fetchCount(prepared, whereFiltered, "COUNT(*) as count");
|
|
17
106
|
|
|
18
107
|
return {
|
|
19
108
|
data: result.total,
|
|
@@ -22,12 +111,9 @@ export const dataOpsMethods = {
|
|
|
22
111
|
},
|
|
23
112
|
|
|
24
113
|
async getOne(options) {
|
|
25
|
-
const {
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
const whereFiltered = addDefaultStateFilter(where, tableQualifier, hasLeftJoin, this.beflyMode);
|
|
29
|
-
const builder = this.createSqlBuilder().select(fields).from(table).where(whereFiltered);
|
|
30
|
-
this.applyLeftJoins(builder, leftJoins);
|
|
114
|
+
const { prepared, whereFiltered } = await this.prepareReadContext(options, "getOne.options");
|
|
115
|
+
const builder = this.createSqlBuilder().select(prepared.fields).from(prepared.table).where(whereFiltered);
|
|
116
|
+
this.applyLeftJoins(builder, prepared.leftJoins);
|
|
31
117
|
|
|
32
118
|
const { sql, params } = builder.toSelectSql();
|
|
33
119
|
const executeRes = await this.execute(sql, params);
|
|
@@ -45,11 +131,8 @@ export const dataOpsMethods = {
|
|
|
45
131
|
},
|
|
46
132
|
|
|
47
133
|
async getList(options) {
|
|
48
|
-
const prepared = await this.
|
|
134
|
+
const { prepared, whereFiltered } = await this.prepareReadContext(options, "getList.options");
|
|
49
135
|
validatePageLimitRange(prepared, options.table);
|
|
50
|
-
|
|
51
|
-
const hasLeftJoin = Array.isArray(prepared.leftJoins) && prepared.leftJoins.length > 0;
|
|
52
|
-
const whereFiltered = addDefaultStateFilter(prepared.where, prepared.tableQualifier, hasLeftJoin, this.beflyMode);
|
|
53
136
|
const countResult = await this.fetchCount(prepared, whereFiltered, "COUNT(*) as total");
|
|
54
137
|
const total = countResult.total;
|
|
55
138
|
|
|
@@ -98,10 +181,7 @@ export const dataOpsMethods = {
|
|
|
98
181
|
const MAX_LIMIT = 100000;
|
|
99
182
|
const WARNING_LIMIT = 10000;
|
|
100
183
|
const prepareOptions = this.buildQueryOptions(options, { page: 1, limit: 10 });
|
|
101
|
-
const prepared = await this.
|
|
102
|
-
|
|
103
|
-
const hasLeftJoin = Array.isArray(prepared.leftJoins) && prepared.leftJoins.length > 0;
|
|
104
|
-
const whereFiltered = addDefaultStateFilter(prepared.where, prepared.tableQualifier, hasLeftJoin, this.beflyMode);
|
|
184
|
+
const { prepared, whereFiltered } = await this.prepareReadContext(prepareOptions, "getAll.options");
|
|
105
185
|
const countResult = await this.fetchCount(prepared, whereFiltered, "COUNT(*) as total");
|
|
106
186
|
const total = countResult.total;
|
|
107
187
|
|
|
@@ -147,11 +227,9 @@ export const dataOpsMethods = {
|
|
|
147
227
|
|
|
148
228
|
async exists(options) {
|
|
149
229
|
validateNoLeftJoinReadOptions(options, "exists", "exists 不支持 leftJoin(请使用显式 query 或拆分查询)");
|
|
150
|
-
const
|
|
151
|
-
const snakeWhere = whereKeysToSnake(clearDeep(options.where || {}));
|
|
152
|
-
const whereFiltered = addDefaultStateFilter(snakeWhere, snakeTable, false, this.beflyMode);
|
|
230
|
+
const prepared = this.prepareSingleTableWhere(options.table, options.where, true);
|
|
153
231
|
|
|
154
|
-
const builder = this.createSqlBuilder().selectRaw("COUNT(1) as cnt").from(snakeTable).where(whereFiltered).limit(1);
|
|
232
|
+
const builder = this.createSqlBuilder().selectRaw("COUNT(1) as cnt").from(prepared.snakeTable).where(prepared.whereFiltered).limit(1);
|
|
155
233
|
const { sql, params } = builder.toSelectSql();
|
|
156
234
|
const executeRes = await this.execute(sql, params);
|
|
157
235
|
const exists = (executeRes.data?.[0]?.cnt || 0) > 0;
|
|
@@ -190,34 +268,8 @@ export const dataOpsMethods = {
|
|
|
190
268
|
const { table, data } = options;
|
|
191
269
|
const snakeTable = snakeCase(table);
|
|
192
270
|
const now = Date.now();
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
if (this.beflyMode === "manual") {
|
|
196
|
-
processed = buildInsertRow({
|
|
197
|
-
data: data,
|
|
198
|
-
now: now,
|
|
199
|
-
beflyMode: this.beflyMode
|
|
200
|
-
});
|
|
201
|
-
} else {
|
|
202
|
-
let id;
|
|
203
|
-
try {
|
|
204
|
-
id = await this.redis.genTimeID();
|
|
205
|
-
} catch (error) {
|
|
206
|
-
throw new Error(`生成 ID 失败,Redis 可能不可用 (table: ${table})`, {
|
|
207
|
-
cause: error,
|
|
208
|
-
code: "runtime",
|
|
209
|
-
subsystem: "db",
|
|
210
|
-
operation: "genTimeId",
|
|
211
|
-
table: table
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
processed = buildInsertRow({
|
|
215
|
-
data: data,
|
|
216
|
-
id: id,
|
|
217
|
-
now: now,
|
|
218
|
-
beflyMode: this.beflyMode
|
|
219
|
-
});
|
|
220
|
-
}
|
|
271
|
+
const insertRows = await this.createInsertRows(table, snakeTable, [data], now);
|
|
272
|
+
const processed = insertRows.processedList[0];
|
|
221
273
|
|
|
222
274
|
assertNoUndefinedInRecord(processed, `insData 插入数据 (table: ${snakeTable})`);
|
|
223
275
|
|
|
@@ -225,19 +277,8 @@ export const dataOpsMethods = {
|
|
|
225
277
|
const { sql, params } = builder.toInsertSql(snakeTable, processed);
|
|
226
278
|
const executeRes = await this.execute(sql, params);
|
|
227
279
|
|
|
228
|
-
const processedId = processed["id"];
|
|
229
|
-
const processedIdNum = isNumber(processedId) ? processedId : 0;
|
|
230
|
-
const lastInsertRowidNum = normalizeSqlMetaNumber(executeRes.data?.lastInsertRowid);
|
|
231
|
-
|
|
232
|
-
const insertedId = this.beflyMode === "manual" ? lastInsertRowidNum || 0 : processedIdNum || lastInsertRowidNum || 0;
|
|
233
|
-
if (this.beflyMode === "manual" && insertedId <= 0) {
|
|
234
|
-
throw new Error(`插入失败:beflyMode=manual 时无法获取 lastInsertRowid (table: ${table})`, {
|
|
235
|
-
cause: null,
|
|
236
|
-
code: "runtime"
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
280
|
return {
|
|
240
|
-
data:
|
|
281
|
+
data: this.resolveInsertedData(table, 1, executeRes, insertRows.ids, "insData"),
|
|
241
282
|
sql: executeRes.sql
|
|
242
283
|
};
|
|
243
284
|
},
|
|
@@ -256,25 +297,8 @@ export const dataOpsMethods = {
|
|
|
256
297
|
|
|
257
298
|
const snakeTable = snakeCase(table);
|
|
258
299
|
const now = Date.now();
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
let processedList;
|
|
262
|
-
if (this.beflyMode === "manual") {
|
|
263
|
-
processedList = dataList.map((data) => {
|
|
264
|
-
return buildInsertRow({ data: data, now: now, beflyMode: this.beflyMode });
|
|
265
|
-
});
|
|
266
|
-
} else {
|
|
267
|
-
const nextIds = [];
|
|
268
|
-
for (let i = 0; i < dataList.length; i++) {
|
|
269
|
-
nextIds.push(await this.redis.genTimeID());
|
|
270
|
-
}
|
|
271
|
-
ids = nextIds;
|
|
272
|
-
processedList = dataList.map((data, index) => {
|
|
273
|
-
const id = nextIds[index];
|
|
274
|
-
validateGeneratedBatchId(id, snakeTable, index);
|
|
275
|
-
return buildInsertRow({ data: data, id: id, now: now, beflyMode: this.beflyMode });
|
|
276
|
-
});
|
|
277
|
-
}
|
|
300
|
+
const insertRows = await this.createInsertRows(table, snakeTable, dataList, now);
|
|
301
|
+
const processedList = insertRows.processedList;
|
|
278
302
|
|
|
279
303
|
const insertFields = assertBatchInsertRowsConsistent(processedList, { table: snakeTable });
|
|
280
304
|
const builder = this.createSqlBuilder();
|
|
@@ -282,29 +306,8 @@ export const dataOpsMethods = {
|
|
|
282
306
|
|
|
283
307
|
try {
|
|
284
308
|
const executeRes = await this.execute(sql, params);
|
|
285
|
-
|
|
286
|
-
if (this.beflyMode === "manual") {
|
|
287
|
-
const firstId = normalizeSqlMetaNumber(executeRes.data?.lastInsertRowid);
|
|
288
|
-
if (firstId <= 0) {
|
|
289
|
-
throw new Error(`批量插入失败:beflyMode=manual 时无法获取 lastInsertRowid (table: ${table})`, {
|
|
290
|
-
cause: null,
|
|
291
|
-
code: "runtime"
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
const outIds = [];
|
|
296
|
-
for (let i = 0; i < dataList.length; i++) {
|
|
297
|
-
outIds.push(firstId + i);
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
return {
|
|
301
|
-
data: outIds,
|
|
302
|
-
sql: executeRes.sql
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
|
|
306
309
|
return {
|
|
307
|
-
data: ids,
|
|
310
|
+
data: this.resolveInsertedData(table, dataList.length, executeRes, insertRows.ids, "insBatch"),
|
|
308
311
|
sql: executeRes.sql
|
|
309
312
|
};
|
|
310
313
|
} catch (error) {
|
|
@@ -409,13 +412,11 @@ export const dataOpsMethods = {
|
|
|
409
412
|
validateTableDataOptions(options, "updData");
|
|
410
413
|
validateTableWhereOptions(options, "updData", true);
|
|
411
414
|
const { table, data, where } = options;
|
|
412
|
-
const
|
|
413
|
-
const snakeWhere = whereKeysToSnake(clearDeep(where));
|
|
415
|
+
const prepared = this.prepareSingleTableWhere(table, where, true);
|
|
414
416
|
|
|
415
417
|
const processed = buildUpdateRow({ data: data, now: Date.now(), allowState: true, beflyMode: this.beflyMode });
|
|
416
|
-
const
|
|
417
|
-
const
|
|
418
|
-
const { sql, params } = builder.toUpdateSql(snakeTable, processed);
|
|
418
|
+
const builder = this.createSqlBuilder().where(prepared.whereFiltered);
|
|
419
|
+
const { sql, params } = builder.toUpdateSql(prepared.snakeTable, processed);
|
|
419
420
|
|
|
420
421
|
const executeRes = await this.execute(sql, params);
|
|
421
422
|
const changes = normalizeSqlMetaNumber(executeRes.data?.affectedRows);
|
|
@@ -428,8 +429,7 @@ export const dataOpsMethods = {
|
|
|
428
429
|
async delData(options) {
|
|
429
430
|
validateTableWhereOptions(options, "delData", true);
|
|
430
431
|
const { table, where } = options;
|
|
431
|
-
const
|
|
432
|
-
const snakeWhere = whereKeysToSnake(clearDeep(where));
|
|
432
|
+
const prepared = this.prepareSingleTableWhere(table, where, true);
|
|
433
433
|
const now = Date.now();
|
|
434
434
|
const processed = {
|
|
435
435
|
state: 0,
|
|
@@ -440,9 +440,8 @@ export const dataOpsMethods = {
|
|
|
440
440
|
processed.updated_at = now;
|
|
441
441
|
}
|
|
442
442
|
|
|
443
|
-
const
|
|
444
|
-
const
|
|
445
|
-
const { sql, params } = builder.toUpdateSql(snakeTable, processed);
|
|
443
|
+
const builder = this.createSqlBuilder().where(prepared.whereFiltered);
|
|
444
|
+
const { sql, params } = builder.toUpdateSql(prepared.snakeTable, processed);
|
|
446
445
|
const executeRes = await this.execute(sql, params);
|
|
447
446
|
const changes = normalizeSqlMetaNumber(executeRes.data?.affectedRows);
|
|
448
447
|
|
|
@@ -456,11 +455,10 @@ export const dataOpsMethods = {
|
|
|
456
455
|
validateTableWhereOptions(options, "delForce", true);
|
|
457
456
|
const { table, where } = options;
|
|
458
457
|
|
|
459
|
-
const
|
|
460
|
-
const snakeWhere = whereKeysToSnake(clearDeep(where));
|
|
458
|
+
const prepared = this.prepareSingleTableWhere(table, where, false);
|
|
461
459
|
|
|
462
|
-
const builder = this.createSqlBuilder().where(
|
|
463
|
-
const { sql, params } = builder.toDeleteSql(snakeTable);
|
|
460
|
+
const builder = this.createSqlBuilder().where(prepared.whereFiltered);
|
|
461
|
+
const { sql, params } = builder.toDeleteSql(prepared.snakeTable);
|
|
464
462
|
|
|
465
463
|
const executeRes = await this.execute(sql, params);
|
|
466
464
|
const changes = normalizeSqlMetaNumber(executeRes.data?.affectedRows);
|
|
@@ -498,15 +496,13 @@ export const dataOpsMethods = {
|
|
|
498
496
|
|
|
499
497
|
async increment(table, field, where, value = 1) {
|
|
500
498
|
validateIncrementOptions(table, field, where, value, "increment");
|
|
501
|
-
const
|
|
499
|
+
const prepared = this.prepareSingleTableWhere(table, where, true);
|
|
502
500
|
const snakeField = snakeCase(field);
|
|
503
501
|
|
|
504
|
-
const
|
|
505
|
-
const whereFiltered = addDefaultStateFilter(snakeWhere, snakeTable, false, this.beflyMode);
|
|
506
|
-
const builder = this.createSqlBuilder().where(whereFiltered);
|
|
502
|
+
const builder = this.createSqlBuilder().where(prepared.whereFiltered);
|
|
507
503
|
const { sql: whereClause, params: whereParams } = builder.getWhereConditions();
|
|
508
504
|
|
|
509
|
-
const quotedTable = quoteIdentMySql(snakeTable);
|
|
505
|
+
const quotedTable = quoteIdentMySql(prepared.snakeTable);
|
|
510
506
|
const quotedField = quoteIdentMySql(snakeField);
|
|
511
507
|
const sql = whereClause ? `UPDATE ${quotedTable} SET ${quotedField} = ${quotedField} + ? WHERE ${whereClause}` : `UPDATE ${quotedTable} SET ${quotedField} = ${quotedField} + ?`;
|
|
512
508
|
|
package/lib/dbHelper/validate.js
CHANGED
|
@@ -1,6 +1,39 @@
|
|
|
1
1
|
import { isFiniteNumber, isNonEmptyString, isNullable, isPlainObject, isString } from "../../utils/is.js";
|
|
2
2
|
import { snakeCase } from "../../utils/util.js";
|
|
3
3
|
|
|
4
|
+
function validateJoinFieldRef(value, label) {
|
|
5
|
+
if (!isNonEmptyString(value)) {
|
|
6
|
+
throw new Error(`${label} 不能为空`, {
|
|
7
|
+
cause: null,
|
|
8
|
+
code: "validation"
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const parts = value.split(".").map((item) => item.trim());
|
|
13
|
+
if (parts.length !== 2 || !isNonEmptyString(parts[0]) || !isNonEmptyString(parts[1])) {
|
|
14
|
+
throw new Error(`${label} 必须是 alias.field 格式`, {
|
|
15
|
+
cause: null,
|
|
16
|
+
code: "validation"
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function parseLeftJoinEquality(joinItem) {
|
|
22
|
+
if (!isNonEmptyString(joinItem)) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const match = joinItem.match(/^([a-zA-Z_][a-zA-Z0-9_]*\.[a-zA-Z_][a-zA-Z0-9_]*) ([a-zA-Z_][a-zA-Z0-9_]*\.[a-zA-Z_][a-zA-Z0-9_]*)$/);
|
|
27
|
+
if (!match) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
left: match[1],
|
|
33
|
+
right: match[2]
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
4
37
|
function assertNonEmptyString(value, label) {
|
|
5
38
|
if (!isNonEmptyString(value)) {
|
|
6
39
|
throw new Error(`${label} 必须是非空字符串`, {
|
|
@@ -147,30 +180,60 @@ function validateLeftJoinItems(leftJoin, label) {
|
|
|
147
180
|
});
|
|
148
181
|
}
|
|
149
182
|
|
|
150
|
-
const
|
|
151
|
-
if (
|
|
152
|
-
throw new Error(`${label}[${i}] 必须是 "
|
|
183
|
+
const parsed = parseLeftJoinEquality(joinItem);
|
|
184
|
+
if (!parsed) {
|
|
185
|
+
throw new Error(`${label}[${i}] 必须是 "left.field right.field" 格式(空格表示等于)`, {
|
|
153
186
|
cause: null,
|
|
154
187
|
code: "validation"
|
|
155
188
|
});
|
|
156
189
|
}
|
|
157
190
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
191
|
+
validateJoinFieldRef(parsed.left, `${label}[${i}] 左侧字段`);
|
|
192
|
+
validateJoinFieldRef(parsed.right, `${label}[${i}] 右侧字段`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function validateQueryTableOption(table, leftJoin, label) {
|
|
197
|
+
if (Array.isArray(table)) {
|
|
198
|
+
if (table.length === 0) {
|
|
199
|
+
throw new Error(`${label}.table 不能为空数组`, {
|
|
162
200
|
cause: null,
|
|
163
201
|
code: "validation"
|
|
164
202
|
});
|
|
165
203
|
}
|
|
166
|
-
|
|
167
|
-
|
|
204
|
+
|
|
205
|
+
for (let i = 0; i < table.length; i++) {
|
|
206
|
+
assertNonEmptyString(table[i], `${label}.table[${i}]`);
|
|
207
|
+
parseTableRef(table[i]);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (Array.isArray(leftJoin) && leftJoin.length > 0) {
|
|
211
|
+
if (table.length !== leftJoin.length + 1) {
|
|
212
|
+
throw new Error(`${label}.table 与 ${label}.leftJoin 数量不匹配,要求 table.length = leftJoin.length + 1`, {
|
|
213
|
+
cause: null,
|
|
214
|
+
code: "validation"
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (table.length > 1) {
|
|
221
|
+
throw new Error(`${label}.table 为数组时必须配合 leftJoin 使用`, {
|
|
168
222
|
cause: null,
|
|
169
223
|
code: "validation"
|
|
170
224
|
});
|
|
171
225
|
}
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
172
228
|
|
|
173
|
-
|
|
229
|
+
assertNonEmptyString(table, `${label}.table`);
|
|
230
|
+
parseTableRef(table);
|
|
231
|
+
|
|
232
|
+
if (Array.isArray(leftJoin) && leftJoin.length > 0) {
|
|
233
|
+
throw new Error(`${label}.leftJoin 启用时,${label}.table 必须是数组`, {
|
|
234
|
+
cause: null,
|
|
235
|
+
code: "validation"
|
|
236
|
+
});
|
|
174
237
|
}
|
|
175
238
|
}
|
|
176
239
|
|
|
@@ -194,7 +257,7 @@ export function validateQueryOptions(options, label) {
|
|
|
194
257
|
});
|
|
195
258
|
}
|
|
196
259
|
|
|
197
|
-
|
|
260
|
+
validateQueryTableOption(options.table, options.leftJoin, label);
|
|
198
261
|
|
|
199
262
|
if (!isNullable(options.where)) {
|
|
200
263
|
validateWhereObject(options.where, `${label}.where`);
|
|
@@ -232,11 +295,7 @@ export function validateQueryOptions(options, label) {
|
|
|
232
295
|
}
|
|
233
296
|
}
|
|
234
297
|
|
|
235
|
-
|
|
236
|
-
assertPlainObjectValue(data, label);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
export function validateBatchDataList(dataList, label) {
|
|
298
|
+
function validateBatchDataList(dataList, label) {
|
|
240
299
|
if (!Array.isArray(dataList)) {
|
|
241
300
|
throw new Error(`${label} 必须是数组`, {
|
|
242
301
|
cause: null,
|
|
@@ -265,7 +324,7 @@ export function validateTableWhereOptions(options, label, required = false) {
|
|
|
265
324
|
|
|
266
325
|
export function validateTableDataOptions(options, label) {
|
|
267
326
|
validateTableName(options.table, `${label}.table`);
|
|
268
|
-
|
|
327
|
+
assertPlainObjectValue(options.data, `${label}.data`);
|
|
269
328
|
}
|
|
270
329
|
|
|
271
330
|
export function validateTableBatchDataOptions(table, dataList, label) {
|
|
@@ -274,20 +333,29 @@ export function validateTableBatchDataOptions(table, dataList, label) {
|
|
|
274
333
|
}
|
|
275
334
|
|
|
276
335
|
export function validateNoLeftJoinReadOptions(options, label, joinErrorMessage) {
|
|
277
|
-
|
|
336
|
+
validateTableName(options.table, `${label}.table`);
|
|
337
|
+
validateWhereObject(options.where, `${label}.where`, false);
|
|
278
338
|
validateNoLeftJoin(options.leftJoin, joinErrorMessage);
|
|
279
339
|
validateLegacyJoinOptions(options, label);
|
|
280
340
|
validateSimpleTableName(options.table, label);
|
|
281
341
|
}
|
|
282
342
|
|
|
283
343
|
export function validateIncrementOptions(table, field, where, value, label) {
|
|
284
|
-
|
|
344
|
+
validateTableName(table, `${label}.table`);
|
|
345
|
+
validateWhereObject(where, `${label}.where`, true);
|
|
285
346
|
validateSafeFieldName(snakeCase(table));
|
|
286
347
|
validateSafeFieldName(snakeCase(field));
|
|
287
348
|
validateIncrementValue(table, field, value);
|
|
288
349
|
}
|
|
289
350
|
|
|
290
351
|
export function validateSimpleTableName(rawTable, label) {
|
|
352
|
+
if (Array.isArray(rawTable)) {
|
|
353
|
+
throw new Error(`${label}.table 不支持数组`, {
|
|
354
|
+
cause: null,
|
|
355
|
+
code: "validation"
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
291
359
|
const table = isString(rawTable) ? rawTable.trim() : "";
|
|
292
360
|
if (!table) {
|
|
293
361
|
throw new Error(`${label}.table 不能为空`, {
|
|
@@ -319,7 +387,7 @@ export function validateSafeFieldName(field) {
|
|
|
319
387
|
}
|
|
320
388
|
|
|
321
389
|
export function validatePageLimitRange(prepared, rawTable) {
|
|
322
|
-
if (prepared.page < 1 || prepared.page >
|
|
390
|
+
if (prepared.page < 1 || prepared.page > 10000) {
|
|
323
391
|
throw new Error(`页码必须在 1 到 10000 之间 (table: ${rawTable}, page: ${prepared.page}, limit: ${prepared.limit})`, {
|
|
324
392
|
cause: null,
|
|
325
393
|
code: "validation"
|
package/lib/sqlBuilder/batch.js
CHANGED
|
@@ -1,22 +1,21 @@
|
|
|
1
|
-
import { SqlErrors } from "./errors.js";
|
|
2
1
|
import { isNonEmptyString } from "../../utils/is.js";
|
|
3
2
|
import { SqlCheck } from "./check.js";
|
|
4
3
|
|
|
5
4
|
export function toDeleteInSql(options) {
|
|
6
5
|
if (!isNonEmptyString(options.table)) {
|
|
7
|
-
throw new Error(
|
|
6
|
+
throw new Error(`toDeleteInSql 需要非空表名 (table: ${String(options.table)})`, {
|
|
8
7
|
cause: null,
|
|
9
8
|
code: "validation"
|
|
10
9
|
});
|
|
11
10
|
}
|
|
12
11
|
if (!isNonEmptyString(options.idField)) {
|
|
13
|
-
throw new Error(
|
|
12
|
+
throw new Error(`toDeleteInSql 需要非空 idField (idField: ${String(options.idField)})`, {
|
|
14
13
|
cause: null,
|
|
15
14
|
code: "validation"
|
|
16
15
|
});
|
|
17
16
|
}
|
|
18
17
|
if (!Array.isArray(options.ids)) {
|
|
19
|
-
throw new Error(
|
|
18
|
+
throw new Error("toDeleteInSql 需要 ids 数组", {
|
|
20
19
|
cause: null,
|
|
21
20
|
code: "validation"
|
|
22
21
|
});
|
|
@@ -36,19 +35,19 @@ export function toDeleteInSql(options) {
|
|
|
36
35
|
|
|
37
36
|
export function toUpdateCaseByIdSql(options) {
|
|
38
37
|
if (!isNonEmptyString(options.table)) {
|
|
39
|
-
throw new Error(
|
|
38
|
+
throw new Error(`toUpdateCaseByIdSql 需要非空表名 (table: ${String(options.table)})`, {
|
|
40
39
|
cause: null,
|
|
41
40
|
code: "validation"
|
|
42
41
|
});
|
|
43
42
|
}
|
|
44
43
|
if (!isNonEmptyString(options.idField)) {
|
|
45
|
-
throw new Error(
|
|
44
|
+
throw new Error(`toUpdateCaseByIdSql 需要非空 idField (idField: ${String(options.idField)})`, {
|
|
46
45
|
cause: null,
|
|
47
46
|
code: "validation"
|
|
48
47
|
});
|
|
49
48
|
}
|
|
50
49
|
if (!Array.isArray(options.rows)) {
|
|
51
|
-
throw new Error(
|
|
50
|
+
throw new Error("toUpdateCaseByIdSql 需要 rows 数组", {
|
|
52
51
|
cause: null,
|
|
53
52
|
code: "validation"
|
|
54
53
|
});
|
|
@@ -57,7 +56,7 @@ export function toUpdateCaseByIdSql(options) {
|
|
|
57
56
|
return { sql: "", params: [] };
|
|
58
57
|
}
|
|
59
58
|
if (!Array.isArray(options.fields)) {
|
|
60
|
-
throw new Error(
|
|
59
|
+
throw new Error("toUpdateCaseByIdSql 需要 fields 数组", {
|
|
61
60
|
cause: null,
|
|
62
61
|
code: "validation"
|
|
63
62
|
});
|