mm_mysql 2.3.2 → 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 +1 -1
- package/sql.js +109 -145
- package/test.js +82 -3
- package/final_test.js +0 -69
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
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
|
}
|
|
@@ -192,10 +191,10 @@ Sql.prototype.toQuery = function (where, sort, view) {
|
|
|
192
191
|
if (!view) {
|
|
193
192
|
view = '*';
|
|
194
193
|
}
|
|
195
|
-
|
|
194
|
+
|
|
196
195
|
// 使用直接拼接的方式,避免模板替换错误
|
|
197
196
|
var sql = `SELECT ${view} FROM \`${this.table}\``;
|
|
198
|
-
|
|
197
|
+
|
|
199
198
|
if (where) {
|
|
200
199
|
sql += ' WHERE ' + where;
|
|
201
200
|
}
|
|
@@ -241,6 +240,7 @@ Sql.prototype.setSql = function (where, set) {
|
|
|
241
240
|
var sql = `UPDATE \`${this.table}\` SET ${set} WHERE ${where};`;
|
|
242
241
|
return this.exec(sql);
|
|
243
242
|
};
|
|
243
|
+
|
|
244
244
|
/**
|
|
245
245
|
* 查询数据
|
|
246
246
|
* @param {string} where 查询条件
|
|
@@ -297,7 +297,7 @@ Sql.prototype.getCountSql = async function (where, sort, view) {
|
|
|
297
297
|
* @param {string} groupby 分组的字段
|
|
298
298
|
* @param {string} view 返回的字段
|
|
299
299
|
* @param {string} sort 排序方式
|
|
300
|
-
* @param method
|
|
300
|
+
* @param {string} method 统计方法
|
|
301
301
|
* @returns {Promise | object} 查询到的内容列表和符合条件总数
|
|
302
302
|
*/
|
|
303
303
|
Sql.prototype.groupMathSql = async function (where, groupby, view, sort, method) {
|
|
@@ -312,7 +312,7 @@ Sql.prototype.groupMathSql = async function (where, groupby, view, sort, method)
|
|
|
312
312
|
}
|
|
313
313
|
} else {
|
|
314
314
|
view_str = ',' + method.toUpperCase() + '(' + escapeId(view_val) + ') ' + method.toLowerCase() + '_' +
|
|
315
|
-
|
|
315
|
+
view_val.replace(/`/g, '');
|
|
316
316
|
}
|
|
317
317
|
var sql = 'SELECT ' + (groupby ? escapeId(groupby) : '') + view_str + ' FROM `' + this.table + '`';
|
|
318
318
|
if (where) {
|
|
@@ -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')) {
|
|
@@ -479,7 +479,7 @@ Sql.prototype._buildExactWhere = function (obj) {
|
|
|
479
479
|
where += ' and ' + escapeId(k.replace('_not', '')) + ' != ' + val;
|
|
480
480
|
} else if (k.endsWith('_has')) {
|
|
481
481
|
var vals = val.trim("'").split(',').map((o) => {
|
|
482
|
-
return
|
|
482
|
+
return escape(o);
|
|
483
483
|
});
|
|
484
484
|
where += ' and ' + escapeId(k.replace('_has', '')) + ' in (' + vals.join(',') + ')';
|
|
485
485
|
} else if (k.endsWith('_like')) {
|
|
@@ -499,7 +499,6 @@ Sql.prototype._buildExactWhere = function (obj) {
|
|
|
499
499
|
Sql.prototype.toSet = function (obj) {
|
|
500
500
|
var set = '';
|
|
501
501
|
for (const k in obj) {
|
|
502
|
-
if (!Object.prototype.hasOwnProperty.call(obj, k)) continue;
|
|
503
502
|
|
|
504
503
|
let val = obj[k];
|
|
505
504
|
if (val === undefined || val === null) continue;
|
|
@@ -535,14 +534,8 @@ Sql.prototype.toAddSql = function (item) {
|
|
|
535
534
|
let value = '';
|
|
536
535
|
|
|
537
536
|
for (const k in item) {
|
|
538
|
-
if (!Object.prototype.hasOwnProperty.call(item, k)) continue;
|
|
539
|
-
|
|
540
537
|
key += ',' + escapeId(k);
|
|
541
|
-
|
|
542
|
-
if (typeof val === 'string') {
|
|
543
|
-
val = val.trim("'");
|
|
544
|
-
}
|
|
545
|
-
value += ',' + escape(val);
|
|
538
|
+
value += ',' + escape(item[k]);
|
|
546
539
|
}
|
|
547
540
|
|
|
548
541
|
// 使用直接拼接的方式,避免模板替换错误
|
|
@@ -562,10 +555,10 @@ Sql.prototype.toDelSql = function (query, like) {
|
|
|
562
555
|
throw new Error('表名未设置');
|
|
563
556
|
}
|
|
564
557
|
const where = this.toWhere(query, like);
|
|
565
|
-
|
|
558
|
+
|
|
566
559
|
// 使用直接拼接的方式,避免模板替换错误
|
|
567
560
|
const sql = `DELETE FROM \`${this.table}\` WHERE ${where};`;
|
|
568
|
-
|
|
561
|
+
|
|
569
562
|
return sql;
|
|
570
563
|
};
|
|
571
564
|
|
|
@@ -582,10 +575,10 @@ Sql.prototype.toSetSql = function (query, item, like) {
|
|
|
582
575
|
}
|
|
583
576
|
const where = this.toWhere(query, like);
|
|
584
577
|
const set = this.toSet(item);
|
|
585
|
-
|
|
578
|
+
|
|
586
579
|
// 使用直接拼接的方式,避免模板替换错误
|
|
587
580
|
const sql = `UPDATE \`${this.table}\` SET ${set} WHERE ${where};`;
|
|
588
|
-
|
|
581
|
+
|
|
589
582
|
return sql;
|
|
590
583
|
};
|
|
591
584
|
|
|
@@ -730,13 +723,8 @@ Sql.prototype._parseSetData = async function (set) {
|
|
|
730
723
|
}
|
|
731
724
|
} else {
|
|
732
725
|
for (const k in set) {
|
|
733
|
-
if (!Object.prototype.hasOwnProperty.call(set, k)) continue;
|
|
734
|
-
|
|
735
726
|
key_str += ',' + escapeId(k);
|
|
736
727
|
let val = set[k];
|
|
737
|
-
if (typeof val === 'string') {
|
|
738
|
-
val = val.trim("'");
|
|
739
|
-
}
|
|
740
728
|
val_str += ',' + escape(val);
|
|
741
729
|
}
|
|
742
730
|
}
|
|
@@ -753,10 +741,10 @@ Sql.prototype._parseSetData = async function (set) {
|
|
|
753
741
|
* @param {string} sort 排序
|
|
754
742
|
* @param {string} view 返回的字段
|
|
755
743
|
* @param {boolean} like 是否使用like匹配, 为空使用默认方式
|
|
756
|
-
* @param {number} timeout 超时时间(毫秒),默认
|
|
744
|
+
* @param {number} timeout 超时时间(毫秒),默认20000ms
|
|
757
745
|
* @returns {Promise|Array} 查询结果
|
|
758
746
|
*/
|
|
759
|
-
Sql.prototype.get = async function (query, sort, view, like, timeout =
|
|
747
|
+
Sql.prototype.get = async function (query, sort, view, like, timeout = 20000) {
|
|
760
748
|
if (!this.table) {
|
|
761
749
|
throw new Error('表名未设置');
|
|
762
750
|
}
|
|
@@ -788,10 +776,10 @@ Sql.prototype.get = async function (query, sort, view, like, timeout = 60000) {
|
|
|
788
776
|
* @param {string} sort 排序
|
|
789
777
|
* @param {string} view 返回的字段
|
|
790
778
|
* @param {boolean} like 是否使用like匹配, 为空使用默认方式
|
|
791
|
-
* @param {number} timeout 超时时间(毫秒),默认
|
|
779
|
+
* @param {number} timeout 超时时间(毫秒),默认20000ms
|
|
792
780
|
* @returns {Promise | object | null} 查询结果
|
|
793
781
|
*/
|
|
794
|
-
Sql.prototype.getObj = async function (query, sort, view, like, timeout =
|
|
782
|
+
Sql.prototype.getObj = async function (query, sort, view, like, timeout = 20000) {
|
|
795
783
|
try {
|
|
796
784
|
// 保存当前分页设置
|
|
797
785
|
const old_page = this.page;
|
|
@@ -828,10 +816,10 @@ Sql.prototype.getObj = async function (query, sort, view, like, timeout = 60000)
|
|
|
828
816
|
* 统计记录数
|
|
829
817
|
* @param {object} query 查询条件
|
|
830
818
|
* @param {boolean} like 是否使用like匹配, 为空使用默认方式
|
|
831
|
-
* @param {number} timeout 超时时间(毫秒),默认
|
|
819
|
+
* @param {number} timeout 超时时间(毫秒),默认20000ms
|
|
832
820
|
* @returns {Promise | number} 记录数
|
|
833
821
|
*/
|
|
834
|
-
Sql.prototype.count = async function (query, like, timeout =
|
|
822
|
+
Sql.prototype.count = async function (query, like, timeout = 20000) {
|
|
835
823
|
if (!this.table) {
|
|
836
824
|
throw new Error('表名未设置');
|
|
837
825
|
}
|
|
@@ -959,9 +947,7 @@ Sql.prototype.toBatchAddSql = function (list) {
|
|
|
959
947
|
Sql.prototype._getKeys = function (obj) {
|
|
960
948
|
const keys = [];
|
|
961
949
|
for (const k in obj) {
|
|
962
|
-
|
|
963
|
-
keys.push(this.escapeId(k));
|
|
964
|
-
}
|
|
950
|
+
keys.push(this.escapeId(k));
|
|
965
951
|
}
|
|
966
952
|
return keys;
|
|
967
953
|
};
|
|
@@ -973,9 +959,6 @@ Sql.prototype._buildValueList = function (list, keys) {
|
|
|
973
959
|
for (const k of keys) {
|
|
974
960
|
const key = k.replace(/`/g, '');
|
|
975
961
|
let val = item[key];
|
|
976
|
-
if (typeof val === 'string') {
|
|
977
|
-
val = val.trim("'");
|
|
978
|
-
}
|
|
979
962
|
values.push(this.escape(val));
|
|
980
963
|
}
|
|
981
964
|
val_list.push(`(${values.join(',')})`);
|
|
@@ -1133,7 +1116,6 @@ Sql.prototype.has = function (param_dt, sql_dt) {
|
|
|
1133
1116
|
return false;
|
|
1134
1117
|
}
|
|
1135
1118
|
for (const key in sql_dt) {
|
|
1136
|
-
if (!Object.prototype.hasOwnProperty.call(sql_dt, key)) continue;
|
|
1137
1119
|
const value = param_dt[key];
|
|
1138
1120
|
if (value !== undefined && value !== null && value !== '') {
|
|
1139
1121
|
return true;
|
|
@@ -1153,7 +1135,6 @@ Sql.prototype.not = function (param_dt, sql_dt) {
|
|
|
1153
1135
|
return undefined;
|
|
1154
1136
|
}
|
|
1155
1137
|
for (const key in param_dt) {
|
|
1156
|
-
if (!Object.prototype.hasOwnProperty.call(param_dt, key)) continue;
|
|
1157
1138
|
if (!sql_dt[key]) {
|
|
1158
1139
|
return key;
|
|
1159
1140
|
}
|
|
@@ -1173,7 +1154,6 @@ Sql.prototype.filterParams = function (param_dt, sql_dt) {
|
|
|
1173
1154
|
return dt;
|
|
1174
1155
|
}
|
|
1175
1156
|
for (const key in param_dt) {
|
|
1176
|
-
if (!Object.prototype.hasOwnProperty.call(param_dt, key)) continue;
|
|
1177
1157
|
if (!sql_dt[key]) {
|
|
1178
1158
|
dt[key] = param_dt[key];
|
|
1179
1159
|
}
|
|
@@ -1191,8 +1171,6 @@ Sql.prototype.filterParams = function (param_dt, sql_dt) {
|
|
|
1191
1171
|
Sql.prototype._tplQueryWithSep = function (param_dt, sql_dt, l) {
|
|
1192
1172
|
const conds = [];
|
|
1193
1173
|
for (const key in param_dt) {
|
|
1194
|
-
if (!Object.prototype.hasOwnProperty.call(param_dt, key)) continue;
|
|
1195
|
-
|
|
1196
1174
|
let value = String(param_dt[key]);
|
|
1197
1175
|
const arr = value.split(l);
|
|
1198
1176
|
const tpl = sql_dt[key];
|
|
@@ -1201,17 +1179,13 @@ Sql.prototype._tplQueryWithSep = function (param_dt, sql_dt, l) {
|
|
|
1201
1179
|
if (arr.length > 1) {
|
|
1202
1180
|
const sub_conds = [];
|
|
1203
1181
|
for (const val of arr) {
|
|
1204
|
-
const clean_val = typeof val === 'string' ? val.trim("'") : val;
|
|
1205
1182
|
// 使用直接替换方式,避免模板替换错误
|
|
1206
|
-
|
|
1207
|
-
sub_conds.push(tpl.replaceAll('{0}', escaped_val));
|
|
1183
|
+
sub_conds.push(tpl.replaceAll('{0}', escape(val).trim("'")));
|
|
1208
1184
|
}
|
|
1209
1185
|
conds.push('(' + sub_conds.join(' || ') + ')');
|
|
1210
1186
|
} else {
|
|
1211
|
-
const clean_val = typeof value === 'string' ? value.trim("'") : value;
|
|
1212
1187
|
// 使用直接替换方式,避免模板替换错误
|
|
1213
|
-
|
|
1214
|
-
conds.push(tpl.replaceAll('{0}', escaped_val));
|
|
1188
|
+
conds.push(tpl.replaceAll('{0}', escape(value).trim("'")));
|
|
1215
1189
|
}
|
|
1216
1190
|
} else {
|
|
1217
1191
|
if (arr.length > 1) {
|
|
@@ -1221,8 +1195,7 @@ Sql.prototype._tplQueryWithSep = function (param_dt, sql_dt, l) {
|
|
|
1221
1195
|
}
|
|
1222
1196
|
conds.push('(' + sub_conds.join(' || ') + ')');
|
|
1223
1197
|
} else {
|
|
1224
|
-
|
|
1225
|
-
conds.push(escapeId(key) + ' = ' + escape(clean_val));
|
|
1198
|
+
conds.push(escapeId(key) + ' = ' + escape(value));
|
|
1226
1199
|
}
|
|
1227
1200
|
}
|
|
1228
1201
|
}
|
|
@@ -1238,15 +1211,10 @@ Sql.prototype._tplQueryWithSep = function (param_dt, sql_dt, l) {
|
|
|
1238
1211
|
Sql.prototype._tplQueryNoSep = function (param_dt, sql_dt) {
|
|
1239
1212
|
const conds = [];
|
|
1240
1213
|
for (const key in param_dt) {
|
|
1241
|
-
|
|
1242
|
-
let value = param_dt[key];
|
|
1243
|
-
if (typeof value === 'string') {
|
|
1244
|
-
value = value.trim("'");
|
|
1245
|
-
}
|
|
1246
|
-
value = escape(value);
|
|
1214
|
+
value = escape(param_dt[key]);
|
|
1247
1215
|
if (sql_dt[key]) {
|
|
1248
1216
|
// 使用直接替换方式,避免模板替换错误
|
|
1249
|
-
conds.push(sql_dt[key].replaceAll('{0}', value));
|
|
1217
|
+
conds.push(sql_dt[key].replaceAll('{0}', value.trim("'")));
|
|
1250
1218
|
} else {
|
|
1251
1219
|
conds.push(escapeId(key) + ' = ' + value);
|
|
1252
1220
|
}
|
|
@@ -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,16 +1300,14 @@ 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
|
// 使用直接替换方式,避免模板替换错误
|
|
1346
|
-
const replaced = sql_dt[key].
|
|
1310
|
+
const replaced = sql_dt[key].replaceAll('{0}', value.trim("'"));
|
|
1347
1311
|
parts.push(' ' + replaced.replace(/\+ -/g, '- ').replace(/- -/g, '+ '));
|
|
1348
1312
|
} else {
|
|
1349
1313
|
parts.push(' ' + escapeId(key) + ' = ' + value);
|
|
@@ -1634,7 +1598,7 @@ Sql.prototype.model = function (model) {
|
|
|
1634
1598
|
});
|
|
1635
1599
|
}
|
|
1636
1600
|
}
|
|
1637
|
-
const new_obj = { ...obj};
|
|
1601
|
+
const new_obj = { ...obj };
|
|
1638
1602
|
new_obj[prop] = value;
|
|
1639
1603
|
return true;
|
|
1640
1604
|
}
|
|
@@ -1673,12 +1637,12 @@ Sql.prototype.addOrSet = async function (where, set, like) {
|
|
|
1673
1637
|
* @returns {Promise<string>} 创建表的 SQL 语句
|
|
1674
1638
|
* @private
|
|
1675
1639
|
*/
|
|
1676
|
-
Sql.prototype._getCreateTableSql = async function() {
|
|
1640
|
+
Sql.prototype._getCreateTableSql = async function () {
|
|
1677
1641
|
try {
|
|
1678
1642
|
// 查询表结构信息(MySQL 使用 SHOW CREATE TABLE)
|
|
1679
1643
|
const sql = `SHOW CREATE TABLE \`${this.table}\``;
|
|
1680
1644
|
const result = await this.run(sql);
|
|
1681
|
-
|
|
1645
|
+
|
|
1682
1646
|
if (result && result.length > 0 && result[0]['Create Table']) {
|
|
1683
1647
|
// 在创建表语句中添加 IF NOT EXISTS 条件
|
|
1684
1648
|
let create_sql = result[0]['Create Table'];
|
|
@@ -1687,7 +1651,7 @@ Sql.prototype._getCreateTableSql = async function() {
|
|
|
1687
1651
|
}
|
|
1688
1652
|
return create_sql + ';';
|
|
1689
1653
|
}
|
|
1690
|
-
|
|
1654
|
+
|
|
1691
1655
|
return '';
|
|
1692
1656
|
} catch (err) {
|
|
1693
1657
|
this.log('warn', '获取表结构失败,将仅备份数据', err);
|
|
@@ -1700,10 +1664,10 @@ Sql.prototype._getCreateTableSql = async function() {
|
|
|
1700
1664
|
* @returns {Promise<string>} 注释信息 SQL 语句
|
|
1701
1665
|
* @private
|
|
1702
1666
|
*/
|
|
1703
|
-
Sql.prototype._getTableComments = async function() {
|
|
1667
|
+
Sql.prototype._getTableComments = async function () {
|
|
1704
1668
|
try {
|
|
1705
1669
|
let commentSql = '';
|
|
1706
|
-
|
|
1670
|
+
|
|
1707
1671
|
// 获取表注释
|
|
1708
1672
|
const tableCommentSql = `
|
|
1709
1673
|
SELECT TABLE_COMMENT
|
|
@@ -1712,14 +1676,14 @@ Sql.prototype._getTableComments = async function() {
|
|
|
1712
1676
|
AND TABLE_NAME = '${this.table}'
|
|
1713
1677
|
`;
|
|
1714
1678
|
const tableCommentResult = await this.run(tableCommentSql);
|
|
1715
|
-
|
|
1679
|
+
|
|
1716
1680
|
if (tableCommentResult && tableCommentResult.length > 0 && tableCommentResult[0].TABLE_COMMENT) {
|
|
1717
1681
|
const tableComment = tableCommentResult[0].TABLE_COMMENT;
|
|
1718
1682
|
if (tableComment && tableComment.trim() !== '') {
|
|
1719
1683
|
commentSql += `\n-- 表注释: ${tableComment}\n`;
|
|
1720
1684
|
}
|
|
1721
1685
|
}
|
|
1722
|
-
|
|
1686
|
+
|
|
1723
1687
|
// 获取列注释
|
|
1724
1688
|
const columnCommentSql = `
|
|
1725
1689
|
SELECT COLUMN_NAME, COLUMN_COMMENT
|
|
@@ -1731,14 +1695,14 @@ Sql.prototype._getTableComments = async function() {
|
|
|
1731
1695
|
ORDER BY ORDINAL_POSITION
|
|
1732
1696
|
`;
|
|
1733
1697
|
const columnCommentResult = await this.run(columnCommentSql);
|
|
1734
|
-
|
|
1698
|
+
|
|
1735
1699
|
if (columnCommentResult && columnCommentResult.length > 0) {
|
|
1736
1700
|
commentSql += '\n-- 列注释:\n';
|
|
1737
1701
|
for (const column of columnCommentResult) {
|
|
1738
1702
|
commentSql += `-- ${column.COLUMN_NAME}: ${column.COLUMN_COMMENT}\n`;
|
|
1739
1703
|
}
|
|
1740
1704
|
}
|
|
1741
|
-
|
|
1705
|
+
|
|
1742
1706
|
return commentSql;
|
|
1743
1707
|
} catch (err) {
|
|
1744
1708
|
this.log('warn', '获取表注释信息失败,将仅备份表结构', err);
|
|
@@ -1752,7 +1716,7 @@ Sql.prototype._getTableComments = async function() {
|
|
|
1752
1716
|
* @param {boolean} create 是否导出创建表语句,默认为false
|
|
1753
1717
|
* @returns {Promise<boolean>} 备份是否成功
|
|
1754
1718
|
*/
|
|
1755
|
-
Sql.prototype.backup = async function(file, create = false) {
|
|
1719
|
+
Sql.prototype.backup = async function (file, create = false) {
|
|
1756
1720
|
if (!file || typeof file !== 'string') {
|
|
1757
1721
|
throw new TypeError('文件路径必须为字符串');
|
|
1758
1722
|
}
|
|
@@ -1764,24 +1728,24 @@ Sql.prototype.backup = async function(file, create = false) {
|
|
|
1764
1728
|
try {
|
|
1765
1729
|
// 获取所有数据
|
|
1766
1730
|
const data = await this.getSql('', '', '*');
|
|
1767
|
-
|
|
1731
|
+
|
|
1768
1732
|
if (!data || !Array.isArray(data)) {
|
|
1769
1733
|
throw new Error('获取数据失败');
|
|
1770
1734
|
}
|
|
1771
1735
|
|
|
1772
1736
|
// 构建 SQL 语句
|
|
1773
1737
|
let sqlContent = '';
|
|
1774
|
-
|
|
1738
|
+
|
|
1775
1739
|
// 添加表结构(简化版,实际应用中可能需要更复杂的表结构导出)
|
|
1776
1740
|
sqlContent += `-- 备份表: ${this.table}\n`;
|
|
1777
1741
|
sqlContent += `-- 备份时间: ${new Date().toISOString()}\n\n`;
|
|
1778
|
-
|
|
1742
|
+
|
|
1779
1743
|
// 如果create为true,添加创建表语句
|
|
1780
1744
|
if (create) {
|
|
1781
1745
|
const createTableSql = await this._getCreateTableSql();
|
|
1782
1746
|
if (createTableSql) {
|
|
1783
1747
|
sqlContent += createTableSql + '\n\n';
|
|
1784
|
-
|
|
1748
|
+
|
|
1785
1749
|
// 添加表注释信息
|
|
1786
1750
|
const commentSql = await this._getTableComments();
|
|
1787
1751
|
if (commentSql) {
|
|
@@ -1789,7 +1753,7 @@ Sql.prototype.backup = async function(file, create = false) {
|
|
|
1789
1753
|
}
|
|
1790
1754
|
}
|
|
1791
1755
|
}
|
|
1792
|
-
|
|
1756
|
+
|
|
1793
1757
|
// 添加数据插入语句
|
|
1794
1758
|
for (const row of data) {
|
|
1795
1759
|
const keys = Object.keys(row).map(key => this.escapeId(key)).join(', ');
|
|
@@ -1800,15 +1764,15 @@ Sql.prototype.backup = async function(file, create = false) {
|
|
|
1800
1764
|
// 写入文件(这里需要文件系统模块,实际使用时需要引入 fs)
|
|
1801
1765
|
const fs = require('fs');
|
|
1802
1766
|
const path = require('path');
|
|
1803
|
-
|
|
1767
|
+
|
|
1804
1768
|
// 确保目录存在
|
|
1805
1769
|
const dir = path.dirname(file);
|
|
1806
1770
|
if (!fs.existsSync(dir)) {
|
|
1807
1771
|
fs.mkdirSync(dir, { recursive: true });
|
|
1808
1772
|
}
|
|
1809
|
-
|
|
1773
|
+
|
|
1810
1774
|
fs.writeFileSync(file, sql_content, 'utf8');
|
|
1811
|
-
|
|
1775
|
+
|
|
1812
1776
|
return true;
|
|
1813
1777
|
} catch (err) {
|
|
1814
1778
|
this.error = err.message;
|
|
@@ -1822,7 +1786,7 @@ Sql.prototype.backup = async function(file, create = false) {
|
|
|
1822
1786
|
* @param {string} file 备份文件路径
|
|
1823
1787
|
* @returns {Promise<boolean>} 恢复是否成功
|
|
1824
1788
|
*/
|
|
1825
|
-
Sql.prototype.restore = async function(file) {
|
|
1789
|
+
Sql.prototype.restore = async function (file) {
|
|
1826
1790
|
if (!file || typeof file !== 'string') {
|
|
1827
1791
|
throw new TypeError('文件路径必须为字符串');
|
|
1828
1792
|
}
|
|
@@ -1833,24 +1797,24 @@ Sql.prototype.restore = async function(file) {
|
|
|
1833
1797
|
|
|
1834
1798
|
try {
|
|
1835
1799
|
const fs = require('fs');
|
|
1836
|
-
|
|
1800
|
+
|
|
1837
1801
|
if (!fs.existsSync(file)) {
|
|
1838
1802
|
throw new Error('备份文件不存在');
|
|
1839
1803
|
}
|
|
1840
1804
|
|
|
1841
1805
|
// 读取 SQL 文件
|
|
1842
1806
|
const sqlContent = fs.readFileSync(file, 'utf8');
|
|
1843
|
-
|
|
1807
|
+
|
|
1844
1808
|
// 解析 SQL 语句
|
|
1845
1809
|
const statements = this._parseSqlStatements(sqlContent);
|
|
1846
|
-
|
|
1810
|
+
|
|
1847
1811
|
// 执行每个 SQL 语句
|
|
1848
1812
|
for (const stmt of statements) {
|
|
1849
1813
|
if (stmt.trim() && !stmt.startsWith('--')) {
|
|
1850
1814
|
await this.exec(stmt);
|
|
1851
1815
|
}
|
|
1852
1816
|
}
|
|
1853
|
-
|
|
1817
|
+
|
|
1854
1818
|
return true;
|
|
1855
1819
|
} catch (err) {
|
|
1856
1820
|
this.error = err.message;
|
|
@@ -1865,7 +1829,7 @@ Sql.prototype.restore = async function(file) {
|
|
|
1865
1829
|
* @returns {string[]} SQL 语句数组
|
|
1866
1830
|
* @private
|
|
1867
1831
|
*/
|
|
1868
|
-
Sql.prototype._parseSqlStatements = function(sql) {
|
|
1832
|
+
Sql.prototype._parseSqlStatements = function (sql) {
|
|
1869
1833
|
const stmts = [];
|
|
1870
1834
|
let cur_stmt = '';
|
|
1871
1835
|
let in_str = false;
|
|
@@ -1893,7 +1857,7 @@ Sql.prototype._parseSqlStatements = function(sql) {
|
|
|
1893
1857
|
i++; // 跳过第二个'-'
|
|
1894
1858
|
continue;
|
|
1895
1859
|
}
|
|
1896
|
-
|
|
1860
|
+
|
|
1897
1861
|
if (in_cmt && char === '\n') {
|
|
1898
1862
|
in_cmt = false;
|
|
1899
1863
|
continue;
|
|
@@ -1943,7 +1907,7 @@ Sql.prototype._parseSqlStatements = function(sql) {
|
|
|
1943
1907
|
* @param {boolean} create 是否导出创建表语句,默认为false
|
|
1944
1908
|
* @returns {Promise<boolean>} 备份是否成功
|
|
1945
1909
|
*/
|
|
1946
|
-
Sql.prototype.backup = async function(file, create = false) {
|
|
1910
|
+
Sql.prototype.backup = async function (file, create = false) {
|
|
1947
1911
|
if (!file || typeof file !== 'string') {
|
|
1948
1912
|
throw new TypeError('文件路径必须为字符串');
|
|
1949
1913
|
}
|
|
@@ -1955,18 +1919,18 @@ Sql.prototype.backup = async function(file, create = false) {
|
|
|
1955
1919
|
try {
|
|
1956
1920
|
// 获取所有数据
|
|
1957
1921
|
const data = await this.getSql('', '', '*');
|
|
1958
|
-
|
|
1922
|
+
|
|
1959
1923
|
if (!data || !Array.isArray(data)) {
|
|
1960
1924
|
throw new Error('获取数据失败');
|
|
1961
1925
|
}
|
|
1962
1926
|
|
|
1963
1927
|
// 构建 SQL 语句
|
|
1964
1928
|
let sql_content = '';
|
|
1965
|
-
|
|
1929
|
+
|
|
1966
1930
|
// 添加表结构(简化版,实际应用中可能需要更复杂的表结构导出)
|
|
1967
1931
|
sql_content += `-- 备份表: ${this.table}\n`;
|
|
1968
1932
|
sql_content += `-- 备份时间: ${new Date().toISOString()}\n\n`;
|
|
1969
|
-
|
|
1933
|
+
|
|
1970
1934
|
// 如果create为true,添加创建表语句
|
|
1971
1935
|
if (create) {
|
|
1972
1936
|
const create_table_sql = await this._getCreateTableSql();
|
|
@@ -1974,7 +1938,7 @@ Sql.prototype.backup = async function(file, create = false) {
|
|
|
1974
1938
|
sql_content += create_table_sql + '\n\n';
|
|
1975
1939
|
}
|
|
1976
1940
|
}
|
|
1977
|
-
|
|
1941
|
+
|
|
1978
1942
|
// 添加数据插入语句
|
|
1979
1943
|
for (const row of data) {
|
|
1980
1944
|
const keys = Object.keys(row).map(key => this.escapeId(key)).join(', ');
|
|
@@ -1985,15 +1949,15 @@ Sql.prototype.backup = async function(file, create = false) {
|
|
|
1985
1949
|
// 写入文件(这里需要文件系统模块,实际使用时需要引入 fs)
|
|
1986
1950
|
const fs = require('fs');
|
|
1987
1951
|
const path = require('path');
|
|
1988
|
-
|
|
1952
|
+
|
|
1989
1953
|
// 确保目录存在
|
|
1990
1954
|
const dir = path.dirname(file);
|
|
1991
1955
|
if (!fs.existsSync(dir)) {
|
|
1992
1956
|
fs.mkdirSync(dir, { recursive: true });
|
|
1993
1957
|
}
|
|
1994
|
-
|
|
1958
|
+
|
|
1995
1959
|
fs.writeFileSync(file, sql_content, 'utf8');
|
|
1996
|
-
|
|
1960
|
+
|
|
1997
1961
|
return true;
|
|
1998
1962
|
} catch (err) {
|
|
1999
1963
|
this.error = err.message;
|
|
@@ -2007,7 +1971,7 @@ Sql.prototype.backup = async function(file, create = false) {
|
|
|
2007
1971
|
* @param {string} file 恢复文件路径
|
|
2008
1972
|
* @returns {Promise<boolean>} 恢复是否成功
|
|
2009
1973
|
*/
|
|
2010
|
-
Sql.prototype.restore = async function(file) {
|
|
1974
|
+
Sql.prototype.restore = async function (file) {
|
|
2011
1975
|
if (!file || typeof file !== 'string') {
|
|
2012
1976
|
throw new TypeError('文件路径必须为字符串');
|
|
2013
1977
|
}
|
|
@@ -2018,7 +1982,7 @@ Sql.prototype.restore = async function(file) {
|
|
|
2018
1982
|
|
|
2019
1983
|
try {
|
|
2020
1984
|
const fs = require('fs');
|
|
2021
|
-
|
|
1985
|
+
|
|
2022
1986
|
// 检查文件是否存在
|
|
2023
1987
|
if (!fs.existsSync(file)) {
|
|
2024
1988
|
throw new Error('备份文件不存在');
|
|
@@ -2026,14 +1990,14 @@ Sql.prototype.restore = async function(file) {
|
|
|
2026
1990
|
|
|
2027
1991
|
// 读取文件内容
|
|
2028
1992
|
const sql_content = fs.readFileSync(file, 'utf8');
|
|
2029
|
-
|
|
1993
|
+
|
|
2030
1994
|
if (!sql_content) {
|
|
2031
1995
|
throw new Error('备份文件为空');
|
|
2032
1996
|
}
|
|
2033
1997
|
|
|
2034
1998
|
// 解析 SQL 语句
|
|
2035
1999
|
const sql_stmts = this._parseSqlStatements(sql_content);
|
|
2036
|
-
|
|
2000
|
+
|
|
2037
2001
|
if (!sql_stmts || sql_stmts.length === 0) {
|
|
2038
2002
|
throw new Error('未找到有效的 SQL 语句');
|
|
2039
2003
|
}
|
|
@@ -2083,12 +2047,12 @@ Sql.prototype.restore = async function(file) {
|
|
|
2083
2047
|
* @returns {Promise<string>} 创建表的 SQL 语句
|
|
2084
2048
|
* @private
|
|
2085
2049
|
*/
|
|
2086
|
-
Sql.prototype._getCreateTableSql = async function() {
|
|
2050
|
+
Sql.prototype._getCreateTableSql = async function () {
|
|
2087
2051
|
try {
|
|
2088
2052
|
// 查询表结构信息(MySQL 使用 SHOW CREATE TABLE)
|
|
2089
2053
|
const sql = `SHOW CREATE TABLE \`${this.table}\``;
|
|
2090
2054
|
const result = await this.run(sql);
|
|
2091
|
-
|
|
2055
|
+
|
|
2092
2056
|
if (result && result.length > 0 && result[0]['Create Table']) {
|
|
2093
2057
|
// 在创建表语句中添加 IF NOT EXISTS 条件
|
|
2094
2058
|
let create_sql = result[0]['Create Table'];
|
|
@@ -2097,7 +2061,7 @@ Sql.prototype._getCreateTableSql = async function() {
|
|
|
2097
2061
|
}
|
|
2098
2062
|
return create_sql + ';';
|
|
2099
2063
|
}
|
|
2100
|
-
|
|
2064
|
+
|
|
2101
2065
|
return '';
|
|
2102
2066
|
} catch (err) {
|
|
2103
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: '***' // 隐藏密码
|
package/final_test.js
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
const { Mysql } = require('./index.js');
|
|
2
|
-
|
|
3
|
-
async function test() {
|
|
4
|
-
const mysql = new Mysql({
|
|
5
|
-
host: '127.0.0.1',
|
|
6
|
-
port: 3306,
|
|
7
|
-
user: 'root',
|
|
8
|
-
password: 'Asd159357',
|
|
9
|
-
database: 'test',
|
|
10
|
-
debug: false
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
await mysql.open();
|
|
14
|
-
console.log('数据库连接成功');
|
|
15
|
-
|
|
16
|
-
const db = mysql.db();
|
|
17
|
-
db.table = 'cms_article';
|
|
18
|
-
|
|
19
|
-
console.log('=== 全面测试所有修复的函数 ===\n');
|
|
20
|
-
|
|
21
|
-
// 测试1: toAddSql函数
|
|
22
|
-
console.log('1. 测试toAddSql函数:');
|
|
23
|
-
const add_sql = db.toAddSql({
|
|
24
|
-
title: '测试标题',
|
|
25
|
-
content: '测试内容',
|
|
26
|
-
regex_field: '~\\d+$',
|
|
27
|
-
special_chars: "'test' AND 1=1"
|
|
28
|
-
});
|
|
29
|
-
console.log(' SQL:', add_sql);
|
|
30
|
-
|
|
31
|
-
// 测试2: toSetSql函数
|
|
32
|
-
console.log('\n2. 测试toSetSql函数:');
|
|
33
|
-
const set_sql = db.toSetSql(
|
|
34
|
-
{ article_id: 1 },
|
|
35
|
-
{ title: '更新标题', pattern: '~\\d+$' }
|
|
36
|
-
);
|
|
37
|
-
console.log(' SQL:', set_sql);
|
|
38
|
-
|
|
39
|
-
// 测试3: toDelSql函数
|
|
40
|
-
console.log('\n3. 测试toDelSql函数:');
|
|
41
|
-
const del_sql = db.toDelSql({ article_id: 999 });
|
|
42
|
-
console.log(' SQL:', del_sql);
|
|
43
|
-
|
|
44
|
-
// 测试4: toQuery函数
|
|
45
|
-
console.log('\n4. 测试toQuery函数:');
|
|
46
|
-
const query_sql = db.toQuery(
|
|
47
|
-
'`title` LIKE \'%test%\'',
|
|
48
|
-
'`article_id` DESC',
|
|
49
|
-
'title, article_id'
|
|
50
|
-
);
|
|
51
|
-
console.log(' SQL:', query_sql);
|
|
52
|
-
|
|
53
|
-
// 测试5: 测试模板查询函数
|
|
54
|
-
console.log('\n5. 测试模板查询函数:');
|
|
55
|
-
try {
|
|
56
|
-
const tpl_result = db.tplQuery(
|
|
57
|
-
{ title: 'test' },
|
|
58
|
-
{ title: '`title` LIKE \'%{0}%\'' }
|
|
59
|
-
);
|
|
60
|
-
console.log(' 模板查询结果:', tpl_result);
|
|
61
|
-
} catch (error) {
|
|
62
|
-
console.log(' 模板查询错误:', error.message);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
console.log('\n=== 所有函数修复验证完成 ===');
|
|
66
|
-
process.exit(0);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
test().catch(console.error);
|