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.
Files changed (5) hide show
  1. package/index.js +52 -60
  2. package/package.json +1 -1
  3. package/sql.js +109 -145
  4. package/test.js +82 -3
  5. 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({ ...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,6 +1,6 @@
1
1
  {
2
2
  "name": "mm_mysql",
3
- "version": "2.3.2",
3
+ "version": "2.3.3",
4
4
  "description": "这是超级美眉mysql帮助函数模块,用于便捷操作mysql,使用await方式,可以避免嵌套函数",
5
5
  "main": "index.js",
6
6
  "dependencies": {
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
- view_val.replace(/`/g, '');
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 "'" + 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')) {
@@ -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 "'" + o + "'";
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
- let val = item[k];
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 超时时间(毫秒),默认30000ms
744
+ * @param {number} timeout 超时时间(毫秒),默认20000ms
757
745
  * @returns {Promise|Array} 查询结果
758
746
  */
759
- Sql.prototype.get = async function (query, sort, view, like, timeout = 60000) {
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 超时时间(毫秒),默认30000ms
779
+ * @param {number} timeout 超时时间(毫秒),默认20000ms
792
780
  * @returns {Promise | object | null} 查询结果
793
781
  */
794
- Sql.prototype.getObj = async function (query, sort, view, like, timeout = 60000) {
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 超时时间(毫秒),默认30000ms
819
+ * @param {number} timeout 超时时间(毫秒),默认20000ms
832
820
  * @returns {Promise | number} 记录数
833
821
  */
834
- Sql.prototype.count = async function (query, like, timeout = 60000) {
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
- if (Object.prototype.hasOwnProperty.call(obj, k)) {
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
- const escaped_val = escape(clean_val);
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
- const escaped_val = escape(clean_val);
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
- const clean_val = typeof value === 'string' ? value.trim("'") : value;
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
- if (!Object.prototype.hasOwnProperty.call(param_dt, key)) continue;
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
- 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
1309
  // 使用直接替换方式,避免模板替换错误
1346
- const replaced = sql_dt[key].replace('{0}', value);
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 = {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: '***' // 隐藏密码
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);