mm_mysql 2.3.1 → 2.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/index.js +52 -60
- package/package.json +8 -7
- package/sql.js +130 -164
- package/test.js +82 -3
package/index.js
CHANGED
|
@@ -13,6 +13,7 @@ class Mysql extends Base {
|
|
|
13
13
|
* 默认配置(优化版,统一使用连接池)
|
|
14
14
|
*/
|
|
15
15
|
static config = {
|
|
16
|
+
name: 'default',
|
|
16
17
|
host: '127.0.0.1',
|
|
17
18
|
port: 3306,
|
|
18
19
|
user: 'root',
|
|
@@ -41,7 +42,7 @@ class Mysql extends Base {
|
|
|
41
42
|
* @param {object} config - 配置对象
|
|
42
43
|
*/
|
|
43
44
|
constructor(config = {}) {
|
|
44
|
-
super(
|
|
45
|
+
super(config);
|
|
45
46
|
this._pool = null;
|
|
46
47
|
// 移除单连接支持,统一使用连接池
|
|
47
48
|
this._use_pool = true; // 强制使用连接池模式
|
|
@@ -285,7 +286,7 @@ Mysql.prototype._getValidConfig = function (is_pool = false) {
|
|
|
285
286
|
* @param {boolean} is_pool_conn - 是否为连接池连接
|
|
286
287
|
* @returns {Promise<void>}
|
|
287
288
|
*/
|
|
288
|
-
async function
|
|
289
|
+
async function safeReleaseConn(conn, is_pool_conn) {
|
|
289
290
|
if (!conn) return;
|
|
290
291
|
|
|
291
292
|
try {
|
|
@@ -297,6 +298,23 @@ async function safeReleaseConnection(conn, is_pool_conn) {
|
|
|
297
298
|
}
|
|
298
299
|
};
|
|
299
300
|
|
|
301
|
+
/**
|
|
302
|
+
* 释放连接(实例方法,简化事务使用)
|
|
303
|
+
* @param {object} conn - 数据库连接对象
|
|
304
|
+
* @returns {Promise<void>}
|
|
305
|
+
*/
|
|
306
|
+
Mysql.prototype._releaseConn = async function(conn) {
|
|
307
|
+
if (!conn) return;
|
|
308
|
+
|
|
309
|
+
try {
|
|
310
|
+
if (this._use_pool && typeof conn.release === 'function') {
|
|
311
|
+
await conn.release();
|
|
312
|
+
}
|
|
313
|
+
} catch (error) {
|
|
314
|
+
// 简化错误处理,避免频繁日志
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
|
|
300
318
|
/**
|
|
301
319
|
* 获取数据库连接(统一使用连接池)
|
|
302
320
|
* @returns {Promise<object>}
|
|
@@ -408,7 +426,7 @@ Mysql.prototype.run = async function (sql, params = []) {
|
|
|
408
426
|
};
|
|
409
427
|
// 在错误情况下也要确保连接释放
|
|
410
428
|
try {
|
|
411
|
-
await
|
|
429
|
+
await safeReleaseConn(conn, is_pool_conn);
|
|
412
430
|
} catch (release_err) {
|
|
413
431
|
this.log('error', '释放连接失败', release_err);
|
|
414
432
|
}
|
|
@@ -420,7 +438,7 @@ Mysql.prototype.run = async function (sql, params = []) {
|
|
|
420
438
|
// 确保连接被释放,避免资源泄漏
|
|
421
439
|
if (!connection_released) {
|
|
422
440
|
try {
|
|
423
|
-
await
|
|
441
|
+
await safeReleaseConn(conn, is_pool_conn);
|
|
424
442
|
} catch (release_err) {
|
|
425
443
|
this.log('error', '释放连接失败', release_err);
|
|
426
444
|
}
|
|
@@ -519,7 +537,7 @@ Mysql.prototype.exec = async function (sql, params = []) {
|
|
|
519
537
|
};
|
|
520
538
|
// 在错误情况下也要确保连接释放
|
|
521
539
|
try {
|
|
522
|
-
await
|
|
540
|
+
await safeReleaseConn(conn, is_pool_conn);
|
|
523
541
|
} catch (release_err) {
|
|
524
542
|
this.log('error', '释放连接失败', release_err);
|
|
525
543
|
}
|
|
@@ -531,7 +549,7 @@ Mysql.prototype.exec = async function (sql, params = []) {
|
|
|
531
549
|
// 确保连接被释放,避免资源泄漏
|
|
532
550
|
if (!connection_released) {
|
|
533
551
|
try {
|
|
534
|
-
await
|
|
552
|
+
await safeReleaseConn(conn, is_pool_conn);
|
|
535
553
|
} catch (release_err) {
|
|
536
554
|
this.log('error', '释放连接失败', release_err);
|
|
537
555
|
}
|
|
@@ -541,98 +559,72 @@ Mysql.prototype.exec = async function (sql, params = []) {
|
|
|
541
559
|
};
|
|
542
560
|
|
|
543
561
|
/**
|
|
544
|
-
*
|
|
562
|
+
* 开始事务(优化版)
|
|
545
563
|
* @returns {Promise<object>} 事务连接对象
|
|
546
564
|
*/
|
|
547
565
|
Mysql.prototype.beginTransaction = async function () {
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
let transaction_rolled_back = false;
|
|
551
|
-
|
|
566
|
+
const conn = await this.getConn();
|
|
567
|
+
|
|
552
568
|
try {
|
|
553
|
-
conn = await this.getConn();
|
|
554
569
|
await conn.beginTransaction();
|
|
555
|
-
|
|
570
|
+
|
|
571
|
+
let ended = false;
|
|
572
|
+
|
|
556
573
|
return {
|
|
557
574
|
connection: conn,
|
|
558
575
|
commit: async () => {
|
|
559
|
-
if (
|
|
560
|
-
this.log('warn', '事务已结束,无需重复提交');
|
|
561
|
-
return;
|
|
562
|
-
}
|
|
576
|
+
if (ended) return;
|
|
563
577
|
await conn.commit();
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
await safeReleaseConnection(conn, this._usePool);
|
|
567
|
-
} catch (release_err) {
|
|
568
|
-
this.log('error', '提交时释放连接失败', release_err);
|
|
569
|
-
}
|
|
578
|
+
ended = true;
|
|
579
|
+
await this._releaseConn(conn);
|
|
570
580
|
},
|
|
571
581
|
rollback: async () => {
|
|
572
|
-
if (
|
|
573
|
-
this.log('warn', '事务已结束,无需重复回滚');
|
|
574
|
-
return;
|
|
575
|
-
}
|
|
582
|
+
if (ended) return;
|
|
576
583
|
await conn.rollback();
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
await safeReleaseConnection(conn, this._usePool);
|
|
580
|
-
} catch (release_err) {
|
|
581
|
-
this.log('error', '回滚时释放连接失败', release_err);
|
|
582
|
-
}
|
|
584
|
+
ended = true;
|
|
585
|
+
await this._releaseConn(conn);
|
|
583
586
|
},
|
|
584
|
-
_is_ended: () =>
|
|
587
|
+
_is_ended: () => ended
|
|
585
588
|
};
|
|
586
589
|
} catch (error) {
|
|
587
590
|
// 如果事务开始失败,确保连接被释放
|
|
588
|
-
|
|
589
|
-
await safeReleaseConnection(conn, this._usePool);
|
|
590
|
-
} catch (release_err) {
|
|
591
|
-
this.log('error', '释放连接失败', release_err);
|
|
592
|
-
}
|
|
591
|
+
await this._releaseConn(conn);
|
|
593
592
|
this.log('error', '事务开始失败', error);
|
|
594
593
|
throw error;
|
|
595
594
|
}
|
|
596
595
|
};
|
|
597
596
|
|
|
598
597
|
/**
|
|
599
|
-
*
|
|
598
|
+
* 在事务中执行多个操作(优化版)
|
|
600
599
|
* @param {Function} callback - 包含事务操作的回调函数
|
|
601
600
|
* @returns {Promise<*>} 回调函数的返回值
|
|
602
601
|
* @throws {TypeError} 当callback参数无效时
|
|
603
602
|
*/
|
|
604
603
|
Mysql.prototype.transaction = async function (callback) {
|
|
605
|
-
// 参数校验
|
|
606
604
|
if (typeof callback !== 'function') {
|
|
607
605
|
throw new TypeError('callback must be function');
|
|
608
606
|
}
|
|
609
607
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
608
|
+
const transaction = await this.beginTransaction();
|
|
609
|
+
|
|
613
610
|
try {
|
|
614
|
-
transaction = await this.beginTransaction();
|
|
615
611
|
const result = await callback(transaction);
|
|
616
|
-
|
|
617
|
-
//
|
|
618
|
-
if (
|
|
612
|
+
|
|
613
|
+
// 如果事务未结束,自动提交
|
|
614
|
+
if (!transaction._is_ended()) {
|
|
619
615
|
await transaction.commit();
|
|
620
616
|
}
|
|
621
|
-
|
|
622
|
-
transaction_executed = true;
|
|
617
|
+
|
|
623
618
|
return result;
|
|
624
619
|
} catch (error) {
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
});
|
|
631
|
-
}
|
|
620
|
+
// 如果事务未结束,自动回滚
|
|
621
|
+
if (!transaction._is_ended()) {
|
|
622
|
+
await transaction.rollback().catch(() => {
|
|
623
|
+
// 简化错误处理,避免频繁日志
|
|
624
|
+
});
|
|
632
625
|
}
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
throw error; // 重新抛出错误,让调用方知道事务失败
|
|
626
|
+
|
|
627
|
+
throw error;
|
|
636
628
|
}
|
|
637
629
|
};
|
|
638
630
|
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mm_mysql",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.3",
|
|
4
4
|
"description": "这是超级美眉mysql帮助函数模块,用于便捷操作mysql,使用await方式,可以避免嵌套函数",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"mm_expand": "^2.0.
|
|
8
|
-
"mysql2": "^3.
|
|
7
|
+
"mm_expand": "^2.0.2",
|
|
8
|
+
"mysql2": "^3.20.0",
|
|
9
|
+
"sqlstring": "^2.3.3"
|
|
9
10
|
},
|
|
10
11
|
"scripts": {
|
|
11
12
|
"start": "node index.js",
|
|
@@ -35,8 +36,8 @@
|
|
|
35
36
|
"node": ">=12.0.0"
|
|
36
37
|
},
|
|
37
38
|
"devDependencies": {
|
|
38
|
-
"eslint": "^
|
|
39
|
-
"eslint-plugin-jsdoc": "^
|
|
40
|
-
"mm_eslint": "^1.
|
|
39
|
+
"eslint": "^10.1.0",
|
|
40
|
+
"eslint-plugin-jsdoc": "^62.9.0",
|
|
41
|
+
"mm_eslint": "^1.7.1"
|
|
41
42
|
}
|
|
42
|
-
}
|
|
43
|
+
}
|
package/sql.js
CHANGED
|
@@ -19,6 +19,48 @@ function escapeId(value, forbidQualified) {
|
|
|
19
19
|
* @property {Function} filter 设置并过滤参数
|
|
20
20
|
*/
|
|
21
21
|
class Sql extends Base {
|
|
22
|
+
/**
|
|
23
|
+
* 过滤查询参数字典
|
|
24
|
+
*/
|
|
25
|
+
static config = {
|
|
26
|
+
/**
|
|
27
|
+
* 分隔符 用于查询时的多条件处理
|
|
28
|
+
*/
|
|
29
|
+
'separator': '|',
|
|
30
|
+
/**
|
|
31
|
+
* 过滤
|
|
32
|
+
*/
|
|
33
|
+
'filter': {
|
|
34
|
+
/**
|
|
35
|
+
* 表名
|
|
36
|
+
*/
|
|
37
|
+
'table': 'table',
|
|
38
|
+
/**
|
|
39
|
+
* 查询的页码
|
|
40
|
+
*/
|
|
41
|
+
'page': 'page',
|
|
42
|
+
/**
|
|
43
|
+
* 查询每页条数
|
|
44
|
+
*/
|
|
45
|
+
'size': 'size',
|
|
46
|
+
/**
|
|
47
|
+
* 操作方式: 传入参数method=add, 支持参数 add增、del删、set改、get查,为空则为get
|
|
48
|
+
*/
|
|
49
|
+
'method': 'method',
|
|
50
|
+
/**
|
|
51
|
+
* 排序
|
|
52
|
+
*/
|
|
53
|
+
'orderby': 'orderby',
|
|
54
|
+
/**
|
|
55
|
+
* 查询显示的字段
|
|
56
|
+
*/
|
|
57
|
+
'field': 'field',
|
|
58
|
+
/**
|
|
59
|
+
* 统计结果: 统计符合条件的结果数,只有当page等于1或0时才会统计
|
|
60
|
+
*/
|
|
61
|
+
'count_ret': 'count_ret'
|
|
62
|
+
}
|
|
63
|
+
};
|
|
22
64
|
/**
|
|
23
65
|
* 数据库管理器
|
|
24
66
|
* @param {Function} run 查询函数
|
|
@@ -102,49 +144,6 @@ class Sql extends Base {
|
|
|
102
144
|
*/
|
|
103
145
|
this.count_ret = 'false';
|
|
104
146
|
|
|
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
147
|
this.like = false;
|
|
149
148
|
}
|
|
150
149
|
}
|
|
@@ -189,17 +188,19 @@ Sql.prototype.filter = function (query) {
|
|
|
189
188
|
* @returns {string} 返回查询条件语句
|
|
190
189
|
*/
|
|
191
190
|
Sql.prototype.toQuery = function (where, sort, view) {
|
|
192
|
-
var sql = 'SELECT {1} FROM `{0}`';
|
|
193
191
|
if (!view) {
|
|
194
192
|
view = '*';
|
|
195
193
|
}
|
|
194
|
+
|
|
195
|
+
// 使用直接拼接的方式,避免模板替换错误
|
|
196
|
+
var sql = `SELECT ${view} FROM \`${this.table}\``;
|
|
197
|
+
|
|
196
198
|
if (where) {
|
|
197
199
|
sql += ' WHERE ' + where;
|
|
198
200
|
}
|
|
199
201
|
if (sort) {
|
|
200
202
|
sql += ' ORDER BY ' + sort.replace(/;/, '');
|
|
201
203
|
}
|
|
202
|
-
sql = sql.replace('{0}', this.table).replace('{1}', view);
|
|
203
204
|
if (this.size && this.page) {
|
|
204
205
|
var start = this.size * (this.page - 1);
|
|
205
206
|
sql += ' limit ' + start + ',' + this.size;
|
|
@@ -214,8 +215,8 @@ Sql.prototype.toQuery = function (where, sort, view) {
|
|
|
214
215
|
* @returns {Promise | object} 执行结果
|
|
215
216
|
*/
|
|
216
217
|
Sql.prototype.addSql = function (key, val) {
|
|
217
|
-
|
|
218
|
-
sql =
|
|
218
|
+
// 使用直接拼接的方式,避免模板替换错误
|
|
219
|
+
var sql = `INSERT INTO \`${this.table}\` (${key}) VALUES (${val});`;
|
|
219
220
|
return this.exec(sql);
|
|
220
221
|
};
|
|
221
222
|
/**
|
|
@@ -224,8 +225,8 @@ Sql.prototype.addSql = function (key, val) {
|
|
|
224
225
|
* @returns {Promise | object} 执行结果
|
|
225
226
|
*/
|
|
226
227
|
Sql.prototype.delSql = function (where) {
|
|
227
|
-
|
|
228
|
-
sql =
|
|
228
|
+
// 使用直接拼接的方式,避免模板替换错误
|
|
229
|
+
var sql = `DELETE FROM \`${this.table}\` WHERE ${where};`;
|
|
229
230
|
return this.exec(sql);
|
|
230
231
|
};
|
|
231
232
|
/**
|
|
@@ -235,10 +236,11 @@ Sql.prototype.delSql = function (where) {
|
|
|
235
236
|
* @returns {Promise | object} 执行结果
|
|
236
237
|
*/
|
|
237
238
|
Sql.prototype.setSql = function (where, set) {
|
|
238
|
-
|
|
239
|
-
sql =
|
|
239
|
+
// 使用直接拼接的方式,避免模板替换错误
|
|
240
|
+
var sql = `UPDATE \`${this.table}\` SET ${set} WHERE ${where};`;
|
|
240
241
|
return this.exec(sql);
|
|
241
242
|
};
|
|
243
|
+
|
|
242
244
|
/**
|
|
243
245
|
* 查询数据
|
|
244
246
|
* @param {string} where 查询条件
|
|
@@ -295,7 +297,7 @@ Sql.prototype.getCountSql = async function (where, sort, view) {
|
|
|
295
297
|
* @param {string} groupby 分组的字段
|
|
296
298
|
* @param {string} view 返回的字段
|
|
297
299
|
* @param {string} sort 排序方式
|
|
298
|
-
* @param method
|
|
300
|
+
* @param {string} method 统计方法
|
|
299
301
|
* @returns {Promise | object} 查询到的内容列表和符合条件总数
|
|
300
302
|
*/
|
|
301
303
|
Sql.prototype.groupMathSql = async function (where, groupby, view, sort, method) {
|
|
@@ -310,7 +312,7 @@ Sql.prototype.groupMathSql = async function (where, groupby, view, sort, method)
|
|
|
310
312
|
}
|
|
311
313
|
} else {
|
|
312
314
|
view_str = ',' + method.toUpperCase() + '(' + escapeId(view_val) + ') ' + method.toLowerCase() + '_' +
|
|
313
|
-
|
|
315
|
+
view_val.replace(/`/g, '');
|
|
314
316
|
}
|
|
315
317
|
var sql = 'SELECT ' + (groupby ? escapeId(groupby) : '') + view_str + ' FROM `' + this.table + '`';
|
|
316
318
|
if (where) {
|
|
@@ -439,9 +441,7 @@ Sql.prototype._buildLikeWhere = function (obj) {
|
|
|
439
441
|
var where = '';
|
|
440
442
|
for (var k in obj) {
|
|
441
443
|
var val = obj[k];
|
|
442
|
-
|
|
443
|
-
val = val.trim("'");
|
|
444
|
-
}
|
|
444
|
+
// 先转义再处理字符串,避免转义字符被错误处理
|
|
445
445
|
val = escape(val);
|
|
446
446
|
if (k.endsWith('_min')) {
|
|
447
447
|
where += ' and ' + escapeId(k.replace('_min', '')) + ' >= ' + val;
|
|
@@ -451,7 +451,7 @@ Sql.prototype._buildLikeWhere = function (obj) {
|
|
|
451
451
|
where += ' and ' + escapeId(k.replace('_not', '')) + ' != ' + val;
|
|
452
452
|
} else if (k.endsWith('_has')) {
|
|
453
453
|
var vals = val.trim("'").split(',').map((o) => {
|
|
454
|
-
return
|
|
454
|
+
return escape(o);
|
|
455
455
|
});
|
|
456
456
|
where += ' and ' + escapeId(k.replace('_has', '')) + ' in (' + vals.join(',') + ')';
|
|
457
457
|
} else if (k.endsWith('_like')) {
|
|
@@ -469,9 +469,7 @@ Sql.prototype._buildExactWhere = function (obj) {
|
|
|
469
469
|
var where = '';
|
|
470
470
|
for (var k in obj) {
|
|
471
471
|
var val = obj[k];
|
|
472
|
-
|
|
473
|
-
val = val.trim("'");
|
|
474
|
-
}
|
|
472
|
+
// 先转义再处理字符串,避免转义字符被错误处理
|
|
475
473
|
val = escape(val);
|
|
476
474
|
if (k.endsWith('_min')) {
|
|
477
475
|
where += ' and ' + escapeId(k.replace('_min', '')) + ' >= ' + val;
|
|
@@ -481,7 +479,7 @@ Sql.prototype._buildExactWhere = function (obj) {
|
|
|
481
479
|
where += ' and ' + escapeId(k.replace('_not', '')) + ' != ' + val;
|
|
482
480
|
} else if (k.endsWith('_has')) {
|
|
483
481
|
var vals = val.trim("'").split(',').map((o) => {
|
|
484
|
-
return
|
|
482
|
+
return escape(o);
|
|
485
483
|
});
|
|
486
484
|
where += ' and ' + escapeId(k.replace('_has', '')) + ' in (' + vals.join(',') + ')';
|
|
487
485
|
} else if (k.endsWith('_like')) {
|
|
@@ -501,14 +499,11 @@ Sql.prototype._buildExactWhere = function (obj) {
|
|
|
501
499
|
Sql.prototype.toSet = function (obj) {
|
|
502
500
|
var set = '';
|
|
503
501
|
for (const k in obj) {
|
|
504
|
-
if (!Object.prototype.hasOwnProperty.call(obj, k)) continue;
|
|
505
502
|
|
|
506
503
|
let val = obj[k];
|
|
507
504
|
if (val === undefined || val === null) continue;
|
|
508
505
|
|
|
509
|
-
|
|
510
|
-
val = val.trim("'");
|
|
511
|
-
}
|
|
506
|
+
// 先转义再处理字符串,避免转义字符被错误处理
|
|
512
507
|
val = escape(val);
|
|
513
508
|
|
|
514
509
|
if (k.endsWith('_add')) {
|
|
@@ -539,20 +534,12 @@ Sql.prototype.toAddSql = function (item) {
|
|
|
539
534
|
let value = '';
|
|
540
535
|
|
|
541
536
|
for (const k in item) {
|
|
542
|
-
if (!Object.prototype.hasOwnProperty.call(item, k)) continue;
|
|
543
|
-
|
|
544
537
|
key += ',' + escapeId(k);
|
|
545
|
-
|
|
546
|
-
if (typeof val === 'string') {
|
|
547
|
-
val = val.trim("'");
|
|
548
|
-
}
|
|
549
|
-
value += ',' + escape(val);
|
|
538
|
+
value += ',' + escape(item[k]);
|
|
550
539
|
}
|
|
551
540
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
.replace('{1}', key.replace(',', ''))
|
|
555
|
-
.replace('{2}', value.replace(',', ''));
|
|
541
|
+
// 使用直接拼接的方式,避免模板替换错误
|
|
542
|
+
const sql = `INSERT INTO \`${this.table}\` (${key.replace(',', '')}) VALUES (${value.replace(',', '')});`;
|
|
556
543
|
|
|
557
544
|
return sql;
|
|
558
545
|
};
|
|
@@ -568,9 +555,10 @@ Sql.prototype.toDelSql = function (query, like) {
|
|
|
568
555
|
throw new Error('表名未设置');
|
|
569
556
|
}
|
|
570
557
|
const where = this.toWhere(query, like);
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
558
|
+
|
|
559
|
+
// 使用直接拼接的方式,避免模板替换错误
|
|
560
|
+
const sql = `DELETE FROM \`${this.table}\` WHERE ${where};`;
|
|
561
|
+
|
|
574
562
|
return sql;
|
|
575
563
|
};
|
|
576
564
|
|
|
@@ -587,10 +575,10 @@ Sql.prototype.toSetSql = function (query, item, like) {
|
|
|
587
575
|
}
|
|
588
576
|
const where = this.toWhere(query, like);
|
|
589
577
|
const set = this.toSet(item);
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
578
|
+
|
|
579
|
+
// 使用直接拼接的方式,避免模板替换错误
|
|
580
|
+
const sql = `UPDATE \`${this.table}\` SET ${set} WHERE ${where};`;
|
|
581
|
+
|
|
594
582
|
return sql;
|
|
595
583
|
};
|
|
596
584
|
|
|
@@ -735,13 +723,8 @@ Sql.prototype._parseSetData = async function (set) {
|
|
|
735
723
|
}
|
|
736
724
|
} else {
|
|
737
725
|
for (const k in set) {
|
|
738
|
-
if (!Object.prototype.hasOwnProperty.call(set, k)) continue;
|
|
739
|
-
|
|
740
726
|
key_str += ',' + escapeId(k);
|
|
741
727
|
let val = set[k];
|
|
742
|
-
if (typeof val === 'string') {
|
|
743
|
-
val = val.trim("'");
|
|
744
|
-
}
|
|
745
728
|
val_str += ',' + escape(val);
|
|
746
729
|
}
|
|
747
730
|
}
|
|
@@ -758,10 +741,10 @@ Sql.prototype._parseSetData = async function (set) {
|
|
|
758
741
|
* @param {string} sort 排序
|
|
759
742
|
* @param {string} view 返回的字段
|
|
760
743
|
* @param {boolean} like 是否使用like匹配, 为空使用默认方式
|
|
761
|
-
* @param {number} timeout 超时时间(毫秒),默认
|
|
744
|
+
* @param {number} timeout 超时时间(毫秒),默认20000ms
|
|
762
745
|
* @returns {Promise|Array} 查询结果
|
|
763
746
|
*/
|
|
764
|
-
Sql.prototype.get = async function (query, sort, view, like, timeout =
|
|
747
|
+
Sql.prototype.get = async function (query, sort, view, like, timeout = 20000) {
|
|
765
748
|
if (!this.table) {
|
|
766
749
|
throw new Error('表名未设置');
|
|
767
750
|
}
|
|
@@ -793,10 +776,10 @@ Sql.prototype.get = async function (query, sort, view, like, timeout = 60000) {
|
|
|
793
776
|
* @param {string} sort 排序
|
|
794
777
|
* @param {string} view 返回的字段
|
|
795
778
|
* @param {boolean} like 是否使用like匹配, 为空使用默认方式
|
|
796
|
-
* @param {number} timeout 超时时间(毫秒),默认
|
|
779
|
+
* @param {number} timeout 超时时间(毫秒),默认20000ms
|
|
797
780
|
* @returns {Promise | object | null} 查询结果
|
|
798
781
|
*/
|
|
799
|
-
Sql.prototype.getObj = async function (query, sort, view, like, timeout =
|
|
782
|
+
Sql.prototype.getObj = async function (query, sort, view, like, timeout = 20000) {
|
|
800
783
|
try {
|
|
801
784
|
// 保存当前分页设置
|
|
802
785
|
const old_page = this.page;
|
|
@@ -833,10 +816,10 @@ Sql.prototype.getObj = async function (query, sort, view, like, timeout = 60000)
|
|
|
833
816
|
* 统计记录数
|
|
834
817
|
* @param {object} query 查询条件
|
|
835
818
|
* @param {boolean} like 是否使用like匹配, 为空使用默认方式
|
|
836
|
-
* @param {number} timeout 超时时间(毫秒),默认
|
|
819
|
+
* @param {number} timeout 超时时间(毫秒),默认20000ms
|
|
837
820
|
* @returns {Promise | number} 记录数
|
|
838
821
|
*/
|
|
839
|
-
Sql.prototype.count = async function (query, like, timeout =
|
|
822
|
+
Sql.prototype.count = async function (query, like, timeout = 20000) {
|
|
840
823
|
if (!this.table) {
|
|
841
824
|
throw new Error('表名未设置');
|
|
842
825
|
}
|
|
@@ -964,9 +947,7 @@ Sql.prototype.toBatchAddSql = function (list) {
|
|
|
964
947
|
Sql.prototype._getKeys = function (obj) {
|
|
965
948
|
const keys = [];
|
|
966
949
|
for (const k in obj) {
|
|
967
|
-
|
|
968
|
-
keys.push(this.escapeId(k));
|
|
969
|
-
}
|
|
950
|
+
keys.push(this.escapeId(k));
|
|
970
951
|
}
|
|
971
952
|
return keys;
|
|
972
953
|
};
|
|
@@ -978,9 +959,6 @@ Sql.prototype._buildValueList = function (list, keys) {
|
|
|
978
959
|
for (const k of keys) {
|
|
979
960
|
const key = k.replace(/`/g, '');
|
|
980
961
|
let val = item[key];
|
|
981
|
-
if (typeof val === 'string') {
|
|
982
|
-
val = val.trim("'");
|
|
983
|
-
}
|
|
984
962
|
values.push(this.escape(val));
|
|
985
963
|
}
|
|
986
964
|
val_list.push(`(${values.join(',')})`);
|
|
@@ -1138,7 +1116,6 @@ Sql.prototype.has = function (param_dt, sql_dt) {
|
|
|
1138
1116
|
return false;
|
|
1139
1117
|
}
|
|
1140
1118
|
for (const key in sql_dt) {
|
|
1141
|
-
if (!Object.prototype.hasOwnProperty.call(sql_dt, key)) continue;
|
|
1142
1119
|
const value = param_dt[key];
|
|
1143
1120
|
if (value !== undefined && value !== null && value !== '') {
|
|
1144
1121
|
return true;
|
|
@@ -1158,7 +1135,6 @@ Sql.prototype.not = function (param_dt, sql_dt) {
|
|
|
1158
1135
|
return undefined;
|
|
1159
1136
|
}
|
|
1160
1137
|
for (const key in param_dt) {
|
|
1161
|
-
if (!Object.prototype.hasOwnProperty.call(param_dt, key)) continue;
|
|
1162
1138
|
if (!sql_dt[key]) {
|
|
1163
1139
|
return key;
|
|
1164
1140
|
}
|
|
@@ -1178,7 +1154,6 @@ Sql.prototype.filterParams = function (param_dt, sql_dt) {
|
|
|
1178
1154
|
return dt;
|
|
1179
1155
|
}
|
|
1180
1156
|
for (const key in param_dt) {
|
|
1181
|
-
if (!Object.prototype.hasOwnProperty.call(param_dt, key)) continue;
|
|
1182
1157
|
if (!sql_dt[key]) {
|
|
1183
1158
|
dt[key] = param_dt[key];
|
|
1184
1159
|
}
|
|
@@ -1196,8 +1171,6 @@ Sql.prototype.filterParams = function (param_dt, sql_dt) {
|
|
|
1196
1171
|
Sql.prototype._tplQueryWithSep = function (param_dt, sql_dt, l) {
|
|
1197
1172
|
const conds = [];
|
|
1198
1173
|
for (const key in param_dt) {
|
|
1199
|
-
if (!Object.prototype.hasOwnProperty.call(param_dt, key)) continue;
|
|
1200
|
-
|
|
1201
1174
|
let value = String(param_dt[key]);
|
|
1202
1175
|
const arr = value.split(l);
|
|
1203
1176
|
const tpl = sql_dt[key];
|
|
@@ -1206,13 +1179,13 @@ Sql.prototype._tplQueryWithSep = function (param_dt, sql_dt, l) {
|
|
|
1206
1179
|
if (arr.length > 1) {
|
|
1207
1180
|
const sub_conds = [];
|
|
1208
1181
|
for (const val of arr) {
|
|
1209
|
-
|
|
1210
|
-
sub_conds.push(tpl.replaceAll('{0}', escape(
|
|
1182
|
+
// 使用直接替换方式,避免模板替换错误
|
|
1183
|
+
sub_conds.push(tpl.replaceAll('{0}', escape(val).trim("'")));
|
|
1211
1184
|
}
|
|
1212
1185
|
conds.push('(' + sub_conds.join(' || ') + ')');
|
|
1213
1186
|
} else {
|
|
1214
|
-
|
|
1215
|
-
conds.push(tpl.replaceAll('{0}', escape(
|
|
1187
|
+
// 使用直接替换方式,避免模板替换错误
|
|
1188
|
+
conds.push(tpl.replaceAll('{0}', escape(value).trim("'")));
|
|
1216
1189
|
}
|
|
1217
1190
|
} else {
|
|
1218
1191
|
if (arr.length > 1) {
|
|
@@ -1222,8 +1195,7 @@ Sql.prototype._tplQueryWithSep = function (param_dt, sql_dt, l) {
|
|
|
1222
1195
|
}
|
|
1223
1196
|
conds.push('(' + sub_conds.join(' || ') + ')');
|
|
1224
1197
|
} else {
|
|
1225
|
-
|
|
1226
|
-
conds.push(escapeId(key) + ' = ' + escape(clean_val));
|
|
1198
|
+
conds.push(escapeId(key) + ' = ' + escape(value));
|
|
1227
1199
|
}
|
|
1228
1200
|
}
|
|
1229
1201
|
}
|
|
@@ -1239,13 +1211,9 @@ Sql.prototype._tplQueryWithSep = function (param_dt, sql_dt, l) {
|
|
|
1239
1211
|
Sql.prototype._tplQueryNoSep = function (param_dt, sql_dt) {
|
|
1240
1212
|
const conds = [];
|
|
1241
1213
|
for (const key in param_dt) {
|
|
1242
|
-
|
|
1243
|
-
let value = param_dt[key];
|
|
1244
|
-
if (typeof value === 'string') {
|
|
1245
|
-
value = value.trim("'");
|
|
1246
|
-
}
|
|
1247
|
-
value = escape(value);
|
|
1214
|
+
value = escape(param_dt[key]);
|
|
1248
1215
|
if (sql_dt[key]) {
|
|
1216
|
+
// 使用直接替换方式,避免模板替换错误
|
|
1249
1217
|
conds.push(sql_dt[key].replaceAll('{0}', value.trim("'")));
|
|
1250
1218
|
} else {
|
|
1251
1219
|
conds.push(escapeId(key) + ' = ' + value);
|
|
@@ -1263,7 +1231,6 @@ Sql.prototype._tplQueryNoSep = function (param_dt, sql_dt) {
|
|
|
1263
1231
|
Sql.prototype._noTplQueryWithSep = function (param_dt, l) {
|
|
1264
1232
|
const conds = [];
|
|
1265
1233
|
for (const key in param_dt) {
|
|
1266
|
-
if (!Object.prototype.hasOwnProperty.call(param_dt, key)) continue;
|
|
1267
1234
|
const value = param_dt[key];
|
|
1268
1235
|
const arr = String(value).split(l);
|
|
1269
1236
|
if (arr.length > 1) {
|
|
@@ -1287,7 +1254,6 @@ Sql.prototype._noTplQueryWithSep = function (param_dt, l) {
|
|
|
1287
1254
|
Sql.prototype._noTplQueryNoSep = function (param_dt) {
|
|
1288
1255
|
const conds = [];
|
|
1289
1256
|
for (const key in param_dt) {
|
|
1290
|
-
if (!Object.prototype.hasOwnProperty.call(param_dt, key)) continue;
|
|
1291
1257
|
conds.push(escapeId(key) + ' = ' + escape(param_dt[key]));
|
|
1292
1258
|
}
|
|
1293
1259
|
return conds.join(' && ');
|
|
@@ -1334,15 +1300,15 @@ Sql.prototype.tplBody = function (param_dt, sql_dt) {
|
|
|
1334
1300
|
const parts = [];
|
|
1335
1301
|
if (!sql_dt || Object.keys(sql_dt).length === 0) {
|
|
1336
1302
|
for (const key in param_dt) {
|
|
1337
|
-
if (!Object.prototype.hasOwnProperty.call(param_dt, key)) continue;
|
|
1338
1303
|
parts.push(' ' + escapeId(key) + ' = ' + escape(param_dt[key]));
|
|
1339
1304
|
}
|
|
1340
1305
|
} else {
|
|
1341
1306
|
for (const key in param_dt) {
|
|
1342
|
-
|
|
1343
|
-
const value = escape(param_dt[key]);
|
|
1307
|
+
let value = escape(param_dt[key]);
|
|
1344
1308
|
if (sql_dt[key]) {
|
|
1345
|
-
|
|
1309
|
+
// 使用直接替换方式,避免模板替换错误
|
|
1310
|
+
const replaced = sql_dt[key].replaceAll('{0}', value.trim("'"));
|
|
1311
|
+
parts.push(' ' + replaced.replace(/\+ -/g, '- ').replace(/- -/g, '+ '));
|
|
1346
1312
|
} else {
|
|
1347
1313
|
parts.push(' ' + escapeId(key) + ' = ' + value);
|
|
1348
1314
|
}
|
|
@@ -1632,7 +1598,7 @@ Sql.prototype.model = function (model) {
|
|
|
1632
1598
|
});
|
|
1633
1599
|
}
|
|
1634
1600
|
}
|
|
1635
|
-
const new_obj = { ...obj};
|
|
1601
|
+
const new_obj = { ...obj };
|
|
1636
1602
|
new_obj[prop] = value;
|
|
1637
1603
|
return true;
|
|
1638
1604
|
}
|
|
@@ -1671,12 +1637,12 @@ Sql.prototype.addOrSet = async function (where, set, like) {
|
|
|
1671
1637
|
* @returns {Promise<string>} 创建表的 SQL 语句
|
|
1672
1638
|
* @private
|
|
1673
1639
|
*/
|
|
1674
|
-
Sql.prototype._getCreateTableSql = async function() {
|
|
1640
|
+
Sql.prototype._getCreateTableSql = async function () {
|
|
1675
1641
|
try {
|
|
1676
1642
|
// 查询表结构信息(MySQL 使用 SHOW CREATE TABLE)
|
|
1677
1643
|
const sql = `SHOW CREATE TABLE \`${this.table}\``;
|
|
1678
1644
|
const result = await this.run(sql);
|
|
1679
|
-
|
|
1645
|
+
|
|
1680
1646
|
if (result && result.length > 0 && result[0]['Create Table']) {
|
|
1681
1647
|
// 在创建表语句中添加 IF NOT EXISTS 条件
|
|
1682
1648
|
let create_sql = result[0]['Create Table'];
|
|
@@ -1685,7 +1651,7 @@ Sql.prototype._getCreateTableSql = async function() {
|
|
|
1685
1651
|
}
|
|
1686
1652
|
return create_sql + ';';
|
|
1687
1653
|
}
|
|
1688
|
-
|
|
1654
|
+
|
|
1689
1655
|
return '';
|
|
1690
1656
|
} catch (err) {
|
|
1691
1657
|
this.log('warn', '获取表结构失败,将仅备份数据', err);
|
|
@@ -1698,10 +1664,10 @@ Sql.prototype._getCreateTableSql = async function() {
|
|
|
1698
1664
|
* @returns {Promise<string>} 注释信息 SQL 语句
|
|
1699
1665
|
* @private
|
|
1700
1666
|
*/
|
|
1701
|
-
Sql.prototype._getTableComments = async function() {
|
|
1667
|
+
Sql.prototype._getTableComments = async function () {
|
|
1702
1668
|
try {
|
|
1703
1669
|
let commentSql = '';
|
|
1704
|
-
|
|
1670
|
+
|
|
1705
1671
|
// 获取表注释
|
|
1706
1672
|
const tableCommentSql = `
|
|
1707
1673
|
SELECT TABLE_COMMENT
|
|
@@ -1710,14 +1676,14 @@ Sql.prototype._getTableComments = async function() {
|
|
|
1710
1676
|
AND TABLE_NAME = '${this.table}'
|
|
1711
1677
|
`;
|
|
1712
1678
|
const tableCommentResult = await this.run(tableCommentSql);
|
|
1713
|
-
|
|
1679
|
+
|
|
1714
1680
|
if (tableCommentResult && tableCommentResult.length > 0 && tableCommentResult[0].TABLE_COMMENT) {
|
|
1715
1681
|
const tableComment = tableCommentResult[0].TABLE_COMMENT;
|
|
1716
1682
|
if (tableComment && tableComment.trim() !== '') {
|
|
1717
1683
|
commentSql += `\n-- 表注释: ${tableComment}\n`;
|
|
1718
1684
|
}
|
|
1719
1685
|
}
|
|
1720
|
-
|
|
1686
|
+
|
|
1721
1687
|
// 获取列注释
|
|
1722
1688
|
const columnCommentSql = `
|
|
1723
1689
|
SELECT COLUMN_NAME, COLUMN_COMMENT
|
|
@@ -1729,14 +1695,14 @@ Sql.prototype._getTableComments = async function() {
|
|
|
1729
1695
|
ORDER BY ORDINAL_POSITION
|
|
1730
1696
|
`;
|
|
1731
1697
|
const columnCommentResult = await this.run(columnCommentSql);
|
|
1732
|
-
|
|
1698
|
+
|
|
1733
1699
|
if (columnCommentResult && columnCommentResult.length > 0) {
|
|
1734
1700
|
commentSql += '\n-- 列注释:\n';
|
|
1735
1701
|
for (const column of columnCommentResult) {
|
|
1736
1702
|
commentSql += `-- ${column.COLUMN_NAME}: ${column.COLUMN_COMMENT}\n`;
|
|
1737
1703
|
}
|
|
1738
1704
|
}
|
|
1739
|
-
|
|
1705
|
+
|
|
1740
1706
|
return commentSql;
|
|
1741
1707
|
} catch (err) {
|
|
1742
1708
|
this.log('warn', '获取表注释信息失败,将仅备份表结构', err);
|
|
@@ -1750,7 +1716,7 @@ Sql.prototype._getTableComments = async function() {
|
|
|
1750
1716
|
* @param {boolean} create 是否导出创建表语句,默认为false
|
|
1751
1717
|
* @returns {Promise<boolean>} 备份是否成功
|
|
1752
1718
|
*/
|
|
1753
|
-
Sql.prototype.backup = async function(file, create = false) {
|
|
1719
|
+
Sql.prototype.backup = async function (file, create = false) {
|
|
1754
1720
|
if (!file || typeof file !== 'string') {
|
|
1755
1721
|
throw new TypeError('文件路径必须为字符串');
|
|
1756
1722
|
}
|
|
@@ -1762,24 +1728,24 @@ Sql.prototype.backup = async function(file, create = false) {
|
|
|
1762
1728
|
try {
|
|
1763
1729
|
// 获取所有数据
|
|
1764
1730
|
const data = await this.getSql('', '', '*');
|
|
1765
|
-
|
|
1731
|
+
|
|
1766
1732
|
if (!data || !Array.isArray(data)) {
|
|
1767
1733
|
throw new Error('获取数据失败');
|
|
1768
1734
|
}
|
|
1769
1735
|
|
|
1770
1736
|
// 构建 SQL 语句
|
|
1771
1737
|
let sqlContent = '';
|
|
1772
|
-
|
|
1738
|
+
|
|
1773
1739
|
// 添加表结构(简化版,实际应用中可能需要更复杂的表结构导出)
|
|
1774
1740
|
sqlContent += `-- 备份表: ${this.table}\n`;
|
|
1775
1741
|
sqlContent += `-- 备份时间: ${new Date().toISOString()}\n\n`;
|
|
1776
|
-
|
|
1742
|
+
|
|
1777
1743
|
// 如果create为true,添加创建表语句
|
|
1778
1744
|
if (create) {
|
|
1779
1745
|
const createTableSql = await this._getCreateTableSql();
|
|
1780
1746
|
if (createTableSql) {
|
|
1781
1747
|
sqlContent += createTableSql + '\n\n';
|
|
1782
|
-
|
|
1748
|
+
|
|
1783
1749
|
// 添加表注释信息
|
|
1784
1750
|
const commentSql = await this._getTableComments();
|
|
1785
1751
|
if (commentSql) {
|
|
@@ -1787,7 +1753,7 @@ Sql.prototype.backup = async function(file, create = false) {
|
|
|
1787
1753
|
}
|
|
1788
1754
|
}
|
|
1789
1755
|
}
|
|
1790
|
-
|
|
1756
|
+
|
|
1791
1757
|
// 添加数据插入语句
|
|
1792
1758
|
for (const row of data) {
|
|
1793
1759
|
const keys = Object.keys(row).map(key => this.escapeId(key)).join(', ');
|
|
@@ -1798,15 +1764,15 @@ Sql.prototype.backup = async function(file, create = false) {
|
|
|
1798
1764
|
// 写入文件(这里需要文件系统模块,实际使用时需要引入 fs)
|
|
1799
1765
|
const fs = require('fs');
|
|
1800
1766
|
const path = require('path');
|
|
1801
|
-
|
|
1767
|
+
|
|
1802
1768
|
// 确保目录存在
|
|
1803
1769
|
const dir = path.dirname(file);
|
|
1804
1770
|
if (!fs.existsSync(dir)) {
|
|
1805
1771
|
fs.mkdirSync(dir, { recursive: true });
|
|
1806
1772
|
}
|
|
1807
|
-
|
|
1773
|
+
|
|
1808
1774
|
fs.writeFileSync(file, sql_content, 'utf8');
|
|
1809
|
-
|
|
1775
|
+
|
|
1810
1776
|
return true;
|
|
1811
1777
|
} catch (err) {
|
|
1812
1778
|
this.error = err.message;
|
|
@@ -1820,7 +1786,7 @@ Sql.prototype.backup = async function(file, create = false) {
|
|
|
1820
1786
|
* @param {string} file 备份文件路径
|
|
1821
1787
|
* @returns {Promise<boolean>} 恢复是否成功
|
|
1822
1788
|
*/
|
|
1823
|
-
Sql.prototype.restore = async function(file) {
|
|
1789
|
+
Sql.prototype.restore = async function (file) {
|
|
1824
1790
|
if (!file || typeof file !== 'string') {
|
|
1825
1791
|
throw new TypeError('文件路径必须为字符串');
|
|
1826
1792
|
}
|
|
@@ -1831,24 +1797,24 @@ Sql.prototype.restore = async function(file) {
|
|
|
1831
1797
|
|
|
1832
1798
|
try {
|
|
1833
1799
|
const fs = require('fs');
|
|
1834
|
-
|
|
1800
|
+
|
|
1835
1801
|
if (!fs.existsSync(file)) {
|
|
1836
1802
|
throw new Error('备份文件不存在');
|
|
1837
1803
|
}
|
|
1838
1804
|
|
|
1839
1805
|
// 读取 SQL 文件
|
|
1840
1806
|
const sqlContent = fs.readFileSync(file, 'utf8');
|
|
1841
|
-
|
|
1807
|
+
|
|
1842
1808
|
// 解析 SQL 语句
|
|
1843
1809
|
const statements = this._parseSqlStatements(sqlContent);
|
|
1844
|
-
|
|
1810
|
+
|
|
1845
1811
|
// 执行每个 SQL 语句
|
|
1846
1812
|
for (const stmt of statements) {
|
|
1847
1813
|
if (stmt.trim() && !stmt.startsWith('--')) {
|
|
1848
1814
|
await this.exec(stmt);
|
|
1849
1815
|
}
|
|
1850
1816
|
}
|
|
1851
|
-
|
|
1817
|
+
|
|
1852
1818
|
return true;
|
|
1853
1819
|
} catch (err) {
|
|
1854
1820
|
this.error = err.message;
|
|
@@ -1863,7 +1829,7 @@ Sql.prototype.restore = async function(file) {
|
|
|
1863
1829
|
* @returns {string[]} SQL 语句数组
|
|
1864
1830
|
* @private
|
|
1865
1831
|
*/
|
|
1866
|
-
Sql.prototype._parseSqlStatements = function(sql) {
|
|
1832
|
+
Sql.prototype._parseSqlStatements = function (sql) {
|
|
1867
1833
|
const stmts = [];
|
|
1868
1834
|
let cur_stmt = '';
|
|
1869
1835
|
let in_str = false;
|
|
@@ -1891,7 +1857,7 @@ Sql.prototype._parseSqlStatements = function(sql) {
|
|
|
1891
1857
|
i++; // 跳过第二个'-'
|
|
1892
1858
|
continue;
|
|
1893
1859
|
}
|
|
1894
|
-
|
|
1860
|
+
|
|
1895
1861
|
if (in_cmt && char === '\n') {
|
|
1896
1862
|
in_cmt = false;
|
|
1897
1863
|
continue;
|
|
@@ -1941,7 +1907,7 @@ Sql.prototype._parseSqlStatements = function(sql) {
|
|
|
1941
1907
|
* @param {boolean} create 是否导出创建表语句,默认为false
|
|
1942
1908
|
* @returns {Promise<boolean>} 备份是否成功
|
|
1943
1909
|
*/
|
|
1944
|
-
Sql.prototype.backup = async function(file, create = false) {
|
|
1910
|
+
Sql.prototype.backup = async function (file, create = false) {
|
|
1945
1911
|
if (!file || typeof file !== 'string') {
|
|
1946
1912
|
throw new TypeError('文件路径必须为字符串');
|
|
1947
1913
|
}
|
|
@@ -1953,18 +1919,18 @@ Sql.prototype.backup = async function(file, create = false) {
|
|
|
1953
1919
|
try {
|
|
1954
1920
|
// 获取所有数据
|
|
1955
1921
|
const data = await this.getSql('', '', '*');
|
|
1956
|
-
|
|
1922
|
+
|
|
1957
1923
|
if (!data || !Array.isArray(data)) {
|
|
1958
1924
|
throw new Error('获取数据失败');
|
|
1959
1925
|
}
|
|
1960
1926
|
|
|
1961
1927
|
// 构建 SQL 语句
|
|
1962
1928
|
let sql_content = '';
|
|
1963
|
-
|
|
1929
|
+
|
|
1964
1930
|
// 添加表结构(简化版,实际应用中可能需要更复杂的表结构导出)
|
|
1965
1931
|
sql_content += `-- 备份表: ${this.table}\n`;
|
|
1966
1932
|
sql_content += `-- 备份时间: ${new Date().toISOString()}\n\n`;
|
|
1967
|
-
|
|
1933
|
+
|
|
1968
1934
|
// 如果create为true,添加创建表语句
|
|
1969
1935
|
if (create) {
|
|
1970
1936
|
const create_table_sql = await this._getCreateTableSql();
|
|
@@ -1972,7 +1938,7 @@ Sql.prototype.backup = async function(file, create = false) {
|
|
|
1972
1938
|
sql_content += create_table_sql + '\n\n';
|
|
1973
1939
|
}
|
|
1974
1940
|
}
|
|
1975
|
-
|
|
1941
|
+
|
|
1976
1942
|
// 添加数据插入语句
|
|
1977
1943
|
for (const row of data) {
|
|
1978
1944
|
const keys = Object.keys(row).map(key => this.escapeId(key)).join(', ');
|
|
@@ -1983,15 +1949,15 @@ Sql.prototype.backup = async function(file, create = false) {
|
|
|
1983
1949
|
// 写入文件(这里需要文件系统模块,实际使用时需要引入 fs)
|
|
1984
1950
|
const fs = require('fs');
|
|
1985
1951
|
const path = require('path');
|
|
1986
|
-
|
|
1952
|
+
|
|
1987
1953
|
// 确保目录存在
|
|
1988
1954
|
const dir = path.dirname(file);
|
|
1989
1955
|
if (!fs.existsSync(dir)) {
|
|
1990
1956
|
fs.mkdirSync(dir, { recursive: true });
|
|
1991
1957
|
}
|
|
1992
|
-
|
|
1958
|
+
|
|
1993
1959
|
fs.writeFileSync(file, sql_content, 'utf8');
|
|
1994
|
-
|
|
1960
|
+
|
|
1995
1961
|
return true;
|
|
1996
1962
|
} catch (err) {
|
|
1997
1963
|
this.error = err.message;
|
|
@@ -2005,7 +1971,7 @@ Sql.prototype.backup = async function(file, create = false) {
|
|
|
2005
1971
|
* @param {string} file 恢复文件路径
|
|
2006
1972
|
* @returns {Promise<boolean>} 恢复是否成功
|
|
2007
1973
|
*/
|
|
2008
|
-
Sql.prototype.restore = async function(file) {
|
|
1974
|
+
Sql.prototype.restore = async function (file) {
|
|
2009
1975
|
if (!file || typeof file !== 'string') {
|
|
2010
1976
|
throw new TypeError('文件路径必须为字符串');
|
|
2011
1977
|
}
|
|
@@ -2016,7 +1982,7 @@ Sql.prototype.restore = async function(file) {
|
|
|
2016
1982
|
|
|
2017
1983
|
try {
|
|
2018
1984
|
const fs = require('fs');
|
|
2019
|
-
|
|
1985
|
+
|
|
2020
1986
|
// 检查文件是否存在
|
|
2021
1987
|
if (!fs.existsSync(file)) {
|
|
2022
1988
|
throw new Error('备份文件不存在');
|
|
@@ -2024,14 +1990,14 @@ Sql.prototype.restore = async function(file) {
|
|
|
2024
1990
|
|
|
2025
1991
|
// 读取文件内容
|
|
2026
1992
|
const sql_content = fs.readFileSync(file, 'utf8');
|
|
2027
|
-
|
|
1993
|
+
|
|
2028
1994
|
if (!sql_content) {
|
|
2029
1995
|
throw new Error('备份文件为空');
|
|
2030
1996
|
}
|
|
2031
1997
|
|
|
2032
1998
|
// 解析 SQL 语句
|
|
2033
1999
|
const sql_stmts = this._parseSqlStatements(sql_content);
|
|
2034
|
-
|
|
2000
|
+
|
|
2035
2001
|
if (!sql_stmts || sql_stmts.length === 0) {
|
|
2036
2002
|
throw new Error('未找到有效的 SQL 语句');
|
|
2037
2003
|
}
|
|
@@ -2081,12 +2047,12 @@ Sql.prototype.restore = async function(file) {
|
|
|
2081
2047
|
* @returns {Promise<string>} 创建表的 SQL 语句
|
|
2082
2048
|
* @private
|
|
2083
2049
|
*/
|
|
2084
|
-
Sql.prototype._getCreateTableSql = async function() {
|
|
2050
|
+
Sql.prototype._getCreateTableSql = async function () {
|
|
2085
2051
|
try {
|
|
2086
2052
|
// 查询表结构信息(MySQL 使用 SHOW CREATE TABLE)
|
|
2087
2053
|
const sql = `SHOW CREATE TABLE \`${this.table}\``;
|
|
2088
2054
|
const result = await this.run(sql);
|
|
2089
|
-
|
|
2055
|
+
|
|
2090
2056
|
if (result && result.length > 0 && result[0]['Create Table']) {
|
|
2091
2057
|
// 在创建表语句中添加 IF NOT EXISTS 条件
|
|
2092
2058
|
let create_sql = result[0]['Create Table'];
|
|
@@ -2095,7 +2061,7 @@ Sql.prototype._getCreateTableSql = async function() {
|
|
|
2095
2061
|
}
|
|
2096
2062
|
return create_sql + ';';
|
|
2097
2063
|
}
|
|
2098
|
-
|
|
2064
|
+
|
|
2099
2065
|
return '';
|
|
2100
2066
|
} catch (err) {
|
|
2101
2067
|
this.log('warn', '获取表结构失败,将仅备份数据', err);
|
package/test.js
CHANGED
|
@@ -17,13 +17,15 @@ class TestMysql {
|
|
|
17
17
|
* @param {object} config - 数据库配置
|
|
18
18
|
*/
|
|
19
19
|
constructor(config) {
|
|
20
|
-
this.config = {
|
|
20
|
+
this.config = {
|
|
21
|
+
host: '127.0.0.1',
|
|
21
22
|
port: 3306,
|
|
22
23
|
user: 'root',
|
|
23
24
|
password: 'Asd159357',
|
|
24
25
|
database: '', // 先不指定数据库,连接成功后再创建
|
|
25
26
|
debug: true,
|
|
26
|
-
|
|
27
|
+
...config || {}
|
|
28
|
+
};
|
|
27
29
|
|
|
28
30
|
this._mysql = null;
|
|
29
31
|
this._test_results = [];
|
|
@@ -474,6 +476,81 @@ TestMysql.prototype.testTrans = async function() {
|
|
|
474
476
|
}
|
|
475
477
|
};
|
|
476
478
|
|
|
479
|
+
/**
|
|
480
|
+
* 真实事务性能测试
|
|
481
|
+
* @returns {Promise<boolean>} 性能测试是否成功
|
|
482
|
+
*/
|
|
483
|
+
TestMysql.prototype.testRealTransaction = async function() {
|
|
484
|
+
try {
|
|
485
|
+
const start_time = Date.now();
|
|
486
|
+
|
|
487
|
+
// 真实的事务操作测试
|
|
488
|
+
const result = await this._mysql.transaction(async (tx) => {
|
|
489
|
+
// 插入测试数据
|
|
490
|
+
const insert_sql = `INSERT INTO ${this._test_table_name} (name, email, age) VALUES (?, ?, ?)`;
|
|
491
|
+
await tx.connection.execute(insert_sql, ['事务测试用户', 'trans_test@example.com', 25]);
|
|
492
|
+
|
|
493
|
+
// 查询验证
|
|
494
|
+
const query_sql = `SELECT * FROM ${this._test_table_name} WHERE name = ?`;
|
|
495
|
+
const rows = await tx.connection.execute(query_sql, ['事务测试用户']);
|
|
496
|
+
|
|
497
|
+
// 删除测试数据
|
|
498
|
+
const delete_sql = `DELETE FROM ${this._test_table_name} WHERE name = ?`;
|
|
499
|
+
await tx.connection.execute(delete_sql, ['事务测试用户']);
|
|
500
|
+
|
|
501
|
+
return { count: rows.length };
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
const duration = Date.now() - start_time;
|
|
505
|
+
this._recordResult('真实事务测试', true, `事务执行耗时: ${duration}ms`);
|
|
506
|
+
return true;
|
|
507
|
+
} catch (error) {
|
|
508
|
+
this._recordResult('真实事务测试', false, error.message);
|
|
509
|
+
return false;
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* 批量事务性能测试
|
|
515
|
+
* @returns {Promise<boolean>} 批量测试是否成功
|
|
516
|
+
*/
|
|
517
|
+
TestMysql.prototype.testBatchTransaction = async function() {
|
|
518
|
+
try {
|
|
519
|
+
const start_time = Date.now();
|
|
520
|
+
|
|
521
|
+
// 批量插入测试
|
|
522
|
+
const result = await this._mysql.transaction(async (tx) => {
|
|
523
|
+
const insert_sql = `INSERT INTO ${this._test_table_name} (name, email, age) VALUES (?, ?, ?)`;
|
|
524
|
+
|
|
525
|
+
// 批量插入5条记录
|
|
526
|
+
for (let i = 0; i < 5; i++) {
|
|
527
|
+
await tx.connection.execute(insert_sql, [
|
|
528
|
+
`批量用户${i}`,
|
|
529
|
+
`batch${i}@example.com`,
|
|
530
|
+
20 + i
|
|
531
|
+
]);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// 验证插入结果
|
|
535
|
+
const count_sql = `SELECT COUNT(*) as count FROM ${this._test_table_name} WHERE name LIKE '批量用户%'`;
|
|
536
|
+
const count_result = await tx.connection.execute(count_sql);
|
|
537
|
+
|
|
538
|
+
// 清理测试数据
|
|
539
|
+
const delete_sql = `DELETE FROM ${this._test_table_name} WHERE name LIKE '批量用户%'`;
|
|
540
|
+
await tx.connection.execute(delete_sql);
|
|
541
|
+
|
|
542
|
+
return { inserted_count: count_result[0].count };
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
const duration = Date.now() - start_time;
|
|
546
|
+
this._recordResult('批量事务测试', true, `批量事务执行耗时: ${duration}ms`);
|
|
547
|
+
return true;
|
|
548
|
+
} catch (error) {
|
|
549
|
+
this._recordResult('批量事务测试', false, error.message);
|
|
550
|
+
return false;
|
|
551
|
+
}
|
|
552
|
+
};
|
|
553
|
+
|
|
477
554
|
/**
|
|
478
555
|
* 测试连接池功能
|
|
479
556
|
* @returns {Promise<boolean>} 连接池测试是否成功
|
|
@@ -576,6 +653,8 @@ TestMysql.prototype.runAllTests = async function() {
|
|
|
576
653
|
{ name: '更新测试', method: 'testUpdate' },
|
|
577
654
|
{ name: '删除测试', method: 'testDelete' },
|
|
578
655
|
{ name: '事务测试', method: 'testTrans' },
|
|
656
|
+
{ name: '真实事务测试', method: 'testRealTransaction' },
|
|
657
|
+
{ name: '批量事务测试', method: 'testBatchTransaction' },
|
|
579
658
|
{ name: '连接池测试', method: 'testConnPool' },
|
|
580
659
|
{ name: '错误处理测试', method: 'testErrorHandling' }
|
|
581
660
|
];
|
|
@@ -691,7 +770,7 @@ if (require.main === module) {
|
|
|
691
770
|
debug: process.env.DEBUG === 'true',
|
|
692
771
|
connection_limit: parseInt(process.env.DB_CONNECTION_LIMIT) || 5
|
|
693
772
|
};
|
|
694
|
-
|
|
773
|
+
|
|
695
774
|
console.log('使用配置:', {
|
|
696
775
|
...test_config,
|
|
697
776
|
password: '***' // 隐藏密码
|