befly 2.3.3 → 3.0.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/checks/conflict.ts +329 -0
- package/checks/table.ts +252 -0
- package/config/env.ts +218 -0
- package/config/fields.ts +55 -0
- package/config/regexAliases.ts +51 -0
- package/config/reserved.ts +96 -0
- package/main.ts +47 -0
- package/package.json +26 -11
- package/plugins/db.ts +60 -0
- package/plugins/logger.ts +28 -0
- package/plugins/redis.ts +47 -0
- package/scripts/syncDb/apply.ts +171 -0
- package/scripts/syncDb/constants.ts +71 -0
- package/scripts/syncDb/ddl.ts +189 -0
- package/scripts/syncDb/helpers.ts +173 -0
- package/scripts/syncDb/index.ts +203 -0
- package/scripts/syncDb/schema.ts +199 -0
- package/scripts/syncDb/sqlite.ts +50 -0
- package/scripts/syncDb/state.ts +106 -0
- package/scripts/syncDb/table.ts +214 -0
- package/scripts/syncDb/tableCreate.ts +148 -0
- package/scripts/syncDb/tests/constants.test.ts +105 -0
- package/scripts/syncDb/tests/ddl.test.ts +134 -0
- package/scripts/syncDb/tests/helpers.test.ts +70 -0
- package/scripts/syncDb/types.ts +92 -0
- package/scripts/syncDb/version.ts +73 -0
- package/scripts/syncDb.ts +10 -0
- package/tsconfig.json +58 -0
- package/types/addon.d.ts +53 -0
- package/types/api.d.ts +249 -0
- package/types/befly.d.ts +230 -0
- package/types/common.d.ts +215 -0
- package/types/context.d.ts +7 -0
- package/types/crypto.d.ts +23 -0
- package/types/database.d.ts +273 -0
- package/types/index.d.ts +450 -0
- package/types/index.ts +438 -0
- package/types/jwt.d.ts +99 -0
- package/types/logger.d.ts +43 -0
- package/types/plugin.d.ts +109 -0
- package/types/redis.d.ts +46 -0
- package/types/tool.d.ts +67 -0
- package/types/validator.d.ts +43 -0
- package/types/validator.ts +43 -0
- package/utils/colors.ts +221 -0
- package/utils/crypto.ts +308 -0
- package/utils/database.ts +348 -0
- package/utils/dbHelper.ts +713 -0
- package/utils/helper.ts +812 -0
- package/utils/index.ts +33 -0
- package/utils/jwt.ts +493 -0
- package/utils/logger.ts +191 -0
- package/utils/redisHelper.ts +321 -0
- package/utils/requestContext.ts +167 -0
- package/utils/sqlBuilder.ts +611 -0
- package/utils/validate.ts +493 -0
- package/utils/{xml.js → xml.ts} +100 -74
- package/.npmrc +0 -3
- package/.prettierignore +0 -2
- package/.prettierrc +0 -11
- package/apis/health/info.js +0 -49
- package/apis/tool/tokenCheck.js +0 -29
- package/bin/befly.js +0 -109
- package/bunfig.toml +0 -3
- package/checks/table.js +0 -206
- package/config/env.js +0 -64
- package/main.js +0 -579
- package/plugins/db.js +0 -46
- package/plugins/logger.js +0 -14
- package/plugins/redis.js +0 -32
- package/plugins/tool.js +0 -8
- package/scripts/syncDb.js +0 -752
- package/scripts/syncDev.js +0 -96
- package/system.js +0 -118
- package/tables/common.json +0 -16
- package/tables/tool.json +0 -6
- package/utils/api.js +0 -27
- package/utils/colors.js +0 -83
- package/utils/crypto.js +0 -260
- package/utils/index.js +0 -334
- package/utils/jwt.js +0 -387
- package/utils/logger.js +0 -143
- package/utils/redisHelper.js +0 -74
- package/utils/sqlBuilder.js +0 -498
- package/utils/sqlManager.js +0 -471
- package/utils/tool.js +0 -31
- package/utils/validate.js +0 -226
package/utils/sqlBuilder.js
DELETED
|
@@ -1,498 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SQL 构造器 - 生产级稳定版本
|
|
3
|
-
*/
|
|
4
|
-
export class SqlBuilder {
|
|
5
|
-
constructor() {
|
|
6
|
-
this.reset();
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
reset() {
|
|
10
|
-
this._select = [];
|
|
11
|
-
this._from = '';
|
|
12
|
-
this._where = [];
|
|
13
|
-
this._joins = [];
|
|
14
|
-
this._orderBy = [];
|
|
15
|
-
this._groupBy = [];
|
|
16
|
-
this._having = [];
|
|
17
|
-
this._limit = null;
|
|
18
|
-
this._offset = null;
|
|
19
|
-
this._params = [];
|
|
20
|
-
return this;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// 字段转义方法 - 处理字段名和表名的着重号转义
|
|
24
|
-
_escapeField(field) {
|
|
25
|
-
if (typeof field !== 'string') {
|
|
26
|
-
return field;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// 去除前后空格
|
|
30
|
-
field = field.trim();
|
|
31
|
-
|
|
32
|
-
// 如果是 * 或已经有着重号,直接返回
|
|
33
|
-
if (field === '*' || field.startsWith('`') || field.includes('(')) {
|
|
34
|
-
return field;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// 处理别名(AS关键字)
|
|
38
|
-
if (field.toUpperCase().includes(' AS ')) {
|
|
39
|
-
const parts = field.split(/\s+AS\s+/i);
|
|
40
|
-
const fieldPart = parts[0].trim();
|
|
41
|
-
const aliasPart = parts[1].trim();
|
|
42
|
-
return `${this._escapeField(fieldPart)} AS ${aliasPart}`;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// 处理表名.字段名的情况(多表联查)
|
|
46
|
-
if (field.includes('.')) {
|
|
47
|
-
const parts = field.split('.');
|
|
48
|
-
return parts
|
|
49
|
-
.map((part) => {
|
|
50
|
-
part = part.trim();
|
|
51
|
-
// 如果是 * 或已经有着重号,不再处理
|
|
52
|
-
if (part === '*' || part.startsWith('`')) {
|
|
53
|
-
return part;
|
|
54
|
-
}
|
|
55
|
-
return `\`${part}\``;
|
|
56
|
-
})
|
|
57
|
-
.join('.');
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// 处理单个字段名
|
|
61
|
-
return `\`${field}\``;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// 转义表名
|
|
65
|
-
_escapeTable(table) {
|
|
66
|
-
if (typeof table !== 'string') {
|
|
67
|
-
return table;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
table = table.trim();
|
|
71
|
-
|
|
72
|
-
// 如果已经有着重号,直接返回
|
|
73
|
-
if (table.startsWith('`')) {
|
|
74
|
-
return table;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// 处理表别名(表名 + 空格 + 别名)
|
|
78
|
-
if (table.includes(' ')) {
|
|
79
|
-
const parts = table.split(/\s+/);
|
|
80
|
-
if (parts.length === 2) {
|
|
81
|
-
// 只有表名和别名的情况
|
|
82
|
-
const tableName = parts[0].trim();
|
|
83
|
-
const alias = parts[1].trim();
|
|
84
|
-
return `\`${tableName}\` ${alias}`;
|
|
85
|
-
} else {
|
|
86
|
-
// 复杂情况,直接返回
|
|
87
|
-
return table;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return `\`${table}\``;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
select(fields = '*') {
|
|
95
|
-
if (Array.isArray(fields)) {
|
|
96
|
-
this._select = [...this._select, ...fields.map((field) => this._escapeField(field))];
|
|
97
|
-
} else if (typeof fields === 'string') {
|
|
98
|
-
this._select.push(this._escapeField(fields));
|
|
99
|
-
} else {
|
|
100
|
-
throw new Error('SELECT fields must be string or array');
|
|
101
|
-
}
|
|
102
|
-
return this;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
from(table) {
|
|
106
|
-
if (typeof table !== 'string' || !table.trim()) {
|
|
107
|
-
throw new Error('FROM table must be a non-empty string');
|
|
108
|
-
}
|
|
109
|
-
this._from = this._escapeTable(table.trim());
|
|
110
|
-
return this;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// 安全的参数验证
|
|
114
|
-
_validateParam(value) {
|
|
115
|
-
if (value === undefined) {
|
|
116
|
-
throw new Error('Parameter value cannot be undefined');
|
|
117
|
-
}
|
|
118
|
-
return value;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// 处理复杂的 where 条件对象
|
|
122
|
-
_processWhereConditions(whereObj) {
|
|
123
|
-
if (!whereObj || typeof whereObj !== 'object') {
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
Object.entries(whereObj).forEach(([key, value]) => {
|
|
128
|
-
// 跳过undefined值
|
|
129
|
-
if (value === undefined) {
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (key === '$and') {
|
|
134
|
-
if (Array.isArray(value)) {
|
|
135
|
-
value.forEach((condition) => this._processWhereConditions(condition));
|
|
136
|
-
}
|
|
137
|
-
} else if (key === '$or') {
|
|
138
|
-
if (Array.isArray(value)) {
|
|
139
|
-
const orConditions = [];
|
|
140
|
-
const tempParams = [];
|
|
141
|
-
|
|
142
|
-
value.forEach((condition) => {
|
|
143
|
-
const tempBuilder = new SqlBuilder();
|
|
144
|
-
tempBuilder._processWhereConditions(condition);
|
|
145
|
-
if (tempBuilder._where.length > 0) {
|
|
146
|
-
orConditions.push(`(${tempBuilder._where.join(' AND ')})`);
|
|
147
|
-
tempParams.push(...tempBuilder._params);
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
if (orConditions.length > 0) {
|
|
152
|
-
this._where.push(`(${orConditions.join(' OR ')})`);
|
|
153
|
-
this._params.push(...tempParams);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
} else if (key.includes('$')) {
|
|
157
|
-
// 一级属性格式:age$gt, role$in 等
|
|
158
|
-
const lastDollarIndex = key.lastIndexOf('$');
|
|
159
|
-
const fieldName = key.substring(0, lastDollarIndex);
|
|
160
|
-
const escapedFieldName = this._escapeField(fieldName);
|
|
161
|
-
const operator = '$' + key.substring(lastDollarIndex + 1);
|
|
162
|
-
|
|
163
|
-
this._validateParam(value);
|
|
164
|
-
|
|
165
|
-
switch (operator) {
|
|
166
|
-
case '$ne':
|
|
167
|
-
case '$not':
|
|
168
|
-
this._where.push(`${escapedFieldName} != ?`);
|
|
169
|
-
this._params.push(value);
|
|
170
|
-
break;
|
|
171
|
-
case '$in':
|
|
172
|
-
if (Array.isArray(value) && value.length > 0) {
|
|
173
|
-
const placeholders = value.map(() => '?').join(',');
|
|
174
|
-
this._where.push(`${escapedFieldName} IN (${placeholders})`);
|
|
175
|
-
this._params.push(...value);
|
|
176
|
-
}
|
|
177
|
-
break;
|
|
178
|
-
case '$nin':
|
|
179
|
-
case '$notIn':
|
|
180
|
-
if (Array.isArray(value) && value.length > 0) {
|
|
181
|
-
const placeholders = value.map(() => '?').join(',');
|
|
182
|
-
this._where.push(`${escapedFieldName} NOT IN (${placeholders})`);
|
|
183
|
-
this._params.push(...value);
|
|
184
|
-
}
|
|
185
|
-
break;
|
|
186
|
-
case '$like':
|
|
187
|
-
this._where.push(`${escapedFieldName} LIKE ?`);
|
|
188
|
-
this._params.push(value);
|
|
189
|
-
break;
|
|
190
|
-
case '$notLike':
|
|
191
|
-
this._where.push(`${escapedFieldName} NOT LIKE ?`);
|
|
192
|
-
this._params.push(value);
|
|
193
|
-
break;
|
|
194
|
-
case '$gt':
|
|
195
|
-
this._where.push(`${escapedFieldName} > ?`);
|
|
196
|
-
this._params.push(value);
|
|
197
|
-
break;
|
|
198
|
-
case '$gte':
|
|
199
|
-
this._where.push(`${escapedFieldName} >= ?`);
|
|
200
|
-
this._params.push(value);
|
|
201
|
-
break;
|
|
202
|
-
case '$lt':
|
|
203
|
-
this._where.push(`${escapedFieldName} < ?`);
|
|
204
|
-
this._params.push(value);
|
|
205
|
-
break;
|
|
206
|
-
case '$lte':
|
|
207
|
-
this._where.push(`${escapedFieldName} <= ?`);
|
|
208
|
-
this._params.push(value);
|
|
209
|
-
break;
|
|
210
|
-
case '$between':
|
|
211
|
-
if (Array.isArray(value) && value.length === 2) {
|
|
212
|
-
this._where.push(`${escapedFieldName} BETWEEN ? AND ?`);
|
|
213
|
-
this._params.push(value[0], value[1]);
|
|
214
|
-
}
|
|
215
|
-
break;
|
|
216
|
-
case '$notBetween':
|
|
217
|
-
if (Array.isArray(value) && value.length === 2) {
|
|
218
|
-
this._where.push(`${escapedFieldName} NOT BETWEEN ? AND ?`);
|
|
219
|
-
this._params.push(value[0], value[1]);
|
|
220
|
-
}
|
|
221
|
-
break;
|
|
222
|
-
case '$null':
|
|
223
|
-
if (value === true) {
|
|
224
|
-
this._where.push(`${escapedFieldName} IS NULL`);
|
|
225
|
-
}
|
|
226
|
-
break;
|
|
227
|
-
case '$notNull':
|
|
228
|
-
if (value === true) {
|
|
229
|
-
this._where.push(`${escapedFieldName} IS NOT NULL`);
|
|
230
|
-
}
|
|
231
|
-
break;
|
|
232
|
-
default:
|
|
233
|
-
this._where.push(`${escapedFieldName} = ?`);
|
|
234
|
-
this._params.push(value);
|
|
235
|
-
}
|
|
236
|
-
} else {
|
|
237
|
-
// 简单的等于条件
|
|
238
|
-
this._validateParam(value);
|
|
239
|
-
const escapedKey = this._escapeField(key);
|
|
240
|
-
this._where.push(`${escapedKey} = ?`);
|
|
241
|
-
this._params.push(value);
|
|
242
|
-
}
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
where(condition, value = null) {
|
|
247
|
-
if (typeof condition === 'object' && condition !== null) {
|
|
248
|
-
// 处理对象形式的where条件,会自动过滤undefined
|
|
249
|
-
this._processWhereConditions(condition);
|
|
250
|
-
} else if (value !== null) {
|
|
251
|
-
this._validateParam(value);
|
|
252
|
-
const escapedCondition = this._escapeField(condition);
|
|
253
|
-
this._where.push(`${escapedCondition} = ?`);
|
|
254
|
-
this._params.push(value);
|
|
255
|
-
} else if (typeof condition === 'string') {
|
|
256
|
-
this._where.push(condition);
|
|
257
|
-
}
|
|
258
|
-
return this;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
leftJoin(table, on) {
|
|
262
|
-
if (typeof table !== 'string' || typeof on !== 'string') {
|
|
263
|
-
throw new Error('JOIN table and condition must be strings');
|
|
264
|
-
}
|
|
265
|
-
const escapedTable = this._escapeTable(table);
|
|
266
|
-
this._joins.push(`LEFT JOIN ${escapedTable} ON ${on}`);
|
|
267
|
-
return this;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
orderBy(fields) {
|
|
271
|
-
if (!Array.isArray(fields)) {
|
|
272
|
-
throw new Error('orderBy must be an array of strings in "field#direction" format');
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
fields.forEach((item) => {
|
|
276
|
-
if (typeof item !== 'string' || !item.includes('#')) {
|
|
277
|
-
throw new Error('orderBy field must be a string in "field#direction" format (e.g., "name#ASC", "id#DESC")');
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
const [fieldName, direction] = item.split('#');
|
|
281
|
-
const cleanField = fieldName.trim();
|
|
282
|
-
const cleanDir = direction.trim().toUpperCase();
|
|
283
|
-
|
|
284
|
-
if (!cleanField) {
|
|
285
|
-
throw new Error('Field name cannot be empty in orderBy');
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
if (!['ASC', 'DESC'].includes(cleanDir)) {
|
|
289
|
-
throw new Error('ORDER BY direction must be ASC or DESC');
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
const escapedField = this._escapeField(cleanField);
|
|
293
|
-
this._orderBy.push(`${escapedField} ${cleanDir}`);
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
return this;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
groupBy(field) {
|
|
300
|
-
if (Array.isArray(field)) {
|
|
301
|
-
const escapedFields = field.filter((f) => typeof f === 'string').map((f) => this._escapeField(f));
|
|
302
|
-
this._groupBy = [...this._groupBy, ...escapedFields];
|
|
303
|
-
} else if (typeof field === 'string') {
|
|
304
|
-
this._groupBy.push(this._escapeField(field));
|
|
305
|
-
}
|
|
306
|
-
return this;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
having(condition) {
|
|
310
|
-
if (typeof condition === 'string') {
|
|
311
|
-
this._having.push(condition);
|
|
312
|
-
}
|
|
313
|
-
return this;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
limit(count, offset = null) {
|
|
317
|
-
if (typeof count !== 'number' || count < 0) {
|
|
318
|
-
throw new Error('LIMIT count must be a non-negative number');
|
|
319
|
-
}
|
|
320
|
-
this._limit = Math.floor(count);
|
|
321
|
-
if (offset !== null) {
|
|
322
|
-
if (typeof offset !== 'number' || offset < 0) {
|
|
323
|
-
throw new Error('OFFSET must be a non-negative number');
|
|
324
|
-
}
|
|
325
|
-
this._offset = Math.floor(offset);
|
|
326
|
-
}
|
|
327
|
-
return this;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
offset(count) {
|
|
331
|
-
if (typeof count !== 'number' || count < 0) {
|
|
332
|
-
throw new Error('OFFSET must be a non-negative number');
|
|
333
|
-
}
|
|
334
|
-
this._offset = Math.floor(count);
|
|
335
|
-
return this;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// 构建 SELECT 查询
|
|
339
|
-
toSelectSql() {
|
|
340
|
-
let sql = 'SELECT ';
|
|
341
|
-
|
|
342
|
-
sql += this._select.length > 0 ? this._select.join(', ') : '*';
|
|
343
|
-
|
|
344
|
-
if (!this._from) {
|
|
345
|
-
throw new Error('FROM table is required');
|
|
346
|
-
}
|
|
347
|
-
sql += ` FROM ${this._from}`;
|
|
348
|
-
|
|
349
|
-
if (this._joins.length > 0) {
|
|
350
|
-
sql += ' ' + this._joins.join(' ');
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
if (this._where.length > 0) {
|
|
354
|
-
sql += ' WHERE ' + this._where.join(' AND ');
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
if (this._groupBy.length > 0) {
|
|
358
|
-
sql += ' GROUP BY ' + this._groupBy.join(', ');
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
if (this._having.length > 0) {
|
|
362
|
-
sql += ' HAVING ' + this._having.join(' AND ');
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
if (this._orderBy.length > 0) {
|
|
366
|
-
sql += ' ORDER BY ' + this._orderBy.join(', ');
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
if (this._limit !== null) {
|
|
370
|
-
sql += ` LIMIT ${this._limit}`;
|
|
371
|
-
if (this._offset !== null) {
|
|
372
|
-
sql += ` OFFSET ${this._offset}`;
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
return { sql, params: [...this._params] };
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
// 构建 INSERT 查询
|
|
380
|
-
toInsertSql(table, data) {
|
|
381
|
-
if (!table || typeof table !== 'string') {
|
|
382
|
-
throw new Error('Table name is required for INSERT');
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
if (!data || typeof data !== 'object') {
|
|
386
|
-
throw new Error('Data is required for INSERT');
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
const escapedTable = this._escapeTable(table);
|
|
390
|
-
|
|
391
|
-
if (Array.isArray(data)) {
|
|
392
|
-
if (data.length === 0) {
|
|
393
|
-
throw new Error('Insert data cannot be empty');
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
const fields = Object.keys(data[0]);
|
|
397
|
-
if (fields.length === 0) {
|
|
398
|
-
throw new Error('Insert data must have at least one field');
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
const escapedFields = fields.map((field) => this._escapeField(field));
|
|
402
|
-
const placeholders = fields.map(() => '?').join(', ');
|
|
403
|
-
const values = data.map(() => `(${placeholders})`).join(', ');
|
|
404
|
-
|
|
405
|
-
const sql = `INSERT INTO ${escapedTable} (${escapedFields.join(', ')}) VALUES ${values}`;
|
|
406
|
-
const params = data.flatMap((row) => fields.map((field) => row[field]));
|
|
407
|
-
|
|
408
|
-
return { sql, params };
|
|
409
|
-
} else {
|
|
410
|
-
const fields = Object.keys(data);
|
|
411
|
-
if (fields.length === 0) {
|
|
412
|
-
throw new Error('Insert data must have at least one field');
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
const escapedFields = fields.map((field) => this._escapeField(field));
|
|
416
|
-
const placeholders = fields.map(() => '?').join(', ');
|
|
417
|
-
const sql = `INSERT INTO ${escapedTable} (${escapedFields.join(', ')}) VALUES (${placeholders})`;
|
|
418
|
-
const params = fields.map((field) => data[field]);
|
|
419
|
-
|
|
420
|
-
return { sql, params };
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
// 构建 UPDATE 查询
|
|
425
|
-
toUpdateSql(table, data) {
|
|
426
|
-
if (!table || typeof table !== 'string') {
|
|
427
|
-
throw new Error('Table name is required for UPDATE');
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
if (!data || typeof data !== 'object' || Array.isArray(data)) {
|
|
431
|
-
throw new Error('Data object is required for UPDATE');
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
const fields = Object.keys(data);
|
|
435
|
-
if (fields.length === 0) {
|
|
436
|
-
throw new Error('Update data must have at least one field');
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
const escapedTable = this._escapeTable(table);
|
|
440
|
-
const setFields = fields.map((field) => `${this._escapeField(field)} = ?`);
|
|
441
|
-
const params = [...Object.values(data), ...this._params];
|
|
442
|
-
|
|
443
|
-
let sql = `UPDATE ${escapedTable} SET ${setFields.join(', ')}`;
|
|
444
|
-
|
|
445
|
-
if (this._where.length > 0) {
|
|
446
|
-
sql += ' WHERE ' + this._where.join(' AND ');
|
|
447
|
-
} else {
|
|
448
|
-
throw new Error('UPDATE requires WHERE condition for safety');
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
return { sql, params };
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
// 构建 DELETE 查询
|
|
455
|
-
toDeleteSql(table) {
|
|
456
|
-
if (!table || typeof table !== 'string') {
|
|
457
|
-
throw new Error('Table name is required for DELETE');
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
const escapedTable = this._escapeTable(table);
|
|
461
|
-
let sql = `DELETE FROM ${escapedTable}`;
|
|
462
|
-
|
|
463
|
-
if (this._where.length > 0) {
|
|
464
|
-
sql += ' WHERE ' + this._where.join(' AND ');
|
|
465
|
-
} else {
|
|
466
|
-
throw new Error('DELETE requires WHERE condition for safety');
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
return { sql, params: [...this._params] };
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
// 构建 COUNT 查询
|
|
473
|
-
toCountSql() {
|
|
474
|
-
let sql = 'SELECT COUNT(*) as total';
|
|
475
|
-
|
|
476
|
-
if (!this._from) {
|
|
477
|
-
throw new Error('FROM table is required for COUNT');
|
|
478
|
-
}
|
|
479
|
-
sql += ` FROM ${this._from}`;
|
|
480
|
-
|
|
481
|
-
if (this._joins.length > 0) {
|
|
482
|
-
sql += ' ' + this._joins.join(' ');
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
if (this._where.length > 0) {
|
|
486
|
-
sql += ' WHERE ' + this._where.join(' AND ');
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
return { sql, params: [...this._params] };
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
/**
|
|
494
|
-
* 创建新的 SQL 构造器实例
|
|
495
|
-
*/
|
|
496
|
-
export function createQueryBuilder() {
|
|
497
|
-
return new SqlBuilder();
|
|
498
|
-
}
|