mm_sqlite 1.3.2 → 1.3.3
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/.gitattributes +2 -0
- package/LICENSE +21 -0
- package/README.md +199 -0
- package/README_EN.md +293 -0
- package/db.js +890 -161
- package/eslint.config.js +235 -0
- package/index.js +910 -169
- package/link_model.js +129 -0
- package/package.json +50 -36
- package/sql.js +1972 -0
- package/sql.json +56 -0
- package/test_backup.js +78 -0
- package/test_db/test_basic.db +0 -0
- package/test_db/test_core_methods.db +0 -0
- package/test_db/test_db.db +0 -0
- package/test_db/test_sql.db +0 -0
- package/test_sql_functions.js +86 -0
- package/test_sqlite.js +104 -0
- package/config.json +0 -0
- package/db/mm.db +0 -0
- package/test.js +0 -84
package/sql.js
ADDED
|
@@ -0,0 +1,1972 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileOverview sql语句帮助类函数
|
|
3
|
+
* @author <a href="http://qww.elins.cn">邱文武</a>
|
|
4
|
+
* @version 1.2
|
|
5
|
+
*/
|
|
6
|
+
const SqlString = require('sqlstring');
|
|
7
|
+
const { Base } = require('mm_expand');
|
|
8
|
+
|
|
9
|
+
function escape(value, stringifyObjects, timeZone) {
|
|
10
|
+
return SqlString.escape(value, stringifyObjects, timeZone);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function escapeId(value, forbidQualified) {
|
|
14
|
+
return SqlString.escapeId(value, forbidQualified);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @class 数据库语法通用类
|
|
19
|
+
* @property {Function} filter 设置并过滤参数
|
|
20
|
+
*/
|
|
21
|
+
class Sql extends Base {
|
|
22
|
+
/**
|
|
23
|
+
* @description 数据库管理器
|
|
24
|
+
* @param {Function} run 查询函数
|
|
25
|
+
* @param {Function} exec 更改函数
|
|
26
|
+
*/
|
|
27
|
+
constructor(run, exec) {
|
|
28
|
+
super();
|
|
29
|
+
/**
|
|
30
|
+
* 查询函数
|
|
31
|
+
*/
|
|
32
|
+
this.run = run;
|
|
33
|
+
/**
|
|
34
|
+
* 更改函数 用于增删改
|
|
35
|
+
*/
|
|
36
|
+
this.exec = exec;
|
|
37
|
+
/**
|
|
38
|
+
* 规避SQL注入函数
|
|
39
|
+
* @param {object} value 值
|
|
40
|
+
* @returns {string} 返回执行结果
|
|
41
|
+
*/
|
|
42
|
+
this.escape = function (value) {
|
|
43
|
+
return escape(value);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 规避排序、SQL注入函数
|
|
48
|
+
* @param {string} key 键
|
|
49
|
+
* @returns {string} 返回执行结果
|
|
50
|
+
*/
|
|
51
|
+
this.escapeId = function (key) {
|
|
52
|
+
return escapeId(key);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* sql语句
|
|
57
|
+
*/
|
|
58
|
+
this.sql = '';
|
|
59
|
+
/**
|
|
60
|
+
* 错误提示
|
|
61
|
+
*/
|
|
62
|
+
this.error;
|
|
63
|
+
/**
|
|
64
|
+
* 查询结果
|
|
65
|
+
*/
|
|
66
|
+
this.results = [];
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 表名
|
|
70
|
+
*/
|
|
71
|
+
this.table = '';
|
|
72
|
+
/**
|
|
73
|
+
* 显示页
|
|
74
|
+
*/
|
|
75
|
+
this.page = 0;
|
|
76
|
+
/**
|
|
77
|
+
* 显示条数
|
|
78
|
+
*/
|
|
79
|
+
this.size = 30;
|
|
80
|
+
/**
|
|
81
|
+
* 请求方式 add、del、set、get、import、export等,跟函数名一致
|
|
82
|
+
*/
|
|
83
|
+
this.method = '';
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* 显示的字段
|
|
87
|
+
*/
|
|
88
|
+
this.field = '';
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* 排序方式
|
|
92
|
+
*/
|
|
93
|
+
this.orderby = '';
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 查询分组
|
|
97
|
+
*/
|
|
98
|
+
this.groupby = '';
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* 是否统计查询结果数
|
|
102
|
+
*/
|
|
103
|
+
this.count_ret = 'false';
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* 过滤查询参数字典
|
|
107
|
+
*/
|
|
108
|
+
this.config = {
|
|
109
|
+
/**
|
|
110
|
+
* 分隔符 用于查询时的多条件处理
|
|
111
|
+
*/
|
|
112
|
+
'separator': '|',
|
|
113
|
+
/**
|
|
114
|
+
* 过滤
|
|
115
|
+
*/
|
|
116
|
+
'filter': {
|
|
117
|
+
/**
|
|
118
|
+
* 表名
|
|
119
|
+
*/
|
|
120
|
+
'table': 'table',
|
|
121
|
+
/**
|
|
122
|
+
* 查询的页码
|
|
123
|
+
*/
|
|
124
|
+
'page': 'page',
|
|
125
|
+
/**
|
|
126
|
+
* 查询每页条数
|
|
127
|
+
*/
|
|
128
|
+
'size': 'size',
|
|
129
|
+
/**
|
|
130
|
+
* 操作方式: 传入参数method=add, 支持参数 add增、del删、set改、get查,为空则为get
|
|
131
|
+
*/
|
|
132
|
+
'method': 'method',
|
|
133
|
+
/**
|
|
134
|
+
* 排序
|
|
135
|
+
*/
|
|
136
|
+
'orderby': 'orderby',
|
|
137
|
+
/**
|
|
138
|
+
* 查询显示的字段
|
|
139
|
+
*/
|
|
140
|
+
'field': 'field',
|
|
141
|
+
/**
|
|
142
|
+
* 统计结果: 统计符合条件的结果数,只有当page等于1或0时才会统计
|
|
143
|
+
*/
|
|
144
|
+
'count_ret': 'count_ret'
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
this.like = false;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* @description 清空查询条件
|
|
154
|
+
* @returns {object} 返回当前对象
|
|
155
|
+
*/
|
|
156
|
+
Sql.prototype.clear = function () {
|
|
157
|
+
this.where = '';
|
|
158
|
+
this.set = '';
|
|
159
|
+
this.order = '';
|
|
160
|
+
this.view = '';
|
|
161
|
+
this.sql = '';
|
|
162
|
+
this.error = '';
|
|
163
|
+
this.like = true;
|
|
164
|
+
this.config = {};
|
|
165
|
+
this.param = {};
|
|
166
|
+
this.page = 0;
|
|
167
|
+
this.size = 0;
|
|
168
|
+
this.run = null;
|
|
169
|
+
this.exec = null;
|
|
170
|
+
return this;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* @description 过滤查询参数
|
|
175
|
+
* @param {object} query 查询参数
|
|
176
|
+
*/
|
|
177
|
+
Sql.prototype.filter = function (query) {
|
|
178
|
+
var m = this.config.filter;
|
|
179
|
+
for (var k in m) {
|
|
180
|
+
var key = m[k];
|
|
181
|
+
if (query[key]) {
|
|
182
|
+
this[k] = query[key];
|
|
183
|
+
delete query[key];
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* 查询条件拼接
|
|
190
|
+
* @param {string} where 查询条件
|
|
191
|
+
* @param {string} sort 排序
|
|
192
|
+
* @param {string} view 返回的字段
|
|
193
|
+
* @returns {string} 返回查询条件语句
|
|
194
|
+
*/
|
|
195
|
+
Sql.prototype.toQuery = function (where, sort, view) {
|
|
196
|
+
if (!view) {
|
|
197
|
+
view = '*';
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// 使用直接拼接的方式,避免模板替换错误
|
|
201
|
+
var sql = `SELECT ${view} FROM \`${this.table}\``;
|
|
202
|
+
|
|
203
|
+
if (where) {
|
|
204
|
+
sql += ' WHERE ' + where;
|
|
205
|
+
}
|
|
206
|
+
if (sort) {
|
|
207
|
+
sql += ' ORDER BY ' + sort.replace(/;/, '');
|
|
208
|
+
}
|
|
209
|
+
if (this.size && this.page) {
|
|
210
|
+
var start = this.size * (this.page - 1);
|
|
211
|
+
sql += ' limit ' + start + ',' + this.size;
|
|
212
|
+
}
|
|
213
|
+
return sql;
|
|
214
|
+
};
|
|
215
|
+
/* === 传字符串参数 === */
|
|
216
|
+
/**
|
|
217
|
+
* 增加数据
|
|
218
|
+
* @param {string} key 用作增加的键集合
|
|
219
|
+
* @param {string} val 用作增加的值集合
|
|
220
|
+
* @returns {Promise | object} 执行结果
|
|
221
|
+
*/
|
|
222
|
+
Sql.prototype.addSql = function (key, val) {
|
|
223
|
+
// 使用直接拼接的方式,避免模板替换错误
|
|
224
|
+
var sql = `INSERT INTO \`${this.table}\` (${key}) VALUES (${val});`;
|
|
225
|
+
return this.exec(sql);
|
|
226
|
+
};/**
|
|
227
|
+
* 删除数据
|
|
228
|
+
* @param {string} where 删除条件
|
|
229
|
+
* @returns {Promise | object} 执行结果
|
|
230
|
+
*/
|
|
231
|
+
Sql.prototype.delSql = function (where) {
|
|
232
|
+
// 使用直接拼接的方式,避免模板替换错误
|
|
233
|
+
var sql = `DELETE FROM \`${this.table}\` WHERE ${where};`;
|
|
234
|
+
return this.exec(sql);
|
|
235
|
+
};
|
|
236
|
+
/**
|
|
237
|
+
* 修改数据
|
|
238
|
+
* @param {string} where 查询条件
|
|
239
|
+
* @param {string} set 修改的键值
|
|
240
|
+
* @returns {Promise | object} 执行结果
|
|
241
|
+
*/
|
|
242
|
+
Sql.prototype.setSql = function (where, set) {
|
|
243
|
+
// 使用直接拼接的方式,避免模板替换错误
|
|
244
|
+
var sql = `UPDATE \`${this.table}\` SET ${set} WHERE ${where};`;
|
|
245
|
+
return this.exec(sql);
|
|
246
|
+
};
|
|
247
|
+
/**
|
|
248
|
+
* @description 查询数据
|
|
249
|
+
* @param {string} where 查询条件
|
|
250
|
+
* @param {string} sort 排序
|
|
251
|
+
* @param {string} view 显示的字段
|
|
252
|
+
* @returns {Promise|Array} 查询结果数组
|
|
253
|
+
*/
|
|
254
|
+
Sql.prototype.getSql = function (where, sort, view) {
|
|
255
|
+
var sql = this.toQuery(where, sort, view);
|
|
256
|
+
return this.run(sql);
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* @description 添加或修改
|
|
261
|
+
* @param {string} where 查询条件
|
|
262
|
+
* @param {string} set 修改的键值
|
|
263
|
+
* @returns {Promise | object} 执行结果
|
|
264
|
+
*/
|
|
265
|
+
Sql.prototype.addOrSetSql = async function (where, set) {
|
|
266
|
+
if (!where || !set) {
|
|
267
|
+
return -1;
|
|
268
|
+
}
|
|
269
|
+
var count = await this.countSql(where);
|
|
270
|
+
if (count === 0) {
|
|
271
|
+
var arr = set.split(',');
|
|
272
|
+
var key = '';
|
|
273
|
+
var value = '';
|
|
274
|
+
for (var i = 0; i < arr.length; i++) {
|
|
275
|
+
var o = arr[i];
|
|
276
|
+
var ar = o.split('=');
|
|
277
|
+
if (ar.length === 2) {
|
|
278
|
+
key += ',' + ar[0];
|
|
279
|
+
value += ',' + ar[1];
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return await this.addSql(key.replace(',', ''), value.replace(',', ''));
|
|
283
|
+
}
|
|
284
|
+
return await this.setSql(where, set);
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* @description 查询符合结果总数
|
|
289
|
+
* @param {string} where 查询条件
|
|
290
|
+
* @returns {Promise | number} 返回结果总数
|
|
291
|
+
*/
|
|
292
|
+
Sql.prototype.countSql = async function (where) {
|
|
293
|
+
var sql = 'SELECT count(*) count FROM `' + this.table + '`';
|
|
294
|
+
if (where) {
|
|
295
|
+
sql += ' WHERE ' + where;
|
|
296
|
+
}
|
|
297
|
+
var n = 0;
|
|
298
|
+
var arr = await this.run(sql);
|
|
299
|
+
if (arr.length) {
|
|
300
|
+
n = arr[0].count;
|
|
301
|
+
}
|
|
302
|
+
return n;
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* @description 查询数据并返回符合条件总数
|
|
307
|
+
* @param {string} where 查询条件
|
|
308
|
+
* @param {string} sort 排序
|
|
309
|
+
* @param {string} view 返回的字段
|
|
310
|
+
* @returns {Promise | object} 查询到的内容列表和符合条件总数
|
|
311
|
+
*/
|
|
312
|
+
Sql.prototype.getCountSql = async function (where, sort, view) {
|
|
313
|
+
var list = [];
|
|
314
|
+
var count = await this.countSql(where);
|
|
315
|
+
if (count > 0) {
|
|
316
|
+
list = await this.getSql(where, sort, view);
|
|
317
|
+
}
|
|
318
|
+
var ret = {
|
|
319
|
+
list: list,
|
|
320
|
+
count: count
|
|
321
|
+
};
|
|
322
|
+
return ret;
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* @description 统计学
|
|
327
|
+
* @param {string} where 查询条件
|
|
328
|
+
* @param {string} groupby 分组的字段
|
|
329
|
+
* @param {string} view 返回的字段
|
|
330
|
+
* @param {string} sort 排序方式
|
|
331
|
+
* @param method
|
|
332
|
+
* @returns {Promise | object} 查询到的内容列表和符合条件总数
|
|
333
|
+
*/
|
|
334
|
+
Sql.prototype.groupMathSql = async function (where, groupby, view, sort, method) {
|
|
335
|
+
if (!view) {
|
|
336
|
+
view = '*';
|
|
337
|
+
}
|
|
338
|
+
var viewStr = '';
|
|
339
|
+
if (view.indexOf(',') !== -1) {
|
|
340
|
+
var arr = view.split(',');
|
|
341
|
+
for (var i = 0; i < arr.length; i++) {
|
|
342
|
+
var str = escapeId(arr[i]);
|
|
343
|
+
viewStr += ',' + method.toUpperCase() + '(' + str + ') ' + method.toLowerCase() + '_' + str.replace(
|
|
344
|
+
/`/g, '');
|
|
345
|
+
}
|
|
346
|
+
} else {
|
|
347
|
+
viewStr = ',' + method.toUpperCase() + '(' + escapeId(view) + ') ' + method.toLowerCase() + '_' +
|
|
348
|
+
view.replace(/`/g, '');
|
|
349
|
+
}
|
|
350
|
+
var sql = 'SELECT ' + (groupby ? escapeId(groupby) : '') + viewStr + ' FROM `' + this.table + '`';
|
|
351
|
+
if (where) {
|
|
352
|
+
sql += ' WHERE ' + where;
|
|
353
|
+
}
|
|
354
|
+
if (groupby) {
|
|
355
|
+
sql += ' GROUP BY ' + escapeId(groupby);
|
|
356
|
+
}
|
|
357
|
+
if (sort) {
|
|
358
|
+
sql += ' ORDER BY ' + sort;
|
|
359
|
+
}
|
|
360
|
+
if (this.size && this.page) {
|
|
361
|
+
var start = this.size * (this.page - 1);
|
|
362
|
+
sql += ' limit ' + start + ',' + this.size;
|
|
363
|
+
}
|
|
364
|
+
return await this.run(sql);
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* @description 分组求平均值
|
|
370
|
+
* @param {string} where 查询条件
|
|
371
|
+
* @param {string} groupby 分组的字段
|
|
372
|
+
* @param {string} view 返回的字段
|
|
373
|
+
* @param {string} sort 排序方式
|
|
374
|
+
* @returns {Promise | object} 查询到的内容列表和符合条件总数
|
|
375
|
+
*/
|
|
376
|
+
Sql.prototype.groupAvgSql = async function (where, groupby, view, sort = '') {
|
|
377
|
+
return await this.groupMathSql(where, groupby, view, sort, 'AVG');
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* @description 分组合计数值
|
|
382
|
+
* @param {string} where 查询条件
|
|
383
|
+
* @param {string} groupby 分组的字段
|
|
384
|
+
* @param {string} view 返回的字段
|
|
385
|
+
* @param {string} sort 排序方式
|
|
386
|
+
* @returns {Promise | object} 查询到的内容列表和符合条件总数
|
|
387
|
+
*/
|
|
388
|
+
Sql.prototype.groupSumSql = async function (where, groupby, view, sort = '') {
|
|
389
|
+
return await this.groupMathSql(where, groupby, view, sort, 'SUM');
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* @description 分组合计不同条数
|
|
394
|
+
* @param {string} where 查询条件
|
|
395
|
+
* @param {string} groupby 分组的字段
|
|
396
|
+
* @param {string} view 返回的字段
|
|
397
|
+
* @param sort
|
|
398
|
+
* @returns {Promise | object} 查询到的内容列表和符合条件总数
|
|
399
|
+
*/
|
|
400
|
+
Sql.prototype.groupCountSql = async function (where, groupby, view, sort = '') {
|
|
401
|
+
return await this.groupMathSql(where, groupby, view, sort, 'COUNT');
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* @description 统计学
|
|
407
|
+
* @param {object} query 查询条件
|
|
408
|
+
* @param {string} groupby 分组的字段
|
|
409
|
+
* @param {string} view 返回的字段
|
|
410
|
+
* @param {string} sort 排序方式
|
|
411
|
+
* @param method
|
|
412
|
+
* @returns {Promise | object} 查询到的内容列表和符合条件总数
|
|
413
|
+
*/
|
|
414
|
+
Sql.prototype.groupMath = async function (query, groupby, view, sort, method) {
|
|
415
|
+
var where = this.toWhere(query, this.like);
|
|
416
|
+
return await this.groupMathSql(where, groupby, view, sort, method);
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* @description 分组求平均值
|
|
421
|
+
* @param {object} query 查询条件
|
|
422
|
+
* @param {string} groupby 分组的字段
|
|
423
|
+
* @param {string} view 返回的字段
|
|
424
|
+
* @param {string} sort 排序方式
|
|
425
|
+
* @returns {Promise | object} 查询到的内容列表和符合条件总数
|
|
426
|
+
*/
|
|
427
|
+
Sql.prototype.groupAvg = async function (query, groupby, view, sort) {
|
|
428
|
+
return await this.groupMath(query, groupby, view, sort, 'AVG');
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* @description 分组合计数值
|
|
433
|
+
* @param {object} query 查询条件
|
|
434
|
+
* @param {string} groupby 分组的字段
|
|
435
|
+
* @param {string} view 返回的字段
|
|
436
|
+
* @param {string} sort 排序方式
|
|
437
|
+
* @returns {Promise | object} 查询到的内容列表和符合条件总数
|
|
438
|
+
*/
|
|
439
|
+
Sql.prototype.groupSum = async function (query, groupby, view, sort) {
|
|
440
|
+
return await this.groupMath(query, groupby, view, sort, 'SUM');
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* @description 分组合计不同条数
|
|
445
|
+
* @param {object} query 查询条件
|
|
446
|
+
* @param {string} groupby 分组的字段
|
|
447
|
+
* @param {string} view 返回的字段
|
|
448
|
+
* @param sort
|
|
449
|
+
* @returns {Promise | object} 查询到的内容列表和符合条件总数
|
|
450
|
+
*/
|
|
451
|
+
Sql.prototype.groupCount = async function (query, groupby, view, sort) {
|
|
452
|
+
return await this.groupMath(query, groupby, view, sort, 'COUNT');
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
/* === 聚合函数 === */
|
|
456
|
+
/**
|
|
457
|
+
* @description 求和函数
|
|
458
|
+
* @param {string} field 字段名
|
|
459
|
+
* @param {object} query 查询条件
|
|
460
|
+
* @returns {number} 求和结果
|
|
461
|
+
*/
|
|
462
|
+
Sql.prototype.sum = async function (field, query) {
|
|
463
|
+
if (!field || typeof field !== 'string') {
|
|
464
|
+
throw new TypeError('字段名必须为字符串');
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
if (!this.table) {
|
|
468
|
+
throw new TypeError('表名未设置');
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const result = await this.groupSum(query, '', field, '');
|
|
472
|
+
const fieldKey = 'sum_' + field.replace(/`/g, '');
|
|
473
|
+
return result && result.length > 0 ? Number(result[0][fieldKey] || result[0].sum_age) : 0;
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* @description 最大值函数
|
|
478
|
+
* @param {string} field 字段名
|
|
479
|
+
* @param {object} query 查询条件
|
|
480
|
+
* @returns {number} 最大值结果
|
|
481
|
+
*/
|
|
482
|
+
Sql.prototype.max = async function (field, query) {
|
|
483
|
+
if (!field || typeof field !== 'string') {
|
|
484
|
+
throw new TypeError('字段名必须为字符串');
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
if (!this.table) {
|
|
488
|
+
throw new TypeError('表名未设置');
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const result = await this.groupMath(query, '', field, '', 'MAX');
|
|
492
|
+
const fieldKey = 'max_' + field.replace(/`/g, '');
|
|
493
|
+
return result && result.length > 0 ? Number(result[0][fieldKey] || result[0].max_age) : 0;
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* @description 最小值函数
|
|
498
|
+
* @param {string} field 字段名
|
|
499
|
+
* @param {object} query 查询条件
|
|
500
|
+
* @returns {number} 最小值结果
|
|
501
|
+
*/
|
|
502
|
+
Sql.prototype.min = async function (field, query) {
|
|
503
|
+
if (!field || typeof field !== 'string') {
|
|
504
|
+
throw new TypeError('字段名必须为字符串');
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (!this.table) {
|
|
508
|
+
throw new TypeError('表名未设置');
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const result = await this.groupMath(query, '', field, '', 'MIN');
|
|
512
|
+
const fieldKey = 'min_' + field.replace(/`/g, '');
|
|
513
|
+
return result && result.length > 0 ? Number(result[0][fieldKey] || result[0].min_age) : 0;
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* @description 平均值函数
|
|
518
|
+
* @param {string} field 字段名
|
|
519
|
+
* @param {object} query 查询条件
|
|
520
|
+
* @returns {number} 平均值结果
|
|
521
|
+
*/
|
|
522
|
+
Sql.prototype.avg = async function (field, query) {
|
|
523
|
+
if (!field || typeof field !== 'string') {
|
|
524
|
+
throw new TypeError('字段名必须为字符串');
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (!this.table) {
|
|
528
|
+
throw new TypeError('表名未设置');
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const result = await this.groupMath(query, '', field, '', 'AVG');
|
|
532
|
+
const fieldKey = 'avg_' + field.replace(/`/g, '');
|
|
533
|
+
return result && result.length > 0 ? Number(result[0][fieldKey] || result[0].avg_age) : 0;
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
/* === 字符串操作函数 === */
|
|
537
|
+
/**
|
|
538
|
+
* @description 字符串连接函数
|
|
539
|
+
* @param {...string} args 要连接的字符串参数
|
|
540
|
+
* @returns {string} 连接后的字符串
|
|
541
|
+
*/
|
|
542
|
+
Sql.prototype.toStr = function (...args) {
|
|
543
|
+
// 如果没有参数,返回空字符串
|
|
544
|
+
if (args.length === 0) {
|
|
545
|
+
return '';
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// 将所有参数转换为字符串并用空格连接
|
|
549
|
+
return args.map(arg => String(arg)).join(' ');
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
/* === 备份恢复函数 === */
|
|
553
|
+
/**
|
|
554
|
+
* @description 备份数据库到 SQL 文件
|
|
555
|
+
* @param {string} file 备份文件路径
|
|
556
|
+
* @returns {Promise<boolean>} 备份是否成功
|
|
557
|
+
*/
|
|
558
|
+
Sql.prototype.backup = async function(file) {
|
|
559
|
+
if (!file || typeof file !== 'string') {
|
|
560
|
+
throw new TypeError('文件路径必须为字符串');
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
if (!this.table) {
|
|
564
|
+
throw new TypeError('表名未设置');
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
try {
|
|
568
|
+
// 获取所有数据
|
|
569
|
+
const data = await this.getSql('', '', '*');
|
|
570
|
+
|
|
571
|
+
if (!data || !Array.isArray(data)) {
|
|
572
|
+
throw new Error('获取数据失败');
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// 构建 SQL 语句
|
|
576
|
+
let sqlContent = '';
|
|
577
|
+
|
|
578
|
+
// 添加表结构(简化版,实际应用中可能需要更复杂的表结构导出)
|
|
579
|
+
sqlContent += `-- 备份表: ${this.table}\n`;
|
|
580
|
+
sqlContent += `-- 备份时间: ${new Date().toISOString()}\n\n`;
|
|
581
|
+
|
|
582
|
+
// 添加数据插入语句
|
|
583
|
+
for (const row of data) {
|
|
584
|
+
const keys = Object.keys(row).map(key => this.escapeId(key)).join(', ');
|
|
585
|
+
const values = Object.values(row).map(value => this.escape(value)).join(', ');
|
|
586
|
+
sqlContent += `INSERT INTO \`${this.table}\` (${keys}) VALUES (${values});\n`;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// 写入文件(这里需要文件系统模块,实际使用时需要引入 fs)
|
|
590
|
+
const fs = require('fs');
|
|
591
|
+
const path = require('path');
|
|
592
|
+
|
|
593
|
+
// 确保目录存在
|
|
594
|
+
const dir = path.dirname(file);
|
|
595
|
+
if (!fs.existsSync(dir)) {
|
|
596
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
fs.writeFileSync(file, sqlContent, 'utf8');
|
|
600
|
+
|
|
601
|
+
return true;
|
|
602
|
+
} catch (err) {
|
|
603
|
+
this.error = err.message;
|
|
604
|
+
this.log('error', '备份数据库失败', err);
|
|
605
|
+
return false;
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* @description 从 SQL 文件恢复数据库
|
|
611
|
+
* @param {string} file 恢复文件路径
|
|
612
|
+
* @returns {Promise<boolean>} 恢复是否成功
|
|
613
|
+
*/
|
|
614
|
+
Sql.prototype.restore = async function(file) {
|
|
615
|
+
if (!file || typeof file !== 'string') {
|
|
616
|
+
throw new TypeError('文件路径必须为字符串');
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
if (!this.table) {
|
|
620
|
+
throw new TypeError('表名未设置');
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
try {
|
|
624
|
+
const fs = require('fs');
|
|
625
|
+
|
|
626
|
+
// 检查文件是否存在
|
|
627
|
+
if (!fs.existsSync(file)) {
|
|
628
|
+
throw new Error('备份文件不存在');
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// 读取文件内容
|
|
632
|
+
const sqlContent = fs.readFileSync(file, 'utf8');
|
|
633
|
+
|
|
634
|
+
if (!sqlContent) {
|
|
635
|
+
throw new Error('备份文件为空');
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// 解析 SQL 语句(按分号分割,但需要处理字符串中的分号)
|
|
639
|
+
const statements = this._parseSqlStatements(sqlContent);
|
|
640
|
+
|
|
641
|
+
// 执行所有 SQL 语句
|
|
642
|
+
for (const statement of statements) {
|
|
643
|
+
if (statement.trim()) {
|
|
644
|
+
// 跳过注释行
|
|
645
|
+
if (!statement.trim().startsWith('--')) {
|
|
646
|
+
await this.exec(statement);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
return true;
|
|
652
|
+
} catch (err) {
|
|
653
|
+
this.error = err.message;
|
|
654
|
+
this.log('error', '恢复数据库失败', err);
|
|
655
|
+
return false;
|
|
656
|
+
}
|
|
657
|
+
};
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* @description 解析 SQL 语句,正确处理字符串中的分号
|
|
661
|
+
* @param {string} sql SQL 内容
|
|
662
|
+
* @returns {string[]} SQL 语句数组
|
|
663
|
+
* @private
|
|
664
|
+
*/
|
|
665
|
+
Sql.prototype._parseSqlStatements = function(sql) {
|
|
666
|
+
const statements = [];
|
|
667
|
+
let currentStatement = '';
|
|
668
|
+
let inString = false;
|
|
669
|
+
let stringChar = '';
|
|
670
|
+
|
|
671
|
+
for (let i = 0; i < sql.length; i++) {
|
|
672
|
+
const char = sql[i];
|
|
673
|
+
|
|
674
|
+
if (char === "'" || char === '"') {
|
|
675
|
+
if (!inString) {
|
|
676
|
+
inString = true;
|
|
677
|
+
stringChar = char;
|
|
678
|
+
} else if (char === stringChar) {
|
|
679
|
+
// 检查是否是转义字符
|
|
680
|
+
if (i > 0 && sql[i-1] === '\\') {
|
|
681
|
+
// 转义字符,继续字符串
|
|
682
|
+
} else {
|
|
683
|
+
inString = false;
|
|
684
|
+
stringChar = '';
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
if (!inString && char === ';') {
|
|
690
|
+
statements.push(currentStatement.trim());
|
|
691
|
+
currentStatement = '';
|
|
692
|
+
} else {
|
|
693
|
+
currentStatement += char;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// 添加最后一个语句(如果没有分号结尾)
|
|
698
|
+
if (currentStatement.trim()) {
|
|
699
|
+
statements.push(currentStatement.trim());
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
return statements.filter(stmt => stmt.length > 0);
|
|
703
|
+
};
|
|
704
|
+
|
|
705
|
+
/* === sql语句拼接函数 === */
|
|
706
|
+
/**
|
|
707
|
+
* @description 转为where语句
|
|
708
|
+
* @param {object} obj 用作拼接的对象
|
|
709
|
+
* @param {boolean} like 是否使用like匹配, 默认不使用
|
|
710
|
+
* @returns {string} where格式sql语句字符串
|
|
711
|
+
*/
|
|
712
|
+
Sql.prototype.toWhere = function (obj, like) {
|
|
713
|
+
var where = '';
|
|
714
|
+
if (like === undefined) {
|
|
715
|
+
like = this.like;
|
|
716
|
+
}
|
|
717
|
+
if (like) {
|
|
718
|
+
for (var k in obj) {
|
|
719
|
+
var val = obj[k];
|
|
720
|
+
if (val && typeof (val) === 'string') {
|
|
721
|
+
val = val.trim("'");
|
|
722
|
+
}
|
|
723
|
+
val = escape(val);
|
|
724
|
+
if (k.endsWith('_min')) {
|
|
725
|
+
where += ' and ' + escapeId(k.replace('_min', '')) + ' >= ' + val;
|
|
726
|
+
} else if (k.endsWith('_max')) {
|
|
727
|
+
where += ' and ' + escapeId(k.replace('_max', '')) + ' <= ' + val;
|
|
728
|
+
} else if (k.endsWith('_not')) {
|
|
729
|
+
where += ' and ' + escapeId(k.replace('_not', '')) + ' != ' + val;
|
|
730
|
+
} else if (k.endsWith('_has')) {
|
|
731
|
+
var vals = val.trim("'").split(',').map((o) => {
|
|
732
|
+
return "'" + o + "'";
|
|
733
|
+
});
|
|
734
|
+
where += ' and ' + escapeId(k.replace('_has', '')) + ' in (' + vals.join(',') + ')';
|
|
735
|
+
} else if (k.endsWith('_like')) {
|
|
736
|
+
where += ' and ' + escapeId(k.replace('_like', '')) + " LIKE '%" + val.trim("'") + "%'";
|
|
737
|
+
} else if (typeof (val) === 'string' && !/^[0-9]+$/.test(val)) {
|
|
738
|
+
where += ' and ' + escapeId(k) + " LIKE '%" + val.trim("'") + "%'";
|
|
739
|
+
} else {
|
|
740
|
+
where += ' and ' + escapeId(k) + ' = ' + val;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
} else {
|
|
744
|
+
for (var k in obj) {
|
|
745
|
+
var val = obj[k];
|
|
746
|
+
if (val && typeof (val) === 'string') {
|
|
747
|
+
val = val.trim("'");
|
|
748
|
+
}
|
|
749
|
+
val = escape(val);
|
|
750
|
+
if (k.endsWith('_min')) {
|
|
751
|
+
where += ' and ' + escapeId(k.replace('_min', '')) + ' >= ' + val;
|
|
752
|
+
} else if (k.endsWith('_max')) {
|
|
753
|
+
where += ' and ' + escapeId(k.replace('_max', '')) + ' <= ' + val;
|
|
754
|
+
} else if (k.endsWith('_not')) {
|
|
755
|
+
where += ' and ' + escapeId(k.replace('_not', '')) + ' != ' + val;
|
|
756
|
+
} else if (k.endsWith('_has')) {
|
|
757
|
+
var vals = val.trim("'").split(',').map((o) => {
|
|
758
|
+
return "'" + o + "'";
|
|
759
|
+
});
|
|
760
|
+
where += ' and ' + escapeId(k.replace('_has', '')) + ' in (' + vals.join(',') + ')';
|
|
761
|
+
} else {
|
|
762
|
+
where += ' and ' + escapeId(k) + ' = ' + val;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
return where.replace(' and ', '');
|
|
767
|
+
};
|
|
768
|
+
|
|
769
|
+
/**
|
|
770
|
+
* @description 转为set语句
|
|
771
|
+
* @param {object} obj 用作拼接的对象
|
|
772
|
+
* @returns {string} set格式sql语句字符串
|
|
773
|
+
*/
|
|
774
|
+
Sql.prototype.toSet = function (obj) {
|
|
775
|
+
var set = '';
|
|
776
|
+
for (var k in obj) {
|
|
777
|
+
if (!Object.prototype.hasOwnProperty.call(obj, k)) continue;
|
|
778
|
+
|
|
779
|
+
var val = obj[k];
|
|
780
|
+
if (val === undefined || val === null) continue;
|
|
781
|
+
|
|
782
|
+
if (typeof val === 'string') {
|
|
783
|
+
val = val.trim("'");
|
|
784
|
+
}
|
|
785
|
+
val = escape(val);
|
|
786
|
+
|
|
787
|
+
if (k.endsWith('_add')) {
|
|
788
|
+
var k2 = escapeId(k.replace('_add', ''));
|
|
789
|
+
set += ',' + k2 + ' = ' + k2 + ' + ' + val;
|
|
790
|
+
} else if (k.endsWith('_del')) {
|
|
791
|
+
var k3 = escapeId(k.replace('_del', ''));
|
|
792
|
+
set += ',' + k3 + ' = ' + k3 + ' - ' + val;
|
|
793
|
+
} else {
|
|
794
|
+
set += ',' + escapeId(k) + ' = ' + val;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
return set.replace(',', '');
|
|
798
|
+
};
|
|
799
|
+
|
|
800
|
+
/**
|
|
801
|
+
* 转添加sql语句
|
|
802
|
+
* @param {object} item 用作添加的键值
|
|
803
|
+
* @returns {string} sql语句
|
|
804
|
+
*/
|
|
805
|
+
Sql.prototype.toAddSql = function (item) {
|
|
806
|
+
if (!this.table || !item || typeof item !== 'object') {
|
|
807
|
+
throw new Error('表名或数据未设置');
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
let key = '';
|
|
811
|
+
let value = '';
|
|
812
|
+
|
|
813
|
+
for (const k in item) {
|
|
814
|
+
if (!Object.prototype.hasOwnProperty.call(item, k)) continue;
|
|
815
|
+
|
|
816
|
+
key += ',' + escapeId(k);
|
|
817
|
+
let val = item[k];
|
|
818
|
+
if (typeof val === 'string') {
|
|
819
|
+
val = val.trim("'");
|
|
820
|
+
}
|
|
821
|
+
value += ',' + escape(val);
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// 使用直接拼接的方式,避免模板替换错误
|
|
825
|
+
const sql = `INSERT INTO \`${this.table}\` (${key.replace(',', '')}) VALUES (${value.replace(',', '')});`;
|
|
826
|
+
|
|
827
|
+
return sql;
|
|
828
|
+
};
|
|
829
|
+
|
|
830
|
+
/**
|
|
831
|
+
* 转删除sql语句
|
|
832
|
+
* @param {object} query 查询键值
|
|
833
|
+
* @param {boolean} like 是否使用like匹配, 为空使用默认方式
|
|
834
|
+
* @returns {string} sql语句
|
|
835
|
+
*/
|
|
836
|
+
Sql.prototype.toDelSql = function (query, like) {
|
|
837
|
+
if (!this.table) {
|
|
838
|
+
throw new Error('表名未设置');
|
|
839
|
+
}
|
|
840
|
+
const where = this.toWhere(query, like);
|
|
841
|
+
|
|
842
|
+
// 使用直接拼接的方式,避免模板替换错误
|
|
843
|
+
const sql = `DELETE FROM \`${this.table}\` WHERE ${where};`;
|
|
844
|
+
|
|
845
|
+
return sql;
|
|
846
|
+
};
|
|
847
|
+
|
|
848
|
+
/**
|
|
849
|
+
* 转修改sql语句
|
|
850
|
+
* @param {object} query 查询的键值集合
|
|
851
|
+
* @param {object} item 修改的键值集合
|
|
852
|
+
* @param {boolean} like 是否使用like匹配, 为空使用默认方式
|
|
853
|
+
* @returns {string} sql语句
|
|
854
|
+
*/
|
|
855
|
+
Sql.prototype.toSetSql = function (query, item, like) {
|
|
856
|
+
if (!this.table) {
|
|
857
|
+
throw new Error('表名未设置');
|
|
858
|
+
}
|
|
859
|
+
const where = this.toWhere(query, like);
|
|
860
|
+
const set = this.toSet(item);
|
|
861
|
+
|
|
862
|
+
// 使用直接拼接的方式,避免模板替换错误
|
|
863
|
+
const sql = `UPDATE \`${this.table}\` SET ${set} WHERE ${where};`;
|
|
864
|
+
|
|
865
|
+
return sql;
|
|
866
|
+
};
|
|
867
|
+
|
|
868
|
+
/**
|
|
869
|
+
* @description 转查询sql语句
|
|
870
|
+
* @param {object} query 查询键值集合
|
|
871
|
+
* @param {string} sort 排序规则
|
|
872
|
+
* @param {string} view 显示的字段
|
|
873
|
+
* @param {boolean} like 是否使用like匹配, 为空使用默认方式
|
|
874
|
+
* @returns {string} sql语句
|
|
875
|
+
*/
|
|
876
|
+
Sql.prototype.toGetSql = function (query, sort, view, like) {
|
|
877
|
+
var where = this.toWhere(query, like);
|
|
878
|
+
return this.toQuery(where, sort, view);
|
|
879
|
+
};
|
|
880
|
+
/* === 传入对象操作 === */
|
|
881
|
+
/**
|
|
882
|
+
* @description 增加数据
|
|
883
|
+
* @param {object} body 添加的对象
|
|
884
|
+
* @returns {Promise | object} 执行结果
|
|
885
|
+
*/
|
|
886
|
+
Sql.prototype.add = async function (body) {
|
|
887
|
+
if (!this.table || !body || typeof body !== 'object') {
|
|
888
|
+
throw new Error('表名或数据未设置');
|
|
889
|
+
}
|
|
890
|
+
try {
|
|
891
|
+
var sql = this.toAddSql(body);
|
|
892
|
+
this.sql = sql;
|
|
893
|
+
var bl = await this.exec(sql);
|
|
894
|
+
|
|
895
|
+
return bl;
|
|
896
|
+
} catch (err) {
|
|
897
|
+
this.error = err.message;
|
|
898
|
+
this.log('error', '添加数据失败', err);
|
|
899
|
+
// 返回默认操作结果对象,保持返回值类型一致
|
|
900
|
+
return 0;
|
|
901
|
+
}
|
|
902
|
+
};
|
|
903
|
+
/**
|
|
904
|
+
* @description 删除数据
|
|
905
|
+
* @param {object} query 查询条件集合
|
|
906
|
+
* @param {boolean} like 是否使用like匹配, 为空使用默认方式
|
|
907
|
+
* @returns {Promise | object} 执行结果
|
|
908
|
+
*/
|
|
909
|
+
Sql.prototype.del = async function (query, like) {
|
|
910
|
+
if (!this.table) {
|
|
911
|
+
throw new Error('表名未设置');
|
|
912
|
+
}
|
|
913
|
+
try {
|
|
914
|
+
var sql = this.toDelSql(query, like);
|
|
915
|
+
this.sql = sql;
|
|
916
|
+
var bl = await this.exec(sql);
|
|
917
|
+
|
|
918
|
+
return bl;
|
|
919
|
+
} catch (err) {
|
|
920
|
+
this.error = err.message;
|
|
921
|
+
this.log('error', '删除数据失败', err);
|
|
922
|
+
// 返回默认操作结果对象,保持返回值类型一致
|
|
923
|
+
return 0;
|
|
924
|
+
}
|
|
925
|
+
};
|
|
926
|
+
|
|
927
|
+
/**
|
|
928
|
+
* @description 修改数据
|
|
929
|
+
* @param {object} query 查询条件集合
|
|
930
|
+
* @param {object} body 修改的键值集合
|
|
931
|
+
* @param {boolean} like 是否使用like匹配, 为空使用默认方式
|
|
932
|
+
* @returns {Promise | object} 执行结果
|
|
933
|
+
*/
|
|
934
|
+
Sql.prototype.set = async function (query, body, like) {
|
|
935
|
+
if (!this.table || !body || typeof body !== 'object') {
|
|
936
|
+
throw new Error('表名或数据未设置');
|
|
937
|
+
}
|
|
938
|
+
try {
|
|
939
|
+
var sql = this.toSetSql(query, body, like);
|
|
940
|
+
this.sql = sql;
|
|
941
|
+
var bl = await this.exec(sql);
|
|
942
|
+
return bl;
|
|
943
|
+
} catch (err) {
|
|
944
|
+
this.error = err.message;
|
|
945
|
+
this.log('error', '修改数据失败', err);
|
|
946
|
+
// 返回默认操作结果对象,保持返回值类型一致
|
|
947
|
+
return 0;
|
|
948
|
+
}
|
|
949
|
+
};
|
|
950
|
+
|
|
951
|
+
/**
|
|
952
|
+
* @description 查询数据
|
|
953
|
+
* @param {object} query 查询条件
|
|
954
|
+
* @param {string} sort 排序
|
|
955
|
+
* @param {string} view 返回的字段
|
|
956
|
+
* @param {boolean} like 是否使用like匹配, 为空使用默认方式
|
|
957
|
+
* @param {number} timeout 超时时间(毫秒),默认30000ms
|
|
958
|
+
* @returns {Promise|Array} 查询结果
|
|
959
|
+
*/
|
|
960
|
+
Sql.prototype.get = async function (query, sort, view, like, timeout = 30000) {
|
|
961
|
+
if (!this.table) {
|
|
962
|
+
throw new Error('表名未设置');
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
try {
|
|
966
|
+
// 使用Promise.race实现超时控制
|
|
967
|
+
const timeout_promise = new Promise((unused_resolve, reject) => {
|
|
968
|
+
setTimeout(() => reject(new Error('查询操作超时')), timeout);
|
|
969
|
+
});
|
|
970
|
+
|
|
971
|
+
return await Promise.race([
|
|
972
|
+
(async () => {
|
|
973
|
+
// 生成SQL并执行
|
|
974
|
+
var sql = this.toGetSql(query, sort, view, like);
|
|
975
|
+
var list = await this.run(sql);
|
|
976
|
+
return list;
|
|
977
|
+
})(),
|
|
978
|
+
timeout_promise
|
|
979
|
+
]);
|
|
980
|
+
} catch (err) {
|
|
981
|
+
this.error = err.message;
|
|
982
|
+
this.log('error', '查询数据失败', err);
|
|
983
|
+
// 返回空数组作为默认值,保持返回值类型一致
|
|
984
|
+
return [];
|
|
985
|
+
}
|
|
986
|
+
};
|
|
987
|
+
|
|
988
|
+
/**
|
|
989
|
+
* 添加或修改数据(存在则修改,不存在则添加)
|
|
990
|
+
* @param {object | string} where 查询条件
|
|
991
|
+
* @param {object | string} set 要设置的数据
|
|
992
|
+
* @param {boolean} like 是否使用like匹配, 为空使用默认方式
|
|
993
|
+
* @returns {Promise<object>} 执行结果
|
|
994
|
+
*/
|
|
995
|
+
Sql.prototype.addOrSet = async function (where, set, like) {
|
|
996
|
+
if (!this.table || !where || !set) {
|
|
997
|
+
throw new Error('表名、条件或数据未设置');
|
|
998
|
+
}
|
|
999
|
+
try {
|
|
1000
|
+
let where_str;
|
|
1001
|
+
|
|
1002
|
+
if (typeof where === 'object') {
|
|
1003
|
+
where_str = await this.toWhere(where, like);
|
|
1004
|
+
} else {
|
|
1005
|
+
where_str = where;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
const count = await this.countSql(whereStr);
|
|
1009
|
+
|
|
1010
|
+
if (count === 0) {
|
|
1011
|
+
let key = '';
|
|
1012
|
+
let value = '';
|
|
1013
|
+
|
|
1014
|
+
if (typeof set === 'string') {
|
|
1015
|
+
const arr = set.split(',');
|
|
1016
|
+
for (const o of arr) {
|
|
1017
|
+
const ar = o.split('=');
|
|
1018
|
+
if (ar.length === 2) {
|
|
1019
|
+
key += ',' + ar[0];
|
|
1020
|
+
value += ',' + ar[1];
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
} else {
|
|
1024
|
+
for (const k in set) {
|
|
1025
|
+
if (!Object.prototype.hasOwnProperty.call(set, k)) continue;
|
|
1026
|
+
|
|
1027
|
+
key += ',' + this.escapeId(k);
|
|
1028
|
+
let val = set[k];
|
|
1029
|
+
if (typeof val === 'string') {
|
|
1030
|
+
val = val.trim("'");
|
|
1031
|
+
}
|
|
1032
|
+
value += ',' + this.escape(val);
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
const bl = await this.addSql(key.replace(',', ''), value.replace(',', ''));
|
|
1037
|
+
|
|
1038
|
+
return bl;
|
|
1039
|
+
} else {
|
|
1040
|
+
let set_vals = set;
|
|
1041
|
+
if (typeof set === 'object') {
|
|
1042
|
+
set_vals = await this.toSet(set);
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
const bl1 = await this.setSql(where_str, set_vals);
|
|
1046
|
+
|
|
1047
|
+
return bl1;
|
|
1048
|
+
}
|
|
1049
|
+
} catch (err) {
|
|
1050
|
+
this.error = err.message;
|
|
1051
|
+
this.log('error', '添加或修改数据失败', err);
|
|
1052
|
+
// 返回默认操作结果对象,保持返回值类型一致
|
|
1053
|
+
return 0;
|
|
1054
|
+
}
|
|
1055
|
+
};
|
|
1056
|
+
|
|
1057
|
+
/**
|
|
1058
|
+
* 统计记录数
|
|
1059
|
+
* @param {object} query 查询条件
|
|
1060
|
+
* @param {boolean} like 是否使用like匹配, 为空使用默认方式
|
|
1061
|
+
* @param {number} timeout 超时时间(毫秒),默认30000ms
|
|
1062
|
+
* @returns {Promise | number} 记录数
|
|
1063
|
+
*/
|
|
1064
|
+
Sql.prototype.count = async function (query, like, timeout = 30000) {
|
|
1065
|
+
if (!this.table) {
|
|
1066
|
+
throw new Error('表名未设置');
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
try {
|
|
1070
|
+
// 添加超时控制
|
|
1071
|
+
const timeout_promise = new Promise((unused_resolve, reject) => {
|
|
1072
|
+
setTimeout(() => reject(new Error('统计操作超时')), timeout);
|
|
1073
|
+
});
|
|
1074
|
+
|
|
1075
|
+
return await Promise.race([
|
|
1076
|
+
(async () => {
|
|
1077
|
+
// 正确生成count SQL
|
|
1078
|
+
const where = typeof query === 'string' ? query : await this.toWhere(query, like);
|
|
1079
|
+
const sql = 'SELECT COUNT(*) as num FROM `' + this.table + '`' + (where ? ' WHERE ' + where : '');
|
|
1080
|
+
this.sql = sql;
|
|
1081
|
+
const list = await this.run(sql);
|
|
1082
|
+
const total = list && list[0] && list[0].num ? parseInt(list[0].num) : 0;
|
|
1083
|
+
|
|
1084
|
+
return total;
|
|
1085
|
+
})(),
|
|
1086
|
+
timeout_promise
|
|
1087
|
+
]);
|
|
1088
|
+
} catch (err) {
|
|
1089
|
+
this.error = err.message;
|
|
1090
|
+
this.log('error', '统计记录数失败', err);
|
|
1091
|
+
// 返回0作为默认统计值,保持返回值类型一致
|
|
1092
|
+
return 0;
|
|
1093
|
+
}
|
|
1094
|
+
};
|
|
1095
|
+
|
|
1096
|
+
/**
|
|
1097
|
+
* 查询数据并返回符合条件总数
|
|
1098
|
+
* @param {object} query 查询条件
|
|
1099
|
+
* @param {boolean} like 是否使用like匹配, 为空使用默认方式
|
|
1100
|
+
* @param {number} timeout 超时时间(毫秒),默认30000ms
|
|
1101
|
+
* @returns {Promise<number>} 记录数
|
|
1102
|
+
*/
|
|
1103
|
+
Sql.prototype.getCount = async function (query, like, timeout = 30000) {
|
|
1104
|
+
return await this.count(query, like, timeout);
|
|
1105
|
+
};
|
|
1106
|
+
|
|
1107
|
+
/* === 传入数组操作 === */
|
|
1108
|
+
/**
|
|
1109
|
+
* 添加多条数据
|
|
1110
|
+
* @param {Array} list 对象数组
|
|
1111
|
+
* @param {number} batch_size 批量大小,默认100
|
|
1112
|
+
* @param {number} timeout 超时时间,默认60000毫秒
|
|
1113
|
+
* @returns {Promise | object} 执行结果
|
|
1114
|
+
*/
|
|
1115
|
+
Sql.prototype.addList = async function (list, batch_size = 100, timeout = 60000) {
|
|
1116
|
+
if (!this.table || !Array.isArray(list) || list.length === 0) {
|
|
1117
|
+
throw new Error('表名或数据列表未设置');
|
|
1118
|
+
}
|
|
1119
|
+
try {
|
|
1120
|
+
// 添加整体操作超时控制
|
|
1121
|
+
const timeout_promise = new Promise((unused_resolve, reject) => {
|
|
1122
|
+
setTimeout(() => reject(new Error('批量添加数据操作超时')), timeout);
|
|
1123
|
+
});
|
|
1124
|
+
|
|
1125
|
+
return await Promise.race([
|
|
1126
|
+
(async () => {
|
|
1127
|
+
// 如果数据量较小,直接处理
|
|
1128
|
+
if (list.length <= batch_size) {
|
|
1129
|
+
// 使用批量插入语法,不使用事务包装
|
|
1130
|
+
this.sql = this.toBatchAddSql(list);
|
|
1131
|
+
return await this.exec(this.sql);
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
// 分批处理大数据量
|
|
1135
|
+
const total = Math.ceil(list.length / batch_size);
|
|
1136
|
+
$.log.info(`开始分批添加数据,共${total}批,每批${batch_size}条`);
|
|
1137
|
+
|
|
1138
|
+
// 分批执行,每批一个单独的批量插入语句
|
|
1139
|
+
let final_res = null;
|
|
1140
|
+
for (let i = 0; i < total; i++) {
|
|
1141
|
+
const batch = list.slice(i * batch_size, (i + 1) * batch_size);
|
|
1142
|
+
$.log.debug(`处理第${i + 1}/${total}批数据,${batch.length}条`);
|
|
1143
|
+
|
|
1144
|
+
// 生成批量插入SQL
|
|
1145
|
+
const batch_sql = this.toBatchAddSql(batch);
|
|
1146
|
+
this.sql = batch_sql;
|
|
1147
|
+
|
|
1148
|
+
// 为每批操作添加超时控制
|
|
1149
|
+
final_res = await Promise.race([
|
|
1150
|
+
this.exec(batch_sql),
|
|
1151
|
+
new Promise((unused_resolve, reject) => {
|
|
1152
|
+
setTimeout(() => reject(new Error(`第${i + 1}批数据处理超时`)), timeout / total);
|
|
1153
|
+
})
|
|
1154
|
+
]);
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
$.log.success(`批量添加数据完成,共${list.length}条`);
|
|
1158
|
+
return final_res;
|
|
1159
|
+
})(),
|
|
1160
|
+
timeout_promise
|
|
1161
|
+
]);
|
|
1162
|
+
} catch (error) {
|
|
1163
|
+
this.log('error', '批量添加数据失败', error);
|
|
1164
|
+
// 返回默认操作结果对象,保持返回值类型一致
|
|
1165
|
+
return 0;
|
|
1166
|
+
}
|
|
1167
|
+
};
|
|
1168
|
+
|
|
1169
|
+
/**
|
|
1170
|
+
* 生成批量插入SQL语句
|
|
1171
|
+
* @param {Array} list 数据列表
|
|
1172
|
+
* @returns {string} 批量插入SQL语句
|
|
1173
|
+
*/
|
|
1174
|
+
Sql.prototype.toBatchAddSql = function (list) {
|
|
1175
|
+
if (!this.table || !Array.isArray(list) || list.length === 0) {
|
|
1176
|
+
throw new Error('表名或数据列表未设置');
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
// 获取第一个对象的键名作为列名
|
|
1180
|
+
const first_obj = list[0];
|
|
1181
|
+
if (!first_obj || typeof first_obj !== 'object') {
|
|
1182
|
+
throw new Error('数据格式错误');
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
let keys = [];
|
|
1186
|
+
for (const k in first_obj) {
|
|
1187
|
+
if (Object.prototype.hasOwnProperty.call(first_obj, k)) {
|
|
1188
|
+
keys.push(this.escapeId(k));
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
// 构建列名部分
|
|
1193
|
+
const columns = keys.join(',');
|
|
1194
|
+
|
|
1195
|
+
// 构建值部分
|
|
1196
|
+
const values = [];
|
|
1197
|
+
for (const item of list) {
|
|
1198
|
+
const values = [];
|
|
1199
|
+
for (const k of keys) {
|
|
1200
|
+
const key = k.replace(/`/g, '');
|
|
1201
|
+
let val = item[key];
|
|
1202
|
+
if (typeof val === 'string') {
|
|
1203
|
+
val = val.trim("'");
|
|
1204
|
+
}
|
|
1205
|
+
values.push(this.escape(val));
|
|
1206
|
+
}
|
|
1207
|
+
values.push(`(${values.join(',')})`);
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
// 生成最终SQL
|
|
1211
|
+
const sql = `INSERT INTO \`${this.table}\` (${columns}) VALUES ${values.join(',')}`;
|
|
1212
|
+
return sql;
|
|
1213
|
+
};
|
|
1214
|
+
|
|
1215
|
+
/**
|
|
1216
|
+
* 删除多条数据
|
|
1217
|
+
* @param {Array} list 对象数组
|
|
1218
|
+
* @param {boolean} like 是否使用like匹配, 为空使用默认方式
|
|
1219
|
+
* @param {number} batch_size 每批处理数量,默认100
|
|
1220
|
+
* @param {number} timeout 超时时间(毫秒),默认60000
|
|
1221
|
+
* @returns {Promise<object>} 执行结果
|
|
1222
|
+
*/
|
|
1223
|
+
Sql.prototype.delList = async function (list, like, batch_size = 100, timeout = 60000) {
|
|
1224
|
+
if (!this.table || !Array.isArray(list) || list.length === 0) {
|
|
1225
|
+
throw new Error('表名或数据列表未设置');
|
|
1226
|
+
}
|
|
1227
|
+
try {
|
|
1228
|
+
// 添加整体操作超时控制
|
|
1229
|
+
const timeout_promise = new Promise((unused_resolve, reject) => {
|
|
1230
|
+
setTimeout(() => reject(new Error('批量删除数据操作超时')), timeout);
|
|
1231
|
+
});
|
|
1232
|
+
|
|
1233
|
+
return await Promise.race([
|
|
1234
|
+
(async () => {
|
|
1235
|
+
// 如果数据量较小,直接处理
|
|
1236
|
+
if (list.length <= batch_size) {
|
|
1237
|
+
let sql = '';
|
|
1238
|
+
for (const item of list) {
|
|
1239
|
+
sql += this.toDelSql(item.query, like);
|
|
1240
|
+
}
|
|
1241
|
+
this.sql = sql;
|
|
1242
|
+
return await this.exec(sql);
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
// 分批处理大数据量
|
|
1246
|
+
const total = Math.ceil(list.length / batch_size);
|
|
1247
|
+
$.log.info(`开始分批删除数据,共${total}批,每批${batch_size}条`);
|
|
1248
|
+
|
|
1249
|
+
// 使用事务包装整个操作以保证原子性
|
|
1250
|
+
let final_res = null;
|
|
1251
|
+
try {
|
|
1252
|
+
// 开始事务
|
|
1253
|
+
await this.exec('BEGIN;', 15000);
|
|
1254
|
+
|
|
1255
|
+
// 分批处理
|
|
1256
|
+
for (let i = 0; i < total; i++) {
|
|
1257
|
+
const batch = list.slice(i * batch_size, (i + 1) * batch_size);
|
|
1258
|
+
$.log.debug(`处理第${i + 1}/${total}批数据,${batch.length}条`);
|
|
1259
|
+
|
|
1260
|
+
let batch_sql = '';
|
|
1261
|
+
for (const item of batch) {
|
|
1262
|
+
batch_sql += this.toDelSql(item.query, like);
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
// 为每批操作添加超时控制
|
|
1266
|
+
final_res = await Promise.race([
|
|
1267
|
+
this.exec(batch_sql),
|
|
1268
|
+
new Promise((unused_resolve, reject) => {
|
|
1269
|
+
setTimeout(() => reject(new Error(`第${i + 1}批数据删除超时`)), timeout / total);
|
|
1270
|
+
})
|
|
1271
|
+
]);
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
// 提交事务
|
|
1275
|
+
await this.exec('COMMIT;', 15000);
|
|
1276
|
+
} catch (error) {
|
|
1277
|
+
// 发生错误时回滚事务
|
|
1278
|
+
try {
|
|
1279
|
+
await this.exec('ROLLBACK;', 15000);
|
|
1280
|
+
this.log('error', '批量删除数据失败,事务已回滚', error);
|
|
1281
|
+
} catch (rollback_err) {
|
|
1282
|
+
this.log('error', '批量删除数据失败,事务回滚也失败', rollback_err);
|
|
1283
|
+
}
|
|
1284
|
+
// 记录错误日志
|
|
1285
|
+
this.log('error', 'SQL语句执行失败', error);
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
$.log.success(`批量删除数据完成,共${list.length}条`);
|
|
1289
|
+
return final_res;
|
|
1290
|
+
})(),
|
|
1291
|
+
timeout_promise
|
|
1292
|
+
]);
|
|
1293
|
+
} catch (err) {
|
|
1294
|
+
this.log('error', '批量删除数据失败', err);
|
|
1295
|
+
// 返回默认操作结果对象,保持返回值类型一致
|
|
1296
|
+
return 0;
|
|
1297
|
+
}
|
|
1298
|
+
};
|
|
1299
|
+
/**
|
|
1300
|
+
* 更新多条数据
|
|
1301
|
+
* @param {Array} list 对象数组
|
|
1302
|
+
* @param {boolean} like 是否使用like匹配, 为空使用默认方式
|
|
1303
|
+
* @param {number} batch_size 每批处理数量,默认100
|
|
1304
|
+
* @param {number} timeout 超时时间(毫秒),默认60000
|
|
1305
|
+
* @returns {Promise<object>} 执行结果
|
|
1306
|
+
*/
|
|
1307
|
+
Sql.prototype.setList = async function (list, like = false, batch_size = 100, timeout = 60000) {
|
|
1308
|
+
if (!this.table || !Array.isArray(list) || list.length === 0) {
|
|
1309
|
+
throw new Error('表名或数据列表未设置');
|
|
1310
|
+
}
|
|
1311
|
+
try {
|
|
1312
|
+
// 添加整体操作超时控制
|
|
1313
|
+
const timeout_promise = new Promise((unused_resolve, reject) => {
|
|
1314
|
+
setTimeout(() => reject(new Error('批量更新数据操作超时')), timeout);
|
|
1315
|
+
});
|
|
1316
|
+
|
|
1317
|
+
return await Promise.race([
|
|
1318
|
+
(async () => {
|
|
1319
|
+
// 如果数据量较小,直接处理
|
|
1320
|
+
if (list.length <= batch_size) {
|
|
1321
|
+
let sql = '';
|
|
1322
|
+
for (const item of list) {
|
|
1323
|
+
sql += this.toSetSql(item.query, item.obj, like);
|
|
1324
|
+
}
|
|
1325
|
+
this.sql = sql;
|
|
1326
|
+
return await this.exec(sql);
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
// 分批处理大数据量
|
|
1330
|
+
const total = Math.ceil(list.length / batch_size);
|
|
1331
|
+
$.log.info(`开始分批更新数据,共${total}批,每批${batch_size}条`);
|
|
1332
|
+
|
|
1333
|
+
// 使用事务包装整个操作以保证原子性
|
|
1334
|
+
let final_res = null;
|
|
1335
|
+
try {
|
|
1336
|
+
// 开始事务
|
|
1337
|
+
await this.exec('BEGIN;', 15000);
|
|
1338
|
+
|
|
1339
|
+
// 分批处理
|
|
1340
|
+
for (let i = 0; i < total; i++) {
|
|
1341
|
+
const batch = list.slice(i * batch_size, (i + 1) * batch_size);
|
|
1342
|
+
$.log.debug(`处理第${i + 1}/${total}批数据,${batch.length}条`);
|
|
1343
|
+
|
|
1344
|
+
let batch_sql = '';
|
|
1345
|
+
for (const item of batch) {
|
|
1346
|
+
batch_sql += this.toSetSql(item.query, item.obj, like);
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
// 为每批操作添加超时控制
|
|
1350
|
+
final_res = await Promise.race([
|
|
1351
|
+
this.exec(batch_sql),
|
|
1352
|
+
new Promise((unused_resolve, reject) => {
|
|
1353
|
+
setTimeout(() => reject(new Error(`第${i + 1}批数据更新超时`)), timeout / total);
|
|
1354
|
+
})
|
|
1355
|
+
]);
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
// 提交事务
|
|
1359
|
+
await this.exec('COMMIT;', 15000);
|
|
1360
|
+
} catch (error) {
|
|
1361
|
+
// 发生错误时回滚事务
|
|
1362
|
+
try {
|
|
1363
|
+
await this.exec('ROLLBACK;', 15000);
|
|
1364
|
+
this.log('error', '批量更新数据失败,事务已回滚', error);
|
|
1365
|
+
} catch (rollback_err) {
|
|
1366
|
+
this.log('error', '批量更新数据失败,事务回滚也失败', rollback_err);
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
$.log.success(`批量更新数据完成,共${list.length}条`);
|
|
1371
|
+
return final_res;
|
|
1372
|
+
})(),
|
|
1373
|
+
timeout_promise
|
|
1374
|
+
]);
|
|
1375
|
+
} catch (err) {
|
|
1376
|
+
this.log('error', '批量更新数据失败', err);
|
|
1377
|
+
// 返回默认操作结果对象,保持返回值类型一致
|
|
1378
|
+
return 0;
|
|
1379
|
+
}
|
|
1380
|
+
};
|
|
1381
|
+
|
|
1382
|
+
/* 辅助类 */
|
|
1383
|
+
/**
|
|
1384
|
+
* 判断SQL模板是否包含某些参数
|
|
1385
|
+
* @param {object} param_dt 参数集合
|
|
1386
|
+
* @param {object} sql_dt sql模板集合
|
|
1387
|
+
* @returns {boolean} 有则返回true,没有则返回false
|
|
1388
|
+
*/
|
|
1389
|
+
Sql.prototype.hasPrm = function (param_dt, sql_dt) {
|
|
1390
|
+
var bl = false;
|
|
1391
|
+
for (var key in sql_dt) {
|
|
1392
|
+
if (Object.prototype.hasOwnProperty.call(sql_dt, key)) {
|
|
1393
|
+
var value = param_dt[key];
|
|
1394
|
+
if (value !== undefined && value !== null && value !== '') {
|
|
1395
|
+
bl = true;
|
|
1396
|
+
break;
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
return bl;
|
|
1401
|
+
};
|
|
1402
|
+
|
|
1403
|
+
/**
|
|
1404
|
+
* 判断某些参数是否没有SQL模板
|
|
1405
|
+
* @param {object} param_dt 参数集合
|
|
1406
|
+
* @param {object} sql_dt sql模板集合
|
|
1407
|
+
* @returns {string|undefined} 没有模板则返回名称,都有则返回undefined
|
|
1408
|
+
*/
|
|
1409
|
+
Sql.prototype.noPrm = function (param_dt, sql_dt) {
|
|
1410
|
+
var name;
|
|
1411
|
+
for (var key in param_dt) {
|
|
1412
|
+
if (!sql_dt[key]) {
|
|
1413
|
+
name = key;
|
|
1414
|
+
break;
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
return name;
|
|
1418
|
+
};
|
|
1419
|
+
|
|
1420
|
+
/**
|
|
1421
|
+
* 过滤参数,仅保留没有sql模板的参数
|
|
1422
|
+
* @param {object} param_dt 参数集合
|
|
1423
|
+
* @param {object} sql_dt sql模板集合
|
|
1424
|
+
* @returns {object} 返回过滤后的参数集合
|
|
1425
|
+
*/
|
|
1426
|
+
Sql.prototype.filtPrm = function (param_dt, sql_dt) {
|
|
1427
|
+
var dt = [];
|
|
1428
|
+
for (var key in param_dt) {
|
|
1429
|
+
if (!sql_dt[key]) {
|
|
1430
|
+
dt.Add(key, o.Value);
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
return dt;
|
|
1434
|
+
};
|
|
1435
|
+
|
|
1436
|
+
/**
|
|
1437
|
+
* 通过模板拼接查询参数
|
|
1438
|
+
* @param {object} param_dt 参数集合
|
|
1439
|
+
* @param {object} sql_dt 模板集合
|
|
1440
|
+
* @returns {string} 返回拼接的查询参数
|
|
1441
|
+
*/
|
|
1442
|
+
Sql.prototype.tplQuery = function (param_dt, sql_dt) {
|
|
1443
|
+
var sql = '';
|
|
1444
|
+
if (sql_dt) {
|
|
1445
|
+
var l = this.config.separator;
|
|
1446
|
+
if (l) {
|
|
1447
|
+
for (var key in param_dt) {
|
|
1448
|
+
var value = param_dt[key] + '';
|
|
1449
|
+
var arr = value.split(l);
|
|
1450
|
+
var tpl = sql_dt[key];
|
|
1451
|
+
if (tpl) {
|
|
1452
|
+
if (arr.length > 1) {
|
|
1453
|
+
// 如果数量大于0,则增加多条件
|
|
1454
|
+
var sl = '(';
|
|
1455
|
+
var len = arr.length;
|
|
1456
|
+
for (var i = 0; i < len; i++) {
|
|
1457
|
+
// 使用直接替换方式,避免模板替换错误
|
|
1458
|
+
const escaped_val = this.escape(arr[i]);
|
|
1459
|
+
sl += ' || ' + tpl.replaceAll('{0}', escaped_val);
|
|
1460
|
+
}
|
|
1461
|
+
sl = sl.replace(' || ', '') + ')';
|
|
1462
|
+
sql += ' && ' + sl;
|
|
1463
|
+
} else {
|
|
1464
|
+
// 使用直接替换方式,避免模板替换错误
|
|
1465
|
+
const escaped_val = this.escape(value);
|
|
1466
|
+
sql += ' && ' + tpl.replaceAll('{0}', escaped_val);
|
|
1467
|
+
}
|
|
1468
|
+
} else {
|
|
1469
|
+
if (arr.length > 1) {
|
|
1470
|
+
// 如果数量大于0,则增加多条件
|
|
1471
|
+
var sl = '(';
|
|
1472
|
+
var len = arr.length;
|
|
1473
|
+
for (var i = 0; i < len; i++) {
|
|
1474
|
+
sl += ' || ' + this.escapeId(key) + ' = ' + this.escape(arr[i]);
|
|
1475
|
+
}
|
|
1476
|
+
sl = sl.replace(' || ', '') + ')';
|
|
1477
|
+
sql += ' && ' + sl;
|
|
1478
|
+
} else {
|
|
1479
|
+
sql += ' && ' + this.escapeId(key) + ' = ' + this.escape(value);
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
} else {
|
|
1484
|
+
for (var key in param_dt) {
|
|
1485
|
+
var value = this.escape(param_dt[key]);
|
|
1486
|
+
if (sql_dt[key]) {
|
|
1487
|
+
// 使用直接替换方式,避免模板替换错误
|
|
1488
|
+
sql += ' && ' + sql_dt[key].replaceAll('{0}', value);
|
|
1489
|
+
} else {
|
|
1490
|
+
sql += ' && ' + this.escapeId(key) + ' = ' + value;
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
} else {
|
|
1495
|
+
// 如果没有模板,则直接拼接参数
|
|
1496
|
+
var l = this.config.separator;
|
|
1497
|
+
if (l) {
|
|
1498
|
+
// 使用分隔数组拼接
|
|
1499
|
+
for (var key in param_dt) {
|
|
1500
|
+
var value = param_dt[key];
|
|
1501
|
+
var arr = value.split(l);
|
|
1502
|
+
if (arr.length > 1) {
|
|
1503
|
+
// 如果数量大于0,则增加多条件
|
|
1504
|
+
var sl = '(';
|
|
1505
|
+
var len = arr.length;
|
|
1506
|
+
for (var i = 0; i < len; i++) {
|
|
1507
|
+
sl += ' || ' + this.escapeId(key) + ' = ' + this.escape(arr[i]);
|
|
1508
|
+
}
|
|
1509
|
+
sl = sl.replace(' || ', '') + ')';
|
|
1510
|
+
sql += ' && ' + sl;
|
|
1511
|
+
} else {
|
|
1512
|
+
sql += ' && ' + this.escapeId(key) + ' = ' + this.escape(value);
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
} else {
|
|
1516
|
+
// 直接拼接
|
|
1517
|
+
for (var key in param_dt) {
|
|
1518
|
+
sql += ' && ' + this.escapeId(key) + ' = ' + this.escape(param_dt[key]);
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
return sql.replace(' && ', '');
|
|
1523
|
+
};
|
|
1524
|
+
|
|
1525
|
+
/**
|
|
1526
|
+
* 通过模板拼接修改参数
|
|
1527
|
+
* @param {object} param_dt 参数集合
|
|
1528
|
+
* @param {object} sql_dt 模板集合
|
|
1529
|
+
* @returns {string} 返回拼接的查询参数
|
|
1530
|
+
*/
|
|
1531
|
+
Sql.prototype.tplBody = function (param_dt, sql_dt) {
|
|
1532
|
+
var sql = '';
|
|
1533
|
+
if (!sql_dt || sql_dt.length === 0) {
|
|
1534
|
+
for (var key in param_dt) {
|
|
1535
|
+
sql += ' , ' + this.escapeId(key) + ' = ' + this.escape(val[key]);
|
|
1536
|
+
}
|
|
1537
|
+
} else {
|
|
1538
|
+
for (var key in param_dt) {
|
|
1539
|
+
var value = this.escape(param_dt[key]);
|
|
1540
|
+
if (sql_dt[key]) {
|
|
1541
|
+
// 使用直接替换方式,避免模板替换错误
|
|
1542
|
+
const replaced = sql_dt[key].replace('{0}', value);
|
|
1543
|
+
sql += ' , ' + replaced.replace('+ -', '- ').replace('- -', '+ ');
|
|
1544
|
+
} else {
|
|
1545
|
+
sql += ' , ' + this.escapeId(key) + ' = ' + value;
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
return sql.replace(' , ', '');
|
|
1550
|
+
};
|
|
1551
|
+
|
|
1552
|
+
/**
|
|
1553
|
+
* 构建实体模型
|
|
1554
|
+
* @param {object} model 模型对象
|
|
1555
|
+
* @returns {object} 返回监听操作的对象
|
|
1556
|
+
*/
|
|
1557
|
+
Sql.prototype.model = function (model) {
|
|
1558
|
+
var self = this;
|
|
1559
|
+
return new Proxy(model, {
|
|
1560
|
+
set: function (obj, prop, value) {
|
|
1561
|
+
var modified_obj = { ...obj };
|
|
1562
|
+
if (typeof (value) === 'number') {
|
|
1563
|
+
var n = obj[prop];
|
|
1564
|
+
var cha = value - n;
|
|
1565
|
+
if (cha > 0) {
|
|
1566
|
+
self.setSql('`' + self.key + '`=' + obj[self.key], '`' + prop + '`=`' +
|
|
1567
|
+
prop + '` + ' + cha);
|
|
1568
|
+
} else if (cha < 0) {
|
|
1569
|
+
self.setSql('`' + self.key + '`=' + obj[self.key], '`' + prop + '`=`' +
|
|
1570
|
+
prop + '` - ' + (-cha));
|
|
1571
|
+
} else {
|
|
1572
|
+
self.setSql('`' + self.key + '`=' + obj[self.key], '`' + prop + '`=' +
|
|
1573
|
+
value);
|
|
1574
|
+
}
|
|
1575
|
+
} else {
|
|
1576
|
+
var query = {};
|
|
1577
|
+
query[self.key] = obj[self.key];
|
|
1578
|
+
var set = {};
|
|
1579
|
+
set[prop] = value;
|
|
1580
|
+
self.set(query, set);
|
|
1581
|
+
}
|
|
1582
|
+
modified_obj[prop] = value;
|
|
1583
|
+
return modified_obj;
|
|
1584
|
+
}
|
|
1585
|
+
});
|
|
1586
|
+
};
|
|
1587
|
+
|
|
1588
|
+
/**
|
|
1589
|
+
* 查询单条数据
|
|
1590
|
+
* @param {object} query 查询条件
|
|
1591
|
+
* @param {string} sort 排序
|
|
1592
|
+
* @param {string} view 返回的字段
|
|
1593
|
+
* @param {boolean} like 是否使用like匹配, 为空使用默认方式
|
|
1594
|
+
* @param {number} timeout 超时时间(毫秒),默认30000ms
|
|
1595
|
+
* @returns {Promise | object | null} 查询结果
|
|
1596
|
+
*/
|
|
1597
|
+
Sql.prototype.getObj = async function (query, sort, view, like, timeout = 30000) {
|
|
1598
|
+
try {
|
|
1599
|
+
// 保存当前分页设置
|
|
1600
|
+
const old_page = this.page;
|
|
1601
|
+
const size = this.size;
|
|
1602
|
+
|
|
1603
|
+
// 设置为只查询一条
|
|
1604
|
+
this.page = 1;
|
|
1605
|
+
this.size = 1;
|
|
1606
|
+
|
|
1607
|
+
// 使用优化后的get方法
|
|
1608
|
+
const list = await this.get(query, sort, view, like, timeout);
|
|
1609
|
+
|
|
1610
|
+
// 恢复分页设置
|
|
1611
|
+
this.page = old_page;
|
|
1612
|
+
this.size = size;
|
|
1613
|
+
|
|
1614
|
+
var obj = null;
|
|
1615
|
+
if (list.length > 0) {
|
|
1616
|
+
obj = list[0];
|
|
1617
|
+
if (this.key) {
|
|
1618
|
+
obj = this.model(obj);
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
return obj;
|
|
1623
|
+
} catch (err) {
|
|
1624
|
+
this.error = err.message;
|
|
1625
|
+
this.log('error', '查询单条数据失败', err);
|
|
1626
|
+
}
|
|
1627
|
+
return null;
|
|
1628
|
+
};
|
|
1629
|
+
|
|
1630
|
+
/**
|
|
1631
|
+
* 从SQL文件加载数据库
|
|
1632
|
+
* @param {string} file - SQL文件路径
|
|
1633
|
+
* @returns {Promise<boolean>} 是否加载成功
|
|
1634
|
+
*/
|
|
1635
|
+
Sql.prototype.load = async function (file) {
|
|
1636
|
+
try {
|
|
1637
|
+
// 记录操作日志
|
|
1638
|
+
if (this.config && this.config.debug) {
|
|
1639
|
+
$.log.debug(`[${this.constructor.name}] [load] 开始从文件加载数据库`, {
|
|
1640
|
+
file: file
|
|
1641
|
+
});
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
// 检查文件是否存在
|
|
1645
|
+
if (!file.hasFile()) {
|
|
1646
|
+
throw new Error(`SQL文件不存在: ${file}`);
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
// 读取SQL文件内容
|
|
1650
|
+
const sql_content = await $.file.readText(file);
|
|
1651
|
+
|
|
1652
|
+
if (!sql_content || sql_content.trim() === '') {
|
|
1653
|
+
throw new Error(`SQL文件内容为空: ${file}`);
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
// 分割SQL语句(按分号分割,但需要注意处理字符串中的分号)
|
|
1657
|
+
const sql_stmts = this._splitSqlStatements(sql_content);
|
|
1658
|
+
|
|
1659
|
+
// 开始事务执行SQL语句
|
|
1660
|
+
await this.exec('BEGIN TRANSACTION');
|
|
1661
|
+
|
|
1662
|
+
try {
|
|
1663
|
+
// 逐个执行SQL语句
|
|
1664
|
+
for (const sql of sql_stmts) {
|
|
1665
|
+
const trim_sql = sql.trim();
|
|
1666
|
+
if (trim_sql) {
|
|
1667
|
+
await this.exec(trim_sql);
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
// 提交事务
|
|
1672
|
+
await this.exec('COMMIT');
|
|
1673
|
+
|
|
1674
|
+
if (this.config && this.config.debug) {
|
|
1675
|
+
$.log.info(`[${this.constructor.name}] [load] 数据库加载成功`, {
|
|
1676
|
+
file: file,
|
|
1677
|
+
stmt_num: sqlStatements.length
|
|
1678
|
+
});
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
return true;
|
|
1682
|
+
} catch (err) {
|
|
1683
|
+
// 回滚事务
|
|
1684
|
+
await this.exec('ROLLBACK').catch(rollback_err => {
|
|
1685
|
+
this.log('error', '事务回滚失败', rollback_err);
|
|
1686
|
+
});
|
|
1687
|
+
|
|
1688
|
+
// 记录错误日志
|
|
1689
|
+
this.log('error', 'SQL语句执行失败', err);
|
|
1690
|
+
}
|
|
1691
|
+
} catch (error) {
|
|
1692
|
+
// 记录错误日志
|
|
1693
|
+
this.log('error', '数据库加载失败', error);
|
|
1694
|
+
}
|
|
1695
|
+
};
|
|
1696
|
+
|
|
1697
|
+
/**
|
|
1698
|
+
* 分割SQL语句
|
|
1699
|
+
* @private
|
|
1700
|
+
* @param {string} sql_content - SQL内容
|
|
1701
|
+
* @returns {Array} SQL语句数组
|
|
1702
|
+
*/
|
|
1703
|
+
Sql.prototype._splitSqlStatements = function (sql_content) {
|
|
1704
|
+
const stmts = [];
|
|
1705
|
+
let in_str = false;
|
|
1706
|
+
let str_char = '';
|
|
1707
|
+
let in_cmt = false;
|
|
1708
|
+
let cur_stmt = '';
|
|
1709
|
+
|
|
1710
|
+
for (let i = 0; i < sql_content.length; i++) {
|
|
1711
|
+
const char = sql_content[i];
|
|
1712
|
+
const next_char = i + 1 < sql_content.length ? sql_content[i + 1] : '';
|
|
1713
|
+
|
|
1714
|
+
// 处理注释
|
|
1715
|
+
if (!in_str && !in_cmt && char === '-' && next_char === '-') {
|
|
1716
|
+
in_cmt = true;
|
|
1717
|
+
i++; // 跳过第二个'-'
|
|
1718
|
+
continue;
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
if (in_cmt && char === '\n') {
|
|
1722
|
+
in_cmt = false;
|
|
1723
|
+
continue;
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
if (in_cmt) {
|
|
1727
|
+
continue;
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
// 处理多行注释
|
|
1731
|
+
if (!in_str && !in_cmt && char === '/' && next_char === '*') {
|
|
1732
|
+
in_cmt = true;
|
|
1733
|
+
i++; // 跳过'*'
|
|
1734
|
+
continue;
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
if (in_cmt && char === '*' && next_char === '/') {
|
|
1738
|
+
in_cmt = false;
|
|
1739
|
+
i++; // 跳过'/'
|
|
1740
|
+
continue;
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
// 处理字符串
|
|
1744
|
+
if (!in_cmt && (char === "'" || char === '"') && (!in_str || str_char === char)) {
|
|
1745
|
+
// 检查是否是转义的引号
|
|
1746
|
+
let escaped = false;
|
|
1747
|
+
for (let j = i - 1; j >= 0; j--) {
|
|
1748
|
+
if (sql_content[j] === '\\') {
|
|
1749
|
+
escaped = !escaped;
|
|
1750
|
+
} else {
|
|
1751
|
+
break;
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
if (!escaped) {
|
|
1756
|
+
if (in_str && str_char === char) {
|
|
1757
|
+
in_str = false;
|
|
1758
|
+
str_char = '';
|
|
1759
|
+
} else if (!in_str) {
|
|
1760
|
+
in_str = true;
|
|
1761
|
+
str_char = char;
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
// 分割语句
|
|
1767
|
+
if (!in_str && !in_cmt && char === ';') {
|
|
1768
|
+
stmts.push(cur_stmt.trim());
|
|
1769
|
+
cur_stmt = '';
|
|
1770
|
+
} else {
|
|
1771
|
+
cur_stmt += char;
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
// 添加最后一个语句(如果有)
|
|
1776
|
+
if (cur_stmt.trim()) {
|
|
1777
|
+
stmts.push(cur_stmt.trim());
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
return stmts;
|
|
1781
|
+
};
|
|
1782
|
+
|
|
1783
|
+
/**
|
|
1784
|
+
* @description 获取创建表的 SQL 语句
|
|
1785
|
+
* @returns {Promise<string>} 创建表的 SQL 语句
|
|
1786
|
+
* @private
|
|
1787
|
+
*/
|
|
1788
|
+
Sql.prototype._getCreateTableSql = async function() {
|
|
1789
|
+
try {
|
|
1790
|
+
// 查询表结构信息(SQLite 使用 sqlite_master 表)
|
|
1791
|
+
const sql = `SELECT sql FROM sqlite_master WHERE type='table' AND name='${this.table}'`;
|
|
1792
|
+
const result = await this.run(sql);
|
|
1793
|
+
|
|
1794
|
+
if (result && result.length > 0 && result[0].sql) {
|
|
1795
|
+
// 在创建表语句中添加 IF NOT EXISTS 条件
|
|
1796
|
+
let create_sql = result[0].sql;
|
|
1797
|
+
if (create_sql.toUpperCase().startsWith('CREATE TABLE')) {
|
|
1798
|
+
create_sql = create_sql.replace('CREATE TABLE', 'CREATE TABLE IF NOT EXISTS');
|
|
1799
|
+
}
|
|
1800
|
+
return create_sql + ';';
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
return '';
|
|
1804
|
+
} catch (err) {
|
|
1805
|
+
this.log('warn', '获取表结构失败,将仅备份数据', err);
|
|
1806
|
+
return '';
|
|
1807
|
+
}
|
|
1808
|
+
};
|
|
1809
|
+
|
|
1810
|
+
/**
|
|
1811
|
+
* @description 备份数据库到 SQL 文件
|
|
1812
|
+
* @param {string} file 备份文件路径
|
|
1813
|
+
* @param {boolean} create 是否导出创建表语句,默认为false
|
|
1814
|
+
* @returns {Promise<boolean>} 备份是否成功
|
|
1815
|
+
*/
|
|
1816
|
+
Sql.prototype.backup = async function(file, create = false) {
|
|
1817
|
+
if (!file || typeof file !== 'string') {
|
|
1818
|
+
throw new TypeError('文件路径必须为字符串');
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
if (!this.table) {
|
|
1822
|
+
throw new TypeError('表名未设置');
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
try {
|
|
1826
|
+
// 获取所有数据
|
|
1827
|
+
const data = await this.getSql('', '', '*');
|
|
1828
|
+
|
|
1829
|
+
if (!data || !Array.isArray(data)) {
|
|
1830
|
+
throw new Error('获取数据失败');
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
// 构建 SQL 语句
|
|
1834
|
+
let sql_content = '';
|
|
1835
|
+
|
|
1836
|
+
// 添加表结构(简化版,实际应用中可能需要更复杂的表结构导出)
|
|
1837
|
+
sql_content += `-- 备份表: ${this.table}\n`;
|
|
1838
|
+
sql_content += `-- 备份时间: ${new Date().toISOString()}\n\n`;
|
|
1839
|
+
|
|
1840
|
+
// 如果create为true,添加创建表语句
|
|
1841
|
+
if (create) {
|
|
1842
|
+
const create_table_sql = await this._getCreateTableSql();
|
|
1843
|
+
if (create_table_sql) {
|
|
1844
|
+
sql_content += create_table_sql + '\n\n';
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
// 添加数据插入语句
|
|
1849
|
+
for (const row of data) {
|
|
1850
|
+
const keys = Object.keys(row).map(key => this.escapeId(key)).join(', ');
|
|
1851
|
+
const values = Object.values(row).map(value => this.escape(value)).join(', ');
|
|
1852
|
+
sql_content += `INSERT INTO \`${this.table}\` (${keys}) VALUES (${values});\n`;
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
// 写入文件(这里需要文件系统模块,实际使用时需要引入 fs)
|
|
1856
|
+
const fs = require('fs');
|
|
1857
|
+
const path = require('path');
|
|
1858
|
+
|
|
1859
|
+
// 确保目录存在
|
|
1860
|
+
const dir = path.dirname(file);
|
|
1861
|
+
if (!fs.existsSync(dir)) {
|
|
1862
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
fs.writeFileSync(file, sql_content, 'utf8');
|
|
1866
|
+
|
|
1867
|
+
return true;
|
|
1868
|
+
} catch (err) {
|
|
1869
|
+
this.error = err.message;
|
|
1870
|
+
this.log('error', '备份数据库失败', err);
|
|
1871
|
+
return false;
|
|
1872
|
+
}
|
|
1873
|
+
};
|
|
1874
|
+
|
|
1875
|
+
/**
|
|
1876
|
+
* @description 从 SQL 文件恢复数据库
|
|
1877
|
+
* @param {string} file 备份文件路径
|
|
1878
|
+
* @returns {Promise<boolean>} 恢复是否成功
|
|
1879
|
+
*/
|
|
1880
|
+
Sql.prototype.restore = async function(file) {
|
|
1881
|
+
if (!file || typeof file !== 'string') {
|
|
1882
|
+
throw new TypeError('文件路径必须为字符串');
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
if (!this.table) {
|
|
1886
|
+
throw new TypeError('表名未设置');
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
try {
|
|
1890
|
+
const fs = require('fs');
|
|
1891
|
+
|
|
1892
|
+
if (!fs.existsSync(file)) {
|
|
1893
|
+
throw new Error('备份文件不存在');
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1896
|
+
// 读取 SQL 文件
|
|
1897
|
+
const sql_content = fs.readFileSync(file, 'utf8');
|
|
1898
|
+
|
|
1899
|
+
// 解析 SQL 语句
|
|
1900
|
+
const stmts = this._parseSqlStatements(sql_content);
|
|
1901
|
+
|
|
1902
|
+
// 执行每个 SQL 语句
|
|
1903
|
+
for (const stmt of stmts) {
|
|
1904
|
+
if (stmt.trim() && !stmt.startsWith('--')) {
|
|
1905
|
+
await this.exec(stmt);
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
return true;
|
|
1910
|
+
} catch (err) {
|
|
1911
|
+
this.error = err.message;
|
|
1912
|
+
this.log('error', '恢复数据库失败', err);
|
|
1913
|
+
return false;
|
|
1914
|
+
}
|
|
1915
|
+
};
|
|
1916
|
+
|
|
1917
|
+
/**
|
|
1918
|
+
* @description 解析 SQL 语句
|
|
1919
|
+
* @param {string} sql SQL 内容
|
|
1920
|
+
* @returns {string[]} SQL 语句数组
|
|
1921
|
+
* @private
|
|
1922
|
+
*/
|
|
1923
|
+
Sql.prototype._parseSqlStatements = function(sql) {
|
|
1924
|
+
const stmts = [];
|
|
1925
|
+
let cur_stmt = '';
|
|
1926
|
+
let in_str = false;
|
|
1927
|
+
let in_cmt = false;
|
|
1928
|
+
let quote_char = '';
|
|
1929
|
+
|
|
1930
|
+
for (let i = 0; i < sql.length; i++) {
|
|
1931
|
+
const char = sql[i];
|
|
1932
|
+
const next_char = sql[i + 1] || '';
|
|
1933
|
+
|
|
1934
|
+
// 处理字符串
|
|
1935
|
+
if (!in_cmt && (char === "'" || char === '"') && (i === 0 || sql[i - 1] !== '\\')) {
|
|
1936
|
+
if (!in_str) {
|
|
1937
|
+
in_str = true;
|
|
1938
|
+
quote_char = char;
|
|
1939
|
+
} else if (char === quote_char) {
|
|
1940
|
+
in_str = false;
|
|
1941
|
+
quote_char = '';
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
// 处理注释
|
|
1946
|
+
if (!in_str && char === '-' && next_char === '-') {
|
|
1947
|
+
in_cmt = true;
|
|
1948
|
+
}
|
|
1949
|
+
if (in_cmt && char === '\n') {
|
|
1950
|
+
in_cmt = false;
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
// 分割语句
|
|
1954
|
+
if (!in_str && !in_cmt && char === ';') {
|
|
1955
|
+
stmts.push(cur_stmt.trim());
|
|
1956
|
+
cur_stmt = '';
|
|
1957
|
+
} else {
|
|
1958
|
+
cur_stmt += char;
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
// 添加最后一个语句(如果有)
|
|
1963
|
+
if (cur_stmt.trim()) {
|
|
1964
|
+
stmts.push(cur_stmt.trim());
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
return stmts;
|
|
1968
|
+
};
|
|
1969
|
+
|
|
1970
|
+
module.exports = {
|
|
1971
|
+
Sql
|
|
1972
|
+
};
|