befly 3.20.10 → 3.21.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 +1626 -0
- package/lib/dbUtil.js +988 -0
- package/lib/sqlBuilder.js +714 -0
- package/package.json +4 -4
- package/plugins/mysql.js +1 -1
- package/scripts/syncDb/context.js +1 -1
- package/lib/dbHelper/builders.js +0 -658
- package/lib/dbHelper/dataOps.js +0 -578
- package/lib/dbHelper/execute.js +0 -128
- package/lib/dbHelper/index.js +0 -24
- package/lib/dbHelper/util.js +0 -95
- package/lib/dbHelper/validate.js +0 -695
- package/lib/sqlBuilder/batch.js +0 -114
- package/lib/sqlBuilder/check.js +0 -82
- package/lib/sqlBuilder/compiler.js +0 -360
- package/lib/sqlBuilder/index.js +0 -200
- package/lib/sqlBuilder/parser.js +0 -305
- package/lib/sqlBuilder/util.js +0 -273
|
@@ -0,0 +1,714 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQL 构造器 - JavaScript 版本
|
|
3
|
+
* 提供链式 API 构建 SQL 查询
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { assertBatchInsertRowsConsistent, assertNoUndefinedParam, escapeField, escapeTable, normalizeLimitValue, normalizeOffsetValue, resolveQuoteIdent, validateParam } from "./dbUtil.js";
|
|
7
|
+
import { isNonEmptyString, isString } from "../utils/is.js";
|
|
8
|
+
|
|
9
|
+
function createModel() {
|
|
10
|
+
return {
|
|
11
|
+
select: [],
|
|
12
|
+
from: null,
|
|
13
|
+
where: { type: "group", join: "AND", items: [] },
|
|
14
|
+
joins: [],
|
|
15
|
+
orderBy: [],
|
|
16
|
+
limit: null,
|
|
17
|
+
offset: null
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* SQL 构建器类
|
|
23
|
+
*/
|
|
24
|
+
export class SqlBuilder {
|
|
25
|
+
_model;
|
|
26
|
+
_quoteIdent;
|
|
27
|
+
|
|
28
|
+
constructor(options = {}) {
|
|
29
|
+
this._quoteIdent = resolveQuoteIdent(options);
|
|
30
|
+
this._model = createModel();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 重置构建器状态
|
|
35
|
+
*/
|
|
36
|
+
reset() {
|
|
37
|
+
this._model = createModel();
|
|
38
|
+
return this;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
_appendWhereNode(root, node) {
|
|
42
|
+
if (node) {
|
|
43
|
+
root.items.push(node);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
_buildArrayOperatorNode(fieldName, operator, value, errorFactory, emptyMessage) {
|
|
48
|
+
if (!Array.isArray(value)) {
|
|
49
|
+
throw new Error(errorFactory(operator), {
|
|
50
|
+
cause: null,
|
|
51
|
+
code: "validation"
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
if (value.length === 0) {
|
|
55
|
+
throw new Error(emptyMessage, {
|
|
56
|
+
cause: null,
|
|
57
|
+
code: "validation"
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return { type: "op", field: fieldName, operator: operator, value: value };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
_buildRangeOrNullOperatorNode(fieldName, operator, value) {
|
|
65
|
+
if (operator === "$between" || operator === "$notBetween") {
|
|
66
|
+
if (Array.isArray(value) && value.length === 2) {
|
|
67
|
+
return { type: "op", field: fieldName, operator: operator, value: value };
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (value === true) {
|
|
73
|
+
return { type: "op", field: fieldName, operator: operator, value: value };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
_buildOperatorNode(fieldName, operator, value) {
|
|
80
|
+
switch (operator) {
|
|
81
|
+
case "$in":
|
|
82
|
+
return this._buildArrayOperatorNode(fieldName, operator, value, (currentOperator) => `$in 操作符的值必须是数组 (operator: ${currentOperator})`, "$in 操作符的数组不能为空。提示:空数组会导致查询永远不匹配任何记录,这通常不是预期行为。请检查查询条件或移除该字段。");
|
|
83
|
+
case "$nin":
|
|
84
|
+
case "$notIn":
|
|
85
|
+
return this._buildArrayOperatorNode(fieldName, operator, value, (currentOperator) => `$nin/$notIn 操作符的值必须是数组 (operator: ${currentOperator})`, "$nin/$notIn 操作符的数组不能为空。提示:空数组会导致查询匹配所有记录,这通常不是预期行为。请检查查询条件或移除该字段。");
|
|
86
|
+
case "$between":
|
|
87
|
+
case "$notBetween":
|
|
88
|
+
case "$null":
|
|
89
|
+
case "$notNull":
|
|
90
|
+
return this._buildRangeOrNullOperatorNode(fieldName, operator, value);
|
|
91
|
+
case "$like":
|
|
92
|
+
case "like":
|
|
93
|
+
case "$leftLike":
|
|
94
|
+
case "leftLike":
|
|
95
|
+
case "$rightLike":
|
|
96
|
+
case "rightLike":
|
|
97
|
+
if (isString(value) && value.trim() === "") {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
validateParam(value);
|
|
101
|
+
return { type: "op", field: fieldName, operator: operator, value: value };
|
|
102
|
+
default:
|
|
103
|
+
validateParam(value);
|
|
104
|
+
return { type: "op", field: fieldName, operator: operator, value: value };
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
_appendFieldCondition(group, key, value) {
|
|
109
|
+
if (key.includes("$")) {
|
|
110
|
+
const lastDollarIndex = key.lastIndexOf("$");
|
|
111
|
+
this._appendWhereNode(group, this._buildOperatorNode(key.substring(0, lastDollarIndex), `$${key.substring(lastDollarIndex + 1)}`, value));
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
116
|
+
for (const [operator, operatorValue] of Object.entries(value)) {
|
|
117
|
+
this._appendWhereNode(group, this._buildOperatorNode(key, operator, operatorValue));
|
|
118
|
+
}
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
this._appendWhereNode(group, this._buildOperatorNode(key, "=", value));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
_appendAndConditions(group, value) {
|
|
126
|
+
if (!Array.isArray(value)) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
for (const condition of value) {
|
|
131
|
+
if (!condition || typeof condition !== "object" || Array.isArray(condition)) {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const sub = this._parseWhereObject(condition);
|
|
136
|
+
if (sub.items.length === 0) {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (sub.join === "AND") {
|
|
141
|
+
for (const item of sub.items) {
|
|
142
|
+
group.items.push(item);
|
|
143
|
+
}
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
group.items.push(sub);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
_appendOrConditions(group, value) {
|
|
152
|
+
if (!Array.isArray(value)) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const orGroup = { type: "group", join: "OR", items: [] };
|
|
157
|
+
for (const condition of value) {
|
|
158
|
+
if (!condition || typeof condition !== "object" || Array.isArray(condition)) {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const sub = this._parseWhereObject(condition);
|
|
163
|
+
if (sub.items.length > 0) {
|
|
164
|
+
orGroup.items.push(sub);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (orGroup.items.length > 0) {
|
|
169
|
+
group.items.push(orGroup);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
_parseWhereObject(whereObj) {
|
|
174
|
+
if (!whereObj || typeof whereObj !== "object") {
|
|
175
|
+
return { type: "group", join: "AND", items: [] };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const group = { type: "group", join: "AND", items: [] };
|
|
179
|
+
|
|
180
|
+
for (const [key, value] of Object.entries(whereObj)) {
|
|
181
|
+
if (value === undefined) {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (key === "$and") {
|
|
186
|
+
this._appendAndConditions(group, value);
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (key === "$or") {
|
|
191
|
+
this._appendOrConditions(group, value);
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
this._appendFieldCondition(group, key, value);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return group;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
_pushWhereParams(target, source) {
|
|
202
|
+
for (const param of source) {
|
|
203
|
+
target.push(param);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
_compileOperatorNode(node) {
|
|
208
|
+
const escapedField = escapeField(node.field, this._quoteIdent);
|
|
209
|
+
|
|
210
|
+
if (node.operator === "$ne" || node.operator === "$not") {
|
|
211
|
+
validateParam(node.value);
|
|
212
|
+
return { sql: `${escapedField} != ?`, params: [node.value] };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (node.operator === "$in") {
|
|
216
|
+
return {
|
|
217
|
+
sql: `${escapedField} IN (${node.value.map(() => "?").join(",")})`,
|
|
218
|
+
params: node.value.slice()
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (node.operator === "$nin" || node.operator === "$notIn") {
|
|
223
|
+
return {
|
|
224
|
+
sql: `${escapedField} NOT IN (${node.value.map(() => "?").join(",")})`,
|
|
225
|
+
params: node.value.slice()
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (node.operator === "$like" || node.operator === "like") {
|
|
230
|
+
validateParam(node.value);
|
|
231
|
+
return { sql: `${escapedField} LIKE ?`, params: [`%${String(node.value)}%`] };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (node.operator === "$leftLike" || node.operator === "leftLike") {
|
|
235
|
+
validateParam(node.value);
|
|
236
|
+
return { sql: `${escapedField} LIKE ?`, params: [`%${String(node.value)}`] };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (node.operator === "$rightLike" || node.operator === "rightLike") {
|
|
240
|
+
validateParam(node.value);
|
|
241
|
+
return { sql: `${escapedField} LIKE ?`, params: [`${String(node.value)}%`] };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (node.operator === "$notLike") {
|
|
245
|
+
validateParam(node.value);
|
|
246
|
+
return { sql: `${escapedField} NOT LIKE ?`, params: [node.value] };
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (node.operator === "$gt") {
|
|
250
|
+
validateParam(node.value);
|
|
251
|
+
return { sql: `${escapedField} > ?`, params: [node.value] };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (node.operator === "$gte") {
|
|
255
|
+
validateParam(node.value);
|
|
256
|
+
return { sql: `${escapedField} >= ?`, params: [node.value] };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (node.operator === "$lt") {
|
|
260
|
+
validateParam(node.value);
|
|
261
|
+
return { sql: `${escapedField} < ?`, params: [node.value] };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (node.operator === "$lte") {
|
|
265
|
+
validateParam(node.value);
|
|
266
|
+
return { sql: `${escapedField} <= ?`, params: [node.value] };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (node.operator === "$between") {
|
|
270
|
+
return { sql: `${escapedField} BETWEEN ? AND ?`, params: [node.value[0], node.value[1]] };
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (node.operator === "$notBetween") {
|
|
274
|
+
return { sql: `${escapedField} NOT BETWEEN ? AND ?`, params: [node.value[0], node.value[1]] };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (node.operator === "$null") {
|
|
278
|
+
return { sql: `${escapedField} IS NULL`, params: [] };
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (node.operator === "$notNull") {
|
|
282
|
+
return { sql: `${escapedField} IS NOT NULL`, params: [] };
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
validateParam(node.value);
|
|
286
|
+
return { sql: `${escapedField} = ?`, params: [node.value] };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
_compileWhereNode(node) {
|
|
290
|
+
if (!node) {
|
|
291
|
+
return { sql: "", params: [] };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (node.type === "raw") {
|
|
295
|
+
return { sql: node.sql, params: Array.isArray(node.params) ? node.params.slice() : [] };
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (node.type === "op") {
|
|
299
|
+
return this._compileOperatorNode(node);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (node.type !== "group" || !node.items || node.items.length === 0) {
|
|
303
|
+
return { sql: "", params: [] };
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const parts = [];
|
|
307
|
+
const params = [];
|
|
308
|
+
|
|
309
|
+
for (const item of node.items) {
|
|
310
|
+
const built = this._compileWhereNode(item);
|
|
311
|
+
if (!built.sql) {
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
let clause = built.sql;
|
|
316
|
+
if (node.join === "OR") {
|
|
317
|
+
clause = `(${clause})`;
|
|
318
|
+
} else if (item.type === "group" && item.join === "OR") {
|
|
319
|
+
clause = `(${clause})`;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
parts.push(clause);
|
|
323
|
+
this._pushWhereParams(params, built.params);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return { sql: parts.join(` ${node.join} `), params: params };
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* 获取 WHERE 条件(供 DbHelper 使用)
|
|
331
|
+
*/
|
|
332
|
+
getWhereConditions() {
|
|
333
|
+
const result = this._compileWhereNode(this._model.where);
|
|
334
|
+
return { sql: result.sql, params: result.params };
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* SELECT 字段
|
|
339
|
+
*/
|
|
340
|
+
select(fields = "*") {
|
|
341
|
+
if (Array.isArray(fields)) {
|
|
342
|
+
for (const field of fields) {
|
|
343
|
+
this._model.select.push({ type: "field", value: field });
|
|
344
|
+
}
|
|
345
|
+
return this;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (isString(fields)) {
|
|
349
|
+
this._model.select.push({ type: "field", value: fields });
|
|
350
|
+
}
|
|
351
|
+
return this;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* SELECT 原始表达式(不做转义)
|
|
356
|
+
*/
|
|
357
|
+
selectRaw(expr) {
|
|
358
|
+
this._model.select.push({ type: "raw", value: expr });
|
|
359
|
+
return this;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* FROM 表名
|
|
364
|
+
*/
|
|
365
|
+
from(table) {
|
|
366
|
+
this._model.from = { type: "table", value: table.trim() };
|
|
367
|
+
return this;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* FROM 原始表达式(不做转义)
|
|
372
|
+
*/
|
|
373
|
+
fromRaw(tableExpr) {
|
|
374
|
+
this._model.from = { type: "raw", value: tableExpr.trim() };
|
|
375
|
+
return this;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* WHERE 条件
|
|
380
|
+
*/
|
|
381
|
+
where(conditionOrField, value) {
|
|
382
|
+
if (conditionOrField && typeof conditionOrField === "object" && !Array.isArray(conditionOrField)) {
|
|
383
|
+
const node = this._parseWhereObject(conditionOrField);
|
|
384
|
+
if (node.items.length > 0) {
|
|
385
|
+
this._model.where.items.push(node);
|
|
386
|
+
}
|
|
387
|
+
return this;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (isString(conditionOrField)) {
|
|
391
|
+
this._appendWhereNode(this._model.where, this._buildOperatorNode(conditionOrField, "=", value));
|
|
392
|
+
}
|
|
393
|
+
return this;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* WHERE 原始片段(不做转义),可附带参数。
|
|
398
|
+
*/
|
|
399
|
+
whereRaw(sql, params) {
|
|
400
|
+
const paramList = Array.isArray(params) ? params : [];
|
|
401
|
+
for (const param of paramList) {
|
|
402
|
+
validateParam(param);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
this._model.where.items.push({ type: "raw", sql: sql, params: paramList });
|
|
406
|
+
return this;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* LEFT JOIN
|
|
411
|
+
*/
|
|
412
|
+
leftJoin(table, on) {
|
|
413
|
+
this._model.joins.push({ type: "left", table: table, on: on });
|
|
414
|
+
return this;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* ORDER BY
|
|
419
|
+
* @param fields - 格式为 ["field#ASC", "field2#DESC"]
|
|
420
|
+
*/
|
|
421
|
+
orderBy(fields) {
|
|
422
|
+
for (const item of fields) {
|
|
423
|
+
if (!isString(item) || !item.includes("#")) {
|
|
424
|
+
throw new Error(`orderBy 字段必须是 "字段#方向" 格式的字符串(例如:"name#ASC", "id#DESC") (item: ${String(item)})`, {
|
|
425
|
+
cause: null,
|
|
426
|
+
code: "validation"
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const parts = item.split("#");
|
|
431
|
+
if (parts.length !== 2) {
|
|
432
|
+
throw new Error(`orderBy 字段必须是 "字段#方向" 格式的字符串(例如:"name#ASC", "id#DESC") (item: ${String(item)})`, {
|
|
433
|
+
cause: null,
|
|
434
|
+
code: "validation"
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const cleanField = parts[0].trim();
|
|
439
|
+
const cleanDir = parts[1].trim().toUpperCase();
|
|
440
|
+
|
|
441
|
+
if (!cleanField) {
|
|
442
|
+
throw new Error(`orderBy 中字段名不能为空 (item: ${item})`, {
|
|
443
|
+
cause: null,
|
|
444
|
+
code: "validation"
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (!["ASC", "DESC"].includes(cleanDir)) {
|
|
449
|
+
throw new Error(`ORDER BY 方向必须是 ASC 或 DESC (direction: ${cleanDir})`, {
|
|
450
|
+
cause: null,
|
|
451
|
+
code: "validation"
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
this._model.orderBy.push({ field: cleanField, dir: cleanDir });
|
|
456
|
+
}
|
|
457
|
+
return this;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* LIMIT
|
|
462
|
+
*/
|
|
463
|
+
limit(count, offset) {
|
|
464
|
+
const result = normalizeLimitValue(count, offset);
|
|
465
|
+
this._model.limit = result.limitValue;
|
|
466
|
+
this._model.offset = result.offsetValue;
|
|
467
|
+
return this;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* OFFSET
|
|
472
|
+
*/
|
|
473
|
+
offset(count) {
|
|
474
|
+
const offsetValue = normalizeOffsetValue(count);
|
|
475
|
+
this._model.offset = offsetValue;
|
|
476
|
+
return this;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* 构建 SELECT 查询
|
|
481
|
+
*/
|
|
482
|
+
toSelectSql() {
|
|
483
|
+
const selectSql =
|
|
484
|
+
this._model.select.length > 0
|
|
485
|
+
? this._model.select
|
|
486
|
+
.map((item) => {
|
|
487
|
+
if (item.type === "raw") {
|
|
488
|
+
return item.value;
|
|
489
|
+
}
|
|
490
|
+
return escapeField(item.value, this._quoteIdent);
|
|
491
|
+
})
|
|
492
|
+
.join(", ")
|
|
493
|
+
: "*";
|
|
494
|
+
|
|
495
|
+
const params = [];
|
|
496
|
+
const fromSql = this._model.from.type === "raw" ? this._model.from.value : escapeTable(this._model.from.value, this._quoteIdent);
|
|
497
|
+
let sql = `SELECT ${selectSql} FROM ${fromSql}`;
|
|
498
|
+
|
|
499
|
+
if (this._model.joins.length > 0) {
|
|
500
|
+
sql += ` ${this._model.joins.map((join) => `${join.type.toUpperCase()} JOIN ${escapeTable(join.table, this._quoteIdent)} ON ${join.on}`).join(" ")}`;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const whereResult = this._compileWhereNode(this._model.where);
|
|
504
|
+
if (whereResult.sql) {
|
|
505
|
+
sql += ` WHERE ${whereResult.sql}`;
|
|
506
|
+
for (const param of whereResult.params) {
|
|
507
|
+
params.push(param);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (this._model.orderBy.length > 0) {
|
|
512
|
+
sql += ` ORDER BY ${this._model.orderBy.map((item) => `${escapeField(item.field, this._quoteIdent)} ${item.dir}`).join(", ")}`;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
if (this._model.limit !== null) {
|
|
516
|
+
sql += ` LIMIT ${this._model.limit}`;
|
|
517
|
+
if (this._model.offset !== null) {
|
|
518
|
+
sql += ` OFFSET ${this._model.offset}`;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
return { sql: sql, params: params };
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* 构建 INSERT 查询
|
|
527
|
+
*/
|
|
528
|
+
toInsertSql(table, data) {
|
|
529
|
+
const escapedTable = escapeTable(table, this._quoteIdent);
|
|
530
|
+
|
|
531
|
+
if (Array.isArray(data)) {
|
|
532
|
+
const fields = assertBatchInsertRowsConsistent(data, { table: table });
|
|
533
|
+
const escapedFields = fields.map((field) => escapeField(field, this._quoteIdent));
|
|
534
|
+
const placeholders = fields.map(() => "?").join(", ");
|
|
535
|
+
const values = data.map(() => `(${placeholders})`).join(", ");
|
|
536
|
+
const params = [];
|
|
537
|
+
|
|
538
|
+
for (const row of data) {
|
|
539
|
+
for (const field of fields) {
|
|
540
|
+
const value = row[field];
|
|
541
|
+
validateParam(value);
|
|
542
|
+
params.push(value);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
return {
|
|
547
|
+
sql: `INSERT INTO ${escapedTable} (${escapedFields.join(", ")}) VALUES ${values}`,
|
|
548
|
+
params: params
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
const fields = Object.keys(data);
|
|
553
|
+
if (fields.length === 0) {
|
|
554
|
+
throw new Error(`插入数据必须至少有一个字段 (table: ${table})`, {
|
|
555
|
+
cause: null,
|
|
556
|
+
code: "validation"
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
for (const field of fields) {
|
|
561
|
+
validateParam(data[field]);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
const escapedFields = fields.map((field) => escapeField(field, this._quoteIdent));
|
|
565
|
+
const placeholders = fields.map(() => "?").join(", ");
|
|
566
|
+
const params = [];
|
|
567
|
+
for (const field of fields) {
|
|
568
|
+
params.push(data[field]);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
return {
|
|
572
|
+
sql: `INSERT INTO ${escapedTable} (${escapedFields.join(", ")}) VALUES (${placeholders})`,
|
|
573
|
+
params: params
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* 构建 UPDATE 查询
|
|
579
|
+
*/
|
|
580
|
+
toUpdateSql(table, data) {
|
|
581
|
+
const fields = Object.keys(data);
|
|
582
|
+
if (fields.length === 0) {
|
|
583
|
+
throw new Error("更新数据必须至少有一个字段", {
|
|
584
|
+
cause: null,
|
|
585
|
+
code: "validation"
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
const params = [];
|
|
590
|
+
for (const value of Object.values(data)) {
|
|
591
|
+
params.push(value);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
const whereResult = this._compileWhereNode(this._model.where);
|
|
595
|
+
for (const param of whereResult.params) {
|
|
596
|
+
params.push(param);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
return {
|
|
600
|
+
sql: `UPDATE ${escapeTable(table, this._quoteIdent)} SET ${fields.map((field) => `${escapeField(field, this._quoteIdent)} = ?`).join(", ")} WHERE ${whereResult.sql}`,
|
|
601
|
+
params: params
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* 构建 DELETE 查询
|
|
607
|
+
*/
|
|
608
|
+
toDeleteSql(table) {
|
|
609
|
+
const whereResult = this._compileWhereNode(this._model.where);
|
|
610
|
+
return {
|
|
611
|
+
sql: `DELETE FROM ${escapeTable(table, this._quoteIdent)} WHERE ${whereResult.sql}`,
|
|
612
|
+
params: whereResult.params
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* 构建 COUNT 查询
|
|
618
|
+
*/
|
|
619
|
+
toCountSql() {
|
|
620
|
+
const params = [];
|
|
621
|
+
const fromSql = this._model.from.type === "raw" ? this._model.from.value : escapeTable(this._model.from.value, this._quoteIdent);
|
|
622
|
+
let sql = `SELECT COUNT(*) as total FROM ${fromSql}`;
|
|
623
|
+
|
|
624
|
+
if (this._model.joins.length > 0) {
|
|
625
|
+
sql += ` ${this._model.joins.map((join) => `${join.type.toUpperCase()} JOIN ${escapeTable(join.table, this._quoteIdent)} ON ${join.on}`).join(" ")}`;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
const whereResult = this._compileWhereNode(this._model.where);
|
|
629
|
+
if (whereResult.sql) {
|
|
630
|
+
sql += ` WHERE ${whereResult.sql}`;
|
|
631
|
+
for (const param of whereResult.params) {
|
|
632
|
+
params.push(param);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
return { sql: sql, params: params };
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
static toDeleteInSql(options) {
|
|
640
|
+
if (options.ids.length === 0) {
|
|
641
|
+
return { sql: "", params: [] };
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
const placeholders = options.ids.map(() => "?").join(",");
|
|
645
|
+
const sql = `DELETE FROM ${options.quoteIdent(options.table)} WHERE ${options.quoteIdent(options.idField)} IN (${placeholders})`;
|
|
646
|
+
const params = [];
|
|
647
|
+
for (const id of options.ids) {
|
|
648
|
+
params.push(id);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
return { sql: sql, params: params };
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
static toUpdateCaseByIdSql(options) {
|
|
655
|
+
if (options.rows.length === 0) {
|
|
656
|
+
return { sql: "", params: [] };
|
|
657
|
+
}
|
|
658
|
+
if (options.fields.length === 0) {
|
|
659
|
+
return { sql: "", params: [] };
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
const ids = options.rows.map((row) => row.id);
|
|
663
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
664
|
+
const setSqlList = [];
|
|
665
|
+
const args = [];
|
|
666
|
+
const quotedId = options.quoteIdent(options.idField);
|
|
667
|
+
|
|
668
|
+
for (const field of options.fields) {
|
|
669
|
+
const whenList = [];
|
|
670
|
+
|
|
671
|
+
for (const row of options.rows) {
|
|
672
|
+
if (!(field in row.data)) {
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
whenList.push("WHEN ? THEN ?");
|
|
677
|
+
args.push(row.id);
|
|
678
|
+
const value = row.data[field];
|
|
679
|
+
assertNoUndefinedParam(value, "SQL 参数值");
|
|
680
|
+
args.push(value);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
if (whenList.length === 0) {
|
|
684
|
+
continue;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
const quotedField = options.quoteIdent(field);
|
|
688
|
+
setSqlList.push(`${quotedField} = CASE ${quotedId} ${whenList.join(" ")} ELSE ${quotedField} END`);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
if (isNonEmptyString(options.updatedAtField)) {
|
|
692
|
+
setSqlList.push(`${options.quoteIdent(options.updatedAtField)} = ?`);
|
|
693
|
+
args.push(options.updatedAtValue);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
for (const id of ids) {
|
|
697
|
+
args.push(id);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
let sql = `UPDATE ${options.quoteIdent(options.table)} SET ${setSqlList.join(", ")} WHERE ${quotedId} IN (${placeholders})`;
|
|
701
|
+
if (options.stateGtZero && options.stateField) {
|
|
702
|
+
sql += ` AND ${options.quoteIdent(options.stateField)} > 0`;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
return { sql: sql, params: args };
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/**
|
|
710
|
+
* 创建新的 SQL 构建器实例
|
|
711
|
+
*/
|
|
712
|
+
export function createQueryBuilder() {
|
|
713
|
+
return new SqlBuilder();
|
|
714
|
+
}
|