mm_sqlite 1.1.4 → 1.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (6) hide show
  1. package/README.md +168 -102
  2. package/db.js +94 -115
  3. package/index.js +756 -555
  4. package/package.json +2 -2
  5. package/sql.js +245 -372
  6. package/sql_builder.js +0 -375
package/index.js CHANGED
@@ -1,55 +1,71 @@
1
1
  const sqlite3 = require('sqlite3').verbose();
2
- const DB = require('./db');
3
- const { BaseService } = require('mm_base_service');
2
+ const {
3
+ DB
4
+ } = require('./db');
5
+ const { Base } = require('mm_expand');
6
+
4
7
  /**
5
8
  * SQLite数据库操作类
6
9
  * @class Sqlite
7
- * @extends BaseService
10
+ * @extends Base
8
11
  */
9
- class Sqlite extends BaseService {
10
- /**
11
- * 默认配置
12
- */
13
- static default_config = {
14
- dir: "./db/".fullname(),
15
- user: "root",
16
- password: "",
17
- database: "mm",
18
- charset: "utf8mb4",
19
- timezone: "+08:00",
20
- connect_timeout: 20000,
21
- acquire_timeout: 20000,
22
- query_timeout: 20000,
23
- connection_limit: 1,
24
- queue_limit: 0,
25
- enable_keep_alive: true,
26
- keep_alive_initial_delay: 10000,
27
- enable_reconnect: true,
28
- reconnect_interval: 1000,
29
- max_reconnect_attempts: 5,
30
- wait_for_connections: true
31
- };
32
-
33
- /**
34
- * 构造函数
35
- * @param {Object} config - 配置对象
36
- */
37
- constructor(config) {
38
- const merged_config = Object.assign({}, Sqlite.default_config, config || {});
39
- super(merged_config);
40
-
41
- this.config = merged_config;
42
- this._connection = null;
43
- this._pool = null;
44
- this._status = 'closed';
45
- this._last_connect_time = 0;
46
- this._reconnecting = false;
47
- this._is_inited = false;
48
- this._is_destroyed = false;
49
- this._db = null;
50
- this._open = false;
51
- this._conn_retry_count = 0;
52
- }
12
+ class Sqlite extends Base {
13
+ /**
14
+ * 默认配置
15
+ */
16
+ static default_config = {
17
+ dir: "./db/".fullname(),
18
+ user: "root",
19
+ password: "",
20
+ database: "mm",
21
+ charset: "utf8mb4",
22
+ timezone: "+08:00",
23
+ connect_timeout: 20000,
24
+ acquire_timeout: 20000,
25
+ query_timeout: 20000,
26
+ connection_limit: 1,
27
+ queue_limit: 0,
28
+ enable_keep_alive: true,
29
+ keep_alive_initial_delay: 10000,
30
+ enable_reconnect: true,
31
+ reconnect_interval: 1000,
32
+ max_reconnect_attempts: 5,
33
+ wait_for_connections: true,
34
+ // 连接池相关配置
35
+ pool_min: 1,
36
+ pool_max: 5,
37
+ pool_acquire_timeout: 30000,
38
+ pool_idle_timeout: 60000,
39
+ pool_reap_interval: 1000
40
+ };
41
+
42
+ /**
43
+ * 构造函数
44
+ * @param {Object} config - 配置对象
45
+ */
46
+ constructor(config) {
47
+ const merged_config = Object.assign({}, Sqlite.default_config, config || {});
48
+ super(merged_config);
49
+
50
+ this.config = merged_config;
51
+ this._connection = null;
52
+ this._pool = null;
53
+ this._status = 'closed';
54
+ this._last_connect_time = 0;
55
+ this._reconnecting = false;
56
+ this._is_inited = false;
57
+ this._is_destroyed = false;
58
+ this._db = null;
59
+ this._open = false;
60
+ this._conn_retry_count = 0;
61
+
62
+ // 连接池相关属性
63
+ this._use_pool = this.config.connection_limit > 1;
64
+ this._pool_connections = [];
65
+ this._pool_available = [];
66
+ this._pool_waiting = [];
67
+ this._pool_reaper = null;
68
+ }
53
69
  }
54
70
 
55
71
  /**
@@ -57,123 +73,112 @@ class Sqlite extends BaseService {
57
73
  * @returns {Promise<void>}
58
74
  */
59
75
  Sqlite.prototype._initService = async function () {
60
- if (this._is_inited) {
61
- $.log.warn('SQLite服务已初始化');
62
- return;
63
- }
64
-
65
- try {
66
- $.log.debug('[Sqlite] [_initService]', '初始化SQLite服务', { config: this.config });
67
- this._is_inited = true;
68
- $.log.debug('[Sqlite] [_initService]', 'SQLite服务初始化完成');
69
- } catch (error) {
70
- $.log.error('[Sqlite] [_initService]', 'SQLite服务初始化失败', { error: error.message });
71
- throw error;
72
- }
76
+ if (this._is_inited) {
77
+ this.logger('warn', 'SQLite服务已初始化');
78
+ return;
79
+ }
80
+
81
+ try {
82
+ this.logger('debug', '初始化SQLite服务', { config: this.config });
83
+ this._is_inited = true;
84
+ this.logger('debug', 'SQLite服务初始化完成');
85
+ } catch (error) {
86
+ this.logger('error', 'SQLite服务初始化失败', error);
87
+ }
73
88
  };
74
89
 
75
90
  /**
76
- * 打开数据库连接
77
- * @param {Number} timeout - 超时时间(毫秒)
78
- * @returns {Promise<boolean>}
91
+ * 获取数据库文件名
92
+ * @returns {string} 数据库文件名
79
93
  */
80
- Sqlite.prototype.open = async function (timeout) {
81
- if (this._status === 'connected' || this._status === 'connecting') {
82
- $.log.warn('数据库连接已存在或正在连接中');
83
- return true;
84
- }
85
-
86
- timeout = timeout || this.config.connect_timeout || 10000;
87
- this._status = 'connecting';
88
-
89
- try {
90
- // 创建连接
91
- const connection_promise = this._openInternal();
92
- const timeout_promise = new Promise((_, reject) => {
93
- setTimeout(() => {
94
- reject(new Error(`数据库连接超时(${timeout}ms)`));
95
- }, timeout);
96
- });
94
+ Sqlite.prototype._getDbFilename = function () {
95
+ var file = this.config.database;
96
+ if (file.indexOf(".") === -1) {
97
+ file += ".db";
98
+ }
97
99
 
98
- try {
99
- await Promise.race([connection_promise, timeout_promise]);
100
-
101
- this._last_connect_time = Date.now();
102
- this._status = 'connected';
103
- $.log.info(`[${this.constructor.name}] [open]`, '数据库连接成功', {
104
- dir: this.config.dir,
105
- database: this.config.database
106
- });
107
- return true;
108
- } catch (connection_err) {
109
- $.log.error(`[${this.constructor.name}] [open]`, '连接过程错误详情', {
110
- error: connection_err.message,
111
- code: connection_err.code,
112
- syscall: connection_err.syscall,
113
- address: connection_err.address,
114
- dir: connection_err.dir,
115
- stack: connection_err.stack
116
- });
117
- throw connection_err;
118
- }
119
- } catch (err) {
120
- this._status = 'closed';
121
- $.log.error(`[${this.constructor.name}] [open]`, '数据库连接失败', {
122
- error: err.message,
123
- dir: this.config.dir,
124
- database: this.config.database
125
- });
126
- throw err;
127
- }
128
- };
100
+ return file.fullname(this.config.dir);
101
+ }
129
102
 
130
103
  /**
131
- * 内部连接方法
104
+ * 内部打开数据库连接
132
105
  * @private
133
106
  * @returns {Promise<void>}
134
107
  */
135
- Sqlite.prototype._openInternal = function () {
136
- return new Promise((resolve, reject) => {
137
- try {
138
-
139
- // 确保数据库目录存在
140
- const file = this._getDatabasePath();
108
+ Sqlite.prototype._openInternal = async function () {
109
+ return new Promise((resolve, reject) => {
110
+ this._db = new sqlite3.Database(this._getDbFilename(), (err) => {
111
+ if (err) {
112
+ reject(err);
113
+ } else {
114
+ resolve();
115
+ }
116
+ });
117
+ });
118
+ };
141
119
 
142
- if (!file.hasDir()) {
143
- file.addDir();
144
- }
120
+ /**
121
+ * 打开数据库连接
122
+ * @param {Number} timeout - 超时时间(毫秒)
123
+ * @returns {Promise<boolean>}
124
+ * @throws {TypeError} 当timeout参数无效时
125
+ */
126
+ Sqlite.prototype.open = async function (timeout) {
127
+ // 参数校验
128
+ if (timeout !== undefined && typeof timeout !== 'number') {
129
+ throw new TypeError('timeout must be number');
130
+ }
145
131
 
146
- // 创建数据库连接
147
- this._db = new sqlite3.Database(file);
148
- this._open = true;
149
- this._conn_retry_count = 0;
132
+ if (this._status === 'connected' || this._status === 'connecting') {
133
+ this.logger('warn', '数据库连接已存在或正在连接中');
134
+ return true;
135
+ }
150
136
 
151
- // 设置连接错误处理
152
- this._db.on('error', (err) => {
153
- $.log.error('SQLite数据库连接错误:', err);
154
- this._handleConnectionError(err);
155
- });
137
+ timeout = timeout || this.config.connect_timeout || 10000;
138
+ this._status = 'connecting';
156
139
 
157
- $.log.debug(`SQLite数据库已打开: ${file}`);
158
- resolve();
159
- } catch (error) {
160
- reject(error);
140
+ try {
141
+ this.config.dir.addDir();
142
+ if (this._use_pool) {
143
+ // 使用连接池模式
144
+ await this._initPool();
145
+ this._last_connect_time = Date.now();
146
+ this._status = 'connected';
147
+ this.logger('info', '数据库连接池初始化成功', {
148
+ dir: this.config.dir,
149
+ database: this.config.database,
150
+ pool_min: this.config.pool_min,
151
+ pool_max: this.config.pool_max
152
+ });
153
+ return true;
154
+ } else {
155
+ // 使用单连接模式
156
+ const connection_promise = this._openInternal();
157
+ const timeout_promise = new Promise((_, reject) => {
158
+ setTimeout(() => {
159
+ reject(new Error(`数据库连接超时(${timeout}ms)`));
160
+ }, timeout);
161
+ });
162
+
163
+ try {
164
+ await Promise.race([connection_promise, timeout_promise]);
165
+
166
+ this._last_connect_time = Date.now();
167
+ this._status = 'connected';
168
+ this.logger('info', '数据库连接成功', {
169
+ dir: this.config.dir,
170
+ database: this.config.database
171
+ });
172
+ return true;
173
+ } catch (connection_err) {
174
+ this.logger('error', '连接过程错误详情', connection_err);
175
+ }
176
+ }
177
+ } catch (err) {
178
+ this._status = 'closed';
179
+ this.logger('error', '数据库连接失败', err);
180
+ return false;
161
181
  }
162
- });
163
- };
164
-
165
- /**
166
- * 获取数据库路径
167
- * @private
168
- * @returns {String}
169
- */
170
- Sqlite.prototype._getDatabasePath = function () {
171
- const db_name = this.config.database || 'mm';
172
- if (db_name.endsWith('.db')) {
173
- db_name = db_name.replace('.db', '');
174
- }
175
- const db_dir = this.config.dir || '/db/'.fullname();
176
- return `./${db_name}.db`.fullname(db_dir);
177
182
  };
178
183
 
179
184
  /**
@@ -181,168 +186,91 @@ Sqlite.prototype._getDatabasePath = function () {
181
186
  * @returns {Promise<boolean>}
182
187
  */
183
188
  Sqlite.prototype.close = function () {
184
- if (this._status !== 'connected') {
185
- $.log.warn('数据库连接未建立');
186
- return Promise.resolve(false);
187
- }
188
-
189
- return new Promise((resolve) => {
190
- try {
191
- if (this._db) {
192
- $.log.debug('关闭数据库连接');
193
- this._db.close((err) => {
194
- if (err) {
195
- $.log.error('关闭数据库失败', { error: err.message });
196
- } else {
197
- $.log.info('数据库已关闭');
198
- }
199
- this._db = null;
200
- this._open = false;
201
- this._status = 'closed';
202
- resolve(!err);
203
- });
204
- } else {
205
- this._status = 'closed';
206
- resolve(true);
207
- }
208
- } catch (err) {
209
- $.log.error('关闭数据库连接时发生异常', { error: err.message });
210
- this._status = 'closed';
211
- resolve(false);
189
+ if (this._status !== 'connected') {
190
+ this.logger('warn', '数据库连接未建立');
191
+ return Promise.resolve(false);
212
192
  }
213
- });
193
+
194
+ return new Promise((resolve) => {
195
+ try {
196
+ if (this._use_pool) {
197
+ // 关闭连接池
198
+ this._closePool();
199
+ this._db = null;
200
+ this._open = false;
201
+ this._status = 'closed';
202
+ this.logger('info', '数据库连接池已关闭');
203
+ resolve(true);
204
+ } else if (this._db) {
205
+ // 单连接模式
206
+ this.logger('debug', '关闭数据库连接');
207
+ this._db.close((err) => {
208
+ if (err) {
209
+ this.logger('error', '关闭数据库失败', err);
210
+ } else {
211
+ this.logger('info', '数据库已关闭');
212
+ }
213
+ this._db = null;
214
+ this._open = false;
215
+ this._status = 'closed';
216
+ resolve(!err);
217
+ });
218
+ } else {
219
+ this._status = 'closed';
220
+ resolve(true);
221
+ }
222
+ } catch (err) {
223
+ this.logger('error', '关闭数据库连接时发生异常', err);
224
+ this._status = 'closed';
225
+ resolve(false);
226
+ }
227
+ });
214
228
  };
215
229
 
216
230
  /**
217
231
  * 获取数据库连接
218
232
  * @param {Number} timeout - 超时时间(毫秒)
219
233
  * @returns {Promise<Object>}
234
+ * @throws {TypeError} 当timeout参数无效时
220
235
  */
221
236
  Sqlite.prototype.getConn = async function (timeout) {
222
- timeout = timeout || this.config.acquire_timeout || this.config.connect_timeout || 20000;
223
-
224
- // $.log.debug(`[${this.constructor.name}] [getConn] 使用超时时间: ${timeout}ms`);
225
-
226
- if (this._status !== 'connected') {
227
- throw new Error('数据库连接未建立');
228
- }
229
-
230
- try {
231
- const connection_promise = new Promise((resolve) => {
232
- resolve(this._db);
233
- });
234
- const timeout_promise = new Promise((_, reject) => {
235
- setTimeout(() => {
236
- reject(new Error(`获取数据库连接超时(${timeout}ms)`));
237
- }, timeout);
238
- });
239
-
240
- const conn = await Promise.race([connection_promise, timeout_promise]);
241
-
242
- // $.log.debug(`[${this.constructor.name}] [getConn] 成功获取数据库连接`);
243
-
244
- // 处理连接错误
245
- conn.on('error', (err) => {
246
- if (err.code && (err.code === 'ECONNRESET' || err.code === 'ETIMEDOUT')) {
247
- this._handleConnectionError(err);
248
- }
249
- });
250
-
251
- return conn;
252
- } catch (error) {
253
- $.log.error(`[${this.constructor.name}] [getConn] 获取连接失败`, {
254
- error: error.message
255
- });
256
-
257
- if (error.code && (error.code === 'ECONNRESET' || error.code === 'ETIMEDOUT')) {
258
- this._handleConnectionError(error);
237
+ // 参数校验
238
+ if (timeout !== undefined && typeof timeout !== 'number') {
239
+ throw new TypeError('timeout must be number');
259
240
  }
260
241
 
261
- throw error;
262
- }
263
- };
242
+ timeout = timeout || this.config.acquire_timeout || this.config.connect_timeout || 20000;
264
243
 
265
- /**
266
- * 处理连接错误
267
- * @private
268
- * @param {Error} err - 错误对象
269
- */
270
- Sqlite.prototype._handleConnectionError = function (err) {
271
- if (this._reconnecting) {
272
- $.log.debug('重连已在进行中');
273
- return;
274
- }
275
-
276
- if (this.config.enable_reconnect) {
277
- $.log.info('开始自动重连...');
278
- this._reconnect(this.config.max_reconnect_attempts || 3)
279
- .catch(reconnect_err => {
280
- $.log.error('重连失败', { error: reconnect_err.message });
281
- this._status = 'closed';
282
- });
283
- } else {
284
- $.log.warn('自动重连已禁用');
285
- this._status = 'closed';
286
- }
287
- };
244
+ if (this._status !== 'connected') {
245
+ throw new Error('数据库连接未建立');
246
+ }
288
247
 
289
- /**
290
- * 重新连接数据库
291
- * @private
292
- * @param {Number} max_retries - 最大重试次数
293
- * @returns {Promise<boolean>}
294
- */
295
- Sqlite.prototype._reconnect = async function (max_retries) {
296
- if (this._reconnecting) {
297
- $.log.warn('重连已在进行中');
298
- return false;
299
- }
300
-
301
- this._reconnecting = true;
302
- let retry_count = 0;
303
-
304
- try {
305
- // 先关闭现有连接
306
- if (this._status === 'connected') {
307
- await this.close();
308
- }
309
-
310
- while (retry_count < max_retries) {
311
- retry_count++;
312
- $.log.info(`第${retry_count}/${max_retries}次尝试重连`);
313
-
314
- try {
315
- const reconnect_timeout = this.config.connect_timeout || this.config.acquire_timeout || 20000;
316
- $.log.debug(`重连超时设置为: ${reconnect_timeout}ms`);
317
- await this.open(reconnect_timeout);
318
- $.log.info('重连成功');
319
- return true;
320
- } catch (err) {
321
- $.log.error(`重连失败(${retry_count}/${max_retries})`, { error: err.message });
248
+ try {
249
+ if (this._use_pool) {
250
+ // 从连接池获取连接
251
+ return await this._getPoolConnection();
252
+ } else {
253
+ // 单连接模式
254
+ const connection_promise = new Promise((resolve) => {
255
+ resolve(this._db);
256
+ });
257
+ const timeout_promise = new Promise((_, reject) => {
258
+ setTimeout(() => {
259
+ reject(new Error(`获取数据库连接超时(${timeout}ms)`));
260
+ }, timeout);
261
+ });
262
+
263
+ const conn = await Promise.race([connection_promise, timeout_promise]);
264
+ return conn;
265
+ }
266
+ } catch (error) {
267
+ this.logger('error', '获取连接失败', error);
322
268
 
323
- if (retry_count < max_retries) {
324
- const wait_time = this.config.reconnect_interval || 1000;
325
- $.log.debug(`等待${wait_time}ms后重试`);
326
- await this._sleep(wait_time);
269
+ if (error.code && (error.code === 'ECONNRESET' || error.code === 'ETIMEDOUT')) {
270
+ this._handleConnectionError(error);
327
271
  }
328
- }
272
+ return null;
329
273
  }
330
-
331
- $.log.error(`达到最大重试次数(${max_retries}),重连失败`);
332
- throw new Error(`重连失败:达到最大重试次数(${max_retries})`);
333
- } finally {
334
- this._reconnecting = false;
335
- }
336
- };
337
-
338
- /**
339
- * 休眠函数
340
- * @private
341
- * @param {Number} ms - 休眠时间(毫秒)
342
- * @returns {Promise<void>}
343
- */
344
- Sqlite.prototype._sleep = function (ms) {
345
- return new Promise(resolve => setTimeout(resolve, ms));
346
274
  };
347
275
 
348
276
  /**
@@ -351,50 +279,82 @@ Sqlite.prototype._sleep = function (ms) {
351
279
  * @param {Array} params - 参数数组
352
280
  * @param {Number} timeout - 超时时间(毫秒)
353
281
  * @returns {Promise<Object>}
282
+ * @throws {TypeError} 当sql参数无效时
354
283
  */
355
284
  Sqlite.prototype.run = async function (sql, params, timeout) {
356
- let conn = null;
357
- timeout = timeout || this.config.query_timeout || 30000;
358
-
359
- try {
360
- // 获取连接
361
- conn = await this.getConn(timeout);
362
-
363
- // 直接在方法内部实现超时控制
364
- const query_promise = new Promise((resolve, reject) => {
365
- conn.all(sql, params || [], (error, rows) => {
366
- if (error) {
367
- reject(error);
368
- } else {
369
- // 保持与MySQL兼容的返回值格式,始终返回数组
370
- resolve(rows);
371
- }
372
- });
373
- });
285
+ // 参数校验
286
+ if (typeof sql !== 'string' || sql.trim() === '') {
287
+ throw new TypeError('sql must be non-empty string');
288
+ }
289
+ if (params !== undefined && !Array.isArray(params)) {
290
+ throw new TypeError('params must be array');
291
+ }
292
+ if (timeout !== undefined && typeof timeout !== 'number') {
293
+ throw new TypeError('timeout must be number');
294
+ }
374
295
 
375
- const timeout_promise = new Promise((_, reject) => {
376
- setTimeout(() => {
377
- reject(new Error(`SQL查询超时: ${timeout}ms`));
378
- }, timeout);
379
- });
296
+ this.error = null;
297
+ let conn = null;
298
+ timeout = timeout || this.config.query_timeout || 30000;
380
299
 
381
- const result = await Promise.race([query_promise, timeout_promise]);
300
+ try {
301
+ // 获取连接
302
+ conn = await this.getConn(timeout);
303
+
304
+ var _this = this;
305
+ // 直接在方法内部实现超时控制
306
+ const query_promise = new Promise((resolve, reject) => {
307
+ conn.all(sql, params || [], (error, rows) => {
308
+ if (error) {
309
+ _this.sql = sql;
310
+ _this.error = {
311
+ code: error.errno,
312
+ message: error.message
313
+ }
314
+ reject(error);
315
+ } else {
316
+ // 保持与MySQL兼容的返回值格式,始终返回数组
317
+ resolve(rows);
318
+ }
319
+ });
320
+ });
382
321
 
383
- return result;
384
- } catch (err) {
385
- $.log.error('[Sqlite] [run]', 'SQL执行失败', {
386
- error: err.message,
387
- sql: typeof sql === 'string' ? sql.substring(0, 200) : sql,
388
- params: params
389
- });
322
+ const timeout_promise = new Promise((_, reject) => {
323
+ setTimeout(() => {
324
+ reject(new Error(`SQL查询超时: ${timeout}ms`));
325
+ }, timeout);
326
+ });
390
327
 
391
- // 处理连接错误,触发重连
392
- if (err.code && (err.code === 'ECONNRESET' || err.code === 'ETIMEDOUT')) {
393
- this._handleConnectionError(err);
394
- }
328
+ const result = await Promise.race([query_promise, timeout_promise]);
395
329
 
396
- throw err;
397
- }
330
+ // 连接池模式下释放连接
331
+ if (this._use_pool && conn) {
332
+ this._releasePoolConnection(conn);
333
+ }
334
+
335
+ return result;
336
+ } catch (err) {
337
+ this.logger('error', 'SQL执行失败', err);
338
+
339
+ // 连接池模式下释放连接
340
+ if (this._use_pool && conn) {
341
+ this._releasePoolConnection(conn);
342
+ }
343
+
344
+ // 处理连接错误,触发重连
345
+ if (err.code && (err.code === 'ECONNRESET' || err.code === 'ETIMEDOUT')) {
346
+ this._handleConnectionError(err);
347
+ }
348
+ this.sql = sql;
349
+ // 记录错误信息
350
+ this.error = {
351
+ code: err.code,
352
+ message: err.message
353
+ };
354
+
355
+ // 返回空数组作为默认值,保持返回值类型一致
356
+ return [];
357
+ }
398
358
  };
399
359
 
400
360
  /**
@@ -403,54 +363,82 @@ Sqlite.prototype.run = async function (sql, params, timeout) {
403
363
  * @param {Array} params - 参数数组
404
364
  * @param {Number} timeout - 超时时间(毫秒)
405
365
  * @returns {Promise<Object>}
366
+ * @throws {TypeError} 当sql参数无效时
406
367
  */
407
368
  Sqlite.prototype.exec = async function (sql, params, timeout) {
408
- let conn = null;
409
- timeout = timeout || this.config.query_timeout || 30000;
410
-
411
- try {
412
- // 获取连接
413
- conn = await this.getConn(timeout);
414
-
415
- // 直接在方法内部实现超时控制
416
- const query_promise = new Promise((resolve, reject) => {
417
- conn.run(sql, params || [], function (error) {
418
- if (error) {
419
- reject(error);
420
- } else {
421
- // 保持与MySQL兼容的返回值格式
422
- resolve({
423
- affected_rows: this.changes || 0,
424
- insert_id: this.lastID || 0,
425
- changed_rows: this.changes || 0
426
- });
427
- }
428
- });
429
- });
369
+ // 参数校验
370
+ if (typeof sql !== 'string' || sql.trim() === '') {
371
+ throw new TypeError('sql must be non-empty string');
372
+ }
373
+ if (params !== undefined && !Array.isArray(params)) {
374
+ throw new TypeError('params must be array');
375
+ }
376
+ if (timeout !== undefined && typeof timeout !== 'number') {
377
+ throw new TypeError('timeout must be number');
378
+ }
430
379
 
431
- const timeout_promise = new Promise((_, reject) => {
432
- setTimeout(() => {
433
- reject(new Error(`SQL执行超时: ${timeout}ms`));
434
- }, timeout);
435
- });
380
+ this.error = null;
381
+ let conn = null;
382
+ timeout = timeout || this.config.query_timeout || 30000;
383
+
384
+ try {
385
+ // 获取连接
386
+ conn = await this.getConn(timeout);
387
+ var _this = this;
388
+ // 直接在方法内部实现超时控制
389
+ const query_promise = new Promise((resolve, reject) => {
390
+ conn.run(sql, params || [], function (error, rows) {
391
+ if (error) {
392
+ _this.sql = error.sql;
393
+ _this.error = {
394
+ code: error.errno,
395
+ message: error.message
396
+ }
397
+ reject(error);
398
+ resolve(0);
399
+ } else {
400
+ // 保持与MySQL兼容的返回值格式
401
+ resolve(this.lastID || this.changes || 1);
402
+ }
403
+ });
404
+ });
436
405
 
437
- const result = await Promise.race([query_promise, timeout_promise]);
406
+ const timeout_promise = new Promise((_, reject) => {
407
+ setTimeout(() => {
408
+ reject(new Error(`SQL执行超时: ${timeout}ms`));
409
+ }, timeout);
410
+ });
438
411
 
439
- return result;
440
- } catch (err) {
441
- $.log.error('[Sqlite] [exec]', 'SQL执行失败', {
442
- error: err.message,
443
- sql: sql.substring(0, 200),
444
- params: params
445
- });
412
+ const result = await Promise.race([query_promise, timeout_promise]);
446
413
 
447
- // 处理连接错误,触发重连
448
- if (err.code && (err.code === 'ECONNRESET' || err.code === 'ETIMEDOUT')) {
449
- this._handleConnectionError(err);
450
- }
414
+ // 连接池模式下释放连接
415
+ if (this._use_pool && conn) {
416
+ this._releasePoolConnection(conn);
417
+ }
418
+
419
+ return result;
420
+ } catch (err) {
421
+ this.logger('error', 'SQL执行失败', err);
422
+
423
+ // 连接池模式下释放连接
424
+ if (this._use_pool && conn) {
425
+ this._releasePoolConnection(conn);
426
+ }
451
427
 
452
- throw err;
453
- }
428
+ // 处理连接错误,触发重连
429
+ if (err.code && (err.code === 'ECONNRESET' || err.code === 'ETIMEDOUT')) {
430
+ this._handleConnectionError(err);
431
+ }
432
+ this.sql = sql;
433
+ // 记录错误信息
434
+ this.error = {
435
+ code: err.code,
436
+ message: err.message
437
+ };
438
+
439
+ // 返回默认操作结果对象,保持返回值类型一致
440
+ return 0;
441
+ }
454
442
  };
455
443
 
456
444
  /**
@@ -459,218 +447,248 @@ Sqlite.prototype.exec = async function (sql, params, timeout) {
459
447
  * @param {Object} condition - 查询条件
460
448
  * @param {Object} options - 选项(order_by, limit, offset等)
461
449
  * @returns {Promise<Array>}
450
+ * @throws {TypeError} 当table参数无效时
462
451
  */
463
452
  Sqlite.prototype.read = async function (table, condition, options) {
464
- try {
465
- // 检查连接状态
466
- if (this._status !== 'connected') {
467
- throw new Error('数据库连接未建立');
453
+ // 参数校验
454
+ if (typeof table !== 'string' || table.trim() === '') {
455
+ throw new TypeError('table must be non-empty string');
468
456
  }
469
-
470
- // 构建基础SQL查询
471
- let sql = `SELECT * FROM ${table}`;
472
- const params = [];
473
-
474
- // 处理条件
475
- if (condition && Object.keys(condition).length > 0) {
476
- const where_clauses = [];
477
- for (const [field, value] of Object.entries(condition)) {
478
- where_clauses.push(`${field} = ?`);
479
- params.push(value);
480
- }
481
- sql += ` WHERE ${where_clauses.join(' AND ')}`;
482
- }
483
-
484
- // 处理排序
485
- if (options && options.order_by) {
486
- sql += ` ORDER BY ${options.order_by}`;
457
+ if (condition !== undefined && (typeof condition !== 'object' || Array.isArray(condition))) {
458
+ throw new TypeError('condition must be object');
487
459
  }
488
-
489
- // 处理分页
490
- if (options && options.limit) {
491
- sql += ` LIMIT ${options.limit}`;
460
+ if (options !== undefined && (typeof options !== 'object' || Array.isArray(options))) {
461
+ throw new TypeError('options must be object');
492
462
  }
493
463
 
494
- if (options && options.offset) {
495
- sql += ` OFFSET ${options.offset}`;
496
- }
497
-
498
- // 记录查询日志
499
- if (this.config.debug) {
500
- $.log.debug(`[${this.constructor.name}] [read] 查询数据`, { sql, params });
501
- }
464
+ try {
465
+ // 检查连接状态
466
+ if (this._status !== 'connected') {
467
+ throw new Error('数据库连接未建立');
468
+ }
502
469
 
503
- // 执行查询
504
- const results = await this.run(sql, params);
470
+ // 构建基础SQL查询
471
+ let sql = `SELECT * FROM ${table}`;
472
+ const params = [];
505
473
 
506
- if (this.config.debug) {
507
- $.log.info(`[${this.constructor.name}] [read] 查询成功`, { count: Array.isArray(results) ? results.length : 1 });
508
- }
474
+ // 处理条件
475
+ if (condition && Object.keys(condition).length > 0) {
476
+ const where_clauses = [];
477
+ for (const [field, value] of Object.entries(condition)) {
478
+ where_clauses.push(`${field} = ?`);
479
+ params.push(value);
480
+ }
481
+ sql += ` WHERE ${where_clauses.join(' AND ')}`;
482
+ }
509
483
 
510
- // 确保返回数组格式
511
- return Array.isArray(results) ? results : [results];
512
- } catch (error) {
513
- // 记录错误日志
514
- $.log.error(`[${this.constructor.name}] [read] 查询失败`, {
515
- error: error.message,
516
- table: table,
517
- condition: condition
518
- });
484
+ // 处理排序
485
+ if (options && options.order_by) {
486
+ sql += ` ORDER BY ${options.order_by}`;
487
+ }
519
488
 
520
- // 抛出错误
521
- throw error;
522
- }
523
- };
489
+ // 处理分页
490
+ if (options && options.limit) {
491
+ sql += ` LIMIT ${options.limit}`;
492
+ }
524
493
 
525
- /**
526
- * 获取数据库操作实例
527
- * @param {String} database - 数据库名称
528
- * @returns {Object} 数据库操作实例
529
- */
530
- Sqlite.prototype.db = function (database) {
531
- if (this._status !== 'connected') {
532
- throw new Error('数据库连接未建立');
533
- }
494
+ if (options && options.offset) {
495
+ sql += ` OFFSET ${options.offset}`;
496
+ }
534
497
 
535
- // 优化:缓存DB实例,避免重复创建
536
- if (!this._db_instance) {
537
- this._db_instance = new DB(this);
498
+ // 记录查询日志
499
+ if (this.config.debug) {
500
+ this.logger('debug', '查询数据', { sql, params });
501
+ }
538
502
 
539
- // 为DB实例添加必要的方法,确保与MySQL接口兼容
540
- this._db_instance.run = this.run.bind(this);
541
- this._db_instance.exec = this.exec.bind(this);
542
- }
503
+ // 执行查询
504
+ const results = await this.run(sql, params);
543
505
 
544
- if (database) {
545
- this._db_instance.database = database;
546
- }
506
+ if (this.config.debug) {
507
+ this.logger('info', '查询成功', { count: Array.isArray(results) ? results.length : 1 });
508
+ }
547
509
 
548
- return this._db_instance;
510
+ // 确保返回数组格式
511
+ return Array.isArray(results) ? results : [results];
512
+ } catch (error) {
513
+ this.logger('error', '查询失败', error);
514
+ // 返回空数组作为默认值,保持返回值类型一致
515
+ return [];
516
+ }
549
517
  };
550
518
 
551
519
  /**
552
- * 获取数据库管理器模型
553
- * @returns {Object} 数据库管理器模型
520
+ * 获取数据库管理器(保持兼容性)
521
+ * @returns {Object} DB实例
554
522
  */
555
- Sqlite.prototype.dbs = function () {
556
- return this.db();
523
+ Sqlite.prototype.db = function () {
524
+ return new DB(this);
557
525
  };
558
526
 
527
+ // /**
528
+ // * 获取数据库操作实例
529
+ // * @param {String} database - 数据库名称
530
+ // * @returns {Object} 数据库操作实例
531
+ // * @throws {TypeError} 当database参数无效时
532
+ // */
533
+ // Sqlite.prototype.db = function (database) {
534
+ // // 参数校验
535
+ // if (database !== undefined && typeof database !== 'string') {
536
+ // throw new TypeError('database must be string');
537
+ // }
538
+
539
+ // if (this._status !== 'connected') {
540
+ // throw new Error('数据库连接未建立');
541
+ // }
542
+
543
+ // // 优化:缓存DB实例,避免重复创建
544
+ // if (!this._db_instance) {
545
+ // this._db_instance = new DB(this);
546
+
547
+ // // 为DB实例添加必要的方法,确保与MySQL接口兼容
548
+ // this._db_instance.run = this.run.bind(this);
549
+ // this._db_instance.exec = this.exec.bind(this);
550
+ // }
551
+
552
+ // if (database) {
553
+ // this._db_instance.database = database;
554
+ // }
555
+
556
+ // return this._db_instance;
557
+ // };
558
+
559
559
  /**
560
560
  * 开始事务
561
561
  * @returns {Promise<Object>} 事务连接对象
562
562
  */
563
563
  Sqlite.prototype.beginTransaction = async function () {
564
- try {
565
- // 检查连接状态
566
- if (this._status !== 'connected') {
567
- throw new Error('数据库连接未建立');
568
- }
569
-
570
- // 获取连接
571
- const connection = await this.getConn();
572
-
573
- // 开始事务
574
- await new Promise((resolve, reject) => {
575
- connection.run('BEGIN TRANSACTION', (error) => {
576
- if (error) {
577
- reject(error);
578
- } else {
579
- resolve();
564
+ try {
565
+ // 检查连接状态
566
+ if (this._status !== 'connected') {
567
+ throw new Error('数据库连接未建立');
580
568
  }
581
- });
582
- });
583
569
 
584
- if (this.config.debug) {
585
- $.log.debug(`[${this.constructor.name}] [beginTransaction] 事务开始`);
586
- }
570
+ // 获取连接
571
+ const connection = await this.getConn();
587
572
 
588
- // 返回事务连接对象,包含提交和回滚方法
589
- return {
590
- connection,
591
- commit: async () => {
573
+ // 开始事务
592
574
  await new Promise((resolve, reject) => {
593
- connection.run('COMMIT', (error) => {
594
- if (error) {
595
- reject(error);
596
- } else {
597
- resolve();
598
- }
599
- });
600
- });
601
- if (this.config.debug) {
602
- $.log.debug(`[${this.constructor.name}] [beginTransaction] 事务提交`);
603
- }
604
- },
605
- rollback: async () => {
606
- await new Promise((resolve, reject) => {
607
- connection.run('ROLLBACK', (error) => {
608
- if (error) {
609
- reject(error);
610
- } else {
611
- resolve();
612
- }
613
- });
575
+ connection.run('BEGIN TRANSACTION', (error) => {
576
+ if (error) {
577
+ reject(error);
578
+ } else {
579
+ resolve();
580
+ }
581
+ });
614
582
  });
583
+
615
584
  if (this.config.debug) {
616
- $.log.debug(`[${this.constructor.name}] [beginTransaction] 事务回滚`);
585
+ this.logger('debug', '事务开始');
617
586
  }
618
- },
619
- exec: async (sql, params) => {
620
- // 在事务中执行SQL
621
- return new Promise((resolve, reject) => {
622
- connection.run(sql, params || [], function (error) {
623
- if (error) {
624
- reject(error);
625
- } else {
626
- resolve({
627
- affected_rows: this.changes,
628
- insert_id: this.lastID,
629
- changed_rows: this.changes,
630
- last_id: this.lastID,
631
- changes: this.changes
632
- });
587
+
588
+ // 返回事务连接对象,包含提交和回滚方法
589
+ return {
590
+ connection,
591
+ commit: async () => {
592
+ await new Promise((resolve, reject) => {
593
+ connection.run('COMMIT', (error) => {
594
+ if (error) {
595
+ reject(error);
596
+ } else {
597
+ resolve();
598
+ }
599
+ });
600
+ });
601
+ if (this.config.debug) {
602
+ this.logger('debug', '事务提交');
603
+ }
604
+
605
+ // 连接池模式下释放连接
606
+ if (this._use_pool) {
607
+ this._releasePoolConnection(connection);
608
+ }
609
+ },
610
+ rollback: async () => {
611
+ await new Promise((resolve, reject) => {
612
+ connection.run('ROLLBACK', (error) => {
613
+ if (error) {
614
+ reject(error);
615
+ } else {
616
+ resolve();
617
+ }
618
+ });
619
+ });
620
+ if (this.config.debug) {
621
+ this.logger('debug', '事务回滚');
622
+ }
623
+
624
+ // 连接池模式下释放连接
625
+ if (this._use_pool) {
626
+ this._releasePoolConnection(connection);
627
+ }
628
+ },
629
+ exec: async (sql, params) => {
630
+ // 在事务中执行SQL
631
+ return new Promise((resolve, reject) => {
632
+ connection.run(sql, params || [], function (error) {
633
+ if (error) {
634
+ this.sql = error.sql;
635
+ this.error = {
636
+ code: error.errno,
637
+ message: error.message
638
+ }
639
+ reject(error);
640
+ resolve(0);
641
+ } else {
642
+ resolve(this.lastID || this.changes || 1);
643
+ }
644
+ });
645
+ });
633
646
  }
634
- });
635
- });
636
- }
637
- };
638
- } catch (error) {
639
- $.log.error(`[${this.constructor.name}] [beginTransaction] 事务开始失败`, { error: error.message });
640
- throw error;
641
- }
647
+ };
648
+ } catch (error) {
649
+ this.logger('error', '事务开始失败', error);
650
+ // 重新抛出异常,因为调用方需要事务对象
651
+ throw error;
652
+ }
642
653
  };
643
654
 
644
655
  /**
645
656
  * 在事务中执行多个操作
646
657
  * @param {Function} callback - 包含事务操作的回调函数
647
658
  * @returns {Promise<*>} 回调函数的返回值
659
+ * @throws {TypeError} 当callback参数无效时
648
660
  */
649
661
  Sqlite.prototype.transaction = async function (callback) {
650
- let transaction = null;
662
+ // 参数校验
663
+ if (typeof callback !== 'function') {
664
+ throw new TypeError('callback must be function');
665
+ }
651
666
 
652
- try {
653
- // 开始事务
654
- transaction = await this.beginTransaction();
667
+ let transaction = null;
655
668
 
656
- // 执行回调函数,传入事务对象
657
- const result = await callback(transaction);
669
+ try {
670
+ // 开始事务
671
+ transaction = await this.beginTransaction();
658
672
 
659
- // 提交事务
660
- await transaction.commit();
673
+ // 执行回调函数,传入事务对象
674
+ const result = await callback(transaction);
661
675
 
662
- return result;
663
- } catch (error) {
664
- // 如果有事务,回滚
665
- if (transaction) {
666
- await transaction.rollback().catch(err => {
667
- $.log.error(`[${this.constructor.name}] [transaction] 事务回滚失败`, { error: err.message });
668
- });
669
- }
676
+ // 提交事务
677
+ await transaction.commit();
670
678
 
671
- $.log.error(`[${this.constructor.name}] [transaction] 事务执行失败`, { error: error.message });
672
- throw error;
673
- }
679
+ return result;
680
+ } catch (error) {
681
+ // 如果有事务,回滚
682
+ if (transaction) {
683
+ await transaction.rollback().catch(err => {
684
+ this.logger('error', '事务回滚失败', err);
685
+ });
686
+ }
687
+
688
+ this.logger('error', '事务执行失败', error);
689
+ // 重新抛出异常,因为调用方需要事务执行结果
690
+ throw error;
691
+ }
674
692
  };
675
693
 
676
694
  /**
@@ -678,14 +696,23 @@ Sqlite.prototype.transaction = async function (callback) {
678
696
  * @param {String} name - 表名
679
697
  * @param {String} key - 主键
680
698
  * @returns {Object} 数据库操作实例
699
+ * @throws {TypeError} 当name参数无效时
681
700
  */
682
701
  Sqlite.prototype.table = function (name, key) {
683
- var db = this.db();
684
- db.table = name;
685
- if (key) {
686
- db.key = key;
687
- }
688
- return db;
702
+ // 参数校验
703
+ if (typeof name !== 'string' || name.trim() === '') {
704
+ throw new TypeError('name must be non-empty string');
705
+ }
706
+ if (key !== undefined && typeof key !== 'string') {
707
+ throw new TypeError('key must be string');
708
+ }
709
+
710
+ var db = this.db();
711
+ db.table = name;
712
+ if (key) {
713
+ db.key = key;
714
+ }
715
+ return db;
689
716
  };
690
717
 
691
718
  // close 方法已在文件前面部分实现,这里不再重复
@@ -694,10 +721,10 @@ Sqlite.prototype.table = function (name, key) {
694
721
  * 确保连接池对象存在
695
722
  */
696
723
  if (!$.pool) {
697
- $.pool = {};
724
+ $.pool = {};
698
725
  }
699
726
  if (!$.pool.sqlite) {
700
- $.pool.sqlite = {};
727
+ $.pool.sqlite = {};
701
728
  }
702
729
 
703
730
  /**
@@ -707,17 +734,191 @@ if (!$.pool.sqlite) {
707
734
  * @return {Object} 返回一个Sqlite类实例
708
735
  */
709
736
  function sqliteAdmin(scope, config) {
710
- if (!scope) {
711
- scope = 'sys';
712
- }
713
- var obj = $.pool.sqlite[scope];
714
- if (!obj) {
715
- $.pool.sqlite[scope] = new Sqlite(config);
716
- obj = $.pool.sqlite[scope];
717
- }
718
- return obj;
737
+ if (!scope) {
738
+ scope = 'sys';
739
+ }
740
+ var obj = $.pool.sqlite[scope];
741
+ if (!obj) {
742
+ $.pool.sqlite[scope] = new Sqlite(config);
743
+ obj = $.pool.sqlite[scope];
744
+ }
745
+ return obj;
719
746
  }
720
747
 
748
+ /**
749
+ * 创建SQLite连接
750
+ * @private
751
+ * @returns {Promise<Object>} 数据库连接对象
752
+ */
753
+ Sqlite.prototype._createConnection = function () {
754
+ return new Promise((resolve, reject) => {
755
+ const db = new sqlite3.Database(this._getDbFilename(), (err) => {
756
+ if (err) {
757
+ reject(err);
758
+ } else {
759
+ resolve(db);
760
+ }
761
+ });
762
+ });
763
+ };
764
+
765
+ /**
766
+ * 初始化连接池
767
+ * @private
768
+ * @returns {Promise<void>}
769
+ */
770
+ Sqlite.prototype._initPool = async function () {
771
+ if (this._pool_reaper) {
772
+ return; // 连接池已初始化
773
+ }
774
+
775
+ try {
776
+ // 创建最小连接数
777
+ for (let i = 0; i < this.config.pool_min; i++) {
778
+ const conn = await this._createConnection();
779
+ this._pool_connections.push(conn);
780
+ this._pool_available.push(conn);
781
+ }
782
+
783
+ // 启动连接回收器
784
+ this._pool_reaper = setInterval(() => {
785
+ this._reapIdleConnections();
786
+ }, this.config.pool_reap_interval);
787
+
788
+ this.logger('debug', '连接池初始化完成', {
789
+ min: this.config.pool_min,
790
+ max: this.config.pool_max,
791
+ current: this._pool_connections.length
792
+ });
793
+ } catch (error) {
794
+ this.logger('error', '连接池初始化失败', error);
795
+ throw error;
796
+ }
797
+ };
798
+
799
+ /**
800
+ * 回收空闲连接
801
+ * @private
802
+ */
803
+ Sqlite.prototype._reapIdleConnections = function () {
804
+ const now = Date.now();
805
+ const idle_timeout = this.config.pool_idle_timeout;
806
+
807
+ for (let i = this._pool_available.length - 1; i >= 0; i--) {
808
+ const conn = this._pool_available[i];
809
+ if (conn._last_used && now - conn._last_used > idle_timeout) {
810
+ // 关闭并移除空闲连接
811
+ conn.close((err) => {
812
+ if (err) {
813
+ this.logger('error', '关闭空闲连接失败', err);
814
+ }
815
+ });
816
+
817
+ // 从连接池中移除
818
+ const conn_index = this._pool_connections.indexOf(conn);
819
+ if (conn_index > -1) {
820
+ this._pool_connections.splice(conn_index, 1);
821
+ }
822
+ this._pool_available.splice(i, 1);
823
+
824
+ this.logger('debug', '回收空闲连接');
825
+ }
826
+ }
827
+ };
828
+
829
+ /**
830
+ * 从连接池获取连接
831
+ * @private
832
+ * @returns {Promise<Object>} 数据库连接
833
+ */
834
+ Sqlite.prototype._getPoolConnection = async function () {
835
+ const acquire_timeout = this.config.pool_acquire_timeout;
836
+ const start_time = Date.now();
837
+
838
+ return new Promise((resolve, reject) => {
839
+ const tryGetConnection = () => {
840
+ // 检查超时
841
+ if (Date.now() - start_time > acquire_timeout) {
842
+ reject(new Error(`获取连接超时(${acquire_timeout}ms)`));
843
+ return;
844
+ }
845
+
846
+ // 检查是否有可用连接
847
+ if (this._pool_available.length > 0) {
848
+ const conn = this._pool_available.shift();
849
+ conn._last_used = Date.now();
850
+ resolve(conn);
851
+ return;
852
+ }
853
+
854
+ // 检查是否可以创建新连接
855
+ if (this._pool_connections.length < this.config.pool_max) {
856
+ this._createConnection()
857
+ .then(conn => {
858
+ this._pool_connections.push(conn);
859
+ conn._last_used = Date.now();
860
+ resolve(conn);
861
+ })
862
+ .catch(reject);
863
+ return;
864
+ }
865
+
866
+ // 没有可用连接,等待一段时间后重试
867
+ setTimeout(tryGetConnection, 100);
868
+ };
869
+
870
+ tryGetConnection();
871
+ });
872
+ };
873
+
874
+ /**
875
+ * 释放连接回连接池
876
+ * @private
877
+ * @param {Object} conn - 数据库连接
878
+ */
879
+ Sqlite.prototype._releasePoolConnection = function (conn) {
880
+ if (this._pool_connections.includes(conn)) {
881
+ conn._last_used = Date.now();
882
+ this._pool_available.push(conn);
883
+
884
+ // 处理等待队列
885
+ if (this._pool_waiting.length > 0) {
886
+ const waiting = this._pool_waiting.shift();
887
+ waiting.resolve(conn);
888
+ }
889
+ }
890
+ };
891
+
892
+ /**
893
+ * 关闭连接池
894
+ * @private
895
+ */
896
+ Sqlite.prototype._closePool = function () {
897
+ if (this._pool_reaper) {
898
+ clearInterval(this._pool_reaper);
899
+ this._pool_reaper = null;
900
+ }
901
+
902
+ // 关闭所有连接
903
+ this._pool_connections.forEach(conn => {
904
+ try {
905
+ conn.close((err) => {
906
+ if (err) {
907
+ this.logger('error', '关闭连接池连接失败', err);
908
+ }
909
+ });
910
+ } catch (error) {
911
+ this.logger('error', '关闭连接池连接异常', error);
912
+ }
913
+ });
914
+
915
+ this._pool_connections = [];
916
+ this._pool_available = [];
917
+ this._pool_waiting = [];
918
+
919
+ this.logger('debug', '连接池已关闭');
920
+ };
921
+
721
922
  // 模块导出
722
923
  exports.Sqlite = Sqlite;
723
924
  exports.sqliteAdmin = sqliteAdmin;