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.
Files changed (4) hide show
  1. package/index.js +52 -60
  2. package/package.json +8 -7
  3. package/sql.js +130 -164
  4. 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({ ...Mysql.config, ...config});
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 safeReleaseConnection(conn, is_pool_conn) {
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 safeReleaseConnection(conn, is_pool_conn);
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 safeReleaseConnection(conn, is_pool_conn);
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 safeReleaseConnection(conn, is_pool_conn);
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 safeReleaseConnection(conn, is_pool_conn);
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
- let conn = null;
549
- let transaction_committed = false;
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 (transaction_committed || transaction_rolled_back) {
560
- this.log('warn', '事务已结束,无需重复提交');
561
- return;
562
- }
576
+ if (ended) return;
563
577
  await conn.commit();
564
- transaction_committed = true;
565
- try {
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 (transaction_committed || transaction_rolled_back) {
573
- this.log('warn', '事务已结束,无需重复回滚');
574
- return;
575
- }
582
+ if (ended) return;
576
583
  await conn.rollback();
577
- transaction_rolled_back = true;
578
- try {
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: () => transaction_committed || transaction_rolled_back
587
+ _is_ended: () => ended
585
588
  };
586
589
  } catch (error) {
587
590
  // 如果事务开始失败,确保连接被释放
588
- try {
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
- let transaction = null;
611
- let transaction_executed = false;
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 (transaction._is_ended && !transaction._is_ended()) {
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
- if (transaction && !transaction_executed) {
626
- // 只有在事务未成功执行的情况下才回滚
627
- if (transaction._is_ended && !transaction._is_ended()) {
628
- await transaction.rollback().catch(err => {
629
- this.log('error', '事务回滚失败', err);
630
- });
631
- }
620
+ // 如果事务未结束,自动回滚
621
+ if (!transaction._is_ended()) {
622
+ await transaction.rollback().catch(() => {
623
+ // 简化错误处理,避免频繁日志
624
+ });
632
625
  }
633
-
634
- this.log('error', '事务执行失败', error);
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.1",
3
+ "version": "2.3.3",
4
4
  "description": "这是超级美眉mysql帮助函数模块,用于便捷操作mysql,使用await方式,可以避免嵌套函数",
5
5
  "main": "index.js",
6
6
  "dependencies": {
7
- "mm_expand": "^2.0.0",
8
- "mysql2": "^3.16.0"
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": "^9.39.2",
39
- "eslint-plugin-jsdoc": "^61.5.0",
40
- "mm_eslint": "^1.1.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
- var sql = 'INSERT INTO `{0}` ({1}) VALUES ({2});';
218
- sql = sql.replace('{0}', this.table).replace('{1}', key).replace('{2}', val);
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
- var sql = 'DELETE FROM `{0}` WHERE {1};';
228
- sql = sql.replace('{0}', this.table).replace('{1}', where);
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
- var sql = 'UPDATE `{0}` SET {1} WHERE {2};';
239
- sql = sql.replace('{0}', this.table).replace('{1}', set).replace('{2}', where);
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
- view_val.replace(/`/g, '');
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
- if (val && typeof (val) === 'string') {
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 "'" + o + "'";
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
- if (val && typeof (val) === 'string') {
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 "'" + o + "'";
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
- if (typeof val === 'string') {
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
- let val = item[k];
546
- if (typeof val === 'string') {
547
- val = val.trim("'");
548
- }
549
- value += ',' + escape(val);
538
+ value += ',' + escape(item[k]);
550
539
  }
551
540
 
552
- const sql = 'INSERT INTO `{0}` ({1}) VALUES ({2});'
553
- .replace('{0}', this.table)
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
- const sql = 'DELETE FROM `{0}` WHERE {1};'
572
- .replace('{0}', this.table)
573
- .replace('{1}', where);
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
- const sql = 'UPDATE `{0}` SET {1} WHERE {2};'
591
- .replace('{0}', this.table)
592
- .replace('{1}', set)
593
- .replace('{2}', where);
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 超时时间(毫秒),默认30000ms
744
+ * @param {number} timeout 超时时间(毫秒),默认20000ms
762
745
  * @returns {Promise|Array} 查询结果
763
746
  */
764
- Sql.prototype.get = async function (query, sort, view, like, timeout = 60000) {
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 超时时间(毫秒),默认30000ms
779
+ * @param {number} timeout 超时时间(毫秒),默认20000ms
797
780
  * @returns {Promise | object | null} 查询结果
798
781
  */
799
- Sql.prototype.getObj = async function (query, sort, view, like, timeout = 60000) {
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 超时时间(毫秒),默认30000ms
819
+ * @param {number} timeout 超时时间(毫秒),默认20000ms
837
820
  * @returns {Promise | number} 记录数
838
821
  */
839
- Sql.prototype.count = async function (query, like, timeout = 60000) {
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
- if (Object.prototype.hasOwnProperty.call(obj, k)) {
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
- const clean_val = typeof val === 'string' ? val.trim("'") : val;
1210
- sub_conds.push(tpl.replaceAll('{0}', escape(clean_val).trim("'")));
1182
+ // 使用直接替换方式,避免模板替换错误
1183
+ sub_conds.push(tpl.replaceAll('{0}', escape(val).trim("'")));
1211
1184
  }
1212
1185
  conds.push('(' + sub_conds.join(' || ') + ')');
1213
1186
  } else {
1214
- const clean_val = typeof value === 'string' ? value.trim("'") : value;
1215
- conds.push(tpl.replaceAll('{0}', escape(clean_val).trim("'")));
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
- const clean_val = typeof value === 'string' ? value.trim("'") : value;
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
- if (!Object.prototype.hasOwnProperty.call(param_dt, key)) continue;
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
- if (!Object.prototype.hasOwnProperty.call(param_dt, key)) continue;
1343
- const value = escape(param_dt[key]);
1307
+ let value = escape(param_dt[key]);
1344
1308
  if (sql_dt[key]) {
1345
- parts.push(' ' + sql_dt[key].replace('{0}', value).replace(/\+ -/g, '- ').replace(/- -/g, '+ '));
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 = {host: '127.0.0.1',
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
- connection_limit: 5, ...config || {}};
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: '***' // 隐藏密码