befly 2.0.9 → 2.0.11
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/package.json +2 -2
- package/plugins/db.js +72 -14
- package/utils/curd.js +134 -63
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "befly",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.11",
|
|
4
4
|
"description": "Buma - 为 Bun 专属打造的 API 接口框架核心引擎",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -51,5 +51,5 @@
|
|
|
51
51
|
"README.md",
|
|
52
52
|
"vitest.config.js"
|
|
53
53
|
],
|
|
54
|
-
"gitHead": "
|
|
54
|
+
"gitHead": "b4be4ddfba91544b051e0c0f379c14de46cbdac9"
|
|
55
55
|
}
|
package/plugins/db.js
CHANGED
|
@@ -78,7 +78,7 @@ export default {
|
|
|
78
78
|
return createQueryBuilder();
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
// 私有方法:通用数据处理函数 - 自动添加ID
|
|
81
|
+
// 私有方法:通用数据处理函数 - 自动添加ID、时间戳和状态
|
|
82
82
|
async #processDataForInsert(data) {
|
|
83
83
|
const now = Date.now();
|
|
84
84
|
|
|
@@ -87,6 +87,7 @@ export default {
|
|
|
87
87
|
data.map(async (item) => ({
|
|
88
88
|
...item,
|
|
89
89
|
id: await befly.redis.genTimeID(),
|
|
90
|
+
state: item.state !== undefined ? item.state : 0,
|
|
90
91
|
created_at: now,
|
|
91
92
|
updated_at: now
|
|
92
93
|
}))
|
|
@@ -95,12 +96,26 @@ export default {
|
|
|
95
96
|
return {
|
|
96
97
|
...data,
|
|
97
98
|
id: await befly.redis.genTimeID(),
|
|
99
|
+
state: data.state !== undefined ? data.state : 0,
|
|
98
100
|
created_at: now,
|
|
99
101
|
updated_at: now
|
|
100
102
|
};
|
|
101
103
|
}
|
|
102
104
|
}
|
|
103
105
|
|
|
106
|
+
// 私有方法:添加默认的state过滤条件
|
|
107
|
+
#addDefaultStateFilter(where = {}) {
|
|
108
|
+
// 检查是否已有state相关条件
|
|
109
|
+
const hasStateCondition = Object.keys(where).some((key) => key === 'state' || key.startsWith('state$'));
|
|
110
|
+
|
|
111
|
+
// 如果没有state条件,添加默认过滤
|
|
112
|
+
if (!hasStateCondition) {
|
|
113
|
+
return { ...where, state$ne: 2 };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return where;
|
|
117
|
+
}
|
|
118
|
+
|
|
104
119
|
// 私有方法:执行 SQL(支持传入连接对象)
|
|
105
120
|
async #executeWithConn(sql, params = [], conn = null) {
|
|
106
121
|
if (!sql || typeof sql !== 'string') {
|
|
@@ -147,7 +162,9 @@ export default {
|
|
|
147
162
|
const { where = {}, fields = '*', leftJoins = [] } = typeof options === 'object' && !Array.isArray(options) ? options : { where: options };
|
|
148
163
|
|
|
149
164
|
try {
|
|
150
|
-
|
|
165
|
+
// 添加默认的state过滤条件
|
|
166
|
+
const filteredWhere = this.#addDefaultStateFilter(where);
|
|
167
|
+
const builder = createQueryBuilder().select(fields).from(table).where(filteredWhere).limit(1);
|
|
151
168
|
|
|
152
169
|
// 添加 LEFT JOIN
|
|
153
170
|
leftJoins.forEach((join) => {
|
|
@@ -179,7 +196,9 @@ export default {
|
|
|
179
196
|
const { where = {}, fields = '*', leftJoins = [], orderBy = [], groupBy = [], having = [], page = 1, pageSize = 10 } = options;
|
|
180
197
|
|
|
181
198
|
try {
|
|
182
|
-
|
|
199
|
+
// 添加默认的state过滤条件
|
|
200
|
+
const filteredWhere = this.#addDefaultStateFilter(where);
|
|
201
|
+
const builder = createQueryBuilder().select(fields).from(table).where(filteredWhere);
|
|
183
202
|
|
|
184
203
|
// 添加 LEFT JOIN
|
|
185
204
|
leftJoins.forEach((join) => {
|
|
@@ -216,12 +235,12 @@ export default {
|
|
|
216
235
|
}
|
|
217
236
|
|
|
218
237
|
const { sql, params } = builder.toSelectSql();
|
|
219
|
-
const
|
|
238
|
+
const rows = await this.#executeWithConn(sql, params, conn);
|
|
220
239
|
|
|
221
240
|
// 获取总数(如果需要分页)
|
|
222
241
|
let total = 0;
|
|
223
242
|
if (numPage > 0 && numPageSize > 0) {
|
|
224
|
-
const countBuilder = createQueryBuilder().from(table).where(
|
|
243
|
+
const countBuilder = createQueryBuilder().from(table).where(filteredWhere);
|
|
225
244
|
|
|
226
245
|
// 计算总数时也要包含 JOIN
|
|
227
246
|
leftJoins.forEach((join) => {
|
|
@@ -241,7 +260,7 @@ export default {
|
|
|
241
260
|
}
|
|
242
261
|
|
|
243
262
|
return {
|
|
244
|
-
|
|
263
|
+
rows: Array.isArray(rows) ? rows : [],
|
|
245
264
|
total,
|
|
246
265
|
page: numPage,
|
|
247
266
|
pageSize: numPageSize
|
|
@@ -261,7 +280,9 @@ export default {
|
|
|
261
280
|
const { where = {}, fields = '*', leftJoins = [], orderBy = [] } = typeof options === 'object' && !Array.isArray(options) ? options : { where: options };
|
|
262
281
|
|
|
263
282
|
try {
|
|
264
|
-
|
|
283
|
+
// 添加默认的state过滤条件
|
|
284
|
+
const filteredWhere = this.#addDefaultStateFilter(where);
|
|
285
|
+
const builder = createQueryBuilder().select(fields).from(table).where(filteredWhere);
|
|
265
286
|
|
|
266
287
|
// 添加 LEFT JOIN
|
|
267
288
|
leftJoins.forEach((join) => {
|
|
@@ -310,7 +331,7 @@ export default {
|
|
|
310
331
|
}
|
|
311
332
|
|
|
312
333
|
// 私有方法:更新数据(支持传入连接对象)
|
|
313
|
-
async #
|
|
334
|
+
async #updDataWithConn(table, data, where, conn = null) {
|
|
314
335
|
if (!table || typeof table !== 'string') {
|
|
315
336
|
throw new Error('表名是必需的');
|
|
316
337
|
}
|
|
@@ -337,7 +358,7 @@ export default {
|
|
|
337
358
|
const { sql, params } = builder.toUpdateSql(table, updateData);
|
|
338
359
|
return await this.#executeWithConn(sql, params, conn);
|
|
339
360
|
} catch (error) {
|
|
340
|
-
Logger.error('
|
|
361
|
+
Logger.error('updData 执行失败:', error);
|
|
341
362
|
throw error;
|
|
342
363
|
}
|
|
343
364
|
}
|
|
@@ -362,6 +383,32 @@ export default {
|
|
|
362
383
|
}
|
|
363
384
|
}
|
|
364
385
|
|
|
386
|
+
// 私有方法:软删除数据(支持传入连接对象)
|
|
387
|
+
async #delData2WithConn(table, where, conn = null) {
|
|
388
|
+
if (!table || typeof table !== 'string') {
|
|
389
|
+
throw new Error('表名是必需的');
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (!where) {
|
|
393
|
+
throw new Error('软删除操作需要 WHERE 条件');
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
try {
|
|
397
|
+
// 软删除:将 state 设置为 2,同时更新 updated_at
|
|
398
|
+
const updateData = {
|
|
399
|
+
state: 2,
|
|
400
|
+
updated_at: Date.now()
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
const builder = createQueryBuilder().where(where);
|
|
404
|
+
const { sql, params } = builder.toUpdateSql(table, updateData);
|
|
405
|
+
return await this.#executeWithConn(sql, params, conn);
|
|
406
|
+
} catch (error) {
|
|
407
|
+
Logger.error('delData2 执行失败:', error);
|
|
408
|
+
throw error;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
365
412
|
// 私有方法:批量插入(支持传入连接对象)
|
|
366
413
|
async #insBatchWithConn(table, dataArray, conn = null) {
|
|
367
414
|
if (!table || typeof table !== 'string') {
|
|
@@ -392,7 +439,9 @@ export default {
|
|
|
392
439
|
const { where = {}, leftJoins = [] } = typeof options === 'object' && !Array.isArray(options) ? options : { where: options };
|
|
393
440
|
|
|
394
441
|
try {
|
|
395
|
-
|
|
442
|
+
// 添加默认的state过滤条件
|
|
443
|
+
const filteredWhere = this.#addDefaultStateFilter(where);
|
|
444
|
+
const builder = createQueryBuilder().from(table).where(filteredWhere);
|
|
396
445
|
|
|
397
446
|
// 添加 LEFT JOIN
|
|
398
447
|
leftJoins.forEach((join) => {
|
|
@@ -441,8 +490,8 @@ export default {
|
|
|
441
490
|
}
|
|
442
491
|
|
|
443
492
|
// 更新数据 - 增强版,自动添加 updated_at,过滤敏感字段
|
|
444
|
-
async
|
|
445
|
-
return await this.#
|
|
493
|
+
async updData(table, data, where) {
|
|
494
|
+
return await this.#updDataWithConn(table, data, where);
|
|
446
495
|
}
|
|
447
496
|
|
|
448
497
|
// 删除数据
|
|
@@ -450,6 +499,11 @@ export default {
|
|
|
450
499
|
return await this.#delDataWithConn(table, where);
|
|
451
500
|
}
|
|
452
501
|
|
|
502
|
+
// 软删除数据 - 将 state 设置为 2
|
|
503
|
+
async delData2(table, where) {
|
|
504
|
+
return await this.#delData2WithConn(table, where);
|
|
505
|
+
}
|
|
506
|
+
|
|
453
507
|
// 批量插入 - 增强版,自动添加 ID 和时间戳
|
|
454
508
|
async insBatch(table, dataArray) {
|
|
455
509
|
return await this.#insBatchWithConn(table, dataArray);
|
|
@@ -498,14 +552,18 @@ export default {
|
|
|
498
552
|
return await this.#insDataWithConn(table, data, conn);
|
|
499
553
|
},
|
|
500
554
|
|
|
501
|
-
|
|
502
|
-
return await this.#
|
|
555
|
+
updData: async (table, data, where) => {
|
|
556
|
+
return await this.#updDataWithConn(table, data, where, conn);
|
|
503
557
|
},
|
|
504
558
|
|
|
505
559
|
delData: async (table, where) => {
|
|
506
560
|
return await this.#delDataWithConn(table, where, conn);
|
|
507
561
|
},
|
|
508
562
|
|
|
563
|
+
delData2: async (table, where) => {
|
|
564
|
+
return await this.#delData2WithConn(table, where, conn);
|
|
565
|
+
},
|
|
566
|
+
|
|
509
567
|
getCount: async (table, options = {}) => {
|
|
510
568
|
return await this.#getCountWithConn(table, options, conn);
|
|
511
569
|
},
|
package/utils/curd.js
CHANGED
|
@@ -20,11 +20,82 @@ export class SqlBuilder {
|
|
|
20
20
|
return this;
|
|
21
21
|
}
|
|
22
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
|
+
|
|
23
94
|
select(fields = '*') {
|
|
24
95
|
if (Array.isArray(fields)) {
|
|
25
|
-
this._select = [...this._select, ...fields];
|
|
96
|
+
this._select = [...this._select, ...fields.map((field) => this._escapeField(field))];
|
|
26
97
|
} else if (typeof fields === 'string') {
|
|
27
|
-
this._select.push(fields);
|
|
98
|
+
this._select.push(this._escapeField(fields));
|
|
28
99
|
} else {
|
|
29
100
|
throw new Error('SELECT fields must be string or array');
|
|
30
101
|
}
|
|
@@ -35,7 +106,7 @@ export class SqlBuilder {
|
|
|
35
106
|
if (typeof table !== 'string' || !table.trim()) {
|
|
36
107
|
throw new Error('FROM table must be a non-empty string');
|
|
37
108
|
}
|
|
38
|
-
this._from = table.trim();
|
|
109
|
+
this._from = this._escapeTable(table.trim());
|
|
39
110
|
return this;
|
|
40
111
|
}
|
|
41
112
|
|
|
@@ -86,6 +157,7 @@ export class SqlBuilder {
|
|
|
86
157
|
// 一级属性格式:age$gt, role$in 等
|
|
87
158
|
const lastDollarIndex = key.lastIndexOf('$');
|
|
88
159
|
const fieldName = key.substring(0, lastDollarIndex);
|
|
160
|
+
const escapedFieldName = this._escapeField(fieldName);
|
|
89
161
|
const operator = '$' + key.substring(lastDollarIndex + 1);
|
|
90
162
|
|
|
91
163
|
this._validateParam(value);
|
|
@@ -93,13 +165,13 @@ export class SqlBuilder {
|
|
|
93
165
|
switch (operator) {
|
|
94
166
|
case '$ne':
|
|
95
167
|
case '$not':
|
|
96
|
-
this._where.push(`${
|
|
168
|
+
this._where.push(`${escapedFieldName} != ?`);
|
|
97
169
|
this._params.push(value);
|
|
98
170
|
break;
|
|
99
171
|
case '$in':
|
|
100
172
|
if (Array.isArray(value) && value.length > 0) {
|
|
101
173
|
const placeholders = value.map(() => '?').join(',');
|
|
102
|
-
this._where.push(`${
|
|
174
|
+
this._where.push(`${escapedFieldName} IN (${placeholders})`);
|
|
103
175
|
this._params.push(...value);
|
|
104
176
|
}
|
|
105
177
|
break;
|
|
@@ -107,64 +179,65 @@ export class SqlBuilder {
|
|
|
107
179
|
case '$notIn':
|
|
108
180
|
if (Array.isArray(value) && value.length > 0) {
|
|
109
181
|
const placeholders = value.map(() => '?').join(',');
|
|
110
|
-
this._where.push(`${
|
|
182
|
+
this._where.push(`${escapedFieldName} NOT IN (${placeholders})`);
|
|
111
183
|
this._params.push(...value);
|
|
112
184
|
}
|
|
113
185
|
break;
|
|
114
186
|
case '$like':
|
|
115
|
-
this._where.push(`${
|
|
187
|
+
this._where.push(`${escapedFieldName} LIKE ?`);
|
|
116
188
|
this._params.push(value);
|
|
117
189
|
break;
|
|
118
190
|
case '$notLike':
|
|
119
|
-
this._where.push(`${
|
|
191
|
+
this._where.push(`${escapedFieldName} NOT LIKE ?`);
|
|
120
192
|
this._params.push(value);
|
|
121
193
|
break;
|
|
122
194
|
case '$gt':
|
|
123
|
-
this._where.push(`${
|
|
195
|
+
this._where.push(`${escapedFieldName} > ?`);
|
|
124
196
|
this._params.push(value);
|
|
125
197
|
break;
|
|
126
198
|
case '$gte':
|
|
127
|
-
this._where.push(`${
|
|
199
|
+
this._where.push(`${escapedFieldName} >= ?`);
|
|
128
200
|
this._params.push(value);
|
|
129
201
|
break;
|
|
130
202
|
case '$lt':
|
|
131
|
-
this._where.push(`${
|
|
203
|
+
this._where.push(`${escapedFieldName} < ?`);
|
|
132
204
|
this._params.push(value);
|
|
133
205
|
break;
|
|
134
206
|
case '$lte':
|
|
135
|
-
this._where.push(`${
|
|
207
|
+
this._where.push(`${escapedFieldName} <= ?`);
|
|
136
208
|
this._params.push(value);
|
|
137
209
|
break;
|
|
138
210
|
case '$between':
|
|
139
211
|
if (Array.isArray(value) && value.length === 2) {
|
|
140
|
-
this._where.push(`${
|
|
212
|
+
this._where.push(`${escapedFieldName} BETWEEN ? AND ?`);
|
|
141
213
|
this._params.push(value[0], value[1]);
|
|
142
214
|
}
|
|
143
215
|
break;
|
|
144
216
|
case '$notBetween':
|
|
145
217
|
if (Array.isArray(value) && value.length === 2) {
|
|
146
|
-
this._where.push(`${
|
|
218
|
+
this._where.push(`${escapedFieldName} NOT BETWEEN ? AND ?`);
|
|
147
219
|
this._params.push(value[0], value[1]);
|
|
148
220
|
}
|
|
149
221
|
break;
|
|
150
222
|
case '$null':
|
|
151
223
|
if (value === true) {
|
|
152
|
-
this._where.push(`${
|
|
224
|
+
this._where.push(`${escapedFieldName} IS NULL`);
|
|
153
225
|
}
|
|
154
226
|
break;
|
|
155
227
|
case '$notNull':
|
|
156
228
|
if (value === true) {
|
|
157
|
-
this._where.push(`${
|
|
229
|
+
this._where.push(`${escapedFieldName} IS NOT NULL`);
|
|
158
230
|
}
|
|
159
231
|
break;
|
|
160
232
|
default:
|
|
161
|
-
this._where.push(`${
|
|
233
|
+
this._where.push(`${escapedFieldName} = ?`);
|
|
162
234
|
this._params.push(value);
|
|
163
235
|
}
|
|
164
236
|
} else {
|
|
165
237
|
// 简单的等于条件
|
|
166
238
|
this._validateParam(value);
|
|
167
|
-
this.
|
|
239
|
+
const escapedKey = this._escapeField(key);
|
|
240
|
+
this._where.push(`${escapedKey} = ?`);
|
|
168
241
|
this._params.push(value);
|
|
169
242
|
}
|
|
170
243
|
});
|
|
@@ -176,7 +249,8 @@ export class SqlBuilder {
|
|
|
176
249
|
this._processWhereConditions(condition);
|
|
177
250
|
} else if (value !== null) {
|
|
178
251
|
this._validateParam(value);
|
|
179
|
-
this.
|
|
252
|
+
const escapedCondition = this._escapeField(condition);
|
|
253
|
+
this._where.push(`${escapedCondition} = ?`);
|
|
180
254
|
this._params.push(value);
|
|
181
255
|
} else if (typeof condition === 'string') {
|
|
182
256
|
this._where.push(condition);
|
|
@@ -188,55 +262,46 @@ export class SqlBuilder {
|
|
|
188
262
|
if (typeof table !== 'string' || typeof on !== 'string') {
|
|
189
263
|
throw new Error('JOIN table and condition must be strings');
|
|
190
264
|
}
|
|
191
|
-
this.
|
|
265
|
+
const escapedTable = this._escapeTable(table);
|
|
266
|
+
this._joins.push(`LEFT JOIN ${escapedTable} ON ${on}`);
|
|
192
267
|
return this;
|
|
193
268
|
}
|
|
194
269
|
|
|
195
|
-
orderBy(
|
|
196
|
-
if (Array.isArray(
|
|
197
|
-
|
|
198
|
-
if (typeof item === 'string' && item.includes('#')) {
|
|
199
|
-
const [fieldName, dir] = item.split('#');
|
|
200
|
-
const cleanDir = (dir || 'ASC').trim().toUpperCase();
|
|
201
|
-
if (!['ASC', 'DESC'].includes(cleanDir)) {
|
|
202
|
-
throw new Error('ORDER BY direction must be ASC or DESC');
|
|
203
|
-
}
|
|
204
|
-
this._orderBy.push(`${fieldName.trim()} ${cleanDir}`);
|
|
205
|
-
} else if (Array.isArray(item) && item.length >= 1) {
|
|
206
|
-
const [fieldName, dir] = item;
|
|
207
|
-
const cleanDir = (dir || 'ASC').toUpperCase();
|
|
208
|
-
if (!['ASC', 'DESC'].includes(cleanDir)) {
|
|
209
|
-
throw new Error('ORDER BY direction must be ASC or DESC');
|
|
210
|
-
}
|
|
211
|
-
this._orderBy.push(`${fieldName} ${cleanDir}`);
|
|
212
|
-
} else if (typeof item === 'string') {
|
|
213
|
-
this._orderBy.push(`${item} ASC`);
|
|
214
|
-
}
|
|
215
|
-
});
|
|
216
|
-
} else if (typeof field === 'string') {
|
|
217
|
-
if (field.includes('#')) {
|
|
218
|
-
const [fieldName, dir] = field.split('#');
|
|
219
|
-
const cleanDir = (dir || 'ASC').trim().toUpperCase();
|
|
220
|
-
if (!['ASC', 'DESC'].includes(cleanDir)) {
|
|
221
|
-
throw new Error('ORDER BY direction must be ASC or DESC');
|
|
222
|
-
}
|
|
223
|
-
this._orderBy.push(`${fieldName.trim()} ${cleanDir}`);
|
|
224
|
-
} else {
|
|
225
|
-
const cleanDir = direction.toUpperCase();
|
|
226
|
-
if (!['ASC', 'DESC'].includes(cleanDir)) {
|
|
227
|
-
throw new Error('ORDER BY direction must be ASC or DESC');
|
|
228
|
-
}
|
|
229
|
-
this._orderBy.push(`${field} ${cleanDir}`);
|
|
230
|
-
}
|
|
270
|
+
orderBy(fields) {
|
|
271
|
+
if (!Array.isArray(fields)) {
|
|
272
|
+
throw new Error('orderBy must be an array of strings in "field#direction" format');
|
|
231
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
|
+
|
|
232
296
|
return this;
|
|
233
297
|
}
|
|
234
298
|
|
|
235
299
|
groupBy(field) {
|
|
236
300
|
if (Array.isArray(field)) {
|
|
237
|
-
|
|
301
|
+
const escapedFields = field.filter((f) => typeof f === 'string').map((f) => this._escapeField(f));
|
|
302
|
+
this._groupBy = [...this._groupBy, ...escapedFields];
|
|
238
303
|
} else if (typeof field === 'string') {
|
|
239
|
-
this._groupBy.push(field);
|
|
304
|
+
this._groupBy.push(this._escapeField(field));
|
|
240
305
|
}
|
|
241
306
|
return this;
|
|
242
307
|
}
|
|
@@ -321,6 +386,8 @@ export class SqlBuilder {
|
|
|
321
386
|
throw new Error('Data is required for INSERT');
|
|
322
387
|
}
|
|
323
388
|
|
|
389
|
+
const escapedTable = this._escapeTable(table);
|
|
390
|
+
|
|
324
391
|
if (Array.isArray(data)) {
|
|
325
392
|
if (data.length === 0) {
|
|
326
393
|
throw new Error('Insert data cannot be empty');
|
|
@@ -331,10 +398,11 @@ export class SqlBuilder {
|
|
|
331
398
|
throw new Error('Insert data must have at least one field');
|
|
332
399
|
}
|
|
333
400
|
|
|
401
|
+
const escapedFields = fields.map((field) => this._escapeField(field));
|
|
334
402
|
const placeholders = fields.map(() => '?').join(', ');
|
|
335
403
|
const values = data.map(() => `(${placeholders})`).join(', ');
|
|
336
404
|
|
|
337
|
-
const sql = `INSERT INTO ${
|
|
405
|
+
const sql = `INSERT INTO ${escapedTable} (${escapedFields.join(', ')}) VALUES ${values}`;
|
|
338
406
|
const params = data.flatMap((row) => fields.map((field) => row[field]));
|
|
339
407
|
|
|
340
408
|
return { sql, params };
|
|
@@ -344,8 +412,9 @@ export class SqlBuilder {
|
|
|
344
412
|
throw new Error('Insert data must have at least one field');
|
|
345
413
|
}
|
|
346
414
|
|
|
415
|
+
const escapedFields = fields.map((field) => this._escapeField(field));
|
|
347
416
|
const placeholders = fields.map(() => '?').join(', ');
|
|
348
|
-
const sql = `INSERT INTO ${
|
|
417
|
+
const sql = `INSERT INTO ${escapedTable} (${escapedFields.join(', ')}) VALUES (${placeholders})`;
|
|
349
418
|
const params = fields.map((field) => data[field]);
|
|
350
419
|
|
|
351
420
|
return { sql, params };
|
|
@@ -367,10 +436,11 @@ export class SqlBuilder {
|
|
|
367
436
|
throw new Error('Update data must have at least one field');
|
|
368
437
|
}
|
|
369
438
|
|
|
370
|
-
const
|
|
439
|
+
const escapedTable = this._escapeTable(table);
|
|
440
|
+
const setFields = fields.map((field) => `${this._escapeField(field)} = ?`);
|
|
371
441
|
const params = [...Object.values(data), ...this._params];
|
|
372
442
|
|
|
373
|
-
let sql = `UPDATE ${
|
|
443
|
+
let sql = `UPDATE ${escapedTable} SET ${setFields.join(', ')}`;
|
|
374
444
|
|
|
375
445
|
if (this._where.length > 0) {
|
|
376
446
|
sql += ' WHERE ' + this._where.join(' AND ');
|
|
@@ -387,7 +457,8 @@ export class SqlBuilder {
|
|
|
387
457
|
throw new Error('Table name is required for DELETE');
|
|
388
458
|
}
|
|
389
459
|
|
|
390
|
-
|
|
460
|
+
const escapedTable = this._escapeTable(table);
|
|
461
|
+
let sql = `DELETE FROM ${escapedTable}`;
|
|
391
462
|
|
|
392
463
|
if (this._where.length > 0) {
|
|
393
464
|
sql += ' WHERE ' + this._where.join(' AND ');
|