mm_mysql 2.2.9 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +316 -87
- package/package.json +1 -1
- package/test_backup.js +0 -101
- package/test_backup_with_comments.js +0 -97
- package/test_parser.js +0 -76
package/index.js
CHANGED
|
@@ -10,12 +10,7 @@ const { DB } = require('./db');
|
|
|
10
10
|
*/
|
|
11
11
|
class Mysql extends Base {
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
14
|
-
* MySQL2支持的连接池配置参数:
|
|
15
|
-
* - host, port, user, password, database
|
|
16
|
-
* - charset, timezone, connect_timeout
|
|
17
|
-
* - connection_limit, acquire_timeout, wait_for_connections
|
|
18
|
-
* - queueLimit参数在MySQL2中可能不被支持,已移除
|
|
13
|
+
* 默认配置(优化版,统一使用连接池)
|
|
19
14
|
*/
|
|
20
15
|
static config = {
|
|
21
16
|
host: '127.0.0.1',
|
|
@@ -25,28 +20,154 @@ class Mysql extends Base {
|
|
|
25
20
|
database: '',
|
|
26
21
|
charset: 'utf8mb4',
|
|
27
22
|
timezone: '+08:00',
|
|
28
|
-
connect_timeout:
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
23
|
+
connect_timeout: 15000,
|
|
24
|
+
|
|
25
|
+
// 连接池优化配置(针对高并发写入场景)
|
|
26
|
+
connection_limit: 100, // 减少连接数,避免资源竞争
|
|
27
|
+
acquire_timeout: 30000, // 延长获取连接超时时间
|
|
28
|
+
wait_for_connections: true, // 保持连接等待机制
|
|
29
|
+
idle_timeout: 300000, // 延长空闲连接超时时间(5分钟)
|
|
30
|
+
max_idle: 30, // 减少最大空闲连接数
|
|
31
|
+
queue_timeout: 60000, // 队列超时时间
|
|
32
|
+
|
|
33
|
+
// 预处理语句缓存优化配置
|
|
34
|
+
statement_cache_size: 50, // 减少缓存大小,避免内存泄漏
|
|
35
|
+
max_prepared_statement_age: 300000, // 延长过期时间(5分钟)
|
|
36
|
+
enable_statement_pooling: true // 启用预处理语句缓存
|
|
32
37
|
};
|
|
33
38
|
|
|
34
39
|
/**
|
|
35
|
-
*
|
|
40
|
+
* 构造函数(统一使用连接池)
|
|
36
41
|
* @param {object} config - 配置对象
|
|
37
42
|
*/
|
|
38
43
|
constructor(config = {}) {
|
|
39
44
|
super({ ...Mysql.config, ...config});
|
|
40
45
|
this._pool = null;
|
|
41
|
-
|
|
42
|
-
this.
|
|
46
|
+
// 移除单连接支持,统一使用连接池
|
|
47
|
+
this._use_pool = true; // 强制使用连接池模式
|
|
48
|
+
|
|
43
49
|
// 错误信息
|
|
44
50
|
this.error = null;
|
|
45
51
|
// 最近执行的SQL语句
|
|
46
52
|
this.sql = '';
|
|
53
|
+
|
|
54
|
+
// 预处理语句缓存
|
|
55
|
+
this._prepared_stmts = new Map();
|
|
56
|
+
this._stmt_cache_size = this.config.statement_cache_size || 100;
|
|
57
|
+
this._cache_hits = 0;
|
|
58
|
+
this._cache_misses = 0;
|
|
59
|
+
|
|
60
|
+
// 启动定期清理任务
|
|
61
|
+
this._startCleanupTask();
|
|
47
62
|
}
|
|
48
63
|
}
|
|
49
64
|
|
|
65
|
+
/**
|
|
66
|
+
* 启动预处理语句清理任务
|
|
67
|
+
* @private
|
|
68
|
+
*/
|
|
69
|
+
Mysql.prototype._startCleanupTask = function() {
|
|
70
|
+
if (this._cleanup_interval) {
|
|
71
|
+
clearInterval(this._cleanup_interval);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
this._cleanup_interval = setInterval(() => {
|
|
75
|
+
this._cleanupExpiredStatements();
|
|
76
|
+
}, 60000); // 每分钟清理一次
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 清理过期预处理语句
|
|
81
|
+
* @private
|
|
82
|
+
*/
|
|
83
|
+
Mysql.prototype._cleanupExpiredStatements = function() {
|
|
84
|
+
const now = Date.now();
|
|
85
|
+
const max_age = this.config.max_prepared_statement_age || 300000;
|
|
86
|
+
|
|
87
|
+
for (const [key, statement_info] of this._prepared_stmts) {
|
|
88
|
+
if (now - statement_info.last_used > max_age) {
|
|
89
|
+
// 异步关闭语句,不阻塞主线程
|
|
90
|
+
if (statement_info.statement && typeof statement_info.statement.close === 'function') {
|
|
91
|
+
statement_info.statement.close().catch(() => {});
|
|
92
|
+
}
|
|
93
|
+
this._prepared_stmts.delete(key);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* 生成预处理语句缓存键
|
|
100
|
+
* @private
|
|
101
|
+
* @param {string} sql - SQL语句
|
|
102
|
+
* @returns {string} 缓存键
|
|
103
|
+
*/
|
|
104
|
+
Mysql.prototype._getStatementCacheKey = function(sql) {
|
|
105
|
+
// 简化SQL语句,移除多余空格和换行,提高缓存命中率
|
|
106
|
+
return sql.trim().replace(/\s+/g, ' ').toLowerCase();
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* 缓存预处理语句
|
|
111
|
+
* @private
|
|
112
|
+
* @param {string} key - 缓存键
|
|
113
|
+
* @param {object} statement - 预处理语句对象
|
|
114
|
+
*/
|
|
115
|
+
Mysql.prototype._cacheStatement = function(key, statement) {
|
|
116
|
+
if (this._prepared_stmts.size >= this._stmt_cache_size) {
|
|
117
|
+
// 移除最久未使用的语句
|
|
118
|
+
const oldest_key = this._findOldestStatement();
|
|
119
|
+
if (oldest_key) {
|
|
120
|
+
const oldest_statement = this._prepared_stmts.get(oldest_key);
|
|
121
|
+
if (oldest_statement.statement && typeof oldest_statement.statement.close === 'function') {
|
|
122
|
+
oldest_statement.statement.close().catch(() => {});
|
|
123
|
+
}
|
|
124
|
+
this._prepared_stmts.delete(oldest_key);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
this._prepared_stmts.set(key, {
|
|
129
|
+
statement: statement,
|
|
130
|
+
last_used: Date.now(),
|
|
131
|
+
use_count: 0
|
|
132
|
+
});
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* 查找最久未使用的预处理语句
|
|
137
|
+
* @private
|
|
138
|
+
* @returns {string} 缓存键
|
|
139
|
+
*/
|
|
140
|
+
Mysql.prototype._findOldestStatement = function() {
|
|
141
|
+
let oldest_key = null;
|
|
142
|
+
let oldest_time = Date.now();
|
|
143
|
+
|
|
144
|
+
for (const [key, statement_info] of this._prepared_stmts) {
|
|
145
|
+
if (statement_info.last_used < oldest_time) {
|
|
146
|
+
oldest_time = statement_info.last_used;
|
|
147
|
+
oldest_key = key;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return oldest_key;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* 获取预处理语句缓存统计信息
|
|
156
|
+
* @returns {object} 统计信息
|
|
157
|
+
*/
|
|
158
|
+
Mysql.prototype.getStatementStats = function() {
|
|
159
|
+
const total = this._cache_hits + this._cache_misses;
|
|
160
|
+
const hit_rate = total > 0 ? (this._cache_hits / total) * 100 : 0;
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
cached_statements: this._prepared_stmts.size,
|
|
164
|
+
cache_hits: this._cache_hits,
|
|
165
|
+
cache_misses: this._cache_misses,
|
|
166
|
+
cache_hit_rate: hit_rate.toFixed(2) + '%',
|
|
167
|
+
max_cache_size: this._stmt_cache_size
|
|
168
|
+
};
|
|
169
|
+
};
|
|
170
|
+
|
|
50
171
|
/**
|
|
51
172
|
* 获取MySQL2支持的配置参数
|
|
52
173
|
* 根据连接类型(连接池或单连接)过滤参数,避免警告
|
|
@@ -105,59 +226,58 @@ Mysql.prototype._getValidConfig = function (is_pool = false) {
|
|
|
105
226
|
};
|
|
106
227
|
|
|
107
228
|
/**
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
Mysql.prototype.open = async function () {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
229
|
+
* 打开数据库连接(统一使用连接池)
|
|
230
|
+
* @returns {Promise<boolean>}
|
|
231
|
+
* @throws {TypeError} 当配置参数无效时
|
|
232
|
+
*/
|
|
233
|
+
Mysql.prototype.open = async function () {
|
|
234
|
+
// 参数校验
|
|
235
|
+
if (!this.config || typeof this.config !== 'object') {
|
|
236
|
+
throw new TypeError('config must be object');
|
|
237
|
+
}
|
|
117
238
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const valid_config = this._getValidConfig(this._usePool);
|
|
239
|
+
try {
|
|
240
|
+
// 统一使用连接池模式
|
|
241
|
+
const valid_config = this._getValidConfig(true);
|
|
122
242
|
this._pool = mysql.createPool(valid_config);
|
|
243
|
+
|
|
123
244
|
// 测试连接池连接
|
|
124
245
|
const conn = await this._pool.getConnection();
|
|
125
246
|
conn.release();
|
|
126
|
-
} else {
|
|
127
|
-
const valid_config = this._getValidConfig(this._usePool);
|
|
128
|
-
this._connection = await mysql.createConnection(valid_config);
|
|
129
|
-
}
|
|
130
247
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
};
|
|
248
|
+
this.log('info', '数据库连接池创建成功');
|
|
249
|
+
return true;
|
|
250
|
+
} catch (error) {
|
|
251
|
+
this.log('error', '数据库连接池创建失败', error);
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
};
|
|
138
255
|
|
|
139
256
|
/**
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
Mysql.prototype.close = async function () {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
257
|
+
* 关闭数据库连接(统一使用连接池)
|
|
258
|
+
* @returns {Promise<boolean>}
|
|
259
|
+
*/
|
|
260
|
+
Mysql.prototype.close = async function () {
|
|
261
|
+
try {
|
|
262
|
+
// 先关闭连接池,再清理预处理语句(避免连接状态问题)
|
|
263
|
+
if (this._pool) {
|
|
264
|
+
await this._pool.end();
|
|
265
|
+
this._pool = null;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// 连接池关闭后,预处理语句会自动失效,只需清理缓存
|
|
269
|
+
if (this._prepared_stmts) {
|
|
270
|
+
// 不再尝试关闭预处理语句,直接清空缓存
|
|
271
|
+
this._prepared_stmts.clear();
|
|
272
|
+
}
|
|
153
273
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
};
|
|
274
|
+
this.log('info', '数据库连接池已关闭');
|
|
275
|
+
return true;
|
|
276
|
+
} catch (error) {
|
|
277
|
+
this.log('error', '关闭连接池失败', error);
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
};
|
|
161
281
|
|
|
162
282
|
/**
|
|
163
283
|
* 安全释放数据库连接
|
|
@@ -178,31 +298,28 @@ async function safeReleaseConnection(conn, is_pool_conn) {
|
|
|
178
298
|
};
|
|
179
299
|
|
|
180
300
|
/**
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
Mysql.prototype.getConn = async function () {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
301
|
+
* 获取数据库连接(统一使用连接池)
|
|
302
|
+
* @returns {Promise<object>}
|
|
303
|
+
* @throws {TypeError} 当连接池不存在时
|
|
304
|
+
*/
|
|
305
|
+
Mysql.prototype.getConn = async function () {
|
|
306
|
+
// 参数校验
|
|
307
|
+
if (!this._pool) {
|
|
308
|
+
await this.open();
|
|
309
|
+
}
|
|
190
310
|
|
|
191
|
-
|
|
192
|
-
|
|
311
|
+
try {
|
|
312
|
+
// 统一从连接池获取连接
|
|
193
313
|
const conn = await this._pool.getConnection();
|
|
194
314
|
return conn;
|
|
195
|
-
}
|
|
196
|
-
|
|
315
|
+
} catch (error) {
|
|
316
|
+
this.log('error', '从连接池获取连接失败', error);
|
|
317
|
+
throw error; // 重新抛出错误,让调用方处理
|
|
197
318
|
}
|
|
198
|
-
}
|
|
199
|
-
this.log('error', '获取连接失败', error);
|
|
200
|
-
}
|
|
201
|
-
return null;
|
|
202
|
-
};
|
|
319
|
+
};
|
|
203
320
|
|
|
204
321
|
/**
|
|
205
|
-
* 执行SQL
|
|
322
|
+
* 执行SQL查询(优化版,支持预处理语句缓存)
|
|
206
323
|
* @param {string} sql - SQL语句
|
|
207
324
|
* @param {Array} params - 参数数组
|
|
208
325
|
* @returns {Promise<object>}
|
|
@@ -225,11 +342,64 @@ Mysql.prototype.run = async function (sql, params = []) {
|
|
|
225
342
|
try {
|
|
226
343
|
// 获取连接
|
|
227
344
|
conn = await this.getConn();
|
|
228
|
-
is_pool_conn = this.
|
|
229
|
-
|
|
230
|
-
//
|
|
231
|
-
|
|
232
|
-
|
|
345
|
+
is_pool_conn = this._use_pool;
|
|
346
|
+
|
|
347
|
+
// 检查是否启用预处理语句缓存
|
|
348
|
+
if (this.config.enable_statement_pooling && params.length > 0) {
|
|
349
|
+
const cache_key = this._getStatementCacheKey(sql);
|
|
350
|
+
let statement_info = this._prepared_stmts.get(cache_key);
|
|
351
|
+
|
|
352
|
+
if (statement_info && statement_info.statement) {
|
|
353
|
+
// 使用缓存的预处理语句
|
|
354
|
+
this._cache_hits++;
|
|
355
|
+
statement_info.last_used = Date.now();
|
|
356
|
+
statement_info.use_count++;
|
|
357
|
+
|
|
358
|
+
// 预先检查连接状态,避免不必要的错误
|
|
359
|
+
if (!conn || conn._fatalError || conn._closed) {
|
|
360
|
+
conn = await this.getConn();
|
|
361
|
+
// 连接已更新,需要重新创建预处理语句
|
|
362
|
+
this._prepared_stmts.delete(cache_key);
|
|
363
|
+
const statement = await conn.prepare(sql);
|
|
364
|
+
this._cacheStatement(cache_key, statement);
|
|
365
|
+
const [rows] = await statement.execute(params);
|
|
366
|
+
return rows;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
try {
|
|
370
|
+
const [rows] = await statement_info.statement.execute(params);
|
|
371
|
+
return rows;
|
|
372
|
+
} catch (stmt_err) {
|
|
373
|
+
// 预处理语句可能已失效(连接关闭等),重新创建
|
|
374
|
+
this.log('warn', '预处理语句执行失败,重新创建', stmt_err);
|
|
375
|
+
this._prepared_stmts.delete(cache_key);
|
|
376
|
+
|
|
377
|
+
// 检查连接是否有效,如果无效则重新获取连接
|
|
378
|
+
if (!conn || conn._fatalError || conn._closed) {
|
|
379
|
+
conn = await this.getConn();
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// 重新创建预处理语句
|
|
383
|
+
const statement = await conn.prepare(sql);
|
|
384
|
+
this._cacheStatement(cache_key, statement);
|
|
385
|
+
|
|
386
|
+
const [rows] = await statement.execute(params);
|
|
387
|
+
return rows;
|
|
388
|
+
}
|
|
389
|
+
} else {
|
|
390
|
+
// 创建新的预处理语句并缓存
|
|
391
|
+
this._cache_misses++;
|
|
392
|
+
const statement = await conn.prepare(sql);
|
|
393
|
+
this._cacheStatement(cache_key, statement);
|
|
394
|
+
|
|
395
|
+
const [rows] = await statement.execute(params);
|
|
396
|
+
return rows;
|
|
397
|
+
}
|
|
398
|
+
} else {
|
|
399
|
+
// 不使用预处理语句缓存,直接查询
|
|
400
|
+
const [rows] = await conn.query(sql, params);
|
|
401
|
+
return rows;
|
|
402
|
+
}
|
|
233
403
|
} catch (err) {
|
|
234
404
|
this.sql = err.sql;
|
|
235
405
|
this.error = {
|
|
@@ -243,6 +413,9 @@ Mysql.prototype.run = async function (sql, params = []) {
|
|
|
243
413
|
this.log('error', '释放连接失败', release_err);
|
|
244
414
|
}
|
|
245
415
|
connection_released = true;
|
|
416
|
+
|
|
417
|
+
// 重新抛出错误,让调用方处理
|
|
418
|
+
throw err;
|
|
246
419
|
} finally {
|
|
247
420
|
// 确保连接被释放,避免资源泄漏
|
|
248
421
|
if (!connection_released) {
|
|
@@ -257,7 +430,7 @@ Mysql.prototype.run = async function (sql, params = []) {
|
|
|
257
430
|
};
|
|
258
431
|
|
|
259
432
|
/**
|
|
260
|
-
* 执行SQL
|
|
433
|
+
* 执行SQL语句(优化版,支持预处理语句缓存)
|
|
261
434
|
* @param {string} sql - SQL语句
|
|
262
435
|
* @param {Array} params - 参数数组
|
|
263
436
|
* @returns {Promise<object>}
|
|
@@ -279,11 +452,64 @@ Mysql.prototype.exec = async function (sql, params = []) {
|
|
|
279
452
|
try {
|
|
280
453
|
// 获取连接
|
|
281
454
|
conn = await this.getConn();
|
|
282
|
-
is_pool_conn = this.
|
|
283
|
-
|
|
284
|
-
//
|
|
285
|
-
|
|
286
|
-
|
|
455
|
+
is_pool_conn = this._use_pool;
|
|
456
|
+
|
|
457
|
+
// 检查是否启用预处理语句缓存
|
|
458
|
+
if (this.config.enable_statement_pooling && params.length > 0) {
|
|
459
|
+
const cache_key = this._getStatementCacheKey(sql);
|
|
460
|
+
let statement_info = this._prepared_stmts.get(cache_key);
|
|
461
|
+
|
|
462
|
+
if (statement_info && statement_info.statement) {
|
|
463
|
+
// 使用缓存的预处理语句
|
|
464
|
+
this._cache_hits++;
|
|
465
|
+
statement_info.last_used = Date.now();
|
|
466
|
+
statement_info.use_count++;
|
|
467
|
+
|
|
468
|
+
// 预先检查连接状态,避免不必要的错误
|
|
469
|
+
if (!conn || conn._fatalError || conn._closed) {
|
|
470
|
+
conn = await this.getConn();
|
|
471
|
+
// 连接已更新,需要重新创建预处理语句
|
|
472
|
+
this._prepared_stmts.delete(cache_key);
|
|
473
|
+
const statement = await conn.prepare(sql);
|
|
474
|
+
this._cacheStatement(cache_key, statement);
|
|
475
|
+
const [result] = await statement.execute(params);
|
|
476
|
+
return result.insertId || result.affectedRows || result.changedRows || 1;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
try {
|
|
480
|
+
const [result] = await statement_info.statement.execute(params);
|
|
481
|
+
return result.insertId || result.affectedRows || result.changedRows || 1;
|
|
482
|
+
} catch (stmt_err) {
|
|
483
|
+
// 预处理语句可能已失效(连接关闭等),重新创建
|
|
484
|
+
this.log('warn', '预处理语句执行失败,重新创建', stmt_err);
|
|
485
|
+
this._prepared_stmts.delete(cache_key);
|
|
486
|
+
|
|
487
|
+
// 检查连接是否有效,如果无效则重新获取连接
|
|
488
|
+
if (!conn || conn._fatalError || conn._closed) {
|
|
489
|
+
conn = await this.getConn();
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// 重新创建预处理语句
|
|
493
|
+
const statement = await conn.prepare(sql);
|
|
494
|
+
this._cacheStatement(cache_key, statement);
|
|
495
|
+
|
|
496
|
+
const [result] = await statement.execute(params);
|
|
497
|
+
return result.insertId || result.affectedRows || result.changedRows || 1;
|
|
498
|
+
}
|
|
499
|
+
} else {
|
|
500
|
+
// 创建新的预处理语句并缓存
|
|
501
|
+
this._cache_misses++;
|
|
502
|
+
const statement = await conn.prepare(sql);
|
|
503
|
+
this._cacheStatement(cache_key, statement);
|
|
504
|
+
|
|
505
|
+
const [result] = await statement.execute(params);
|
|
506
|
+
return result.insertId || result.affectedRows || result.changedRows || 1;
|
|
507
|
+
}
|
|
508
|
+
} else {
|
|
509
|
+
// 对于写入语句,直接执行(不使用预处理语句缓存)
|
|
510
|
+
const [result] = await conn.execute(sql, params);
|
|
511
|
+
return result.insertId || result.affectedRows || result.changedRows || 1;
|
|
512
|
+
}
|
|
287
513
|
} catch (error) {
|
|
288
514
|
this.log('error', 'SQL执行失败', error);
|
|
289
515
|
this.sql = error.sql;
|
|
@@ -298,6 +524,9 @@ Mysql.prototype.exec = async function (sql, params = []) {
|
|
|
298
524
|
this.log('error', '释放连接失败', release_err);
|
|
299
525
|
}
|
|
300
526
|
connection_released = true;
|
|
527
|
+
|
|
528
|
+
// 重新抛出错误,让调用方处理
|
|
529
|
+
throw error;
|
|
301
530
|
} finally {
|
|
302
531
|
// 确保连接被释放,避免资源泄漏
|
|
303
532
|
if (!connection_released) {
|
package/package.json
CHANGED
package/test_backup.js
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileOverview 备份恢复功能测试脚本
|
|
3
|
-
* @version 1.0
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const {Sql} = require('./sql');
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
// 模拟数据库操作函数
|
|
10
|
-
async function mockRun(sql) {
|
|
11
|
-
console.log('执行查询SQL:', sql);
|
|
12
|
-
|
|
13
|
-
// 模拟返回数据
|
|
14
|
-
if (sql.includes('SELECT')) {
|
|
15
|
-
return [
|
|
16
|
-
{ id: 1, name: '张三', age: 25 },
|
|
17
|
-
{ id: 2, name: '李四', age: 30 },
|
|
18
|
-
{ id: 3, name: '王五', age: 28 }
|
|
19
|
-
];
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
if (sql.includes('count')) {
|
|
23
|
-
return [{ count: 3 }];
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// 模拟SHOW CREATE TABLE查询
|
|
27
|
-
if (sql.includes('SHOW CREATE TABLE')) {
|
|
28
|
-
return [{
|
|
29
|
-
'Create Table': 'CREATE TABLE `test_table` (\n `id` int(11) NOT NULL AUTO_INCREMENT,\n `name` varchar(50) NOT NULL,\n `age` int(11) DEFAULT NULL,\n PRIMARY KEY (`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4'
|
|
30
|
-
}];
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return [];
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async function mockExec(sql) {
|
|
37
|
-
console.log('执行更新SQL:', sql);
|
|
38
|
-
return { affectedRows: 1, insertId: 1 };
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
async function testBackupRestore() {
|
|
42
|
-
console.log('=== 测试MySQL备份恢复功能 ===');
|
|
43
|
-
|
|
44
|
-
// 创建 SQL 实例
|
|
45
|
-
const sql = new Sql(mockRun, mockExec);
|
|
46
|
-
sql.table = 'test_table';
|
|
47
|
-
|
|
48
|
-
const backupFile1 = './backup/test_mysql_backup1.sql';
|
|
49
|
-
const backupFile2 = './backup/test_mysql_backup2.sql';
|
|
50
|
-
|
|
51
|
-
try {
|
|
52
|
-
// 测试不带创建表语句的备份
|
|
53
|
-
console.log('\n1. 测试不带创建表语句的备份...');
|
|
54
|
-
const backupResult1 = await sql.backup(backupFile1);
|
|
55
|
-
console.log('备份结果:', backupResult1 ? '成功' : '失败');
|
|
56
|
-
|
|
57
|
-
if (backupResult1) {
|
|
58
|
-
// 读取备份文件内容
|
|
59
|
-
const fs = require('fs');
|
|
60
|
-
if (fs.existsSync(backupFile1)) {
|
|
61
|
-
const content = fs.readFileSync(backupFile1, 'utf8');
|
|
62
|
-
console.log('备份文件内容:');
|
|
63
|
-
console.log(content);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// 测试带创建表语句的备份
|
|
68
|
-
console.log('\n2. 测试带创建表语句的备份...');
|
|
69
|
-
const backupResult2 = await sql.backup(backupFile2, true);
|
|
70
|
-
console.log('备份结果:', backupResult2 ? '成功' : '失败');
|
|
71
|
-
|
|
72
|
-
if (backupResult2) {
|
|
73
|
-
// 读取备份文件内容
|
|
74
|
-
const fs = require('fs');
|
|
75
|
-
if (fs.existsSync(backupFile2)) {
|
|
76
|
-
const content = fs.readFileSync(backupFile2, 'utf8');
|
|
77
|
-
console.log('备份文件内容:');
|
|
78
|
-
console.log(content);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// 测试恢复
|
|
83
|
-
console.log('\n3. 测试恢复功能...');
|
|
84
|
-
const restoreResult = await sql.restore(backupFile2);
|
|
85
|
-
console.log('恢复结果:', restoreResult ? '成功' : '失败');
|
|
86
|
-
|
|
87
|
-
console.log('\n=== 测试完成 ===');
|
|
88
|
-
|
|
89
|
-
} catch (error) {
|
|
90
|
-
console.error('测试出错:', error.message);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// 运行测试
|
|
95
|
-
if (require.main === module) {
|
|
96
|
-
testBackupRestore();
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
module.exports = {
|
|
100
|
-
testBackupRestore
|
|
101
|
-
};
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
const { Sql } = require('./sql');
|
|
2
|
-
|
|
3
|
-
// 创建模拟的数据库操作函数,包含注释信息
|
|
4
|
-
const mockRun = function(sql) {
|
|
5
|
-
console.log('执行查询SQL:', sql);
|
|
6
|
-
|
|
7
|
-
// 模拟查询结果
|
|
8
|
-
if (sql.includes('SHOW CREATE TABLE')) {
|
|
9
|
-
return [{
|
|
10
|
-
'Create Table': `CREATE TABLE \`test_table\` (
|
|
11
|
-
\`id\` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
|
12
|
-
\`name\` varchar(50) NOT NULL COMMENT '用户姓名',
|
|
13
|
-
\`age\` int(11) DEFAULT NULL COMMENT '用户年龄',
|
|
14
|
-
PRIMARY KEY (\`id\`)
|
|
15
|
-
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='测试用户表'`
|
|
16
|
-
}];
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// 模拟表注释查询
|
|
20
|
-
if (sql.includes('information_schema.TABLES')) {
|
|
21
|
-
return [{
|
|
22
|
-
TABLE_COMMENT: '测试用户表'
|
|
23
|
-
}];
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// 模拟列注释查询
|
|
27
|
-
if (sql.includes('information_schema.COLUMNS')) {
|
|
28
|
-
return [
|
|
29
|
-
{ COLUMN_NAME: 'id', COLUMN_COMMENT: '主键ID' },
|
|
30
|
-
{ COLUMN_NAME: 'name', COLUMN_COMMENT: '用户姓名' },
|
|
31
|
-
{ COLUMN_NAME: 'age', COLUMN_COMMENT: '用户年龄' }
|
|
32
|
-
];
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// 模拟数据查询
|
|
36
|
-
if (sql.includes('SELECT * FROM')) {
|
|
37
|
-
return [
|
|
38
|
-
{ id: 1, name: '张三', age: 25 },
|
|
39
|
-
{ id: 2, name: '李四', age: 30 },
|
|
40
|
-
{ id: 3, name: '王五', age: 28 }
|
|
41
|
-
];
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return [];
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
const mockExec = function(sql) {
|
|
48
|
-
console.log('执行更新SQL:', sql);
|
|
49
|
-
return { affectedRows: 1 };
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
async function testBackupWithComments() {
|
|
53
|
-
console.log('=== 测试MySQL备份恢复功能(包含注释) ===');
|
|
54
|
-
|
|
55
|
-
// 创建 SQL 实例
|
|
56
|
-
const sql = new Sql(mockRun, mockExec);
|
|
57
|
-
sql.table = 'test_table';
|
|
58
|
-
|
|
59
|
-
const backupFile = './backup/test_mysql_backup_with_comments.sql';
|
|
60
|
-
|
|
61
|
-
try {
|
|
62
|
-
// 测试带创建表语句和注释的备份
|
|
63
|
-
console.log('\n1. 测试带创建表语句和注释的备份...');
|
|
64
|
-
const backupResult = await sql.backup(backupFile, true);
|
|
65
|
-
console.log('备份结果:', backupResult ? '成功' : '失败');
|
|
66
|
-
|
|
67
|
-
if (backupResult) {
|
|
68
|
-
// 读取备份文件内容
|
|
69
|
-
const fs = require('fs');
|
|
70
|
-
if (fs.existsSync(backupFile)) {
|
|
71
|
-
const content = fs.readFileSync(backupFile, 'utf8');
|
|
72
|
-
console.log('备份文件内容:');
|
|
73
|
-
console.log(content);
|
|
74
|
-
|
|
75
|
-
// 检查是否包含注释信息
|
|
76
|
-
if (content.includes("COMMENT '主键ID'") && content.includes("COMMENT '用户姓名'") &&
|
|
77
|
-
content.includes("COMMENT '用户年龄'") && content.includes("COMMENT='测试用户表'")) {
|
|
78
|
-
console.log('✅ 备份文件成功包含注释信息');
|
|
79
|
-
} else {
|
|
80
|
-
console.log('❌ 备份文件未包含注释信息');
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// 测试恢复功能
|
|
86
|
-
console.log('\n2. 测试恢复功能...');
|
|
87
|
-
const restoreResult = await sql.restore(backupFile);
|
|
88
|
-
console.log('恢复结果:', restoreResult ? '成功' : '失败');
|
|
89
|
-
|
|
90
|
-
console.log('\n=== 测试完成 ===');
|
|
91
|
-
|
|
92
|
-
} catch (error) {
|
|
93
|
-
console.error('测试出错:', error.message);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
testBackupWithComments().catch(console.error);
|
package/test_parser.js
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
const { Sql } = require('./sql');
|
|
2
|
-
|
|
3
|
-
// 创建模拟的数据库操作函数
|
|
4
|
-
const mockRun = function(sql) {
|
|
5
|
-
console.log('执行查询SQL:', sql);
|
|
6
|
-
// 模拟查询结果
|
|
7
|
-
if (sql.includes('SHOW CREATE TABLE')) {
|
|
8
|
-
return [{
|
|
9
|
-
'Create Table': `CREATE TABLE \`test_table\` (
|
|
10
|
-
\`id\` int(11) NOT NULL AUTO_INCREMENT,
|
|
11
|
-
\`name\` varchar(50) NOT NULL,
|
|
12
|
-
\`age\` int(11) DEFAULT NULL,
|
|
13
|
-
PRIMARY KEY (\`id\`)
|
|
14
|
-
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`
|
|
15
|
-
}];
|
|
16
|
-
}
|
|
17
|
-
return [];
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const mockExec = function(sql) {
|
|
21
|
-
console.log('执行更新SQL:', sql);
|
|
22
|
-
return { affectedRows: 1 };
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
async function testSqlParser() {
|
|
26
|
-
console.log('=== 测试SQL解析器注释处理功能 ===');
|
|
27
|
-
|
|
28
|
-
// 创建 SQL 实例
|
|
29
|
-
const sql = new Sql(mockRun, mockExec);
|
|
30
|
-
|
|
31
|
-
// 测试包含各种注释的SQL内容
|
|
32
|
-
const testSql = `
|
|
33
|
-
-- 这是一个单行注释
|
|
34
|
-
/* 这是一个多行注释
|
|
35
|
-
可以跨多行 */
|
|
36
|
-
CREATE TABLE IF NOT EXISTS \`test_table\` (
|
|
37
|
-
\`id\` int(11) NOT NULL AUTO_INCREMENT,
|
|
38
|
-
\`name\` varchar(50) NOT NULL,
|
|
39
|
-
\`age\` int(11) DEFAULT NULL,
|
|
40
|
-
PRIMARY KEY (\`id\`)
|
|
41
|
-
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
42
|
-
|
|
43
|
-
-- 另一个单行注释
|
|
44
|
-
INSERT INTO \`test_table\` (\`id\`, \`name\`, \`age\`) VALUES (1, '张三', 25);
|
|
45
|
-
/* 另一个多行注释 */
|
|
46
|
-
INSERT INTO \`test_table\` (\`id\`, \`name\`, \`age\`) VALUES (2, '李四', 30);
|
|
47
|
-
|
|
48
|
-
-- 包含字符串的测试
|
|
49
|
-
INSERT INTO \`test_table\` (\`id\`, \`name\`, \`age\`) VALUES (3, '王--五', 28);
|
|
50
|
-
INSERT INTO \`test_table\` (\`id\`, \`name\`, \`age\`) VALUES (4, '李/*四*/', 35);
|
|
51
|
-
`;
|
|
52
|
-
|
|
53
|
-
console.log('原始SQL内容:');
|
|
54
|
-
console.log(testSql);
|
|
55
|
-
console.log('\n解析后的SQL语句:');
|
|
56
|
-
|
|
57
|
-
const statements = sql._parseSqlStatements(testSql);
|
|
58
|
-
statements.forEach((stmt, index) => {
|
|
59
|
-
console.log(`\n语句 ${index + 1}:`);
|
|
60
|
-
console.log(stmt);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
console.log('\n=== 解析完成 ===');
|
|
64
|
-
console.log(`共解析出 ${statements.length} 个有效语句`);
|
|
65
|
-
|
|
66
|
-
// 验证解析结果
|
|
67
|
-
const expectedStatements = 5; // 应该解析出5个有效语句
|
|
68
|
-
if (statements.length === expectedStatements) {
|
|
69
|
-
console.log('✅ SQL解析器注释处理功能正常');
|
|
70
|
-
} else {
|
|
71
|
-
console.log('❌ SQL解析器注释处理功能异常');
|
|
72
|
-
console.log(`期望解析出 ${expectedStatements} 个语句,实际解析出 ${statements.length} 个`);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
testSqlParser().catch(console.error);
|