mm_mysql 2.0.1 → 2.0.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/db.js CHANGED
@@ -7,10 +7,12 @@ const Sql = require('./sql');
7
7
  class DB extends Sql {
8
8
  /**
9
9
  * @description 数据库管理器
10
- * @param {String} database 数据库名称
10
+ * @param {Object} mysql - MySQL实例
11
11
  */
12
12
  constructor(mysql) {
13
13
  super(mysql.run, mysql.exec);
14
+ // 保存mysql实例引用
15
+ this._mysql = mysql;
14
16
  // 事务中
15
17
  this.task = 0;
16
18
 
@@ -27,6 +29,21 @@ class DB extends Sql {
27
29
  }
28
30
  }
29
31
 
32
+ /**
33
+ * 创建超时Promise
34
+ * @private
35
+ * @param {Number} timeout - 超时时间(毫秒)
36
+ * @param {String} message - 错误信息
37
+ * @returns {Promise<Error>}
38
+ */
39
+ DB.prototype._createTimeoutPromise = function(timeout, message) {
40
+ return new Promise((_, reject) => {
41
+ setTimeout(() => {
42
+ reject(new Error(message));
43
+ }, timeout);
44
+ });
45
+ };
46
+
30
47
  /**
31
48
  * 获取数据库名
32
49
  * @return {String} 数据库
@@ -42,400 +59,613 @@ DB.prototype.database = function() {
42
59
  * @return {Object} 返回管理器
43
60
  */
44
61
  DB.prototype.new = function(table, key) {
45
- var db = this.parent().db();
62
+ const db = this.parent().db();
46
63
  db.table = table;
47
64
  if (key) {
48
65
  db.key = key;
49
66
  } else {
50
- var arr = table.split("_");
67
+ const arr = table.split("_");
51
68
  db.key = arr[arr.length - 1] + "_id";
52
69
  }
53
70
  return db;
54
71
  };
55
72
 
73
+ /**
74
+ * 执行SQL查询
75
+ * @param {string} sql SQL语句
76
+ * @param {Array} params 参数数组
77
+ * @param {Number} timeout 超时时间(毫秒)
78
+ * @returns {Promise<Object>} 查询结果
79
+ */
80
+ DB.prototype.run = async function(sql, params = [], timeout = 30000) {
81
+ if (!this._mysql) {
82
+ throw new Error('MySQL实例未初始化');
83
+ }
84
+ try {
85
+ // 使用Promise.race实现超时控制
86
+ return await Promise.race([
87
+ this._mysql.run(sql, params),
88
+ this._createTimeoutPromise(timeout, `查询执行超时: ${sql.substring(0, 100)}...`)
89
+ ]);
90
+ } catch (error) {
91
+ $.log.error(`[DB] [run] 查询执行失败`, {
92
+ error: error.message,
93
+ sql: sql.substring(0, 200),
94
+ params: JSON.stringify(params.slice(0, 5))
95
+ });
96
+ throw error;
97
+ }
98
+ };
99
+
100
+ /**
101
+ * 执行SQL命令
102
+ * @param {string} sql SQL语句
103
+ * @param {Number} timeout 超时时间(毫秒)
104
+ * @returns {Promise<Object>} 执行结果
105
+ */
106
+ DB.prototype.exec = async function(sql, timeout = 30000) {
107
+ if (!this._mysql) {
108
+ throw new Error('MySQL实例未初始化');
109
+ }
110
+ try {
111
+ // 使用Promise.race实现超时控制
112
+ return await Promise.race([
113
+ this._mysql.exec(sql),
114
+ this._createTimeoutPromise(timeout, `命令执行超时: ${sql.substring(0, 100)}...`)
115
+ ]);
116
+ } catch (error) {
117
+ $.log.error(`[DB] [exec] 命令执行失败`, {
118
+ error: error.message,
119
+ sql: sql.substring(0, 200)
120
+ });
121
+ throw error;
122
+ }
123
+ };
124
+
125
+ /**
126
+ * 获取数据库连接
127
+ * @param {Number} timeout 超时时间(毫秒)
128
+ * @returns {Promise<Object>} 数据库连接对象
129
+ */
130
+ DB.prototype.getConn = async function(timeout = 15000) {
131
+ if (!this._mysql) {
132
+ throw new Error('MySQL实例未初始化');
133
+ }
134
+ try {
135
+ // 使用Promise.race实现超时控制
136
+ return await Promise.race([
137
+ this._mysql.getConn(),
138
+ this._createTimeoutPromise(timeout, '获取数据库连接超时')
139
+ ]);
140
+ } catch (error) {
141
+ $.log.error(`[DB] [getConn] 获取连接失败`, {
142
+ error: error.message
143
+ });
144
+ throw error;
145
+ }
146
+ };
147
+
56
148
  /**
57
149
  * 事务开始
150
+ * @param {String} identifier 事务标识符
151
+ * @param {Number} timeout 超时时间(毫秒)
152
+ * @return {Promise<Object>} 执行结果
58
153
  */
59
- DB.prototype.start = async function(identifier = "point_1") {
154
+ DB.prototype.start = async function(identifier = "point_1", timeout = 15000) {
60
155
  this.task = 1;
61
- return await this.exec("SET AUTOCOMMIT=0;BEGIN;");
156
+ try {
157
+ return await this.exec("SET AUTOCOMMIT=0;BEGIN;", timeout);
158
+ } catch (err) {
159
+ this.task = 0;
160
+ $.log.error(`[DB] [start] 事务开始失败: ${err.message}`);
161
+ throw err;
162
+ }
62
163
  };
63
164
 
64
165
  /**
65
166
  * 提交
167
+ * @param {Number} timeout 超时时间(毫秒)
168
+ * @return {Promise<Object>} 执行结果
66
169
  */
67
- DB.prototype.commit = async function() {
170
+ DB.prototype.commit = async function(timeout = 15000) {
171
+ if (this.task !== 1) {
172
+ $.log.warn("[DB] [commit] 没有活跃的事务可提交");
173
+ return 0;
174
+ }
68
175
  this.task = 2;
69
- return this.exec("COMMIT;");
176
+ try {
177
+ const result = await this.exec("COMMIT;", timeout);
178
+ // 重置事务状态
179
+ this.task = 0;
180
+ this.task_sql = '';
181
+ return result;
182
+ } catch (err) {
183
+ $.log.error(`[DB] [commit] 事务提交失败: ${err.message}`);
184
+ // 尝试回滚
185
+ try {
186
+ await this.exec("ROLLBACK;", timeout);
187
+ } catch (rollbackErr) {
188
+ $.log.error(`[DB] [commit] 事务回滚也失败: ${rollbackErr.message}`);
189
+ }
190
+ this.task = 0;
191
+ this.task_sql = '';
192
+ throw err;
193
+ }
70
194
  };
71
195
 
72
196
  /**
73
197
  * 事务结束
74
198
  */
75
- DB.prototype.end = async function() {
76
- this.task = 3;
199
+ DB.prototype.end = function() {
200
+ // 确保事务状态被重置
201
+ this.task = 0;
202
+ this.task_sql = '';
77
203
  };
78
204
 
79
205
  /**
80
206
  * 滚回
207
+ * @param {String} identifier 事务标识符
208
+ * @param {Number} timeout 超时时间(毫秒)
209
+ * @return {Promise<Object>} 执行结果
81
210
  */
82
- DB.prototype.back = async function(identifier = "point_1") {
211
+ DB.prototype.back = async function(identifier = "point_1", timeout = 15000) {
212
+ if (this.task !== 1) {
213
+ $.log.warn("[DB] [back] 没有活跃的事务可回滚");
214
+ this.task = 0;
215
+ this.task_sql = '';
216
+ return 0;
217
+ }
83
218
  this.task = 3;
84
- await this.exec("ROLLBACK;");
85
- return this.exec("SET AUTOCOMMIT=1;");
219
+ try {
220
+ await this.exec("ROLLBACK;", timeout);
221
+ const result = await this.exec("SET AUTOCOMMIT=1;", timeout);
222
+ // 重置事务状态
223
+ this.task = 0;
224
+ this.task_sql = '';
225
+ return result;
226
+ } catch (err) {
227
+ $.log.error(`[DB] [back] 事务回滚失败: ${err.message}`);
228
+ this.task = 0;
229
+ this.task_sql = '';
230
+ throw err;
231
+ }
86
232
  };
87
233
 
88
234
  /**
89
235
  * @description 获取所有表名
90
236
  * @param {String} table 表名关键词, 可以 *table*包含、*table后缀、table*前缀 查询
237
+ * @param {Number} timeout 超时时间(毫秒)
91
238
  * @return {Promise|Array} 表名数组
92
239
  */
93
- DB.prototype.tables = async function(table) {
94
- var list = await this.run("show tables");
95
- var key = 'Tables_in_' + this.database();
96
- if (table) {
97
- list = list.search(table, key);
240
+ DB.prototype.tables = async function(table, timeout = 15000) {
241
+ try {
242
+ const list = await this.run("show tables", [], timeout);
243
+ const key = 'Tables_in_' + this.database();
244
+ if (table) {
245
+ // 简单的过滤实现
246
+ const filtered = [];
247
+ for (const item of list) {
248
+ const value = item[key];
249
+ if (value && value.includes(table)) {
250
+ filtered.push(item);
251
+ }
252
+ }
253
+ return filtered.map(item => item[key]);
254
+ }
255
+ // 使用原生方法提取表名
256
+ return list.map(item => item[key]);
257
+ } catch (err) {
258
+ $.log.error(`[DB] [tables] 获取表名失败: ${err.message}`);
259
+ return [];
98
260
  }
99
- return list.getArr(key);
100
261
  };
101
262
 
102
263
  /**
103
264
  * @description 获取所有表字段
104
265
  * @param {String} table 表名
266
+ * @param {String} field_name 字段名,可选
267
+ * @param {Number} timeout 超时时间(毫秒)
105
268
  * @return {Promise|Array} 字段信息列表
106
269
  */
107
- DB.prototype.fields = async function(table, field_name) {
108
- if (!table) {
109
- table = this.table;
110
- }
111
- var field =
112
- 'COLUMN_NAME as `name`,ORDINAL_POSITION as `cid`,COLUMN_DEFAULT as `dflt_value`,IS_NULLABLE as `notnull`,COLUMN_TYPE as `type`,COLUMN_KEY as `pk`,EXTRA as `auto`,COLUMN_COMMENT as `note`';
113
- var sql = "select " + field + " from information_schema.COLUMNS where `table_name` = '" + table +
114
- "' and `table_schema` = '" + this.database() + "'";
115
- if (field_name) {
116
- sql += " AND COLUMN_NAME='" + field_name + "'";
117
- }
118
- var list = await this.run(sql);
119
- var len = list.length;
120
- for (var i = 0; i < len; i++) {
121
- list[i].pk = list[i].pk ? true : false;
122
- list[i].notnull = list[i].notnull == 'NO' ? true : false;
123
- }
124
- return list;
125
- };
126
-
127
- /**
128
- * @description 设置类型
129
- * @param {String} field 字段名
130
- * @param {String} type 类型名,常用类型 mediumint, int, varchar, datetime
131
- * @param {String} value 默认值
132
- * @param {Boolean} not_null 是否非空字段 true为非空,false为可空
133
- * @param {Boolean} auto 自动
134
- * @return {String} 返回最终类型
135
- */
136
- DB.prototype.setType = function(field, type, value, not_null, auto) {
137
- if (!type) {
138
- type = 'int';
139
- }
140
- switch (type) {
141
- case "str":
142
- case "varchar":
143
- case "string":
144
- type = "varchar(255)";
145
- if (not_null) {
146
- type += " NOT NULL";
147
- }
148
- if (value) {
149
- type += " DEFAULT '" + value + "'";
150
- } else {
151
- type += " DEFAULT ''";
152
- }
153
- break;
154
- case "number":
155
- type = "int(11) NOT NULL";
156
- if (auto) {
157
- type += " AUTO_INCREMENT";
158
- } else if (value) {
159
- type += " DEFAULT " + value;
160
- } else {
161
- type += " DEFAULT 0";
162
- }
163
- break;
164
- case "bool":
165
- case "tinyint":
166
- type = "tinyint(1) UNSIGNED NOT NULL";
167
- if (value) {
168
- type += " DEFAULT " + value;
169
- } else {
170
- type += " DEFAULT 0";
171
- }
172
- break;
173
- case "datetime":
174
- if (!value) {
175
- value = "1970-01-01 00:00:00";
176
- }
177
- type += " DEFAULT '" + value + "'";
178
- break;
179
- case "timestamp":
180
- if (auto) {
181
- if (field.indexOf('update') !== -1 || field.indexOf('last') !== -1) {
182
- type += " DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP";
183
- } else {
184
- type += " DEFAULT CURRENT_TIMESTAMP";
185
- }
186
- } else {
187
- type += " DEFAULT CURRENT_TIMESTAMP";
188
- }
189
- break;
190
- case "date":
191
- if (!value) {
192
- value = "1970-01-01";
193
- }
194
- type += " NOT NULL DEFAULT '" + value + "'";
195
- break;
196
- case "time":
197
- if (!value) {
198
- value = "00:00:00";
199
- }
200
- type += " NOT NULL DEFAULT '" + value + "'";
201
- break;
202
- case "double":
203
- if (type == "double") {
204
- type = "double(10, 2)"
205
- }
206
- type += " NOT NULL";
207
- if (value) {
208
- type += " DEFAULT '" + value + "'";
209
- } else {
210
- type += " DEFAULT 0";
211
- }
212
- break;
213
- case "float":
214
- if (type == "float") {
215
- type = "float(17, 8)"
216
- }
217
- type += " NOT NULL";
218
- if (value) {
219
- type += " DEFAULT '" + value + "'";
220
- } else {
221
- type += " DEFAULT 0";
222
- }
223
- break;
224
- case "longtext":
225
- case "text":
226
- if (type == "text") {
227
- type = "text NULL"
228
- }
229
- else if (type == "longtext") {
230
- type = "longtext NULL"
231
- }
232
- break;
233
- default:
234
- if (type.indexOf('var') !== -1) {
235
- if (not_null) {
236
- type += " NOT NULL"
237
- }
238
- if (value) {
239
- type += " DEFAULT '" + value + "'";
240
- } else {
241
- type += " DEFAULT ''";
242
- }
243
- } else {
244
- type += " UNSIGNED NOT NULL";
245
- if (auto) {
246
- type += " AUTO_INCREMENT";
247
- } else {
248
- if (value) {
249
- type += " DEFAULT '" + value + "'";
250
- } else {
251
- type += " DEFAULT 0";
252
- }
253
- }
254
- }
255
- break;
270
+ DB.prototype.fields = async function(table, field_name, timeout = 15000) {
271
+ try {
272
+ const targetTable = table || this.table;
273
+ if (!targetTable) {
274
+ $.log.error("[DB] [fields] 未指定表名");
275
+ return [];
276
+ }
277
+ const field = 'COLUMN_NAME as `name`,ORDINAL_POSITION as `cid`,COLUMN_DEFAULT as `dflt_value`,IS_NULLABLE as `notnull`,COLUMN_TYPE as `type`,COLUMN_KEY as `pk`,EXTRA as `auto`,COLUMN_COMMENT as `note`';
278
+ let sql = "select " + field + " from information_schema.COLUMNS where `table_name` = '" + targetTable +
279
+ "' and `table_schema` = '" + this.database() + "'";
280
+ if (field_name) {
281
+ sql += " AND COLUMN_NAME='" + field_name + "'";
282
+ }
283
+ const list = await this.run(sql, [], timeout);
284
+ const len = list.length;
285
+ for (let i = 0; i < len; i++) {
286
+ list[i].pk = !!list[i].pk;
287
+ list[i].notnull = list[i].notnull === 'NO';
288
+ }
289
+ return list;
290
+ } catch (err) {
291
+ $.log.error(`[DB] [fields] 获取字段信息失败: ${err.message}`);
292
+ return [];
256
293
  }
257
- return type;
258
294
  };
259
295
 
260
296
  /**
261
297
  * @description 创建数据表
262
298
  * @param {String} table 表名
263
299
  * @param {String} field 主键字段名
264
- * @param {String} type 类型名,常用类型 mediumint, int, varchar
300
+ * @param {String} type 类型名,常用类型 mediumint, int, varchar
265
301
  * @param {Boolean} auto 是否自增字段, 默认为自增字段
266
302
  * @param {String} commit 注释
303
+ * @param {Number} timeout 超时时间(毫秒)
267
304
  * @return {Promise|Number} 创建成功返回1,失败返回0
268
305
  */
269
- DB.prototype.addTable = async function(table, field, type, auto, commit = '') {
270
- if (!field) {
271
- field = "id";
272
- }
273
- var sql = "CREATE TABLE IF NOT EXISTS `{0}` (`{1}` {2})".replace('{0}', table).replace(
274
- '{1}', field).replace('{2}', this.setType(field, type, null, true, auto) + ' PRIMARY KEY');
275
- if (commit) {
276
- sql += " COMMENT = '" + commit + "';"
277
- } else {
278
- sql += ";"
306
+ DB.prototype.addTable = async function(table, field, type = 'int', auto = true, commit = '', timeout = 15000) {
307
+ try {
308
+ if (!table || typeof table !== 'string') {
309
+ $.log.error("[DB] [addTable] 表名无效");
310
+ return 0;
311
+ }
312
+ if (!field) {
313
+ field = "id";
314
+ }
315
+ // 构建完整的字段定义
316
+ let fieldDef = `\`${field}\` ${type}(11)`;
317
+ fieldDef += " NOT NULL";
318
+ if (auto) {
319
+ fieldDef += " AUTO_INCREMENT";
320
+ }
321
+ if (commit) {
322
+ fieldDef += " COMMENT '" + commit + "'";
323
+ }
324
+ fieldDef += " PRIMARY KEY";
325
+
326
+ let sql = "CREATE TABLE IF NOT EXISTS \`" + table + "\` (" + fieldDef + ")";
327
+ sql += " ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;";
328
+
329
+ // 执行SQL并设置表名
330
+ const result = await this.exec(sql, timeout);
331
+ // 设置实例的表名属性,以便后续操作使用
332
+ this.table = table;
333
+ return result && result.affectedRows !== undefined ? 1 : 0;
334
+ } catch (err) {
335
+ $.log.error(`[DB] [addTable] 创建表失败: ${err.message}`);
336
+ return 0;
279
337
  }
280
- var bl = await this.exec(sql);
281
- return bl;
282
338
  };
283
339
 
284
340
  /**
285
- * @description 清空数据表
286
- * @param {Boolean} reset 是否重置自增ID
287
- * @return {Promise|Number} 清空成功返回1,失败返回0
341
+ * @description 删除表
342
+ * @param {String} table 表名
343
+ * @param {Number} timeout 超时时间(毫秒)
344
+ * @return {Promise|Number} 操作结果
288
345
  */
289
- DB.prototype.clearTable = async function(reset = true, table = '') {
290
- var sql = reset ? "TRUNCATE table `{0}`;" : "DELETE FROM `{0}`";
291
- var bl = await this.exec(sql.replace('{0}', table || this.table));
292
- return bl;
346
+ DB.prototype.dropTable = function(table, timeout = 15000) {
347
+ if (!table || typeof table !== 'string') {
348
+ $.log.error("[DB] [dropTable] 表名无效");
349
+ return 0;
350
+ }
351
+ return this.exec("DROP TABLE IF EXISTS \`" + table + "\`;", timeout);
352
+ };
353
+
354
+ /**
355
+ * @description 修改表名
356
+ * @param {String} table 旧表名
357
+ * @param {String} new_table 新表名
358
+ * @param {Number} timeout 超时时间(毫秒)
359
+ * @return {Promise|Number} 操作结果
360
+ */
361
+ DB.prototype.renameTable = function(table, new_table, timeout = 15000) {
362
+ if (!table || !new_table) {
363
+ $.log.error("[DB] [renameTable] 表名参数不完整");
364
+ return 0;
365
+ }
366
+ return this.exec("RENAME TABLE \`" + table + "\` TO \`" + new_table + "\`;", timeout);
293
367
  };
294
368
 
295
369
  /**
296
370
  * @description 添加字段
297
371
  * @param {String} field 字段名
298
- * @param {String} type 类型名,常用类型 mediumint, int, float, double, varchar, tinyint, text, date, datetime, time, timestamp
299
- * @param {String|Number} value 默认值
300
- * @param {Boolean} not_null 是否非空字段 true为非空,false为可空
301
- * @param {Boolean} auto 是否自动(如果为数字类型则自增增段,如果为时间类型则默认事件)
302
- * @param {Boolean} isKey 是否主键
372
+ * @param {String} type 类型名
373
+ * @param {String} value 默认值
374
+ * @param {Boolean} not_null 是否非空
375
+ * @param {Boolean} auto 是否自增
376
+ * @param {String} comment 注释
377
+ * @param {Number} timeout 超时时间(毫秒)
303
378
  * @return {Promise|Number} 添加成功返回1,失败返回0
304
379
  */
305
- DB.prototype.field_add = async function(field, type, value, not_null, auto, isKey) {
306
- var sql =
307
- "SELECT COUNT(*) as `count` FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='{0}' AND table_name='{1}' AND COLUMN_NAME='{2}'";
308
- sql = sql.replace('{0}', this.database()).replace('{1}', this.table).replace('{2}', field);
309
- var arr = await this.run(sql);
310
- if (arr && arr.length > 0) {
311
- if (arr[0].count == 0) {
312
- var type = this.setType(field, type, value, not_null, auto);
313
- var sql = "ALTER Table `{0}` ADD `{1}` {2}";
314
- sql = sql.replace('{0}', this.table).replace('{1}', field).replace('{2}', type);
315
- if (isKey) {
316
- sql += " , ADD PRIMARY KEY (`" + field + "`)";
380
+ DB.prototype.field_add = async function(field, type, value = '', not_null = true, auto = false, comment = '', timeout = 15000) {
381
+ try {
382
+ // 确保表名已设置
383
+ if (!this.table || !field || !type) {
384
+ $.log.error("[DB] [field_add] 表名、字段名或类型未指定");
385
+ return 0;
386
+ }
387
+
388
+ // 构建字段定义
389
+ let fieldDef = `\`${field}\` ${type}`;
390
+
391
+ // 添加非空约束
392
+ if (not_null) {
393
+ fieldDef += " NOT NULL";
394
+ }
395
+
396
+ // 添加自增属性
397
+ if (auto) {
398
+ fieldDef += " AUTO_INCREMENT";
399
+ }
400
+
401
+ // 添加默认值
402
+ if (value !== undefined && value !== null && value !== '') {
403
+ if (typeof value === 'string') {
404
+ fieldDef += " DEFAULT '" + value + "'";
317
405
  } else {
318
- sql += ";";
406
+ fieldDef += " DEFAULT " + value;
319
407
  }
320
- return await this.exec(sql);
321
408
  }
409
+
410
+ // 添加注释
411
+ if (comment) {
412
+ fieldDef += " COMMENT '" + comment + "'";
413
+ }
414
+
415
+ // 使用ADD COLUMN而不是CHANGE COLUMN
416
+ const sql = `ALTER TABLE \`${this.table}\` ADD COLUMN ${fieldDef};`;
417
+ const result = await this.exec(sql, timeout);
418
+ return result && result.affectedRows !== undefined ? 1 : 0;
419
+ } catch (err) {
420
+ $.log.error(`[DB] [field_add] 添加字段失败: ${err.message}`);
421
+ return 0;
322
422
  }
323
- return 0;
324
423
  };
325
424
 
326
425
  /**
327
- * @description 添加字段
426
+ * @description 修改字段
328
427
  * @param {String} field 字段名
329
- * @param {String} type 类型名,常用类型 mediumint, int, float, double, varchar, tinyint, text, date, datetime, time, timestamp
330
- * @param {String|Number} value 默认值
331
- * @param {Boolean} not_null 是否非空字段 true为非空,false为可空
332
- * @param {Boolean} auto 是否自动(如果为数字类型则自增增段,如果为时间类型则默认事件)
428
+ * @param {String} type 类型名
429
+ * @param {String} value 默认值
430
+ * @param {Boolean} not_null 是否非空
431
+ * @param {Boolean} auto 是否自增
333
432
  * @param {Boolean} isKey 是否主键
334
- * @param {String} new_name 新名称
335
- * @return {Promise|Number} 添加成功返回1,失败返回0
433
+ * @param {String} new_name 新字段名
434
+ * @param {Number} timeout 超时时间(毫秒)
435
+ * @return {Promise|Number} 修改成功返回1,失败返回0
336
436
  */
337
- DB.prototype.field_set = async function(field, type, value, not_null, auto, isKey, new_name) {
338
- var sql =
339
- "SELECT COUNT(*) as `count` FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='{0}' AND table_name='{1}' AND COLUMN_NAME='{2}'";
340
- sql = sql.replace('{0}', this.database()).replace('{1}', this.table).replace('{2}', field);
341
- var arr = await this.run(sql);
342
- if (arr && arr.length > 0) {
343
- if (arr[0].count == 0) {
437
+ DB.prototype.field_set = async function(field, type, value = '', not_null = true, auto = false, isKey = false, new_name = '', timeout = 15000) {
438
+ try {
439
+ if (!field || !type || !this.table) {
440
+ $.log.error("[DB] [field_set] 参数不完整或表名未设置");
344
441
  return 0;
345
442
  }
443
+ const targetName = new_name || field;
444
+ let fieldDef = `\`${targetName}\` ${type}`;
445
+ if (not_null) {
446
+ fieldDef += " NOT NULL";
447
+ }
448
+ if (auto) {
449
+ fieldDef += " AUTO_INCREMENT";
450
+ }
451
+ if (value !== undefined && value !== null && value !== '') {
452
+ if (typeof value === 'string') {
453
+ fieldDef += " DEFAULT '" + value + "'";
454
+ } else {
455
+ fieldDef += " DEFAULT " + value;
456
+ }
457
+ }
458
+ let sql = `ALTER TABLE \`${this.table}\` CHANGE COLUMN \`${field}\` ${fieldDef}`;
459
+ if (isKey) {
460
+ sql += ", ADD PRIMARY KEY (\`" + targetName + "\`)";
461
+ }
462
+ sql += ";";
463
+ const result = await this.exec(sql, timeout);
464
+ return result && result.affectedRows !== undefined ? 1 : 0;
465
+ } catch (err) {
466
+ $.log.error(`[DB] [field_set] 修改字段失败: ${err.message}`);
467
+ return 0;
346
468
  }
469
+ };
347
470
 
348
- var type = this.setType(field, type, value, not_null, auto);
349
- if (type.has('text')) {
350
- type = type.replace('NOT NULL', '').replace("DEFAULT ''", "");
471
+ /**
472
+ * @description 修改字段类型
473
+ * @param {String} table 表名
474
+ * @param {String} field 字段名
475
+ * @param {String} type 字段类型
476
+ * @param {Number} timeout 超时时间(毫秒)
477
+ * @return {Promise|Number} 操作结果
478
+ */
479
+ DB.prototype.field_edit = function(table, field, type, timeout = 15000) {
480
+ if (!table || !field || !type) {
481
+ $.log.error("[DB] [field_edit] 参数不完整");
482
+ return 0;
351
483
  }
484
+ return this.exec("ALTER TABLE \`" + table + "\` MODIFY COLUMN \`" + field + "\` " + type + ";", timeout);
485
+ };
352
486
 
353
- if (!new_name) {
354
- new_name = field;
487
+ /**
488
+ * @description 删除字段
489
+ * @param {String} table 表名
490
+ * @param {String} field 字段名
491
+ * @param {Number} timeout 超时时间(毫秒)
492
+ * @return {Promise|Number} 操作结果
493
+ */
494
+ DB.prototype.field_del = function(table, field, timeout = 15000) {
495
+ if (!table || !field) {
496
+ $.log.error("[DB] [field_del] 参数不完整");
497
+ return 0;
355
498
  }
499
+ return this.exec("ALTER TABLE \`" + table + "\` DROP COLUMN \`" + field + "\`;", timeout);
500
+ };
356
501
 
357
- sql = "ALTER TABLE `{0}` CHANGE COLUMN `{1}` `{2}` {3}";
358
- sql = sql.replace('{0}', this.table).replace('{1}', field).replace('{2}', new_name).replaceAll('{3}', type);
359
- if (isKey) {
360
- sql += ", DROP PRIMARY KEY, ADD PRIMARY KEY (" + new_name + ") USING BTREE;"
361
- } else {
362
- sql += ";";
502
+ /**
503
+ * @description 修改字段名
504
+ * @param {String} table 表名
505
+ * @param {String} field 旧字段名
506
+ * @param {String} new_field 新字段名
507
+ * @param {String} type 字段类型
508
+ * @param {Number} timeout 超时时间(毫秒)
509
+ * @return {Promise|Number} 操作结果
510
+ */
511
+ DB.prototype.field_rename = function(table, field, new_field, type, timeout = 15000) {
512
+ if (!table || !field || !new_field) {
513
+ $.log.error("[DB] [field_rename] 参数不完整");
514
+ return 0;
363
515
  }
364
- return await this.exec(sql);
516
+ return this.exec("ALTER TABLE \`" + table + "\` CHANGE \`" + field + "\` \`" + new_field + "\` " + type + ";", timeout);
365
517
  };
366
518
 
367
519
  /**
368
- * @description 删除字段
369
- * @param {String} field 字段名
370
- * @return {Promise|Number} 删除成功返回1,失败返回0
520
+ * @description 获取表创建语句
521
+ * @param {String} table 表名
522
+ * @param {Number} timeout 超时时间(毫秒)
523
+ * @return {Promise|String} 创建语句
371
524
  */
372
- DB.prototype.field_del = async function(field) {
373
- var sql =
374
- "SELECT COUNT(*) as `count` FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='{0}' AND table_name='{1}' AND COLUMN_NAME='{2}'";
375
- sql = sql.replace('{0}', this.database()).replace('{1}', this.table).replace('{2}', field);
376
- var arr = await this.run(sql);
377
- if (arr && arr.length > 0) {
378
- if (arr[0].count > 0) {
379
- sql = "ALTER Table `{0}` DROP `{1}`;".replace('{0}', this.table).replace('{1}', field);
380
- return await this.exec(sql);
525
+ DB.prototype.getCreateTable = async function(table, timeout = 15000) {
526
+ if (!table) {
527
+ $.log.error("[DB] [getCreateTable] 表名不能为空");
528
+ return '';
529
+ }
530
+ try {
531
+ const list = await this.run("SHOW CREATE TABLE \`" + table + "\`;", [], timeout);
532
+ if (list && list.length > 0) {
533
+ return list[0]["Create Table"] || list[0]["create table"];
381
534
  }
535
+ return '';
536
+ } catch (err) {
537
+ $.log.error(`[DB] [getCreateTable] 获取表结构失败: ${err.message}`);
538
+ return '';
382
539
  }
383
- return 0;
384
540
  };
385
541
 
386
542
  /**
387
- * @description 拼接字段信息SQL
388
- * @param {Object} fd 字段信息
389
- * @return {String} sql语段
543
+ * @description 获取表数据的SQL插入语句
544
+ * @param {String} table 表名
545
+ * @param {Number} batchSize 批量处理大小
546
+ * @param {Number} timeout 超时时间(毫秒)
547
+ * @return {Promise|String} SQL插入语句
390
548
  */
391
- DB.prototype.field_sql = function(fd) {
392
- var sql = "`{0}`".replace('{0}', fd.name);
393
- sql += " " + fd.type;
394
- if (fd.notnull) {
395
- sql += " NOT NULL";
396
- }
397
- if (fd.auto) {
398
- if (fd.dflt_value) {
399
- if (fd.dflt_value === '0000-00-00 00:00:00') {
400
- fd.dflt_value = 'CURRENT_TIMESTAMP';
549
+ DB.prototype.getTableData = async function(table, batchSize = 100, timeout = 60000) {
550
+ if (!table) {
551
+ $.log.error("[DB] [getTableData] 表名不能为空");
552
+ return '';
553
+ }
554
+ try {
555
+ // 获取表数据
556
+ const rows = await this.run(`SELECT * FROM \`${table}\``, [], timeout);
557
+ let sql = '';
558
+
559
+ if (rows && rows.length > 0) {
560
+ sql += `-- 表数据: ${table}\n`;
561
+
562
+ // 批量处理大数据量
563
+ for (let j = 0; j < rows.length; j += batchSize) {
564
+ const batch = rows.slice(j, j + batchSize);
565
+ for (const row of batch) {
566
+ try {
567
+ const rowValues = Object.values(row)
568
+ .map(value => {
569
+ if (value === null) return 'NULL';
570
+ if (typeof value === 'boolean') return value ? 1 : 0;
571
+ if (typeof value === 'number') return value;
572
+ if (value instanceof Date) {
573
+ return "'" + value.toISOString().slice(0, 19).replace('T', ' ') + "'";
574
+ }
575
+ // 处理字符串,转义特殊字符
576
+ if (typeof value === 'string') {
577
+ return "'" + value
578
+ .replace(/[\\']/g, '\\$&')
579
+ .replace(/\n/g, '\\n')
580
+ .replace(/\r/g, '\\r')
581
+ .replace(/\t/g, '\\t')
582
+ .replace(/\u0000/g, '\\0') + "'";
583
+ }
584
+ return "'" + String(value || '') + "'";
585
+ }).join(',');
586
+ sql += `INSERT INTO \`${table}\` VALUES (${rowValues});\n`;
587
+ } catch (rowErr) {
588
+ $.log.error(`[DB] [getTableData] 处理行数据失败: ${rowErr.message}`);
589
+ }
590
+ }
401
591
  }
402
- sql += " DEFAULT " + fd.dflt_value;
592
+ sql += '\n';
403
593
  }
404
- sql += " " + fd.auto;
405
- } else if (fd.dflt_value) {
406
- if (fd.dflt_value === '0000-00-00 00:00:00') {
407
- fd.dflt_value = '1970-01-01 00:00:00';
408
- }
409
- sql += " DEFAULT " + fd.dflt_value;
594
+ return sql;
595
+ } catch (err) {
596
+ $.log.error(`[DB] [getTableData] 获取表数据失败: ${err.message}`);
597
+ return '';
410
598
  }
411
- if (fd.note) {
412
- sql += " COMMENT '" + fd.note + "'";
599
+ };
600
+
601
+ /**
602
+ * @description 清空表数据
603
+ * @param {String} table 表名
604
+ * @param {Number} timeout 超时时间(毫秒)
605
+ * @return {Promise|Number} 操作结果
606
+ */
607
+ DB.prototype.emptyTable = function(table, timeout = 15000) {
608
+ if (!table || typeof table !== 'string') {
609
+ $.log.error("[DB] [emptyTable] 表名无效");
610
+ return 0;
611
+ }
612
+ return this.exec("TRUNCATE TABLE \`" + table + "\`;", timeout);
613
+ };
614
+
615
+ /**
616
+ * @description 检查表是否存在
617
+ * @param {String} table 表名
618
+ * @param {Number} timeout 超时时间(毫秒)
619
+ * @return {Promise|Boolean} 是否存在
620
+ */
621
+ DB.prototype.hasTable = async function(table, timeout = 15000) {
622
+ if (!table || typeof table !== 'string') {
623
+ return false;
413
624
  }
414
- if (fd.pk) {
415
- sql += " PRIMARY KEY";
625
+ try {
626
+ const list = await this.run("SHOW TABLES LIKE '" + table + "'", [], timeout);
627
+ return list && list.length > 0;
628
+ } catch (err) {
629
+ $.log.error(`[DB] [hasTable] 检查表是否存在失败: ${err.message}`);
630
+ return false;
416
631
  }
417
- return sql;
418
632
  };
419
633
 
420
634
  /**
421
- * @description 修改字段名称
635
+ * @description 检查字段是否存在
636
+ * @param {String} table 表名
422
637
  * @param {String} field 字段名
423
- * @param {String} name 变更后名称
424
- * @return {Promise|Number} 修改成功返回1,失败返回0
638
+ * @param {Number} timeout 超时时间(毫秒)
639
+ * @return {Promise|Boolean} 是否存在
425
640
  */
426
- DB.prototype.field_name = async function(field, name) {
427
- var fields = await this.fields(this.table, field);
428
- if (!fields) {
429
- return -1;
641
+ DB.prototype.hasField = async function(table, field, timeout = 15000) {
642
+ if (!table || !field) {
643
+ return false;
430
644
  }
431
- if (fields.length === 0) {
645
+ try {
646
+ const list = await this.fields(table, field, timeout);
647
+ return list && list.length > 0;
648
+ } catch (err) {
649
+ $.log.error(`[DB] [hasField] 检查字段是否存在失败: ${err.message}`);
650
+ return false;
651
+ }
652
+ };
653
+
654
+ /**
655
+ * @description 备份表
656
+ * @param {String} table 表名
657
+ * @param {String} backup 备份表名
658
+ * @param {Number} timeout 超时时间(毫秒)
659
+ * @return {Promise|Number} 操作结果
660
+ */
661
+ DB.prototype.backupTable = function(table, backup, timeout = 60000) {
662
+ if (!table || !backup) {
663
+ $.log.error("[DB] [backupTable] 参数不完整");
432
664
  return 0;
433
665
  }
434
- var sql_sub = this.field_sql(fields[0]);
435
- sql_sub = sql_sub.replace("`" + field + "`", "`" + name + "`").replace(' PRIMARY KEY', '');
436
- var sql = "alter table `{0}` change `{1}` " + sql_sub;
437
- sql = sql.replace('{0}', this.table).replace('{1}', field);
438
- return await this.exec(sql);
666
+ return this.exec("CREATE TABLE \`" + backup + "\` LIKE \`" + table + "\`; INSERT INTO \`" + backup + "\` SELECT * FROM \`" + table + "\`;", timeout);
439
667
  };
440
668
 
441
- exports.DB = DB;
669
+ module.exports = {
670
+ DB
671
+ };