befly 3.21.2 → 3.22.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apis/admin/list.js +1 -1
- package/apis/admin/upd.js +2 -2
- package/apis/dict/upd.js +1 -1
- package/apis/dictType/upd.js +1 -1
- package/apis/role/all.js +1 -1
- package/apis/role/list.js +1 -1
- package/apis/role/upd.js +1 -1
- package/apis/source/imageList.js +27 -0
- package/apis/upload/file.js +105 -0
- package/checks/config.js +2 -0
- package/configs/beflyConfig.json +2 -1
- package/configs/beflyMenus.json +12 -0
- package/index.js +5 -5
- package/lib/dbHelper.js +170 -751
- package/lib/dbParse.js +1045 -0
- package/lib/dbUtil.js +52 -508
- package/lib/sqlBuilder.js +78 -294
- package/package.json +2 -2
- package/paths.js +2 -2
- package/router/static.js +2 -1
- package/sql/befly.sql +20 -0
- package/tables/file.json +81 -0
- package/utils/datetime.js +29 -32
package/lib/dbParse.js
ADDED
|
@@ -0,0 +1,1045 @@
|
|
|
1
|
+
import { isNonEmptyString, isNullable, isPlainObject, isString } from "../utils/is.js";
|
|
2
|
+
import { snakeCase } from "../utils/util.js";
|
|
3
|
+
import { assertNoExprField, clearDeep, fieldsToSnake, normalizeQualifierField, orderByToSnake, parseFieldAliasParts, parseTableRef, processJoinField, processJoinOn, processJoinOrderBy, processJoinWhere, whereKeysToSnake } from "./dbUtil.js";
|
|
4
|
+
|
|
5
|
+
function assertNonEmptyString(value, label) {
|
|
6
|
+
if (!isNonEmptyString(value)) {
|
|
7
|
+
throw new Error(`${label} 必须是非空字符串`, {
|
|
8
|
+
cause: null,
|
|
9
|
+
code: "validation"
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function assertPlainObjectValue(value, label) {
|
|
15
|
+
if (!isPlainObject(value)) {
|
|
16
|
+
throw new Error(`${label} 必须是对象`, {
|
|
17
|
+
cause: null,
|
|
18
|
+
code: "validation"
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function validateWhereObject(where, label, required = false) {
|
|
24
|
+
if (isNullable(where)) {
|
|
25
|
+
if (required) {
|
|
26
|
+
throw new Error(`${label} 必须是对象`, {
|
|
27
|
+
cause: null,
|
|
28
|
+
code: "validation"
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!isPlainObject(where)) {
|
|
35
|
+
throw new Error(`${label} 必须是对象`, {
|
|
36
|
+
cause: null,
|
|
37
|
+
code: "validation"
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const visitWhereMeta = (currentWhere, currentLabel) => {
|
|
42
|
+
if (!isPlainObject(currentWhere)) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
for (const [key, value] of Object.entries(currentWhere)) {
|
|
47
|
+
if (key === "$exclude") {
|
|
48
|
+
if (isNullable(value)) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!isPlainObject(value)) {
|
|
53
|
+
throw new Error(`${currentLabel}.$exclude 必须是对象`, {
|
|
54
|
+
cause: null,
|
|
55
|
+
code: "validation"
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
for (const [excludeKey, excludeValue] of Object.entries(value)) {
|
|
60
|
+
assertNonEmptyString(excludeKey, `${currentLabel}.$exclude 的字段名`);
|
|
61
|
+
if (!Array.isArray(excludeValue)) {
|
|
62
|
+
throw new Error(`${currentLabel}.$exclude.${excludeKey} 必须是数组`, {
|
|
63
|
+
cause: null,
|
|
64
|
+
code: "validation"
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if ((key === "$or" || key === "$and") && Array.isArray(value)) {
|
|
72
|
+
for (let i = 0; i < value.length; i++) {
|
|
73
|
+
if (isPlainObject(value[i])) {
|
|
74
|
+
visitWhereMeta(value[i], `${currentLabel}.${key}[${i}]`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (isPlainObject(value)) {
|
|
81
|
+
visitWhereMeta(value, `${currentLabel}.${key}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
visitWhereMeta(where, label);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function validateOrderByItems(orderBy, label) {
|
|
90
|
+
if (!Array.isArray(orderBy)) {
|
|
91
|
+
throw new Error(`${label} 必须是数组`, {
|
|
92
|
+
cause: null,
|
|
93
|
+
code: "validation"
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
for (const item of orderBy) {
|
|
98
|
+
if (!isString(item) || !item.includes("#")) {
|
|
99
|
+
throw new Error(`${label} 字段必须是 "字段#方向" 格式的字符串`, {
|
|
100
|
+
cause: null,
|
|
101
|
+
code: "validation"
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const parts = item.split("#");
|
|
106
|
+
if (parts.length !== 2) {
|
|
107
|
+
throw new Error(`${label} 字段必须是 "字段#方向" 格式的字符串`, {
|
|
108
|
+
cause: null,
|
|
109
|
+
code: "validation"
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const field = isString(parts[0]) ? parts[0].trim() : "";
|
|
114
|
+
const direction = isString(parts[1]) ? parts[1].trim().toUpperCase() : "";
|
|
115
|
+
if (!field) {
|
|
116
|
+
throw new Error(`${label} 中字段名不能为空`, {
|
|
117
|
+
cause: null,
|
|
118
|
+
code: "validation"
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
if (direction !== "ASC" && direction !== "DESC") {
|
|
122
|
+
throw new Error(`${label} 方向必须是 ASC 或 DESC`, {
|
|
123
|
+
cause: null,
|
|
124
|
+
code: "validation"
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function assertJoinFieldRef(value, label) {
|
|
131
|
+
if (!isNonEmptyString(value)) {
|
|
132
|
+
throw new Error(`${label} 不能为空`, {
|
|
133
|
+
cause: null,
|
|
134
|
+
code: "validation"
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const parts = value.split(".").map((item) => item.trim());
|
|
139
|
+
if (parts.length !== 2 || !isNonEmptyString(parts[0]) || !isNonEmptyString(parts[1])) {
|
|
140
|
+
throw new Error(`${label} 必须是 alias.field 格式`, {
|
|
141
|
+
cause: null,
|
|
142
|
+
code: "validation"
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function parseLeftJoinEquality(joinItem) {
|
|
148
|
+
if (!isNonEmptyString(joinItem)) {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
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_]*)$/);
|
|
153
|
+
if (!match) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
left: match[1],
|
|
159
|
+
right: match[2]
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function validateLeftJoinItems(leftJoin, label) {
|
|
164
|
+
if (!Array.isArray(leftJoin)) {
|
|
165
|
+
throw new Error(`${label} 必须是数组`, {
|
|
166
|
+
cause: null,
|
|
167
|
+
code: "validation"
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
for (let i = 0; i < leftJoin.length; i++) {
|
|
172
|
+
const joinItem = leftJoin[i];
|
|
173
|
+
if (!isNonEmptyString(joinItem)) {
|
|
174
|
+
throw new Error(`${label}[${i}] 必须是非空字符串`, {
|
|
175
|
+
cause: null,
|
|
176
|
+
code: "validation"
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const parsed = parseLeftJoinEquality(joinItem);
|
|
181
|
+
if (!parsed) {
|
|
182
|
+
throw new Error(`${label}[${i}] 必须是 "left.field right.field" 格式(空格表示等于)`, {
|
|
183
|
+
cause: null,
|
|
184
|
+
code: "validation"
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
assertJoinFieldRef(parsed.left, `${label}[${i}] 左侧字段`);
|
|
189
|
+
assertJoinFieldRef(parsed.right, `${label}[${i}] 右侧字段`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function validateQueryOptions(options, label) {
|
|
194
|
+
if (!isPlainObject(options)) {
|
|
195
|
+
throw new Error(`${label} 必须是对象`, {
|
|
196
|
+
cause: null,
|
|
197
|
+
code: "validation"
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (Array.isArray(options.table)) {
|
|
202
|
+
if (options.table.length === 0) {
|
|
203
|
+
throw new Error(`${label}.table 不能为空数组`, {
|
|
204
|
+
cause: null,
|
|
205
|
+
code: "validation"
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
for (let i = 0; i < options.table.length; i++) {
|
|
210
|
+
assertNonEmptyString(options.table[i], `${label}.table[${i}]`);
|
|
211
|
+
parseTableRef(options.table[i]);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (Array.isArray(options.leftJoin) && options.leftJoin.length > 0) {
|
|
215
|
+
if (options.table.length !== options.leftJoin.length + 1) {
|
|
216
|
+
throw new Error(`${label}.table 与 ${label}.leftJoin 数量不匹配,要求 table.length = leftJoin.length + 1`, {
|
|
217
|
+
cause: null,
|
|
218
|
+
code: "validation"
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
} else if (options.table.length > 1) {
|
|
222
|
+
throw new Error(`${label}.table 为数组时必须配合 leftJoin 使用`, {
|
|
223
|
+
cause: null,
|
|
224
|
+
code: "validation"
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
} else {
|
|
228
|
+
assertNonEmptyString(options.table, `${label}.table`);
|
|
229
|
+
parseTableRef(options.table);
|
|
230
|
+
|
|
231
|
+
if (Array.isArray(options.leftJoin) && options.leftJoin.length > 0) {
|
|
232
|
+
throw new Error(`${label}.leftJoin 启用时,${label}.table 必须是数组`, {
|
|
233
|
+
cause: null,
|
|
234
|
+
code: "validation"
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (!isNullable(options.where)) {
|
|
240
|
+
validateWhereObject(options.where, `${label}.where`);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (!isNullable(options.fields) && !Array.isArray(options.fields)) {
|
|
244
|
+
throw new Error(`${label}.fields 必须是数组`, {
|
|
245
|
+
cause: null,
|
|
246
|
+
code: "validation"
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (!isNullable(options.orderBy)) {
|
|
251
|
+
validateOrderByItems(options.orderBy, `${label}.orderBy`);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (!isNullable(options.leftJoin)) {
|
|
255
|
+
validateLeftJoinItems(options.leftJoin, `${label}.leftJoin`);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (!isNullable(options.page) && (!Number.isFinite(options.page) || Math.floor(options.page) !== options.page)) {
|
|
259
|
+
throw new Error(`${label}.page 必须是整数`, {
|
|
260
|
+
cause: null,
|
|
261
|
+
code: "validation"
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (!isNullable(options.limit) && (!Number.isFinite(options.limit) || Math.floor(options.limit) !== options.limit)) {
|
|
266
|
+
throw new Error(`${label}.limit 必须是整数`, {
|
|
267
|
+
cause: null,
|
|
268
|
+
code: "validation"
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function assertBatchDataList(dataList, label) {
|
|
274
|
+
if (!Array.isArray(dataList)) {
|
|
275
|
+
throw new Error(`${label} 必须是数组`, {
|
|
276
|
+
cause: null,
|
|
277
|
+
code: "validation"
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
for (let i = 0; i < dataList.length; i++) {
|
|
282
|
+
if (!isPlainObject(dataList[i])) {
|
|
283
|
+
throw new Error(`${label}[${i}] 必须是对象`, {
|
|
284
|
+
cause: null,
|
|
285
|
+
code: "validation"
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function validateNoLeftJoinReadOptions(options, label, joinErrorMessage) {
|
|
292
|
+
assertNonEmptyString(options.table, `${label}.table`);
|
|
293
|
+
validateWhereObject(options.where, `${label}.where`, false);
|
|
294
|
+
if (Array.isArray(options.leftJoin) && options.leftJoin.length > 0) {
|
|
295
|
+
throw new Error(joinErrorMessage, {
|
|
296
|
+
cause: null,
|
|
297
|
+
code: "validation"
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (Array.isArray(options.table)) {
|
|
302
|
+
throw new Error(`${label}.table 不支持数组`, {
|
|
303
|
+
cause: null,
|
|
304
|
+
code: "validation"
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const table = isString(options.table) ? options.table.trim() : "";
|
|
309
|
+
if (!table) {
|
|
310
|
+
throw new Error(`${label}.table 不能为空`, {
|
|
311
|
+
cause: null,
|
|
312
|
+
code: "validation"
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
if (table.includes(" ")) {
|
|
316
|
+
throw new Error(`${label} 不支持别名表写法(table: ${table})`, {
|
|
317
|
+
cause: null,
|
|
318
|
+
code: "validation"
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
if (table.includes(".")) {
|
|
322
|
+
throw new Error(`${label} 不支持 schema.table 写法(table: ${table})`, {
|
|
323
|
+
cause: null,
|
|
324
|
+
code: "validation"
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (!isNullable(options.orderBy)) {
|
|
329
|
+
validateOrderByItems(options.orderBy, `${label}.orderBy`);
|
|
330
|
+
}
|
|
331
|
+
if (!isNullable(options.page) && (!Number.isFinite(options.page) || Math.floor(options.page) !== options.page)) {
|
|
332
|
+
throw new Error(`${label}.page 必须是整数`, {
|
|
333
|
+
cause: null,
|
|
334
|
+
code: "validation"
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
if (!isNullable(options.limit) && (!Number.isFinite(options.limit) || Math.floor(options.limit) !== options.limit)) {
|
|
338
|
+
throw new Error(`${label}.limit 必须是整数`, {
|
|
339
|
+
cause: null,
|
|
340
|
+
code: "validation"
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function assertSafeFieldName(field) {
|
|
346
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(field)) {
|
|
347
|
+
throw new Error(`无效的字段名: ${field},只允许字母、数字和下划线`, {
|
|
348
|
+
cause: null,
|
|
349
|
+
code: "validation"
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function assertPageLimitRange(page, limit, rawTable) {
|
|
355
|
+
if (page < 1 || page > 10000) {
|
|
356
|
+
throw new Error(`页码必须在 1 到 10000 之间 (table: ${rawTable}, page: ${page}, limit: ${limit})`, {
|
|
357
|
+
cause: null,
|
|
358
|
+
code: "validation"
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
if (limit < 1 || limit > 100000) {
|
|
362
|
+
throw new Error(`每页数量必须在 1 到 100000 之间 (table: ${rawTable}, page: ${page}, limit: ${limit})`, {
|
|
363
|
+
cause: null,
|
|
364
|
+
code: "validation"
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function validateIncrementOptions(table, field, where, value, label) {
|
|
370
|
+
assertNonEmptyString(table, `${label}.table`);
|
|
371
|
+
validateWhereObject(where, `${label}.where`, true);
|
|
372
|
+
assertSafeFieldName(snakeCase(table));
|
|
373
|
+
assertSafeFieldName(snakeCase(field));
|
|
374
|
+
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
375
|
+
throw new Error(`自增值必须是有效的数字 (table: ${table}, field: ${field}, value: ${value})`, {
|
|
376
|
+
cause: null,
|
|
377
|
+
code: "validation"
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function assertEffectiveWhere(where, label) {
|
|
383
|
+
if (!where || Object.keys(where).length === 0) {
|
|
384
|
+
throw new Error(`${label} 需要有效的 WHERE 条件`, {
|
|
385
|
+
cause: null,
|
|
386
|
+
code: "validation"
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function assertDefinedParamValue(value) {
|
|
392
|
+
if (value === undefined) {
|
|
393
|
+
throw new Error("SQL 参数值 不能为 undefined", {
|
|
394
|
+
cause: null,
|
|
395
|
+
code: "validation"
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function normalizeTableRef(tableRef) {
|
|
401
|
+
const parsed = parseTableRef(tableRef);
|
|
402
|
+
const schemaPart = parsed.schema ? snakeCase(parsed.schema) : null;
|
|
403
|
+
const tablePart = snakeCase(parsed.table);
|
|
404
|
+
const aliasPart = parsed.alias ? snakeCase(parsed.alias) : null;
|
|
405
|
+
let result = schemaPart ? `${schemaPart}.${tablePart}` : tablePart;
|
|
406
|
+
if (aliasPart) {
|
|
407
|
+
result = `${result} ${aliasPart}`;
|
|
408
|
+
}
|
|
409
|
+
return result;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function parseFieldSelection(fields) {
|
|
413
|
+
if (!fields || fields.length === 0) {
|
|
414
|
+
return { type: "all", fields: [] };
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (fields.some((field) => field === "*")) {
|
|
418
|
+
throw new Error("fields 不支持 * 星号,请使用空数组 [] 或不传参数表示查询所有字段", {
|
|
419
|
+
cause: null,
|
|
420
|
+
code: "validation"
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (fields.some((field) => !isNonEmptyString(field))) {
|
|
425
|
+
throw new Error("fields 不能包含空字符串或无效值", {
|
|
426
|
+
cause: null,
|
|
427
|
+
code: "validation"
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
for (const rawField of fields) {
|
|
432
|
+
const checkField = rawField.startsWith("!") ? rawField.substring(1) : rawField;
|
|
433
|
+
assertNoExprField(checkField);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const includeFields = fields.filter((field) => !field.startsWith("!"));
|
|
437
|
+
const excludeFields = fields.filter((field) => field.startsWith("!"));
|
|
438
|
+
|
|
439
|
+
if (includeFields.length > 0 && excludeFields.length === 0) {
|
|
440
|
+
return { type: "include", fields: includeFields };
|
|
441
|
+
}
|
|
442
|
+
if (excludeFields.length > 0 && includeFields.length === 0) {
|
|
443
|
+
return { type: "exclude", fields: excludeFields.map((field) => field.substring(1)) };
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
throw new Error('fields 不能同时包含普通字段和排除字段(! 开头)。只能使用以下3种方式之一:\n1. 空数组 [] 或不传(查询所有)\n2. 全部指定字段 ["id", "name"]\n3. 全部排除字段 ["!password", "!token"]', {
|
|
447
|
+
cause: null,
|
|
448
|
+
code: "validation"
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function getTableQualifier(tableRef) {
|
|
453
|
+
const parsed = parseTableRef(tableRef);
|
|
454
|
+
if (parsed.alias) {
|
|
455
|
+
return snakeCase(parsed.alias);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
return snakeCase(parsed.table);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function parseJoinFieldSelection(fields, mainTableRef) {
|
|
462
|
+
if (!fields || fields.length === 0) {
|
|
463
|
+
throw new Error("leftJoin 查询必须显式传 fields,不支持空 fields 或查询全部字段", {
|
|
464
|
+
cause: null,
|
|
465
|
+
code: "validation"
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const mainQualifier = getTableQualifier(mainTableRef);
|
|
470
|
+
const mainWildcard = `${mainQualifier}.*`;
|
|
471
|
+
const includeFields = [];
|
|
472
|
+
const mainExcludeFields = [];
|
|
473
|
+
let mainAllIncluded = false;
|
|
474
|
+
|
|
475
|
+
for (const rawField of fields) {
|
|
476
|
+
if (!isNonEmptyString(rawField)) {
|
|
477
|
+
throw new Error("fields 不能包含空字符串或无效值", {
|
|
478
|
+
cause: null,
|
|
479
|
+
code: "validation"
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const trimmed = rawField.trim();
|
|
484
|
+
if (trimmed === "*") {
|
|
485
|
+
throw new Error("fields 不支持 * 星号,请使用空数组 [] 或不传参数表示查询所有字段", {
|
|
486
|
+
cause: null,
|
|
487
|
+
code: "validation"
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
if (trimmed.startsWith("!")) {
|
|
492
|
+
const fieldPart = trimmed.substring(1).trim();
|
|
493
|
+
if (!isNonEmptyString(fieldPart)) {
|
|
494
|
+
throw new Error("fields 不能包含空字符串或无效值", {
|
|
495
|
+
cause: null,
|
|
496
|
+
code: "validation"
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
if (/\s/.test(fieldPart)) {
|
|
500
|
+
throw new Error("leftJoin 排除字段不支持别名", {
|
|
501
|
+
cause: null,
|
|
502
|
+
code: "validation"
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
assertNoExprField(fieldPart);
|
|
507
|
+
const normalizedField = normalizeQualifierField(fieldPart);
|
|
508
|
+
if (normalizedField === mainWildcard) {
|
|
509
|
+
throw new Error("leftJoin 主表排除字段不能使用 *", {
|
|
510
|
+
cause: null,
|
|
511
|
+
code: "validation"
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
if (!normalizedField.startsWith(`${mainQualifier}.`)) {
|
|
515
|
+
throw new Error("leftJoin 排除字段仅支持主表字段,请使用主表限定符", {
|
|
516
|
+
cause: null,
|
|
517
|
+
code: "validation"
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
mainExcludeFields.push(normalizedField.substring(mainQualifier.length + 1));
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
assertNoExprField(trimmed);
|
|
526
|
+
const aliasParts = parseFieldAliasParts(trimmed);
|
|
527
|
+
const normalizedField = normalizeQualifierField(aliasParts ? aliasParts.fieldPart.trim() : trimmed);
|
|
528
|
+
if (normalizedField === mainWildcard) {
|
|
529
|
+
if (aliasParts) {
|
|
530
|
+
throw new Error("leftJoin 主表全字段不支持别名", {
|
|
531
|
+
cause: null,
|
|
532
|
+
code: "validation"
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
mainAllIncluded = true;
|
|
536
|
+
continue;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
includeFields.push(trimmed);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (mainExcludeFields.length > 0 && !mainAllIncluded) {
|
|
543
|
+
throw new Error("leftJoin 使用主表排除字段时,必须同时传主表限定符.*", {
|
|
544
|
+
cause: null,
|
|
545
|
+
code: "validation"
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
return {
|
|
550
|
+
type: "join",
|
|
551
|
+
includeFields: includeFields,
|
|
552
|
+
mainAllIncluded: mainAllIncluded,
|
|
553
|
+
mainExcludeFields: mainExcludeFields,
|
|
554
|
+
mainQualifier: mainQualifier
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function createWhereRoot(join = "AND") {
|
|
559
|
+
return { type: "group", join: join, items: [] };
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function appendWhereNode(root, node) {
|
|
563
|
+
if (node) {
|
|
564
|
+
root.items.push(node);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function buildArrayOperatorNode(fieldName, operator, value, errorFactory, emptyMessage) {
|
|
569
|
+
if (!Array.isArray(value)) {
|
|
570
|
+
throw new Error(errorFactory(operator), {
|
|
571
|
+
cause: null,
|
|
572
|
+
code: "validation"
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
if (value.length === 0) {
|
|
576
|
+
throw new Error(emptyMessage, {
|
|
577
|
+
cause: null,
|
|
578
|
+
code: "validation"
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
return { type: "op", field: fieldName, operator: operator, value: value };
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function buildRangeOrNullOperatorNode(fieldName, operator, value) {
|
|
586
|
+
if (operator === "$between" || operator === "$notBetween") {
|
|
587
|
+
if (Array.isArray(value) && value.length === 2) {
|
|
588
|
+
return { type: "op", field: fieldName, operator: operator, value: value };
|
|
589
|
+
}
|
|
590
|
+
return null;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
if (value === true) {
|
|
594
|
+
return { type: "op", field: fieldName, operator: operator, value: value };
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
return null;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
function buildOperatorNode(fieldName, operator, value) {
|
|
601
|
+
switch (operator) {
|
|
602
|
+
case "$in":
|
|
603
|
+
return buildArrayOperatorNode(fieldName, operator, value, (currentOperator) => `$in 操作符的值必须是数组 (operator: ${currentOperator})`, "$in 操作符的数组不能为空");
|
|
604
|
+
case "$notIn":
|
|
605
|
+
return buildArrayOperatorNode(fieldName, operator, value, (currentOperator) => `$notIn 操作符的值必须是数组 (operator: ${currentOperator})`, "$notIn 操作符的数组不能为空");
|
|
606
|
+
case "$between":
|
|
607
|
+
case "$notBetween":
|
|
608
|
+
case "$null":
|
|
609
|
+
case "$notNull":
|
|
610
|
+
return buildRangeOrNullOperatorNode(fieldName, operator, value);
|
|
611
|
+
case "$like":
|
|
612
|
+
case "$leftLike":
|
|
613
|
+
case "$rightLike":
|
|
614
|
+
if (isString(value) && value.trim() === "") {
|
|
615
|
+
return null;
|
|
616
|
+
}
|
|
617
|
+
assertDefinedParamValue(value);
|
|
618
|
+
return { type: "op", field: fieldName, operator: operator, value: value };
|
|
619
|
+
default:
|
|
620
|
+
assertDefinedParamValue(value);
|
|
621
|
+
return { type: "op", field: fieldName, operator: operator, value: value };
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
function appendFieldCondition(group, key, value) {
|
|
626
|
+
if (key.includes("$")) {
|
|
627
|
+
const lastDollarIndex = key.lastIndexOf("$");
|
|
628
|
+
appendWhereNode(group, buildOperatorNode(key.substring(0, lastDollarIndex), `$${key.substring(lastDollarIndex + 1)}`, value));
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
633
|
+
for (const [operator, operatorValue] of Object.entries(value)) {
|
|
634
|
+
appendWhereNode(group, buildOperatorNode(key, operator, operatorValue));
|
|
635
|
+
}
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
appendWhereNode(group, buildOperatorNode(key, "=", value));
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function appendAndConditions(group, value) {
|
|
643
|
+
if (!Array.isArray(value)) {
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
for (const condition of value) {
|
|
648
|
+
if (!condition || typeof condition !== "object" || Array.isArray(condition)) {
|
|
649
|
+
continue;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
const sub = parseWhereObject(condition);
|
|
653
|
+
if (sub.items.length === 0) {
|
|
654
|
+
continue;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
if (sub.join === "AND") {
|
|
658
|
+
for (const item of sub.items) {
|
|
659
|
+
group.items.push(item);
|
|
660
|
+
}
|
|
661
|
+
continue;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
group.items.push(sub);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
function appendOrConditions(group, value) {
|
|
669
|
+
if (!Array.isArray(value)) {
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
const orGroup = createWhereRoot("OR");
|
|
674
|
+
for (const condition of value) {
|
|
675
|
+
if (!condition || typeof condition !== "object" || Array.isArray(condition)) {
|
|
676
|
+
continue;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
const sub = parseWhereObject(condition);
|
|
680
|
+
if (sub.items.length > 0) {
|
|
681
|
+
orGroup.items.push(sub);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
if (orGroup.items.length > 0) {
|
|
686
|
+
group.items.push(orGroup);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function parseWhereObject(whereObj) {
|
|
691
|
+
if (!whereObj || typeof whereObj !== "object") {
|
|
692
|
+
return createWhereRoot();
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
const group = createWhereRoot();
|
|
696
|
+
|
|
697
|
+
for (const [key, value] of Object.entries(whereObj)) {
|
|
698
|
+
if (value === undefined) {
|
|
699
|
+
continue;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
if (key === "$and") {
|
|
703
|
+
appendAndConditions(group, value);
|
|
704
|
+
continue;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
if (key === "$or") {
|
|
708
|
+
appendOrConditions(group, value);
|
|
709
|
+
continue;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
appendFieldCondition(group, key, value);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
return group;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
function applyDefaultStateFilter(where = {}, table, hasLeftJoin = false, beflyMode = "auto") {
|
|
719
|
+
if (beflyMode === "manual") {
|
|
720
|
+
return where;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
const hasStateCondition = Object.keys(where).some((key) => key.startsWith("state") || key.includes(".state"));
|
|
724
|
+
if (hasStateCondition) {
|
|
725
|
+
return where;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
const result = {};
|
|
729
|
+
for (const [key, value] of Object.entries(where)) {
|
|
730
|
+
result[key] = value;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
if (hasLeftJoin && table) {
|
|
734
|
+
result[`${table}.state$gt`] = 0;
|
|
735
|
+
return result;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
result.state$gt = 0;
|
|
739
|
+
return result;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
function parseLeftJoinItem(joinTable, joinItem) {
|
|
743
|
+
const parts = joinItem.split(" ");
|
|
744
|
+
return {
|
|
745
|
+
type: "left",
|
|
746
|
+
table: normalizeTableRef(joinTable),
|
|
747
|
+
on: processJoinOn(`${parts[0]} = ${parts[1]}`)
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
function parseStructuredOrderBy(orderBy) {
|
|
752
|
+
return orderBy.map((item) => {
|
|
753
|
+
const parts = item.split("#");
|
|
754
|
+
return {
|
|
755
|
+
field: parts[0].trim(),
|
|
756
|
+
dir: parts[1].trim().toUpperCase()
|
|
757
|
+
};
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
export class DbParse {
|
|
762
|
+
tables;
|
|
763
|
+
beflyMode;
|
|
764
|
+
getTableColumns;
|
|
765
|
+
|
|
766
|
+
constructor(options = {}) {
|
|
767
|
+
this.tables = isPlainObject(options.tables) ? options.tables : {};
|
|
768
|
+
this.beflyMode = options.beflyMode === "manual" ? "manual" : "auto";
|
|
769
|
+
this.getTableColumns = options.getTableColumns;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
classifyFields(fields) {
|
|
773
|
+
return parseFieldSelection(fields);
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
classifyJoinFields(fields, mainTableRef) {
|
|
777
|
+
return parseJoinFieldSelection(fields, mainTableRef);
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
parseLeftJoinItem(joinTable, joinItem) {
|
|
781
|
+
return parseLeftJoinItem(joinTable, joinItem);
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
applyDefaultStateFilter(where, table, hasLeftJoin = false) {
|
|
785
|
+
return applyDefaultStateFilter(where, table, hasLeftJoin, this.beflyMode);
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
async parseRead(options, mode) {
|
|
789
|
+
const label = mode === "one" ? "getOne.options" : mode === "list" ? "getList.options" : "getAll.options";
|
|
790
|
+
validateQueryOptions(options, label);
|
|
791
|
+
|
|
792
|
+
const hasLeftJoin = Array.isArray(options.leftJoin) && options.leftJoin.length > 0;
|
|
793
|
+
const classifiedFields = hasLeftJoin ? parseJoinFieldSelection(options.fields, options.table[0]) : parseFieldSelection(options.fields);
|
|
794
|
+
const cleanWhere = clearDeep(options.where || {});
|
|
795
|
+
|
|
796
|
+
let prepared;
|
|
797
|
+
if (hasLeftJoin) {
|
|
798
|
+
prepared = await this._normalizeLeftJoinReadOptions(options, cleanWhere, classifiedFields);
|
|
799
|
+
} else {
|
|
800
|
+
prepared = await this._normalizeSingleTableReadOptions(options, cleanWhere, classifiedFields);
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
if (mode === "list") {
|
|
804
|
+
assertPageLimitRange(prepared.page, prepared.limit, options.table);
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
return prepared;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
parseCount(options) {
|
|
811
|
+
validateQueryOptions(options, "getCount.options");
|
|
812
|
+
const cleanWhere = clearDeep(options.where || {});
|
|
813
|
+
const hasLeftJoin = Array.isArray(options.leftJoin) && options.leftJoin.length > 0;
|
|
814
|
+
|
|
815
|
+
if (hasLeftJoin) {
|
|
816
|
+
return this._normalizeLeftJoinCountOptions(options, cleanWhere);
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
const snakeTable = snakeCase(Array.isArray(options.table) ? options.table[0] : options.table);
|
|
820
|
+
const normalizedWhere = applyDefaultStateFilter(whereKeysToSnake(cleanWhere), snakeTable, false, this.beflyMode);
|
|
821
|
+
return {
|
|
822
|
+
table: snakeTable,
|
|
823
|
+
fields: [],
|
|
824
|
+
where: parseWhereObject(normalizedWhere),
|
|
825
|
+
leftJoins: [],
|
|
826
|
+
orderBy: [],
|
|
827
|
+
page: options.page || 1,
|
|
828
|
+
limit: options.limit || 10
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
parseExists(options) {
|
|
833
|
+
validateNoLeftJoinReadOptions(options, "exists", "exists 不支持 leftJoin(请使用显式 query 或拆分查询)");
|
|
834
|
+
return this._prepareSingleTableWhere(options.table, options.where, true, false, "exists");
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
async parseFieldValue(options) {
|
|
838
|
+
validateNoLeftJoinReadOptions(options, "getFieldValue", "getFieldValue 不支持 leftJoin(请使用 getOne/getList 并自行取字段)");
|
|
839
|
+
assertSafeFieldName(options.field);
|
|
840
|
+
|
|
841
|
+
const readOptions = {
|
|
842
|
+
table: options.table,
|
|
843
|
+
fields: [options.field]
|
|
844
|
+
};
|
|
845
|
+
if (options.where !== undefined) readOptions.where = options.where;
|
|
846
|
+
if (options.orderBy !== undefined) readOptions.orderBy = options.orderBy;
|
|
847
|
+
if (options.page !== undefined) readOptions.page = options.page;
|
|
848
|
+
if (options.limit !== undefined) readOptions.limit = options.limit;
|
|
849
|
+
|
|
850
|
+
return {
|
|
851
|
+
field: options.field,
|
|
852
|
+
prepared: await this.parseRead(readOptions, "one")
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
parseInsert(options) {
|
|
857
|
+
assertNonEmptyString(options.table, "insData.table");
|
|
858
|
+
assertPlainObjectValue(options.data, "insData.data");
|
|
859
|
+
return {
|
|
860
|
+
table: options.table,
|
|
861
|
+
snakeTable: snakeCase(options.table),
|
|
862
|
+
data: options.data
|
|
863
|
+
};
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
parseInsertBatch(table, dataList) {
|
|
867
|
+
assertNonEmptyString(table, "insBatch.table");
|
|
868
|
+
assertBatchDataList(dataList, "insBatch.dataList");
|
|
869
|
+
return {
|
|
870
|
+
table: table,
|
|
871
|
+
snakeTable: snakeCase(table),
|
|
872
|
+
dataList: dataList
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
parseUpdate(options) {
|
|
877
|
+
assertNonEmptyString(options.table, "updData.table");
|
|
878
|
+
assertPlainObjectValue(options.data, "updData.data");
|
|
879
|
+
validateWhereObject(options.where, "updData.where", true);
|
|
880
|
+
return {
|
|
881
|
+
table: options.table,
|
|
882
|
+
snakeTable: snakeCase(options.table),
|
|
883
|
+
where: this._prepareSingleTableWhere(options.table, options.where, true, true, "updData").where,
|
|
884
|
+
data: options.data
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
parseDelete(options, force = false) {
|
|
889
|
+
const label = force ? "delForce" : "delData";
|
|
890
|
+
assertNonEmptyString(options.table, `${label}.table`);
|
|
891
|
+
validateWhereObject(options.where, `${label}.where`, true);
|
|
892
|
+
return {
|
|
893
|
+
table: options.table,
|
|
894
|
+
snakeTable: snakeCase(options.table),
|
|
895
|
+
where: this._prepareSingleTableWhere(options.table, options.where, !force, true, label).where
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
parseDelForceBatch(table, ids) {
|
|
900
|
+
assertNonEmptyString(table, "delForceBatch.table");
|
|
901
|
+
if (!Array.isArray(ids)) {
|
|
902
|
+
throw new Error("delForceBatch.ids 必须是数组", {
|
|
903
|
+
cause: null,
|
|
904
|
+
code: "validation"
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
return {
|
|
908
|
+
table: table,
|
|
909
|
+
snakeTable: snakeCase(table),
|
|
910
|
+
ids: ids
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
parseUpdateBatch(table, dataList) {
|
|
915
|
+
assertNonEmptyString(table, "updBatch.table");
|
|
916
|
+
assertBatchDataList(dataList, "updBatch.dataList");
|
|
917
|
+
return {
|
|
918
|
+
table: table,
|
|
919
|
+
snakeTable: snakeCase(table),
|
|
920
|
+
dataList: dataList
|
|
921
|
+
};
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
parseIncrement(table, field, where, value, label = "increment") {
|
|
925
|
+
validateIncrementOptions(table, field, where, value, label);
|
|
926
|
+
const prepared = this._prepareSingleTableWhere(table, where, true, true, label);
|
|
927
|
+
return {
|
|
928
|
+
table: table,
|
|
929
|
+
snakeTable: prepared.snakeTable,
|
|
930
|
+
snakeField: snakeCase(field),
|
|
931
|
+
where: prepared.where,
|
|
932
|
+
value: value
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
async _normalizeLeftJoinReadOptions(options, cleanWhere, classifiedFields) {
|
|
937
|
+
const mainTableRef = options.table[0];
|
|
938
|
+
const processedFields = await this._joinFieldsToSnake(mainTableRef, classifiedFields);
|
|
939
|
+
const joinTableRefs = options.table.slice(1);
|
|
940
|
+
const normalizedTableRef = normalizeTableRef(mainTableRef);
|
|
941
|
+
const mainQualifier = getTableQualifier(mainTableRef);
|
|
942
|
+
const joins = options.leftJoin.map((joinItem, index) => parseLeftJoinItem(joinTableRefs[index], joinItem));
|
|
943
|
+
const normalizedWhere = applyDefaultStateFilter(processJoinWhere(cleanWhere), mainQualifier, true, this.beflyMode);
|
|
944
|
+
|
|
945
|
+
return {
|
|
946
|
+
table: normalizedTableRef,
|
|
947
|
+
fields: processedFields,
|
|
948
|
+
where: parseWhereObject(normalizedWhere),
|
|
949
|
+
leftJoins: joins,
|
|
950
|
+
orderBy: parseStructuredOrderBy(processJoinOrderBy(options.orderBy || [])),
|
|
951
|
+
page: options.page || 1,
|
|
952
|
+
limit: options.limit || 10
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
_normalizeLeftJoinCountOptions(options, cleanWhere) {
|
|
957
|
+
const mainTableRef = options.table[0];
|
|
958
|
+
const joinTableRefs = options.table.slice(1);
|
|
959
|
+
const normalizedTableRef = normalizeTableRef(mainTableRef);
|
|
960
|
+
const mainQualifier = getTableQualifier(mainTableRef);
|
|
961
|
+
const joins = options.leftJoin.map((joinItem, index) => parseLeftJoinItem(joinTableRefs[index], joinItem));
|
|
962
|
+
const normalizedWhere = applyDefaultStateFilter(processJoinWhere(cleanWhere), mainQualifier, true, this.beflyMode);
|
|
963
|
+
|
|
964
|
+
return {
|
|
965
|
+
table: normalizedTableRef,
|
|
966
|
+
fields: [],
|
|
967
|
+
where: parseWhereObject(normalizedWhere),
|
|
968
|
+
leftJoins: joins,
|
|
969
|
+
orderBy: [],
|
|
970
|
+
page: options.page || 1,
|
|
971
|
+
limit: options.limit || 10
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
async _normalizeSingleTableReadOptions(options, cleanWhere, classifiedFields) {
|
|
976
|
+
const tableRef = Array.isArray(options.table) ? options.table[0] : options.table;
|
|
977
|
+
const snakeTable = snakeCase(tableRef);
|
|
978
|
+
const processedFields = await fieldsToSnake(tableRef, classifiedFields, this.getTableColumns);
|
|
979
|
+
const normalizedWhere = applyDefaultStateFilter(whereKeysToSnake(cleanWhere), snakeTable, false, this.beflyMode);
|
|
980
|
+
|
|
981
|
+
return {
|
|
982
|
+
table: snakeTable,
|
|
983
|
+
fields: processedFields,
|
|
984
|
+
where: parseWhereObject(normalizedWhere),
|
|
985
|
+
leftJoins: [],
|
|
986
|
+
orderBy: parseStructuredOrderBy(orderByToSnake(options.orderBy || [])),
|
|
987
|
+
page: options.page || 1,
|
|
988
|
+
limit: options.limit || 10
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
_prepareSingleTableWhere(table, where, useDefaultStateFilter, required, label) {
|
|
993
|
+
const snakeTable = snakeCase(table);
|
|
994
|
+
const normalizedWhere = whereKeysToSnake(clearDeep(where || {}));
|
|
995
|
+
if (required) {
|
|
996
|
+
assertEffectiveWhere(normalizedWhere, label);
|
|
997
|
+
}
|
|
998
|
+
const whereWithDefault = useDefaultStateFilter ? applyDefaultStateFilter(normalizedWhere, snakeTable, false, this.beflyMode) : normalizedWhere;
|
|
999
|
+
return {
|
|
1000
|
+
snakeTable: snakeTable,
|
|
1001
|
+
where: parseWhereObject(whereWithDefault)
|
|
1002
|
+
};
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
async _joinFieldsToSnake(mainTableRef, classifiedFields) {
|
|
1006
|
+
if (classifiedFields.type !== "join") {
|
|
1007
|
+
return [];
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
const processedFields = [];
|
|
1011
|
+
const seenFields = new Set();
|
|
1012
|
+
const pushField = (field) => {
|
|
1013
|
+
if (seenFields.has(field)) {
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
seenFields.add(field);
|
|
1017
|
+
processedFields.push(field);
|
|
1018
|
+
};
|
|
1019
|
+
|
|
1020
|
+
if (classifiedFields.mainAllIncluded) {
|
|
1021
|
+
const mainColumns = await this.getTableColumns(mainTableRef);
|
|
1022
|
+
const excludeFieldSet = new Set(classifiedFields.mainExcludeFields.map((field) => snakeCase(field)));
|
|
1023
|
+
|
|
1024
|
+
for (const column of mainColumns) {
|
|
1025
|
+
if (excludeFieldSet.has(column)) {
|
|
1026
|
+
continue;
|
|
1027
|
+
}
|
|
1028
|
+
pushField(`${classifiedFields.mainQualifier}.${column}`);
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
for (const field of classifiedFields.includeFields) {
|
|
1033
|
+
pushField(processJoinField(field));
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
if (processedFields.length === 0) {
|
|
1037
|
+
throw new Error(`排除字段后没有剩余字段可查询。表: ${mainTableRef}, 排除: ${classifiedFields.mainExcludeFields.join(", ")}`, {
|
|
1038
|
+
cause: null,
|
|
1039
|
+
code: "validation"
|
|
1040
|
+
});
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
return processedFields;
|
|
1044
|
+
}
|
|
1045
|
+
}
|