mm_sqlite 1.3.9 → 1.4.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 +88 -458
- package/package.json +1 -1
- package/pool.js +246 -0
- package/test_db/test.db +0 -0
- package/test_db/test_basic.db +0 -0
- package/test_db/test_core_methods.db +0 -0
package/index.js
CHANGED
|
@@ -3,6 +3,8 @@ const {
|
|
|
3
3
|
DB
|
|
4
4
|
} = require('./db');
|
|
5
5
|
const { Base } = require('mm_expand');
|
|
6
|
+
const SQLitePool = require('./pool');
|
|
7
|
+
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
10
|
* SQLite数据库操作类
|
|
@@ -21,23 +23,15 @@ class SQLite extends Base {
|
|
|
21
23
|
database: 'mm',
|
|
22
24
|
charset: 'utf8mb4',
|
|
23
25
|
timezone: '+08:00',
|
|
24
|
-
connect_timeout: 5000, //
|
|
25
|
-
acquire_timeout: 5000, //
|
|
26
|
-
query_timeout: 10000, //
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
max_reconnect_attempts: 0,
|
|
34
|
-
wait_for_connections: false,
|
|
35
|
-
// 连接池相关配置
|
|
36
|
-
pool_min: 1,
|
|
37
|
-
pool_max: 3, // 减少最大连接池大小
|
|
38
|
-
pool_acquire_timeout: 5000, // 减少连接池获取超时
|
|
39
|
-
pool_idle_timeout: 30000, // 减少空闲超时
|
|
40
|
-
pool_reap_interval: 5000 // 减少回收间隔
|
|
26
|
+
connect_timeout: 5000, // 连接超时时间
|
|
27
|
+
acquire_timeout: 5000, // 获取连接超时时间
|
|
28
|
+
query_timeout: 10000, // 查询超时时间
|
|
29
|
+
// 连接池配置(统一使用连接池模式)
|
|
30
|
+
pool_min: 1, // 最小连接数
|
|
31
|
+
pool_max: 3, // 最大连接数
|
|
32
|
+
pool_acquire_timeout: 5000, // 连接池获取超时
|
|
33
|
+
pool_idle_timeout: 30000, // 空闲超时
|
|
34
|
+
pool_reap_interval: 5000 // 回收间隔
|
|
41
35
|
};
|
|
42
36
|
|
|
43
37
|
/**
|
|
@@ -46,9 +40,6 @@ class SQLite extends Base {
|
|
|
46
40
|
*/
|
|
47
41
|
constructor(config) {
|
|
48
42
|
super(config);
|
|
49
|
-
this._pool_connections = [];
|
|
50
|
-
this._pool_available = [];
|
|
51
|
-
this._pool_waiting = [];
|
|
52
43
|
}
|
|
53
44
|
}
|
|
54
45
|
|
|
@@ -58,7 +49,6 @@ class SQLite extends Base {
|
|
|
58
49
|
*/
|
|
59
50
|
SQLite.prototype._preset = function () {
|
|
60
51
|
this._connection = null;
|
|
61
|
-
this._pool = null;
|
|
62
52
|
this._status = 'closed';
|
|
63
53
|
this._last_connect_time = 0;
|
|
64
54
|
this._reconnecting = false;
|
|
@@ -67,29 +57,8 @@ SQLite.prototype._preset = function () {
|
|
|
67
57
|
this._db = null;
|
|
68
58
|
this._open = false;
|
|
69
59
|
this._conn_retry_count = 0;
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
this._use_pool = this.config.connection_limit > 1;
|
|
73
|
-
this._pool_reaper = null;
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* 初始化服务
|
|
78
|
-
* @returns {Promise<void>}
|
|
79
|
-
*/
|
|
80
|
-
SQLite.prototype._initService = async function () {
|
|
81
|
-
if (this._is_inited) {
|
|
82
|
-
this.log('warn', 'SQLite服务已初始化');
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
try {
|
|
87
|
-
this.log('debug', '初始化SQLite服务', { config: this.config });
|
|
88
|
-
this._is_inited = true;
|
|
89
|
-
this.log('debug', 'SQLite服务初始化完成');
|
|
90
|
-
} catch (error) {
|
|
91
|
-
this.log('error', 'SQLite服务初始化失败', error);
|
|
92
|
-
}
|
|
60
|
+
// 初始化连接池实例
|
|
61
|
+
this._pool = new SQLitePool(this.config, this._getDbFilename.bind(this), this.log.bind(this));
|
|
93
62
|
};
|
|
94
63
|
|
|
95
64
|
/**
|
|
@@ -106,24 +75,7 @@ SQLite.prototype._getDbFilename = function () {
|
|
|
106
75
|
};
|
|
107
76
|
|
|
108
77
|
/**
|
|
109
|
-
*
|
|
110
|
-
* @private
|
|
111
|
-
* @returns {Promise<void>}
|
|
112
|
-
*/
|
|
113
|
-
SQLite.prototype._openInternal = async function () {
|
|
114
|
-
return new Promise((resolve, reject) => {
|
|
115
|
-
this._db = new sqlite3.Database(this._getDbFilename(), (err) => {
|
|
116
|
-
if (err) {
|
|
117
|
-
reject(err);
|
|
118
|
-
} else {
|
|
119
|
-
resolve();
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* 打开数据库连接
|
|
78
|
+
* 打开数据库连接(统一使用连接池模式)
|
|
127
79
|
* @param {number} timeout - 超时时间(毫秒)
|
|
128
80
|
* @returns {Promise<boolean>}
|
|
129
81
|
* @throws {TypeError} 当timeout参数无效时
|
|
@@ -139,65 +91,16 @@ SQLite.prototype.open = async function (timeout) {
|
|
|
139
91
|
return true;
|
|
140
92
|
}
|
|
141
93
|
|
|
142
|
-
const connect_timeout = timeout || this.config.connect_timeout || 10000;
|
|
143
94
|
this._status = 'connecting';
|
|
144
95
|
|
|
145
96
|
try {
|
|
146
97
|
this.config.dir.addDir();
|
|
147
|
-
if (this._use_pool) {
|
|
148
|
-
// 使用连接池模式
|
|
149
|
-
await this._initPool();
|
|
150
|
-
this._last_connect_time = Date.now();
|
|
151
|
-
this._status = 'connected';
|
|
152
|
-
this.log('info', '数据库连接池初始化成功', {
|
|
153
|
-
dir: this.config.dir,
|
|
154
|
-
database: this.config.database,
|
|
155
|
-
pool_min: this.config.pool_min,
|
|
156
|
-
pool_max: this.config.pool_max
|
|
157
|
-
});
|
|
158
|
-
return true;
|
|
159
|
-
} else {
|
|
160
|
-
// 使用单连接模式 - 替换Promise.race为更可靠的超时控制
|
|
161
|
-
let timeout_id = null;
|
|
162
|
-
let connection_success = false;
|
|
163
|
-
|
|
164
|
-
try {
|
|
165
|
-
// 设置超时控制
|
|
166
|
-
const timeout_promise = new Promise((resolve, reject) => {
|
|
167
|
-
timeout_id = setTimeout(() => {
|
|
168
|
-
if (!connection_success) {
|
|
169
|
-
reject(new Error(`数据库连接超时(${connect_timeout}ms)`));
|
|
170
|
-
}
|
|
171
|
-
}, connect_timeout);
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
// 执行连接
|
|
175
|
-
await this._openInternal();
|
|
176
|
-
connection_success = true;
|
|
177
|
-
|
|
178
|
-
// 清除超时定时器
|
|
179
|
-
if (timeout_id) {
|
|
180
|
-
clearTimeout(timeout_id);
|
|
181
|
-
timeout_id = null;
|
|
182
|
-
}
|
|
183
98
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
});
|
|
190
|
-
return true;
|
|
191
|
-
} catch (connection_err) {
|
|
192
|
-
// 确保超时定时器被清除
|
|
193
|
-
if (timeout_id) {
|
|
194
|
-
clearTimeout(timeout_id);
|
|
195
|
-
timeout_id = null;
|
|
196
|
-
}
|
|
197
|
-
this.log('error', '连接过程错误详情', connection_err);
|
|
198
|
-
throw connection_err;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
99
|
+
// 统一使用连接池模式
|
|
100
|
+
await this._pool.init();
|
|
101
|
+
this._last_connect_time = Date.now();
|
|
102
|
+
this._status = 'connected';
|
|
103
|
+
return true;
|
|
201
104
|
} catch (err) {
|
|
202
105
|
this._status = 'closed';
|
|
203
106
|
this.log('error', '数据库连接失败', err);
|
|
@@ -206,7 +109,7 @@ SQLite.prototype.open = async function (timeout) {
|
|
|
206
109
|
};
|
|
207
110
|
|
|
208
111
|
/**
|
|
209
|
-
*
|
|
112
|
+
* 关闭数据库连接(统一使用连接池模式)
|
|
210
113
|
* @returns {Promise<boolean>}
|
|
211
114
|
*/
|
|
212
115
|
SQLite.prototype.close = function () {
|
|
@@ -217,32 +120,12 @@ SQLite.prototype.close = function () {
|
|
|
217
120
|
|
|
218
121
|
return new Promise((resolve) => {
|
|
219
122
|
try {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
this.log('info', '数据库连接池已关闭');
|
|
227
|
-
resolve(true);
|
|
228
|
-
} else if (this._db) {
|
|
229
|
-
// 单连接模式
|
|
230
|
-
this.log('debug', '关闭数据库连接');
|
|
231
|
-
this._db.close((err) => {
|
|
232
|
-
if (err) {
|
|
233
|
-
this.log('error', '关闭数据库失败', err);
|
|
234
|
-
} else {
|
|
235
|
-
this.log('info', '数据库已关闭');
|
|
236
|
-
}
|
|
237
|
-
this._db = null;
|
|
238
|
-
this._open = false;
|
|
239
|
-
this._status = 'closed';
|
|
240
|
-
resolve(!err);
|
|
241
|
-
});
|
|
242
|
-
} else {
|
|
243
|
-
this._status = 'closed';
|
|
244
|
-
resolve(true);
|
|
245
|
-
}
|
|
123
|
+
// 统一关闭连接池
|
|
124
|
+
this._pool.close();
|
|
125
|
+
this._db = null;
|
|
126
|
+
this._open = false;
|
|
127
|
+
this._status = 'closed';
|
|
128
|
+
resolve(true);
|
|
246
129
|
} catch (err) {
|
|
247
130
|
this.log('error', '关闭数据库连接时发生异常', err);
|
|
248
131
|
this._status = 'closed';
|
|
@@ -252,7 +135,7 @@ SQLite.prototype.close = function () {
|
|
|
252
135
|
};
|
|
253
136
|
|
|
254
137
|
/**
|
|
255
|
-
*
|
|
138
|
+
* 获取数据库连接(统一使用连接池模式)
|
|
256
139
|
* @param {number} timeout - 超时时间(毫秒)
|
|
257
140
|
* @returns {Promise<object>}
|
|
258
141
|
* @throws {TypeError} 当timeout参数无效时
|
|
@@ -270,32 +153,8 @@ SQLite.prototype.getConn = async function (timeout) {
|
|
|
270
153
|
}
|
|
271
154
|
|
|
272
155
|
try {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
return await this._getPoolConnection();
|
|
276
|
-
} else {
|
|
277
|
-
// 单连接模式 - 替换Promise.race为更可靠的超时控制
|
|
278
|
-
let timeout_id = null;
|
|
279
|
-
|
|
280
|
-
try {
|
|
281
|
-
return await new Promise((resolve, reject) => {
|
|
282
|
-
// 设置超时定时器
|
|
283
|
-
timeout_id = setTimeout(() => {
|
|
284
|
-
reject(new Error(`获取数据库连接超时(${acquire_timeout}ms)`));
|
|
285
|
-
}, acquire_timeout);
|
|
286
|
-
|
|
287
|
-
// 直接返回连接,因为这是单连接模式
|
|
288
|
-
resolve(this._db);
|
|
289
|
-
});
|
|
290
|
-
} catch (error) {
|
|
291
|
-
// 确保超时定时器被清除
|
|
292
|
-
if (timeout_id) {
|
|
293
|
-
clearTimeout(timeout_id);
|
|
294
|
-
timeout_id = null;
|
|
295
|
-
}
|
|
296
|
-
throw error;
|
|
297
|
-
}
|
|
298
|
-
}
|
|
156
|
+
// 统一从连接池获取连接
|
|
157
|
+
return await this._pool.getConnection(acquire_timeout);
|
|
299
158
|
} catch (error) {
|
|
300
159
|
this.log('error', '获取连接失败', error);
|
|
301
160
|
return null;
|
|
@@ -323,18 +182,16 @@ SQLite.prototype.run = async function (sql, params = [], timeout = 30000) {
|
|
|
323
182
|
}
|
|
324
183
|
|
|
325
184
|
this.error = null;
|
|
185
|
+
this.sql = '';
|
|
326
186
|
let conn = null;
|
|
327
187
|
const query_timeout = timeout || this.config.query_timeout || 30000;
|
|
328
|
-
|
|
188
|
+
let ret = [];
|
|
329
189
|
try {
|
|
330
190
|
// 获取连接
|
|
331
191
|
conn = await this.getConn(query_timeout);
|
|
332
|
-
|
|
333
|
-
const self = this;
|
|
334
|
-
// 替换Promise.race为更可靠的超时控制
|
|
335
192
|
let timeout_id = null;
|
|
336
|
-
|
|
337
|
-
|
|
193
|
+
|
|
194
|
+
ret = await new Promise((resolve, reject) => {
|
|
338
195
|
// 设置超时定时器
|
|
339
196
|
timeout_id = setTimeout(() => {
|
|
340
197
|
reject(new Error(`SQL查询超时: ${timeout}ms`));
|
|
@@ -347,14 +204,8 @@ SQLite.prototype.run = async function (sql, params = [], timeout = 30000) {
|
|
|
347
204
|
clearTimeout(timeout_id);
|
|
348
205
|
timeout_id = null;
|
|
349
206
|
}
|
|
350
|
-
|
|
351
207
|
if (error) {
|
|
352
|
-
|
|
353
|
-
self.error = {
|
|
354
|
-
code: error.errno,
|
|
355
|
-
message: error.message
|
|
356
|
-
};
|
|
357
|
-
// 不抛出错误,保持与MySQL一致的错误处理行为
|
|
208
|
+
reject(error);
|
|
358
209
|
resolve([]);
|
|
359
210
|
} else {
|
|
360
211
|
// 保持与MySQL兼容的返回值格式,始终返回数组
|
|
@@ -362,25 +213,19 @@ SQLite.prototype.run = async function (sql, params = [], timeout = 30000) {
|
|
|
362
213
|
}
|
|
363
214
|
});
|
|
364
215
|
});
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
this.log('error', 'SQL执行失败', err);
|
|
374
|
-
|
|
375
|
-
// 连接池模式下释放连接
|
|
376
|
-
if (this._use_pool && conn) {
|
|
377
|
-
this._releasePoolConnection(conn);
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// 简化错误处理:SQLite不需要复杂的连接错误处理
|
|
381
|
-
// 直接返回空数组,保持与MySQL一致的错误处理行为
|
|
382
|
-
return [];
|
|
216
|
+
} catch (error) {
|
|
217
|
+
this.log('error', 'SQL执行失败', error);
|
|
218
|
+
this.sql = sql;
|
|
219
|
+
this.error = {
|
|
220
|
+
code: error.errno,
|
|
221
|
+
message: error.message
|
|
222
|
+
};
|
|
223
|
+
ret = [];
|
|
383
224
|
}
|
|
225
|
+
finally {
|
|
226
|
+
await releaseConn(conn);
|
|
227
|
+
}
|
|
228
|
+
return ret;
|
|
384
229
|
};
|
|
385
230
|
|
|
386
231
|
/**
|
|
@@ -404,22 +249,22 @@ SQLite.prototype.exec = async function (sql, params, timeout) {
|
|
|
404
249
|
}
|
|
405
250
|
|
|
406
251
|
this.error = null;
|
|
252
|
+
this.sql = '';
|
|
407
253
|
let conn = null;
|
|
408
254
|
timeout = timeout || this.config.query_timeout || 30000;
|
|
409
|
-
|
|
255
|
+
let ret = 0;
|
|
410
256
|
try {
|
|
411
257
|
// 获取连接
|
|
412
258
|
conn = await this.getConn(timeout);
|
|
413
|
-
|
|
259
|
+
this.sql = sql;
|
|
414
260
|
// 替换Promise.race为更可靠的超时控制
|
|
415
261
|
let timeout_id = null;
|
|
416
|
-
|
|
417
|
-
const result = await new Promise((resolve, reject) => {
|
|
262
|
+
ret = await new Promise((resolve, reject) => {
|
|
418
263
|
// 设置超时定时器
|
|
419
264
|
timeout_id = setTimeout(() => {
|
|
420
265
|
reject(new Error(`SQL执行超时: ${timeout}ms`));
|
|
421
266
|
}, timeout);
|
|
422
|
-
|
|
267
|
+
|
|
423
268
|
// 执行SQL语句
|
|
424
269
|
conn.run(sql, params || [], function (error) {
|
|
425
270
|
// 清除超时定时器
|
|
@@ -427,40 +272,28 @@ SQLite.prototype.exec = async function (sql, params, timeout) {
|
|
|
427
272
|
clearTimeout(timeout_id);
|
|
428
273
|
timeout_id = null;
|
|
429
274
|
}
|
|
430
|
-
|
|
431
275
|
if (error) {
|
|
432
|
-
|
|
433
|
-
_this.error = {
|
|
434
|
-
code: error.errno,
|
|
435
|
-
message: error.message
|
|
436
|
-
};
|
|
437
|
-
// 不抛出错误,保持与MySQL一致的错误处理行为
|
|
438
|
-
resolve(0);
|
|
276
|
+
reject(error);
|
|
439
277
|
} else {
|
|
440
|
-
// 保持与MySQL兼容的返回值格式
|
|
441
278
|
resolve(this.lastID || this.changes || 1);
|
|
442
279
|
}
|
|
443
280
|
});
|
|
444
281
|
});
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
} catch (err) {
|
|
453
|
-
this.log('error', 'SQL执行失败', err);
|
|
454
|
-
|
|
455
|
-
// 连接池模式下释放连接
|
|
456
|
-
if (this._use_pool && conn) {
|
|
457
|
-
this._releasePoolConnection(conn);
|
|
458
|
-
}
|
|
459
|
-
|
|
282
|
+
} catch (error) {
|
|
283
|
+
this.log('error', 'SQL执行失败', error);
|
|
284
|
+
this.sql = sql;
|
|
285
|
+
this.error = {
|
|
286
|
+
code: error.errno,
|
|
287
|
+
message: error.message
|
|
288
|
+
};
|
|
460
289
|
// 简化错误处理:SQLite不需要复杂的连接错误处理
|
|
461
290
|
// 返回0,保持与MySQL一致的错误处理行为
|
|
462
|
-
|
|
291
|
+
ret = 0;
|
|
292
|
+
}
|
|
293
|
+
finally {
|
|
294
|
+
await releaseConn(conn);
|
|
463
295
|
}
|
|
296
|
+
return ret;
|
|
464
297
|
};
|
|
465
298
|
|
|
466
299
|
/**
|
|
@@ -526,7 +359,7 @@ SQLite.prototype.read = async function (table, condition, options) {
|
|
|
526
359
|
const results = await this.run(sql, params);
|
|
527
360
|
|
|
528
361
|
if (this.config.debug) {
|
|
529
|
-
this.log('
|
|
362
|
+
this.log('debug', '查询成功', { count: Array.isArray(results) ? results.length : 1 });
|
|
530
363
|
}
|
|
531
364
|
|
|
532
365
|
// 确保返回数组格式
|
|
@@ -592,10 +425,15 @@ SQLite.prototype.beginTrans = async function () {
|
|
|
592
425
|
// 获取连接
|
|
593
426
|
const conn = await this.getConn();
|
|
594
427
|
|
|
428
|
+
// 标记连接为事务中
|
|
429
|
+
conn._in_transaction = true;
|
|
430
|
+
|
|
595
431
|
// 开始事务
|
|
596
432
|
await new Promise((resolve, reject) => {
|
|
597
433
|
conn.run('BEGIN TRANSACTION', (error) => {
|
|
598
434
|
if (error) {
|
|
435
|
+
// 事务开始失败,清除事务标记
|
|
436
|
+
conn._in_transaction = false;
|
|
599
437
|
reject(error);
|
|
600
438
|
} else {
|
|
601
439
|
resolve();
|
|
@@ -624,10 +462,9 @@ SQLite.prototype.beginTrans = async function () {
|
|
|
624
462
|
this.log('debug', '事务提交');
|
|
625
463
|
}
|
|
626
464
|
|
|
627
|
-
//
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
}
|
|
465
|
+
// 清除事务标记并释放连接
|
|
466
|
+
conn._in_transaction = false;
|
|
467
|
+
await releaseConn(conn);
|
|
631
468
|
},
|
|
632
469
|
rollback: async () => {
|
|
633
470
|
await new Promise((resolve, reject) => {
|
|
@@ -642,11 +479,9 @@ SQLite.prototype.beginTrans = async function () {
|
|
|
642
479
|
if (this.config.debug) {
|
|
643
480
|
this.log('debug', '事务回滚');
|
|
644
481
|
}
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
this._releasePoolConnection(conn);
|
|
649
|
-
}
|
|
482
|
+
// 清除事务标记并释放连接
|
|
483
|
+
conn._in_transaction = false;
|
|
484
|
+
await releaseConn(conn);
|
|
650
485
|
},
|
|
651
486
|
exec: async (sql, params) => {
|
|
652
487
|
// 在事务中执行SQL
|
|
@@ -737,223 +572,6 @@ SQLite.prototype.table = function (name, key) {
|
|
|
737
572
|
return db;
|
|
738
573
|
};
|
|
739
574
|
|
|
740
|
-
/**
|
|
741
|
-
* 创建SQLite连接
|
|
742
|
-
* @private
|
|
743
|
-
* @returns {Promise<object>} 数据库连接对象
|
|
744
|
-
*/
|
|
745
|
-
SQLite.prototype._createConnection = function () {
|
|
746
|
-
return new Promise((resolve, reject) => {
|
|
747
|
-
const db = new sqlite3.Database(this._getDbFilename(), (err) => {
|
|
748
|
-
if (err) {
|
|
749
|
-
reject(err);
|
|
750
|
-
} else {
|
|
751
|
-
resolve(db);
|
|
752
|
-
}
|
|
753
|
-
});
|
|
754
|
-
});
|
|
755
|
-
};
|
|
756
|
-
|
|
757
|
-
/**
|
|
758
|
-
* 初始化连接池
|
|
759
|
-
* @private
|
|
760
|
-
* @returns {Promise<void>}
|
|
761
|
-
*/
|
|
762
|
-
SQLite.prototype._initPool = async function () {
|
|
763
|
-
if (this._pool_reaper) {
|
|
764
|
-
return; // 连接池已初始化
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
try {
|
|
768
|
-
// 创建最小连接数
|
|
769
|
-
for (let i = 0; i < this.config.pool_min; i++) {
|
|
770
|
-
const conn = await this._createConnection();
|
|
771
|
-
this._pool_connections.push(conn);
|
|
772
|
-
this._pool_available.push(conn);
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
// 启动连接回收器
|
|
776
|
-
this._pool_reaper = setInterval(() => {
|
|
777
|
-
this._reapIdleConnections();
|
|
778
|
-
}, this.config.pool_reap_interval);
|
|
779
|
-
|
|
780
|
-
this.log('debug', '连接池初始化完成', {
|
|
781
|
-
min: this.config.pool_min,
|
|
782
|
-
max: this.config.pool_max,
|
|
783
|
-
current: this._pool_connections.length
|
|
784
|
-
});
|
|
785
|
-
} catch (error) {
|
|
786
|
-
this.log('error', '连接池初始化失败', error);
|
|
787
|
-
throw error;
|
|
788
|
-
}
|
|
789
|
-
};
|
|
790
|
-
|
|
791
|
-
/**
|
|
792
|
-
* 回收空闲连接
|
|
793
|
-
* @private
|
|
794
|
-
*/
|
|
795
|
-
SQLite.prototype._reapIdleConnections = function () {
|
|
796
|
-
const now = Date.now();
|
|
797
|
-
const idle_timeout = this.config.pool_idle_timeout;
|
|
798
|
-
|
|
799
|
-
for (let i = this._pool_available.length - 1; i >= 0; i--) {
|
|
800
|
-
const conn = this._pool_available[i];
|
|
801
|
-
if (conn._last_used && now - conn._last_used > idle_timeout) {
|
|
802
|
-
// 关闭并移除空闲连接
|
|
803
|
-
conn.close((err) => {
|
|
804
|
-
if (err) {
|
|
805
|
-
this.log('error', '关闭空闲连接失败', err);
|
|
806
|
-
}
|
|
807
|
-
});
|
|
808
|
-
|
|
809
|
-
// 从连接池中移除
|
|
810
|
-
const conn_idx = this._pool_connections.indexOf(conn);
|
|
811
|
-
if (conn_idx > -1) {
|
|
812
|
-
this._pool_connections.splice(conn_idx, 1);
|
|
813
|
-
}
|
|
814
|
-
this._pool_available.splice(i, 1);
|
|
815
|
-
|
|
816
|
-
this.log('debug', '回收空闲连接');
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
};
|
|
820
|
-
|
|
821
|
-
/**
|
|
822
|
-
* 从连接池获取连接
|
|
823
|
-
* @private
|
|
824
|
-
* @returns {Promise<object>} 数据库连接
|
|
825
|
-
*/
|
|
826
|
-
SQLite.prototype._getPoolConnection = async function () {
|
|
827
|
-
const acquire_timeout = this.config.pool_acquire_timeout || 10000;
|
|
828
|
-
const start_time = Date.now();
|
|
829
|
-
|
|
830
|
-
return new Promise((resolve, reject) => {
|
|
831
|
-
let timeout_id = null;
|
|
832
|
-
|
|
833
|
-
const cleanup = () => {
|
|
834
|
-
if (timeout_id) {
|
|
835
|
-
clearTimeout(timeout_id);
|
|
836
|
-
timeout_id = null;
|
|
837
|
-
}
|
|
838
|
-
};
|
|
839
|
-
|
|
840
|
-
const tryGetConnection = () => {
|
|
841
|
-
// 检查超时
|
|
842
|
-
if (Date.now() - start_time > acquire_timeout) {
|
|
843
|
-
cleanup();
|
|
844
|
-
reject(new Error(`获取连接超时(${acquire_timeout}ms)`));
|
|
845
|
-
return;
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
// 检查是否有可用连接
|
|
849
|
-
if (this._pool_available.length > 0) {
|
|
850
|
-
const conn = this._pool_available.shift();
|
|
851
|
-
conn._last_used = Date.now();
|
|
852
|
-
cleanup();
|
|
853
|
-
resolve(conn);
|
|
854
|
-
return;
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
// 检查是否可以创建新连接
|
|
858
|
-
if (this._pool_connections.length < this.config.pool_max) {
|
|
859
|
-
this._createConnection()
|
|
860
|
-
.then(conn => {
|
|
861
|
-
this._pool_connections.push(conn);
|
|
862
|
-
conn._last_used = Date.now();
|
|
863
|
-
cleanup();
|
|
864
|
-
resolve(conn);
|
|
865
|
-
})
|
|
866
|
-
.catch(err => {
|
|
867
|
-
cleanup();
|
|
868
|
-
reject(err);
|
|
869
|
-
});
|
|
870
|
-
return;
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
// 没有可用连接,等待一段时间后重试(避免无限循环)
|
|
874
|
-
timeout_id = setTimeout(tryGetConnection, 50);
|
|
875
|
-
};
|
|
876
|
-
|
|
877
|
-
tryGetConnection();
|
|
878
|
-
});
|
|
879
|
-
};
|
|
880
|
-
|
|
881
|
-
/**
|
|
882
|
-
* 释放连接回连接池
|
|
883
|
-
* @private
|
|
884
|
-
* @param {object} conn - 数据库连接
|
|
885
|
-
*/
|
|
886
|
-
SQLite.prototype._releaseConn = function (conn) {
|
|
887
|
-
if (this._pool_connections.includes(conn)) {
|
|
888
|
-
conn._last_used = Date.now();
|
|
889
|
-
this._pool_available.push(conn);
|
|
890
|
-
|
|
891
|
-
// 处理等待队列
|
|
892
|
-
if (this._pool_waiting.length > 0) {
|
|
893
|
-
const waiting = this._pool_waiting.shift();
|
|
894
|
-
waiting.resolve(conn);
|
|
895
|
-
}
|
|
896
|
-
}
|
|
897
|
-
};
|
|
898
|
-
|
|
899
|
-
/**
|
|
900
|
-
* 释放连接池连接
|
|
901
|
-
* @private
|
|
902
|
-
* @param {object} conn - 数据库连接
|
|
903
|
-
*/
|
|
904
|
-
SQLite.prototype._releasePoolConnection = function (conn) {
|
|
905
|
-
if (!conn || !this._pool_connections.includes(conn)) {
|
|
906
|
-
return;
|
|
907
|
-
}
|
|
908
|
-
|
|
909
|
-
// 标记连接最后使用时间
|
|
910
|
-
conn._last_used = Date.now();
|
|
911
|
-
|
|
912
|
-
// 将连接放回可用连接池
|
|
913
|
-
if (!this._pool_available.includes(conn)) {
|
|
914
|
-
this._pool_available.push(conn);
|
|
915
|
-
}
|
|
916
|
-
|
|
917
|
-
// 如果有等待的连接请求,立即处理
|
|
918
|
-
if (this._pool_waiting.length > 0) {
|
|
919
|
-
const waiting = this._pool_waiting.shift();
|
|
920
|
-
const next_conn = this._pool_available.shift();
|
|
921
|
-
if (next_conn && waiting.resolve) {
|
|
922
|
-
waiting.resolve(next_conn);
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
};
|
|
926
|
-
|
|
927
|
-
/**
|
|
928
|
-
* 关闭连接池
|
|
929
|
-
* @private
|
|
930
|
-
*/
|
|
931
|
-
SQLite.prototype._closePool = function () {
|
|
932
|
-
if (this._pool_reaper) {
|
|
933
|
-
clearInterval(this._pool_reaper);
|
|
934
|
-
this._pool_reaper = null;
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
// 关闭所有连接
|
|
938
|
-
this._pool_connections.forEach(conn => {
|
|
939
|
-
try {
|
|
940
|
-
conn.close((err) => {
|
|
941
|
-
if (err) {
|
|
942
|
-
this.log('error', '关闭连接池连接失败', err);
|
|
943
|
-
}
|
|
944
|
-
});
|
|
945
|
-
} catch (error) {
|
|
946
|
-
this.log('error', '关闭连接池连接异常', error);
|
|
947
|
-
}
|
|
948
|
-
});
|
|
949
|
-
|
|
950
|
-
this._pool_connections = [];
|
|
951
|
-
this._pool_available = [];
|
|
952
|
-
this._pool_waiting = [];
|
|
953
|
-
|
|
954
|
-
this.log('debug', '连接池已关闭');
|
|
955
|
-
};
|
|
956
|
-
|
|
957
575
|
/**
|
|
958
576
|
* 确保连接池对象存在
|
|
959
577
|
*/
|
|
@@ -982,6 +600,18 @@ function sqliteAdmin(scope, config) {
|
|
|
982
600
|
return obj;
|
|
983
601
|
}
|
|
984
602
|
|
|
603
|
+
/**
|
|
604
|
+
* 安全释放数据库连接
|
|
605
|
+
* @param {object} conn - 数据库连接对象
|
|
606
|
+
* @returns {Promise<void>}
|
|
607
|
+
*/
|
|
608
|
+
async function releaseConn(conn) {
|
|
609
|
+
if (!conn) {
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
await conn.release();
|
|
613
|
+
}
|
|
614
|
+
|
|
985
615
|
// 模块导出
|
|
986
616
|
exports.SQLite = SQLite;
|
|
987
617
|
exports.sqliteAdmin = sqliteAdmin;
|
package/package.json
CHANGED
package/pool.js
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
const sqlite3 = require('sqlite3').verbose();
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SQLite连接池类
|
|
5
|
+
* @class SQLitePool
|
|
6
|
+
*/
|
|
7
|
+
class SQLitePool {
|
|
8
|
+
/**
|
|
9
|
+
* 构造函数
|
|
10
|
+
* @param {object} config - 连接池配置
|
|
11
|
+
* @param {function} getDbFilename - 获取数据库文件名的函数
|
|
12
|
+
* @param {function} log - 日志函数
|
|
13
|
+
*/
|
|
14
|
+
constructor(config, getDbFilename, log) {
|
|
15
|
+
this.config = config;
|
|
16
|
+
this._getDbFilename = getDbFilename;
|
|
17
|
+
this.log = log;
|
|
18
|
+
|
|
19
|
+
// 连接池状态
|
|
20
|
+
this._pool_connections = [];
|
|
21
|
+
this._pool_available = [];
|
|
22
|
+
this._pool_waiting = [];
|
|
23
|
+
this._pool_reaper = null;
|
|
24
|
+
this._is_initialized = false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 初始化连接池
|
|
29
|
+
* @returns {Promise<void>}
|
|
30
|
+
*/
|
|
31
|
+
async init() {
|
|
32
|
+
if (this._is_initialized) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
// 创建最小连接数
|
|
38
|
+
for (let i = 0; i < this.config.pool_min; i++) {
|
|
39
|
+
const conn = await this._createConnection();
|
|
40
|
+
this._pool_connections.push(conn);
|
|
41
|
+
this._pool_available.push(conn);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 启动连接回收器
|
|
45
|
+
this._pool_reaper = setInterval(() => {
|
|
46
|
+
this._reapIdleConnections();
|
|
47
|
+
}, this.config.pool_reap_interval);
|
|
48
|
+
|
|
49
|
+
this._is_initialized = true;
|
|
50
|
+
} catch (error) {
|
|
51
|
+
this.log('error', '连接池初始化失败', error);
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 获取数据库连接
|
|
58
|
+
* @param {number} timeout - 超时时间(毫秒)
|
|
59
|
+
* @returns {Promise<object>} 数据库连接
|
|
60
|
+
*/
|
|
61
|
+
async getConnection(timeout) {
|
|
62
|
+
const acquire_timeout = timeout || this.config.pool_acquire_timeout || 10000;
|
|
63
|
+
const start_time = Date.now();
|
|
64
|
+
|
|
65
|
+
return new Promise((resolve, reject) => {
|
|
66
|
+
let timeout_id = null;
|
|
67
|
+
|
|
68
|
+
const cleanup = () => {
|
|
69
|
+
if (timeout_id) {
|
|
70
|
+
clearTimeout(timeout_id);
|
|
71
|
+
timeout_id = null;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const tryGetConnection = () => {
|
|
76
|
+
// 检查超时
|
|
77
|
+
if (Date.now() - start_time > acquire_timeout) {
|
|
78
|
+
cleanup();
|
|
79
|
+
reject(new Error(`获取连接超时(${acquire_timeout}ms)`));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 检查是否有可用连接
|
|
84
|
+
if (this._pool_available.length > 0) {
|
|
85
|
+
const conn = this._pool_available.shift();
|
|
86
|
+
conn._last_used = Date.now();
|
|
87
|
+
cleanup();
|
|
88
|
+
resolve(conn);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// 检查是否可以创建新连接
|
|
93
|
+
if (this._pool_connections.length < this.config.pool_max) {
|
|
94
|
+
this._createConnection()
|
|
95
|
+
.then(conn => {
|
|
96
|
+
this._pool_connections.push(conn);
|
|
97
|
+
conn._last_used = Date.now();
|
|
98
|
+
conn.release = () => this.releaseConn(conn);
|
|
99
|
+
cleanup();
|
|
100
|
+
resolve(conn);
|
|
101
|
+
})
|
|
102
|
+
.catch(err => {
|
|
103
|
+
cleanup();
|
|
104
|
+
reject(err);
|
|
105
|
+
});
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// 没有可用连接且无法创建新连接,加入等待队列
|
|
110
|
+
this._pool_waiting.push({
|
|
111
|
+
resolve: (conn) => {
|
|
112
|
+
cleanup();
|
|
113
|
+
resolve(conn);
|
|
114
|
+
},
|
|
115
|
+
reject: (err) => {
|
|
116
|
+
cleanup();
|
|
117
|
+
reject(err);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// 设置重试定时器
|
|
122
|
+
timeout_id = setTimeout(() => {
|
|
123
|
+
tryGetConnection();
|
|
124
|
+
}, 100);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
tryGetConnection();
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* 释放连接
|
|
133
|
+
* @param {object} conn - 数据库连接
|
|
134
|
+
*/
|
|
135
|
+
releaseConn(conn) {
|
|
136
|
+
if (!conn || !this._pool_connections.includes(conn)) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// 标记连接最后使用时间
|
|
141
|
+
conn._last_used = Date.now();
|
|
142
|
+
|
|
143
|
+
// 将连接放回可用连接池
|
|
144
|
+
if (!this._pool_available.includes(conn)) {
|
|
145
|
+
this._pool_available.push(conn);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// 如果有等待的连接请求,立即处理
|
|
149
|
+
if (this._pool_waiting.length > 0) {
|
|
150
|
+
const waiting = this._pool_waiting.shift();
|
|
151
|
+
const next_conn = this._pool_available.shift();
|
|
152
|
+
if (next_conn && waiting.resolve) {
|
|
153
|
+
waiting.resolve(next_conn);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* 关闭连接池
|
|
160
|
+
*/
|
|
161
|
+
close() {
|
|
162
|
+
if (this._pool_reaper) {
|
|
163
|
+
clearInterval(this._pool_reaper);
|
|
164
|
+
this._pool_reaper = null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// 关闭所有连接
|
|
168
|
+
this._pool_connections.forEach(conn => {
|
|
169
|
+
try {
|
|
170
|
+
conn.close((err) => {
|
|
171
|
+
if (err) {
|
|
172
|
+
this.log('error', '关闭连接池连接失败', err);
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
} catch (error) {
|
|
176
|
+
this.log('error', '关闭连接池连接异常', error);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
this._pool_connections = [];
|
|
181
|
+
this._pool_available = [];
|
|
182
|
+
this._pool_waiting = [];
|
|
183
|
+
this._is_initialized = false;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* 获取连接池统计信息
|
|
188
|
+
* @returns {object} 统计信息
|
|
189
|
+
*/
|
|
190
|
+
getStats() {
|
|
191
|
+
return {
|
|
192
|
+
total: this._pool_connections.length,
|
|
193
|
+
available: this._pool_available.length,
|
|
194
|
+
waiting: this._pool_waiting.length,
|
|
195
|
+
is_initialized: this._is_initialized
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* 创建SQLite连接
|
|
201
|
+
* @private
|
|
202
|
+
* @returns {Promise<object>} 数据库连接对象
|
|
203
|
+
*/
|
|
204
|
+
_createConnection() {
|
|
205
|
+
return new Promise((resolve, reject) => {
|
|
206
|
+
const db = new sqlite3.Database(this._getDbFilename(), (err) => {
|
|
207
|
+
if (err) {
|
|
208
|
+
reject(err);
|
|
209
|
+
} else {
|
|
210
|
+
db.release = () => this.releaseConn(db);
|
|
211
|
+
resolve(db);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* 回收空闲连接
|
|
219
|
+
* @private
|
|
220
|
+
*/
|
|
221
|
+
_reapIdleConnections() {
|
|
222
|
+
const now = Date.now();
|
|
223
|
+
const idle_timeout = this.config.pool_idle_timeout;
|
|
224
|
+
|
|
225
|
+
for (let i = this._pool_available.length - 1; i >= 0; i--) {
|
|
226
|
+
const conn = this._pool_available[i];
|
|
227
|
+
if (conn._last_used && now - conn._last_used > idle_timeout) {
|
|
228
|
+
// 关闭并移除空闲连接
|
|
229
|
+
conn.close((err) => {
|
|
230
|
+
if (err) {
|
|
231
|
+
this.log('error', '关闭空闲连接失败', err);
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// 从连接池中移除
|
|
236
|
+
const conn_idx = this._pool_connections.indexOf(conn);
|
|
237
|
+
if (conn_idx > -1) {
|
|
238
|
+
this._pool_connections.splice(conn_idx, 1);
|
|
239
|
+
}
|
|
240
|
+
this._pool_available.splice(i, 1);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
module.exports = SQLitePool;
|
package/test_db/test.db
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|