mm_sqlite 1.5.5 → 1.5.7

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 CHANGED
@@ -1,670 +1,24 @@
1
- const {
2
- DB
3
- } = require('./db');
4
- const { Base } = require('mm_expand');
5
- const SQLitePool = require('./pool');
6
-
7
-
8
- /**
9
- * SQLite数据库操作类
10
- * @class SQLite
11
- * @augments Base
12
- */
13
- class SQLite extends Base {
14
- /**
15
- * 默认配置
16
- */
17
- static config = {
18
- name: 'default',
19
- dir: './db/'.fullname(),
20
- user: 'root',
21
- password: '',
22
- database: 'mm',
23
- charset: 'utf8mb4',
24
- timezone: '+08:00',
25
- connect_timeout: 5000, // 连接超时时间
26
- acquire_timeout: 5000, // 获取连接超时时间
27
- query_timeout: 10000, // 查询超时时间
28
- // 连接池配置(统一使用连接池模式)
29
- pool_min: 1, // 最小连接数
30
- pool_max: 3, // 最大连接数
31
- pool_acquire_timeout: 5000, // 连接池获取超时
32
- pool_idle_timeout: 30000, // 空闲超时
33
- pool_reap_interval: 5000 // 回收间隔
34
- };
35
-
36
- /**
37
- * 构造函数
38
- * @param {object} config - 配置对象
39
- */
40
- constructor(config) {
41
- super(config);
42
- }
43
- }
44
-
45
- /**
46
- * 预初始化数据库连接
47
- * @private
48
- */
49
- SQLite.prototype._preset = function () {
50
- this._connection = null;
51
- this._status = 'closed';
52
- this._last_connect_time = 0;
53
- this._reconnecting = false;
54
- this._is_inited = false;
55
- this._is_destroyed = false;
56
- this._db = null;
57
- this._open = false;
58
- this._conn_retry_count = 0;
59
- // 初始化连接池实例
60
- this._pool = new SQLitePool(this.config, this._getDbFilename.bind(this), this.log.bind(this));
61
- };
62
-
63
- /**
64
- * 获取数据库文件名
65
- * @returns {string} 数据库文件名
66
- */
67
- SQLite.prototype._getDbFilename = function () {
68
- var file = this.config.database;
69
- if (file.indexOf('.') === -1) {
70
- file += '.db';
71
- }
72
-
73
- return file.fullname(this.config.dir);
74
- };
75
-
76
- /**
77
- * 打开数据库连接(统一使用连接池模式)
78
- * @param {number} timeout - 超时时间(毫秒)
79
- * @returns {Promise<boolean>}
80
- * @throws {TypeError} 当timeout参数无效时
81
- */
82
- SQLite.prototype.open = async function (timeout) {
83
- // 参数校验
84
- if (timeout !== undefined && typeof timeout !== 'number') {
85
- throw new TypeError('timeout must be number');
86
- }
87
-
88
- if (this._status === 'connected' || this._status === 'connecting') {
89
- this.log('warn', '数据库连接已存在或正在连接中');
90
- return true;
91
- }
92
-
93
- this._status = 'connecting';
94
-
95
- try {
96
- this.config.dir.addDir();
97
-
98
- // 统一使用连接池模式
99
- await this._pool.init();
100
- this._last_connect_time = Date.now();
101
- this._status = 'connected';
102
- return true;
103
- } catch (err) {
104
- this._status = 'closed';
105
- this.log('error', '数据库连接失败', err);
106
- return false;
107
- }
108
- };
109
-
110
- /**
111
- * 关闭数据库连接(统一使用连接池模式)
112
- * @returns {Promise<boolean>}
113
- */
114
- SQLite.prototype.close = async function () {
115
- if (this._status !== 'connected') {
116
- this.log('warn', '数据库连接未建立');
117
- return false;
118
- }
119
-
120
- try {
121
- // 统一关闭连接池
122
- this._pool.close();
123
- this._db = null;
124
- this._open = false;
125
- this._status = 'closed';
126
- return true;
127
- } catch (err) {
128
- this.log('error', '关闭数据库连接时发生异常', err);
129
- this._status = 'closed';
130
- return false;
131
- }
132
- };
133
-
134
- /**
135
- * 获取数据库连接(统一使用连接池模式)
136
- * @param {number} timeout - 超时时间(毫秒)
137
- * @returns {Promise<object>}
138
- * @throws {TypeError} 当timeout参数无效时
139
- */
140
- SQLite.prototype.getConn = async function (timeout) {
141
- // 参数校验
142
- if (timeout !== undefined && typeof timeout !== 'number') {
143
- throw new TypeError('timeout must be number');
144
- }
145
-
146
- const acquire_timeout = timeout || this.config.acquire_timeout || this.config.connect_timeout || 20000;
147
-
148
- if (this._status !== 'connected') {
149
- await this.open(acquire_timeout);
150
- }
151
-
152
- // 统一从连接池获取连接
153
- return await this._pool.getConnection(acquire_timeout);
154
- };
155
-
156
- /**
157
- * 执行SQL查询
158
- * @param {string} sql - SQL语句
159
- * @param {Array} params - 参数数组
160
- * @param {number} timeout - 超时时间(毫秒)
161
- * @returns {Promise<object>}
162
- * @throws {TypeError} 当sql参数无效时
163
- */
164
- SQLite.prototype.run = async function (sql, params = [], timeout = 30000) {
165
- // 参数校验
166
- if (typeof sql !== 'string' || sql.trim() === '') {
167
- throw new TypeError('sql must be non-empty string');
168
- }
169
- if (params !== undefined && !Array.isArray(params)) {
170
- throw new TypeError('params must be array');
171
- }
172
- if (timeout !== undefined && typeof timeout !== 'number') {
173
- throw new TypeError('timeout must be number');
174
- }
175
-
176
- // 注意:同 scope 下为单例共享,this.error/this.sql 并发时以最后一次写入为准
177
- this.error = null;
178
- this.sql = '';
179
- let conn = null;
180
- const query_timeout = timeout || this.config.query_timeout || 30000;
181
- let ret = [];
182
- try {
183
- // 获取连接
184
- conn = await this.getConn(query_timeout);
185
-
186
- // better-sqlite3 同步查询,无需 Promise 包装
187
- const stmt = conn.prepare(sql);
188
- ret = params.length > 0 ? stmt.all(params) : stmt.all();
189
- } catch (error) {
190
- console.error('SQL执行失败', error);
191
- this.sql = sql;
192
- this.error = {
193
- code: error.code || error.errno,
194
- message: error.message
195
- };
196
- ret = [];
197
- } finally {
198
- await releaseConn(conn);
199
- }
200
- return ret;
201
- };
202
-
203
- /**
204
- * 执行SQL语句(用于执行非查询语句如INSERT/UPDATE/DELETE)
205
- * @param {string} sql - SQL语句
206
- * @param {Array} params - 参数数组
207
- * @param {number} timeout - 超时时间(毫秒)
208
- * @returns {Promise<object>}
209
- * @throws {TypeError} 当sql参数无效时
210
- */
211
- SQLite.prototype.exec = async function (sql, params = [], timeout) {
212
- // 参数校验
213
- if (typeof sql !== 'string' || sql.trim() === '') {
214
- throw new TypeError('sql must be non-empty string');
215
- }
216
- if (params !== undefined && !Array.isArray(params)) {
217
- throw new TypeError('params must be array');
218
- }
219
- if (timeout !== undefined && typeof timeout !== 'number') {
220
- throw new TypeError('timeout must be number');
221
- }
222
-
223
- // 注意:同 scope 下为单例共享,this.error/this.sql 并发时以最后一次写入为准
224
- this.error = null;
225
- this.sql = '';
226
- let conn = null;
227
- timeout = timeout || this.config.query_timeout || 30000;
228
- let ret = 0;
229
- try {
230
- // 获取连接
231
- conn = await this.getConn(timeout);
232
- this.sql = sql;
233
-
234
- const trimmed = sql.trim().toUpperCase().replace(/;+$/, '');
235
- let info;
236
- // 事务命令不能作为预编译语句,需用 exec() 直接执行
237
- if (trimmed === 'BEGIN' || trimmed === 'BEGIN TRANSACTION' ||
238
- trimmed === 'COMMIT' || trimmed === 'ROLLBACK') {
239
- conn.exec(sql);
240
- info = { changes: 0, lastInsertRowid: 0 };
241
- } else {
242
- const stmt = conn.prepare(sql);
243
- info = params && params.length > 0 ? stmt.run(params) : stmt.run();
244
- }
245
- ret = info.lastInsertRowid || info.changes || 1;
246
- } catch (error) {
247
- console.error('SQL执行失败', error);
248
- this.sql = sql;
249
- this.error = {
250
- code: error.code || error.errno,
251
- message: error.message
252
- };
253
- ret = 0;
254
- } finally {
255
- await releaseConn(conn);
256
- }
257
- return ret;
258
- };
259
-
260
- /**
261
- * 执行多语句SQL脚本(专用于导入)
262
- * 逐条执行DML语句统计变更行数,其他语句使用exec执行
263
- * @param {string} sql - SQL脚本内容,可含多条以分号分隔的语句
264
- * @returns {Promise<object>} 执行结果统计对象
265
- * @throws {TypeError} 当sql参数无效时
266
- */
267
- SQLite.prototype.runSql = async function (sql) {
268
- // 参数校验
269
- if (typeof sql !== 'string' || sql.trim() === '') {
270
- throw new TypeError('sql must be non-empty string');
271
- }
272
-
273
- // 注意:同 scope 下为单例共享,this.error/this.sql 并发时以最后一次写入为准
274
- this.error = null;
275
- this.sql = '';
276
- let conn = null;
277
- let ret = { success: false, statements: 0, affected_rows: 0, error: null };
278
- try {
279
- // 获取连接
280
- conn = await this.getConn();
281
- this.sql = sql;
282
-
283
- // 拆分语句列表
284
- let stmts = splitStatements(sql);
285
- ret.statements = stmts.length;
286
- // 逐条执行,DML语句统计变更行数
287
- for (let stmt_sql of stmts) {
288
- if (/^(INSERT|UPDATE|DELETE|REPLACE)\b/i.test(stmt_sql)) {
289
- let info = conn.prepare(stmt_sql).run();
290
- ret.affected_rows += info.changes || 0;
291
- } else {
292
- conn.exec(stmt_sql);
293
- }
294
- }
295
- ret.success = true;
296
- } catch (error) {
297
- console.error('SQL执行失败', error);
298
- this.sql = sql;
299
- this.error = {
300
- code: error.code || error.errno,
301
- message: error.message
302
- };
303
- ret.error = error.message;
304
- } finally {
305
- await releaseConn(conn);
306
- }
307
- return ret;
308
- };
309
-
310
- /**
311
- * 拆分SQL脚本为语句列表
312
- * 去除注释后按分号分割,过滤空语句
313
- * @private
314
- * @param {string} sql - SQL脚本内容
315
- * @returns {Array} 语句列表
316
- */
317
- function splitStatements(sql) {
318
- // 去除行注释和块注释
319
- let clean_sql = sql
320
- .replace(/--[^\n]*/g, '')
321
- .replace(/\/\*[\s\S]*?\*\//g, '')
322
- .trim();
323
- let stmts = [];
324
- for (let part of clean_sql.split(';')) {
325
- let stmt = part.trim();
326
- if (stmt !== '') {
327
- stmts.push(stmt);
328
- }
329
- }
330
- return stmts;
331
- }
332
-
333
- /**
334
- * 读取整张表的数据
335
- * @param {string} table - 表名
336
- * @param {object} condition - 查询条件
337
- * @param {object} options - 选项(order_by, limit, offset等)
338
- * @returns {Promise<Array>}
339
- * @throws {TypeError} 当table参数无效时
340
- */
341
- SQLite.prototype.read = async function (table, condition = {}, options = {}) {
342
- // 参数校验
343
- if (typeof table !== 'string' || table.trim() === '') {
344
- throw new TypeError('table must be non-empty string');
345
- }
346
- if (condition !== undefined && (typeof condition !== 'object' || Array.isArray(condition))) {
347
- throw new TypeError('condition must be object');
348
- }
349
- if (options !== undefined && (typeof options !== 'object' || Array.isArray(options))) {
350
- throw new TypeError('options must be object');
351
- }
352
-
353
- // 检查连接状态
354
- if (this._status !== 'connected') {
355
- throw new Error('数据库连接未建立');
356
- }
357
-
358
- // 构建基础SQL查询
359
- let sql = `SELECT * FROM ${table}`;
360
- const params = [];
361
-
362
- // 处理条件
363
- if (condition && Object.keys(condition).length > 0) {
364
- const where_clauses = [];
365
- for (const [field, value] of Object.entries(condition)) {
366
- where_clauses.push(`${field} = ?`);
367
- params.push(value);
368
- }
369
- sql += ` WHERE ${where_clauses.join(' AND ')}`;
370
- }
371
-
372
- // 处理排序
373
- if (options && options.order_by) {
374
- sql += ` ORDER BY ${options.order_by}`;
375
- }
376
-
377
- // 处理分页
378
- if (options && options.limit) {
379
- sql += ` LIMIT ${options.limit}`;
380
- }
381
-
382
- if (options && options.offset) {
383
- sql += ` OFFSET ${options.offset}`;
384
- }
385
-
386
- // 记录查询日志
387
- if (this.config.debug) {
388
- this.log('debug', '查询数据', { sql, params });
389
- }
390
-
391
- // 执行查询
392
- const results = await this.run(sql, params);
393
-
394
- if (this.config.debug) {
395
- this.log('debug', '查询成功', { count: Array.isArray(results) ? results.length : 1 });
396
- }
397
-
398
- // 确保返回数组格式
399
- return Array.isArray(results) ? results : [results];
400
- };
401
-
402
- /**
403
- * 获取数据库管理器(保持兼容性)
404
- * @returns {object} DB实例
405
- */
406
- SQLite.prototype.db = function () {
407
- return new DB(this);
408
- };
409
-
410
- /**
411
- * 初始化数据库服务(保持兼容性)
412
- * @returns {Promise<void>}
413
- */
414
- SQLite.prototype.init = async function () {
415
- await this.open();
416
- };
417
-
418
- /**
419
- * 获取连接池状态信息
420
- * @returns {object} 连接池状态对象
421
- */
422
- SQLite.prototype.getPoolStats = function () {
423
- if (!this._pool || !this._pool._is_initialized) {
424
- return {
425
- status: 'not_initialized',
426
- message: '连接池未初始化'
427
- };
428
- }
429
-
430
- return {
431
- status: 'healthy',
432
- ...this._pool.getStats()
433
- };
434
- };
435
-
436
- /**
437
- * 健康检查
438
- * @returns {Promise<object>} 健康检查结果
439
- */
440
- SQLite.prototype.healthCheck = async function () {
441
- try {
442
- if (this._status !== 'connected') {
443
- return {
444
- status: 'error',
445
- message: '数据库连接未初始化'
446
- };
447
- }
448
-
449
- // 执行简单的查询来检查连接状态
450
- let result = await this.run('SELECT 1 as health_check');
451
-
452
- if (result && result.length > 0 && result[0].health_check === 1) {
453
- return {
454
- status: 'healthy',
455
- message: '数据库连接正常',
456
- ...this.getPoolStats()
457
- };
458
- } else {
459
- return {
460
- status: 'error',
461
- message: '健康检查查询失败'
462
- };
463
- }
464
- } catch (error) {
465
- this.log('error', '健康检查失败', error);
466
- return {
467
- status: 'error',
468
- message: `健康检查失败: ${error.message}`
469
- };
470
- }
471
- };
472
-
473
- // /**
474
- // * 获取数据库操作实例
475
- // * @param {String} database - 数据库名称
476
- // * @returns {Object} 数据库操作实例
477
- // * @throws {TypeError} 当database参数无效时
478
- // */
479
- // SQLite.prototype.db = function (database) {
480
- // // 参数校验
481
- // if (database !== undefined && typeof database !== 'string') {
482
- // throw new TypeError('database must be string');
483
- // }
484
-
485
- // if (this._status !== 'connected') {
486
- // throw new Error('数据库连接未建立');
487
- // }
488
-
489
- // // 优化:缓存DB实例,避免重复创建
490
- // if (!this._db_instance) {
491
- // this._db_instance = new DB(this);
492
-
493
- // // 为DB实例添加必要的方法,确保与MySQL接口兼容
494
- // this._db_instance.run = this.run.bind(this);
495
- // this._db_instance.exec = this.exec.bind(this);
496
- // }
497
-
498
- // if (database) {
499
- // this._db_instance.database = database;
500
- // }
501
-
502
- // return this._db_instance;
503
- // };
504
-
505
- /**
506
- * 开始事务
507
- * @returns {Promise<object>} 事务连接对象
508
- */
509
- SQLite.prototype.beginTransaction = async function () {
510
- try {
511
- // 检查连接状态
512
- if (this._status !== 'connected') {
513
- throw new Error('数据库连接未建立');
514
- }
515
-
516
- // 获取连接
517
- const conn = await this.getConn();
518
-
519
- // 标记连接为事务中
520
- conn._in_transaction = true;
521
-
522
- // 开始事务(better-sqlite3 同步执行)
523
- conn.exec('BEGIN TRANSACTION');
524
-
525
- if (this.config.debug) {
526
- this.log('debug', '事务开始');
527
- }
528
-
529
- // 返回事务连接对象,包含提交和回滚方法
530
- return {
531
- conn,
532
- commit: async () => {
533
- conn.exec('COMMIT');
534
- if (this.config.debug) {
535
- this.log('debug', '事务提交');
536
- }
537
-
538
- // 清除事务标记并释放连接
539
- conn._in_transaction = false;
540
- await releaseConn(conn);
541
- },
542
- rollback: async () => {
543
- conn.exec('ROLLBACK');
544
- if (this.config.debug) {
545
- this.log('debug', '事务回滚');
546
- }
547
- // 清除事务标记并释放连接
548
- conn._in_transaction = false;
549
- await releaseConn(conn);
550
- },
551
- exec: async (sql, params) => {
552
- // 在事务中执行SQL
553
- const stmt = conn.prepare(sql);
554
- const info = params && params.length > 0 ? stmt.run(params) : stmt.run();
555
- return info.lastInsertRowid || info.changes || 1;
556
- }
557
- };
558
- } catch (error) {
559
- this.log('error', '事务开始失败', error);
560
- // 重新抛出异常,因为调用方需要事务对象
561
- throw error;
562
- }
563
- };
564
-
565
- /**
566
- * 在事务中执行多个操作
567
- * @param {Function} callback - 包含事务操作的回调函数
568
- * @returns {Promise<*>} 回调函数的返回值
569
- * @throws {TypeError} 当callback参数无效时
570
- */
571
- SQLite.prototype.transaction = async function (callback) {
572
- // 参数校验
573
- if (typeof callback !== 'function') {
574
- throw new TypeError('callback must be function');
575
- }
576
-
577
- let trans = null;
578
-
579
- try {
580
- // 开始事务
581
- trans = await this.beginTransaction();
582
-
583
- // 执行回调函数,传入事务对象
584
- const result = await callback(trans);
585
-
586
- // 提交事务
587
- await trans.commit();
588
-
589
- return result;
590
- } catch (error) {
591
- // 如果有事务,回滚
592
- if (trans) {
593
- await trans.rollback().catch(err => {
594
- this.log('error', '事务回滚失败', err);
595
- });
596
- }
597
-
598
- this.log('error', '事务执行失败', error);
599
- // 重新抛出异常,因为调用方需要事务执行结果
600
- throw error;
601
- }
602
- };
603
-
604
- /**
605
- * 设置当前操作的表名
606
- * @param {string} name - 表名
607
- * @param {string} key - 主键
608
- * @returns {object} 数据库操作实例
609
- * @throws {TypeError} 当name参数无效时
610
- */
611
- SQLite.prototype.table = function (name, key) {
612
- // 参数校验
613
- if (typeof name !== 'string' || name.trim() === '') {
614
- throw new TypeError('name must be non-empty string');
615
- }
616
- if (key !== undefined && typeof key !== 'string') {
617
- throw new TypeError('key must be string');
618
- }
619
-
620
- var db = this.db();
621
- db.table = name;
622
- if (key) {
623
- db.key = key;
624
- }
625
- return db;
626
- };
627
-
628
- /**
629
- * 确保连接池对象存在
630
- */
631
- if (!$.pool) {
632
- $.pool = {};
633
- }
634
- if (!$.pool.sqlite) {
635
- $.pool.sqlite = {};
636
- }
637
-
638
- /**
639
- * Sqlite管理器,用于创建缓存
640
- * @param {string} scope 作用域
641
- * @param {object} config 配置参数
642
- * @returns {object} 返回一个Sqlite类实例
643
- */
644
- function sqliteAdmin(scope, config) {
645
- if (!scope) {
646
- scope = 'sys';
647
- }
648
- var obj = $.pool.sqlite[scope];
649
- if (!obj) {
650
- $.pool.sqlite[scope] = new SQLite(config);
651
- obj = $.pool.sqlite[scope];
652
- }
653
- return obj;
654
- }
655
-
656
- /**
657
- * 安全释放数据库连接
658
- * @param {object} conn - 数据库连接对象
659
- * @returns {Promise<void>}
660
- */
661
- async function releaseConn(conn) {
662
- if (!conn) {
663
- return;
664
- }
665
- await conn.release();
666
- }
667
-
668
- // 模块导出
669
- exports.SQLite = SQLite;
670
- exports.sqliteAdmin = sqliteAdmin;
1
+ /**
2
+ * mm_sqlite SQLite数据库模块
3
+ * @description 参考 mm_redis 结构,index.js 为薄包装,类定义在 lib/sqlite.js
4
+ */
5
+ const SQLite = require('./lib/sqlite');
6
+ const Admin = require('mm_corejs').Admin;
7
+
8
+ /**
9
+ * SQLite 管理器实例(Admin 工厂)
10
+ * 支持 os.js 调用:require('mm_sqlite').admin.setServer(os) -> admin.get('os', config)
11
+ */
12
+ let admin = new Admin();
13
+ admin.setClass(SQLite);
14
+
15
+ module.exports = {
16
+ SQLite,
17
+ admin,
18
+ sqliteAdmin(scope, config) {
19
+ if (!scope) {
20
+ scope = 'os';
21
+ }
22
+ return admin.get(scope, config);
23
+ }
24
+ };