mm_mysql 2.0.2 → 2.0.4
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/README.md +266 -234
- package/db.js +516 -286
- package/index.js +1022 -394
- package/package.json +13 -12
- package/sql.js +993 -396
- package/config.json +0 -8
- package/link_model.js +0 -132
- package/sql.json +0 -56
- package/test.js +0 -846
- package/upgrade.sql +0 -34
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 {
|
|
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
|
-
|
|
62
|
+
const db = this.parent().db();
|
|
46
63
|
db.table = table;
|
|
47
64
|
if (key) {
|
|
48
65
|
db.key = key;
|
|
49
66
|
} else {
|
|
50
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
76
|
-
|
|
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
|
-
|
|
85
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
|
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
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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 {
|
|
287
|
-
* @
|
|
341
|
+
* @description 删除表
|
|
342
|
+
* @param {String} table 表名
|
|
343
|
+
* @param {Number} timeout 超时时间(毫秒)
|
|
344
|
+
* @return {Promise|Number} 操作结果
|
|
288
345
|
*/
|
|
289
|
-
DB.prototype.
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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
|
|
299
|
-
* @param {String
|
|
300
|
-
* @param {Boolean} not_null
|
|
301
|
-
* @param {Boolean} auto
|
|
302
|
-
* @param {
|
|
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,
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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
|
-
|
|
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
|
|
330
|
-
* @param {String
|
|
331
|
-
* @param {Boolean} not_null
|
|
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
|
-
* @
|
|
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
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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
|
-
|
|
349
|
-
|
|
350
|
-
|
|
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
|
-
|
|
354
|
-
|
|
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
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
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
|
|
516
|
+
return this.exec("ALTER TABLE \`" + table + "\` CHANGE \`" + field + "\` \`" + new_field + "\` " + type + ";", timeout);
|
|
365
517
|
};
|
|
366
518
|
|
|
367
519
|
/**
|
|
368
|
-
* @description
|
|
369
|
-
* @param {String}
|
|
370
|
-
* @
|
|
520
|
+
* @description 获取表创建语句
|
|
521
|
+
* @param {String} table 表名
|
|
522
|
+
* @param {Number} timeout 超时时间(毫秒)
|
|
523
|
+
* @return {Promise|String} 创建语句
|
|
371
524
|
*/
|
|
372
|
-
DB.prototype.
|
|
373
|
-
|
|
374
|
-
"
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
return
|
|
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
|
|
388
|
-
* @param {
|
|
389
|
-
* @
|
|
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.
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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 +=
|
|
592
|
+
sql += '\n';
|
|
403
593
|
}
|
|
404
|
-
sql
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
|
|
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
|
-
|
|
412
|
-
|
|
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
|
-
|
|
415
|
-
|
|
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 {
|
|
424
|
-
* @return {Promise|
|
|
638
|
+
* @param {Number} timeout 超时时间(毫秒)
|
|
639
|
+
* @return {Promise|Boolean} 是否存在
|
|
425
640
|
*/
|
|
426
|
-
DB.prototype.
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
return -1;
|
|
641
|
+
DB.prototype.hasField = async function(table, field, timeout = 15000) {
|
|
642
|
+
if (!table || !field) {
|
|
643
|
+
return false;
|
|
430
644
|
}
|
|
431
|
-
|
|
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
|
-
|
|
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
|
|
669
|
+
module.exports = {
|
|
670
|
+
DB
|
|
671
|
+
};
|