monsqlize 1.0.0 → 1.0.2
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/CHANGELOG.md +92 -2419
- package/README.md +630 -1070
- package/index.d.ts +252 -15
- package/lib/cache.js +8 -8
- package/lib/common/validation.js +64 -1
- package/lib/connect.js +3 -3
- package/lib/errors.js +10 -0
- package/lib/index.js +118 -9
- package/lib/infrastructure/ssh-tunnel-ssh2.js +211 -0
- package/lib/infrastructure/ssh-tunnel.js +40 -0
- package/lib/infrastructure/uri-parser.js +35 -0
- package/lib/lock/Lock.js +66 -0
- package/lib/lock/errors.js +27 -0
- package/lib/lock/index.js +12 -0
- package/lib/logger.js +1 -1
- package/lib/model/examples/test.js +4 -4
- package/lib/mongodb/common/accessor-helpers.js +17 -3
- package/lib/mongodb/connect.js +68 -13
- package/lib/mongodb/index.js +140 -7
- package/lib/mongodb/management/collection-ops.js +4 -4
- package/lib/mongodb/management/index-ops.js +18 -18
- package/lib/mongodb/management/validation-ops.js +3 -3
- package/lib/mongodb/queries/aggregate.js +14 -5
- package/lib/mongodb/queries/chain.js +52 -45
- package/lib/mongodb/queries/count.js +16 -6
- package/lib/mongodb/queries/distinct.js +15 -6
- package/lib/mongodb/queries/find-and-count.js +22 -13
- package/lib/mongodb/queries/find-by-ids.js +5 -5
- package/lib/mongodb/queries/find-one-by-id.js +1 -1
- package/lib/mongodb/queries/find-one.js +12 -3
- package/lib/mongodb/queries/find-page.js +12 -0
- package/lib/mongodb/queries/find.js +15 -6
- package/lib/mongodb/queries/index.js +1 -0
- package/lib/mongodb/queries/watch.js +537 -0
- package/lib/mongodb/writes/delete-many.js +20 -11
- package/lib/mongodb/writes/delete-one.js +18 -9
- package/lib/mongodb/writes/find-one-and-delete.js +19 -10
- package/lib/mongodb/writes/find-one-and-replace.js +36 -20
- package/lib/mongodb/writes/find-one-and-update.js +36 -20
- package/lib/mongodb/writes/increment-one.js +16 -7
- package/lib/mongodb/writes/index.js +13 -13
- package/lib/mongodb/writes/insert-batch.js +46 -37
- package/lib/mongodb/writes/insert-many.js +22 -13
- package/lib/mongodb/writes/insert-one.js +18 -9
- package/lib/mongodb/writes/replace-one.js +33 -17
- package/lib/mongodb/writes/result-handler.js +14 -14
- package/lib/mongodb/writes/update-many.js +34 -18
- package/lib/mongodb/writes/update-one.js +33 -17
- package/lib/mongodb/writes/upsert-one.js +25 -9
- package/lib/operators.js +1 -1
- package/lib/redis-cache-adapter.js +3 -3
- package/lib/slow-query-log/base-storage.js +69 -0
- package/lib/slow-query-log/batch-queue.js +96 -0
- package/lib/slow-query-log/config-manager.js +195 -0
- package/lib/slow-query-log/index.js +237 -0
- package/lib/slow-query-log/mongodb-storage.js +323 -0
- package/lib/slow-query-log/query-hash.js +38 -0
- package/lib/transaction/DistributedCacheLockManager.js +240 -5
- package/lib/transaction/Transaction.js +1 -1
- package/lib/utils/objectid-converter.js +566 -0
- package/package.json +11 -5
package/lib/mongodb/connect.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
const { MongoClient } = require('mongodb');
|
|
2
|
+
const { SSHTunnelManager } = require('../infrastructure/ssh-tunnel');
|
|
3
|
+
const { parseUri } = require('../infrastructure/uri-parser');
|
|
2
4
|
|
|
3
5
|
// 懒加载 MongoDB Memory Server(仅在需要时加载)
|
|
4
6
|
let MongoMemoryServer;
|
|
@@ -117,26 +119,65 @@ async function stopMemoryServer(logger) {
|
|
|
117
119
|
|
|
118
120
|
/**
|
|
119
121
|
* 建立 MongoDB 连接(适配器内部使用)
|
|
120
|
-
* @param {{ databaseName: string, config: { uri?: string, options?: object, useMemoryServer?: boolean, readPreference?: string }, logger: any, defaults: object, type?: string }} params
|
|
121
|
-
* @returns {Promise<{ client: import('mongodb').MongoClient, db: any }>} 返回已连接的 client
|
|
122
|
+
* @param {{ databaseName: string, config: { uri?: string, options?: object, useMemoryServer?: boolean, readPreference?: string, ssh?: object, remoteHost?: string, remotePort?: number, mongoHost?: string, mongoPort?: number }, logger: any, defaults: object, type?: string }} params
|
|
123
|
+
* @returns {Promise<{ client: import('mongodb').MongoClient, db: any, sshTunnel?: any }>} 返回已连接的 client、默认 db 句柄(若可用)和 SSH 隧道实例(若使用)
|
|
122
124
|
*/
|
|
123
125
|
async function connectMongo({ databaseName, config, logger, defaults, type = 'mongodb' }) {
|
|
124
|
-
let { uri, options = {}, useMemoryServer, memoryServerOptions, readPreference } = config || {};
|
|
126
|
+
let { uri, options = {}, useMemoryServer, memoryServerOptions, readPreference, ssh } = config || {};
|
|
127
|
+
|
|
128
|
+
let sshTunnel = null;
|
|
129
|
+
let effectiveUri = uri;
|
|
130
|
+
|
|
131
|
+
// ===== SSH 隧道逻辑 =====
|
|
132
|
+
if (ssh) {
|
|
133
|
+
logger?.info?.('🔐 Establishing SSH tunnel for MongoDB...');
|
|
134
|
+
|
|
135
|
+
// 解析MongoDB目标地址(优先级:显式配置 > URI解析)
|
|
136
|
+
let remoteHost = config.remoteHost || config.mongoHost;
|
|
137
|
+
let remotePort = config.remotePort || config.mongoPort;
|
|
138
|
+
|
|
139
|
+
if (!remoteHost || !remotePort) {
|
|
140
|
+
try {
|
|
141
|
+
const parsed = parseUri(uri);
|
|
142
|
+
remoteHost = parsed.host;
|
|
143
|
+
remotePort = parsed.port;
|
|
144
|
+
} catch (err) {
|
|
145
|
+
throw new Error('SSH tunnel requires remoteHost and remotePort, or a valid MongoDB URI');
|
|
146
|
+
}
|
|
147
|
+
}
|
|
125
148
|
|
|
126
|
-
|
|
127
|
-
|
|
149
|
+
// 使用SSH隧道管理器工厂
|
|
150
|
+
sshTunnel = SSHTunnelManager.create(ssh, remoteHost, remotePort, {
|
|
151
|
+
logger,
|
|
152
|
+
name: 'MongoDB'
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
await sshTunnel.connect();
|
|
157
|
+
|
|
158
|
+
// 使用隧道URI
|
|
159
|
+
effectiveUri = sshTunnel.getTunnelUri('mongodb', uri);
|
|
160
|
+
|
|
161
|
+
logger?.info?.(`✅ MongoDB will connect via SSH tunnel: ${sshTunnel.getLocalAddress()}`);
|
|
162
|
+
} catch (err) {
|
|
163
|
+
logger?.error?.('❌ SSH tunnel connection failed', err);
|
|
164
|
+
throw err;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// ===== Memory Server 逻辑 =====
|
|
168
|
+
else if (useMemoryServer === true) {
|
|
128
169
|
try {
|
|
129
|
-
|
|
170
|
+
effectiveUri = await startMemoryServer(logger, memoryServerOptions);
|
|
130
171
|
} catch (err) {
|
|
131
172
|
// 如果启动内存服务器失败,且没有提供 uri,抛出错误
|
|
132
|
-
if (!
|
|
173
|
+
if (!effectiveUri) {
|
|
133
174
|
throw new Error('Failed to start Memory Server and no URI provided');
|
|
134
175
|
}
|
|
135
|
-
|
|
176
|
+
logger?.warn?.('Failed to start Memory Server, using provided URI', { uri: effectiveUri });
|
|
136
177
|
}
|
|
137
178
|
}
|
|
138
179
|
|
|
139
|
-
if (!
|
|
180
|
+
if (!effectiveUri) throw new Error('MongoDB connect requires config.uri or config.useMemoryServer');
|
|
140
181
|
|
|
141
182
|
// 🔑 合并 readPreference 到 MongoClient options
|
|
142
183
|
const clientOptions = { ...options };
|
|
@@ -144,17 +185,21 @@ async function connectMongo({ databaseName, config, logger, defaults, type = 'mo
|
|
|
144
185
|
clientOptions.readPreference = readPreference;
|
|
145
186
|
}
|
|
146
187
|
|
|
147
|
-
const client = new MongoClient(
|
|
188
|
+
const client = new MongoClient(effectiveUri, clientOptions);
|
|
148
189
|
try {
|
|
149
190
|
await client.connect();
|
|
150
191
|
let db = null;
|
|
151
192
|
try { db = client.db(databaseName); } catch (_) { db = null; }
|
|
152
193
|
const ctx = buildLogContext({ type, databaseName, defaults, config });
|
|
153
194
|
// try { logger && logger.info && logger.info('✅ MongoDB connected', ctx); } catch (_) {}
|
|
154
|
-
return { client, db };
|
|
195
|
+
return { client, db, sshTunnel };
|
|
155
196
|
} catch (err) {
|
|
197
|
+
// 连接失败,清理SSH隧道
|
|
198
|
+
if (sshTunnel) {
|
|
199
|
+
await sshTunnel.close();
|
|
200
|
+
}
|
|
156
201
|
const ctx = buildLogContext({ type, databaseName, defaults, config });
|
|
157
|
-
|
|
202
|
+
logger?.error?.('❌ MongoDB connection failed', ctx, err);
|
|
158
203
|
throw err;
|
|
159
204
|
}
|
|
160
205
|
}
|
|
@@ -164,11 +209,21 @@ async function connectMongo({ databaseName, config, logger, defaults, type = 'mo
|
|
|
164
209
|
* @param {import('mongodb').MongoClient} client
|
|
165
210
|
* @param {any} logger
|
|
166
211
|
* @param {boolean} [stopMemory=false] - 是否同时停止内存服务器
|
|
212
|
+
* @param {any} [sshTunnel=null] - SSH隧道实例(如果使用)
|
|
167
213
|
*/
|
|
168
|
-
async function closeMongo(client, logger, stopMemory = false) {
|
|
214
|
+
async function closeMongo(client, logger, stopMemory = false, sshTunnel = null) {
|
|
169
215
|
if (!client) return;
|
|
170
216
|
try { await client.close(); } catch (e) { try { logger && logger.warn && logger.warn('MongoDB close error', e && (e.stack || e)); } catch (_) { } }
|
|
171
217
|
|
|
218
|
+
// 关闭SSH隧道
|
|
219
|
+
if (sshTunnel) {
|
|
220
|
+
try {
|
|
221
|
+
await sshTunnel.close();
|
|
222
|
+
} catch (e) {
|
|
223
|
+
logger?.warn?.('SSH tunnel close error', e);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
172
227
|
// 如果指定停止内存服务器
|
|
173
228
|
if (stopMemory) {
|
|
174
229
|
await stopMemoryServer(logger);
|
package/lib/mongodb/index.js
CHANGED
|
@@ -17,7 +17,8 @@ const {
|
|
|
17
17
|
createCountOps,
|
|
18
18
|
createAggregateOps,
|
|
19
19
|
createDistinctOps,
|
|
20
|
-
createFindPageOps
|
|
20
|
+
createFindPageOps, // 分页查询工厂函数
|
|
21
|
+
createWatchOps // 🆕 watch 方法
|
|
21
22
|
} = require('./queries');
|
|
22
23
|
|
|
23
24
|
const {
|
|
@@ -45,7 +46,7 @@ const {
|
|
|
45
46
|
createDeleteOneOps,
|
|
46
47
|
createDeleteManyOps,
|
|
47
48
|
createFindOneAndDeleteOps
|
|
48
|
-
} = require(
|
|
49
|
+
} = require('./writes');
|
|
49
50
|
|
|
50
51
|
const { EventEmitter } = require('events');
|
|
51
52
|
module.exports = class {
|
|
@@ -95,7 +96,7 @@ module.exports = class {
|
|
|
95
96
|
|
|
96
97
|
try {
|
|
97
98
|
this._connecting = (async () => {
|
|
98
|
-
const { client, db } = await connectMongo({
|
|
99
|
+
const { client, db, sshTunnel } = await connectMongo({
|
|
99
100
|
databaseName: this.databaseName,
|
|
100
101
|
config: this.config,
|
|
101
102
|
logger: this.logger,
|
|
@@ -104,6 +105,12 @@ module.exports = class {
|
|
|
104
105
|
});
|
|
105
106
|
this.client = client;
|
|
106
107
|
this.db = db;
|
|
108
|
+
this._sshTunnel = sshTunnel; // 🔴 保存SSH隧道实例
|
|
109
|
+
|
|
110
|
+
// 🔴 初始化慢查询日志存储
|
|
111
|
+
this._initializeSlowQueryLog();
|
|
112
|
+
|
|
113
|
+
|
|
107
114
|
try { this.emit && this.emit('connected', { type: this.type, db: this.databaseName, scope: this.defaults?.namespace?.scope }); } catch (_) { }
|
|
108
115
|
return this.client;
|
|
109
116
|
})();
|
|
@@ -150,7 +157,8 @@ module.exports = class {
|
|
|
150
157
|
{ db: ns.db, coll: ns.coll, iid, type: this.type },
|
|
151
158
|
options,
|
|
152
159
|
fn,
|
|
153
|
-
mongoSlowLogShaper
|
|
160
|
+
mongoSlowLogShaper,
|
|
161
|
+
this.onSlowQueryEmit // 🔴 传递慢查询回调
|
|
154
162
|
);
|
|
155
163
|
}
|
|
156
164
|
|
|
@@ -198,7 +206,12 @@ module.exports = class {
|
|
|
198
206
|
}, this.logger, this.defaults, {
|
|
199
207
|
keyBuilder: mongoKeyBuilder,
|
|
200
208
|
slowLogShaper: mongoSlowLogShaper,
|
|
201
|
-
onSlowQueryEmit: (meta) => {
|
|
209
|
+
onSlowQueryEmit: (meta) => {
|
|
210
|
+
try {
|
|
211
|
+
// 触发slow-query事件,由事件监听器处理保存
|
|
212
|
+
this.emit && this.emit('slow-query', meta);
|
|
213
|
+
} catch (_) { }
|
|
214
|
+
},
|
|
202
215
|
onQueryEmit: (meta) => { try { this.emit && this.emit('query', meta); } catch (_) { } }
|
|
203
216
|
});
|
|
204
217
|
|
|
@@ -218,7 +231,8 @@ module.exports = class {
|
|
|
218
231
|
mongoSlowLogShaper,
|
|
219
232
|
type: this.type,
|
|
220
233
|
cache: this.cache,
|
|
221
|
-
getCache: () => this.cache // 动态获取 cache(支持测试时的临时替换)
|
|
234
|
+
getCache: () => this.cache, // 动态获取 cache(支持测试时的临时替换)
|
|
235
|
+
autoConvertConfig: this.autoConvertConfig // ✅ v1.3.0: 传递 ObjectId 自动转换配置
|
|
222
236
|
};
|
|
223
237
|
|
|
224
238
|
// ========================================
|
|
@@ -248,6 +262,8 @@ module.exports = class {
|
|
|
248
262
|
// explain 功能已集成到 find() 的链式调用和 options 参数中
|
|
249
263
|
// 分页查询
|
|
250
264
|
...createFindPageOps(moduleContext),
|
|
265
|
+
// 🆕 watch 方法 - Change Streams (v1.1.0)
|
|
266
|
+
...createWatchOps(moduleContext),
|
|
251
267
|
// 写操作方法 - Insert
|
|
252
268
|
...createInsertOneOps(moduleContext),
|
|
253
269
|
...createInsertManyOps(moduleContext),
|
|
@@ -302,6 +318,19 @@ module.exports = class {
|
|
|
302
318
|
* 关闭连接并释放资源
|
|
303
319
|
*/
|
|
304
320
|
async close() {
|
|
321
|
+
// 🔴 关闭慢查询日志管理器
|
|
322
|
+
if (this.slowQueryLogManager) {
|
|
323
|
+
try {
|
|
324
|
+
await this.slowQueryLogManager.close();
|
|
325
|
+
this.slowQueryLogManager = null;
|
|
326
|
+
this.onSlowQueryEmit = null;
|
|
327
|
+
} catch (err) {
|
|
328
|
+
if (this.logger.error) {
|
|
329
|
+
this.logger.error('[SlowQueryLog] Failed to close manager:', err);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
305
334
|
if (this.client) {
|
|
306
335
|
await closeMongo(this.client, this.logger);
|
|
307
336
|
}
|
|
@@ -455,4 +484,108 @@ module.exports = class {
|
|
|
455
484
|
return collectionOps.runCommand(command, options);
|
|
456
485
|
}
|
|
457
486
|
|
|
458
|
-
|
|
487
|
+
/**
|
|
488
|
+
* 🔴 初始化慢查询日志存储
|
|
489
|
+
* @private
|
|
490
|
+
*/
|
|
491
|
+
_initializeSlowQueryLog() {
|
|
492
|
+
const slowQueryLogConfig = this.defaults?.slowQueryLog;
|
|
493
|
+
|
|
494
|
+
// 支持 boolean 快捷配置
|
|
495
|
+
if (!slowQueryLogConfig) {
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
try {
|
|
500
|
+
const { SlowQueryLogManager, SlowQueryLogConfigManager } = require('../slow-query-log');
|
|
501
|
+
|
|
502
|
+
// 使用配置管理器合并配置
|
|
503
|
+
const mergedConfig = SlowQueryLogConfigManager.mergeConfig(
|
|
504
|
+
slowQueryLogConfig,
|
|
505
|
+
this.type // 业务库类型(用于自动推断storage.type)
|
|
506
|
+
);
|
|
507
|
+
|
|
508
|
+
// 验证配置
|
|
509
|
+
SlowQueryLogConfigManager.validate(mergedConfig, this.type);
|
|
510
|
+
|
|
511
|
+
// 如果未启用,直接返回
|
|
512
|
+
if (!mergedConfig.enabled) {
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// 创建慢查询日志管理器
|
|
517
|
+
this.slowQueryLogManager = new SlowQueryLogManager(
|
|
518
|
+
mergedConfig,
|
|
519
|
+
this.client, // 传递MongoDB客户端(复用连接)
|
|
520
|
+
this.type, // 业务库类型
|
|
521
|
+
this.logger
|
|
522
|
+
);
|
|
523
|
+
|
|
524
|
+
// 🔴 使用事件监听器代替直接回调
|
|
525
|
+
// 监听 'slow-query' 事件,自动保存慢查询日志
|
|
526
|
+
this.on('slow-query', async (meta) => {
|
|
527
|
+
if (this.slowQueryLogManager) {
|
|
528
|
+
try {
|
|
529
|
+
await this.slowQueryLogManager.save(meta);
|
|
530
|
+
} catch (err) {
|
|
531
|
+
// 保存失败不影响主流程
|
|
532
|
+
if (this.logger.error) {
|
|
533
|
+
this.logger.error('[SlowQueryLog] Save failed:', err);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
if (this.logger.info) {
|
|
540
|
+
this.logger.info('[SlowQueryLog] Manager initialized');
|
|
541
|
+
}
|
|
542
|
+
} catch (err) {
|
|
543
|
+
if (this.logger.error) {
|
|
544
|
+
this.logger.error('[SlowQueryLog] Failed to initialize:', err);
|
|
545
|
+
}
|
|
546
|
+
// 初始化失败不影响主流程
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* 🔴 获取慢查询日志(查询接口)
|
|
552
|
+
* @param {Object} filter - 查询条件
|
|
553
|
+
* @param {Object} options - 查询选项
|
|
554
|
+
* @returns {Promise<Object[]>}
|
|
555
|
+
*/
|
|
556
|
+
async getSlowQueryLogs(filter, options) {
|
|
557
|
+
if (!this.slowQueryLogManager) {
|
|
558
|
+
throw new Error('Slow query log is not enabled');
|
|
559
|
+
}
|
|
560
|
+
return this.slowQueryLogManager.query(filter, options);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* 🔴 关闭MongoDB连接和SSH隧道
|
|
565
|
+
* @param {boolean} [stopMemory=false] - 是否停止Memory Server
|
|
566
|
+
*/
|
|
567
|
+
async close(stopMemory = false) {
|
|
568
|
+
if (!this.client) {
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
try {
|
|
573
|
+
await closeMongo(
|
|
574
|
+
this.client,
|
|
575
|
+
this.logger,
|
|
576
|
+
stopMemory,
|
|
577
|
+
this._sshTunnel // 传递SSH隧道实例
|
|
578
|
+
);
|
|
579
|
+
|
|
580
|
+
this.client = null;
|
|
581
|
+
this.db = null;
|
|
582
|
+
this._sshTunnel = null;
|
|
583
|
+
|
|
584
|
+
this.emit?.('closed', { type: this.type, db: this.databaseName });
|
|
585
|
+
} catch (err) {
|
|
586
|
+
this.logger?.error?.('❌ Failed to close MongoDB connection', err);
|
|
587
|
+
throw err;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
};
|
|
@@ -104,7 +104,7 @@ module.exports = function createCollectionOps(context) {
|
|
|
104
104
|
// 使用 MongoDB 原生命令获取集合统计
|
|
105
105
|
const result = await db.command({
|
|
106
106
|
collStats: collection.collectionName,
|
|
107
|
-
scale
|
|
107
|
+
scale
|
|
108
108
|
});
|
|
109
109
|
|
|
110
110
|
return {
|
|
@@ -302,7 +302,7 @@ module.exports = function createCollectionOps(context) {
|
|
|
302
302
|
|
|
303
303
|
const command = {
|
|
304
304
|
convertToCapped: collection.collectionName,
|
|
305
|
-
size
|
|
305
|
+
size
|
|
306
306
|
};
|
|
307
307
|
|
|
308
308
|
if (options.max) {
|
|
@@ -314,7 +314,7 @@ module.exports = function createCollectionOps(context) {
|
|
|
314
314
|
if (logger) {
|
|
315
315
|
logger.info('Collection converted to capped', {
|
|
316
316
|
collection: collection.collectionName,
|
|
317
|
-
size
|
|
317
|
+
size,
|
|
318
318
|
max: options.max
|
|
319
319
|
});
|
|
320
320
|
}
|
|
@@ -323,7 +323,7 @@ module.exports = function createCollectionOps(context) {
|
|
|
323
323
|
ok: result.ok,
|
|
324
324
|
collection: collection.collectionName,
|
|
325
325
|
capped: true,
|
|
326
|
-
size
|
|
326
|
+
size
|
|
327
327
|
};
|
|
328
328
|
} catch (error) {
|
|
329
329
|
if (logger) {
|
|
@@ -85,7 +85,7 @@ function createIndexOps(context, databaseName, collectionName, nativeCollection)
|
|
|
85
85
|
const normalizedOptions = normalizeIndexOptions(options);
|
|
86
86
|
|
|
87
87
|
// 3. 记录开始日志
|
|
88
|
-
logger.debug(
|
|
88
|
+
logger.debug('[createIndex] 开始创建索引', {
|
|
89
89
|
ns,
|
|
90
90
|
keys,
|
|
91
91
|
options: normalizedOptions
|
|
@@ -96,7 +96,7 @@ function createIndexOps(context, databaseName, collectionName, nativeCollection)
|
|
|
96
96
|
|
|
97
97
|
// 5. 记录成功日志
|
|
98
98
|
const duration = Date.now() - startTime;
|
|
99
|
-
logger.info(
|
|
99
|
+
logger.info('[createIndex] 索引创建成功', {
|
|
100
100
|
ns,
|
|
101
101
|
indexName: typeof result === 'string' ? result : result.name || 'unknown',
|
|
102
102
|
keys,
|
|
@@ -111,7 +111,7 @@ function createIndexOps(context, databaseName, collectionName, nativeCollection)
|
|
|
111
111
|
// 7. 错误处理
|
|
112
112
|
const duration = Date.now() - startTime;
|
|
113
113
|
|
|
114
|
-
logger.error(
|
|
114
|
+
logger.error('[createIndex] 索引创建失败', {
|
|
115
115
|
ns,
|
|
116
116
|
keys,
|
|
117
117
|
options,
|
|
@@ -207,7 +207,7 @@ function createIndexOps(context, databaseName, collectionName, nativeCollection)
|
|
|
207
207
|
}));
|
|
208
208
|
|
|
209
209
|
// 3. 记录开始日志
|
|
210
|
-
logger.debug(
|
|
210
|
+
logger.debug('[createIndexes] 开始批量创建索引', {
|
|
211
211
|
ns,
|
|
212
212
|
count: normalizedSpecs.length,
|
|
213
213
|
specs: normalizedSpecs
|
|
@@ -218,7 +218,7 @@ function createIndexOps(context, databaseName, collectionName, nativeCollection)
|
|
|
218
218
|
|
|
219
219
|
// 5. 记录成功日志
|
|
220
220
|
const duration = Date.now() - startTime;
|
|
221
|
-
logger.info(
|
|
221
|
+
logger.info('[createIndexes] 批量创建索引成功', {
|
|
222
222
|
ns,
|
|
223
223
|
count: normalizedSpecs.length,
|
|
224
224
|
indexNames: result,
|
|
@@ -232,7 +232,7 @@ function createIndexOps(context, databaseName, collectionName, nativeCollection)
|
|
|
232
232
|
// 7. 错误处理
|
|
233
233
|
const duration = Date.now() - startTime;
|
|
234
234
|
|
|
235
|
-
logger.error(
|
|
235
|
+
logger.error('[createIndexes] 批量创建索引失败', {
|
|
236
236
|
ns,
|
|
237
237
|
count: indexSpecs?.length || 0,
|
|
238
238
|
error: error.message,
|
|
@@ -269,7 +269,7 @@ function createIndexOps(context, databaseName, collectionName, nativeCollection)
|
|
|
269
269
|
|
|
270
270
|
try {
|
|
271
271
|
// 1. 记录开始日志
|
|
272
|
-
logger.debug(
|
|
272
|
+
logger.debug('[listIndexes] 开始列出索引', { ns });
|
|
273
273
|
|
|
274
274
|
// 2. 执行查询
|
|
275
275
|
const cursor = nativeCollection.listIndexes();
|
|
@@ -277,7 +277,7 @@ function createIndexOps(context, databaseName, collectionName, nativeCollection)
|
|
|
277
277
|
|
|
278
278
|
// 3. 记录成功日志
|
|
279
279
|
const duration = Date.now() - startTime;
|
|
280
|
-
logger.debug(
|
|
280
|
+
logger.debug('[listIndexes] 列出索引成功', {
|
|
281
281
|
ns,
|
|
282
282
|
count: indexes.length,
|
|
283
283
|
indexNames: indexes.map(idx => idx.name),
|
|
@@ -291,7 +291,7 @@ function createIndexOps(context, databaseName, collectionName, nativeCollection)
|
|
|
291
291
|
// 5. 错误处理
|
|
292
292
|
const duration = Date.now() - startTime;
|
|
293
293
|
|
|
294
|
-
logger.error(
|
|
294
|
+
logger.error('[listIndexes] 列出索引失败', {
|
|
295
295
|
ns,
|
|
296
296
|
error: error.message,
|
|
297
297
|
code: error.code,
|
|
@@ -301,7 +301,7 @@ function createIndexOps(context, databaseName, collectionName, nativeCollection)
|
|
|
301
301
|
// MongoDB 命名空间不存在错误(集合不存在)
|
|
302
302
|
if (error.code === 26) {
|
|
303
303
|
// 返回空数组(集合不存在时没有索引)
|
|
304
|
-
logger.debug(
|
|
304
|
+
logger.debug('[listIndexes] 集合不存在,返回空数组', { ns });
|
|
305
305
|
return [];
|
|
306
306
|
}
|
|
307
307
|
|
|
@@ -347,14 +347,14 @@ function createIndexOps(context, databaseName, collectionName, nativeCollection)
|
|
|
347
347
|
}
|
|
348
348
|
|
|
349
349
|
// 2. 记录开始日志
|
|
350
|
-
logger.debug(
|
|
350
|
+
logger.debug('[dropIndex] 开始删除索引', { ns, indexName });
|
|
351
351
|
|
|
352
352
|
// 3. 执行删除
|
|
353
353
|
const result = await nativeCollection.dropIndex(indexName);
|
|
354
354
|
|
|
355
355
|
// 4. 记录成功日志
|
|
356
356
|
const duration = Date.now() - startTime;
|
|
357
|
-
logger.info(
|
|
357
|
+
logger.info('[dropIndex] 删除索引成功', {
|
|
358
358
|
ns,
|
|
359
359
|
indexName,
|
|
360
360
|
result,
|
|
@@ -368,7 +368,7 @@ function createIndexOps(context, databaseName, collectionName, nativeCollection)
|
|
|
368
368
|
// 6. 错误处理
|
|
369
369
|
const duration = Date.now() - startTime;
|
|
370
370
|
|
|
371
|
-
logger.error(
|
|
371
|
+
logger.error('[dropIndex] 删除索引失败', {
|
|
372
372
|
ns,
|
|
373
373
|
indexName,
|
|
374
374
|
error: error.message,
|
|
@@ -381,7 +381,7 @@ function createIndexOps(context, databaseName, collectionName, nativeCollection)
|
|
|
381
381
|
if (errorMsg.includes('_id_') || errorMsg.includes('cannot drop _id index')) {
|
|
382
382
|
throw createError(
|
|
383
383
|
'INVALID_ARGUMENT',
|
|
384
|
-
|
|
384
|
+
'不允许删除 _id 索引',
|
|
385
385
|
{ ns, indexName, cause: error }
|
|
386
386
|
);
|
|
387
387
|
}
|
|
@@ -418,14 +418,14 @@ function createIndexOps(context, databaseName, collectionName, nativeCollection)
|
|
|
418
418
|
|
|
419
419
|
try {
|
|
420
420
|
// 1. 记录开始日志
|
|
421
|
-
logger.debug(
|
|
421
|
+
logger.debug('[dropIndexes] 开始删除所有索引', { ns });
|
|
422
422
|
|
|
423
423
|
// 2. 执行删除(MongoDB 会自动保留 _id 索引)
|
|
424
424
|
const result = await nativeCollection.dropIndexes();
|
|
425
425
|
|
|
426
426
|
// 3. 记录成功日志
|
|
427
427
|
const duration = Date.now() - startTime;
|
|
428
|
-
logger.info(
|
|
428
|
+
logger.info('[dropIndexes] 删除所有索引成功', {
|
|
429
429
|
ns,
|
|
430
430
|
result,
|
|
431
431
|
duration
|
|
@@ -438,7 +438,7 @@ function createIndexOps(context, databaseName, collectionName, nativeCollection)
|
|
|
438
438
|
// 5. 错误处理
|
|
439
439
|
const duration = Date.now() - startTime;
|
|
440
440
|
|
|
441
|
-
logger.error(
|
|
441
|
+
logger.error('[dropIndexes] 删除所有索引失败', {
|
|
442
442
|
ns,
|
|
443
443
|
error: error.message,
|
|
444
444
|
code: error.code,
|
|
@@ -447,7 +447,7 @@ function createIndexOps(context, databaseName, collectionName, nativeCollection)
|
|
|
447
447
|
|
|
448
448
|
// 命名空间不存在(集合不存在)
|
|
449
449
|
if (error.code === 26) {
|
|
450
|
-
logger.debug(
|
|
450
|
+
logger.debug('[dropIndexes] 集合不存在,无需删除索引', { ns });
|
|
451
451
|
return { ok: 1, msg: '集合不存在,无索引可删除', nIndexesWas: 0 };
|
|
452
452
|
}
|
|
453
453
|
|
|
@@ -79,7 +79,7 @@ function createValidationOps(context) {
|
|
|
79
79
|
|
|
80
80
|
const command = {
|
|
81
81
|
collMod: collection.collectionName,
|
|
82
|
-
validator
|
|
82
|
+
validator
|
|
83
83
|
};
|
|
84
84
|
|
|
85
85
|
if (options.validationLevel) {
|
|
@@ -150,7 +150,7 @@ function createValidationOps(context) {
|
|
|
150
150
|
|
|
151
151
|
logger.info('Validation level set', {
|
|
152
152
|
collection: collection.collectionName,
|
|
153
|
-
level
|
|
153
|
+
level
|
|
154
154
|
});
|
|
155
155
|
|
|
156
156
|
return {
|
|
@@ -201,7 +201,7 @@ function createValidationOps(context) {
|
|
|
201
201
|
|
|
202
202
|
logger.info('Validation action set', {
|
|
203
203
|
collection: collection.collectionName,
|
|
204
|
-
action
|
|
204
|
+
action
|
|
205
205
|
});
|
|
206
206
|
|
|
207
207
|
return {
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const { AggregateChain } = require('./chain');
|
|
7
|
+
const { convertAggregationPipeline } = require('../../utils/objectid-converter');
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* 创建 aggregate 查询操作
|
|
@@ -40,12 +41,20 @@ function createAggregateOps(context) {
|
|
|
40
41
|
* @returns {Promise<Array>|ReadableStream|AggregateChain} 聚合结果数组或可读流(当 stream: true 时);当 explain=true 时返回执行计划;默认返回 AggregateChain 实例支持链式调用
|
|
41
42
|
*/
|
|
42
43
|
aggregate: (pipeline = [], options = {}) => {
|
|
44
|
+
// ✅ v1.3.0: 自动转换聚合管道中的 ObjectId 字符串
|
|
45
|
+
const convertedPipeline = convertAggregationPipeline(pipeline, 0, {
|
|
46
|
+
logger: context.logger,
|
|
47
|
+
excludeFields: context.autoConvertConfig?.excludeFields,
|
|
48
|
+
customFieldPatterns: context.autoConvertConfig?.customFieldPatterns,
|
|
49
|
+
maxDepth: context.autoConvertConfig?.maxDepth || 5
|
|
50
|
+
});
|
|
51
|
+
|
|
43
52
|
// 如果没有提供 options 或 options 为空对象,返回 AggregateChain 以支持完整的链式调用
|
|
44
53
|
const hasOptions = options && Object.keys(options).length > 0;
|
|
45
54
|
|
|
46
55
|
if (!hasOptions) {
|
|
47
56
|
// 返回 AggregateChain 实例,支持 .hint().collation() 等链式调用
|
|
48
|
-
return new AggregateChain(context,
|
|
57
|
+
return new AggregateChain(context, convertedPipeline, {});
|
|
49
58
|
}
|
|
50
59
|
|
|
51
60
|
// 如果提供了 options,执行原有逻辑(向后兼容)
|
|
@@ -70,13 +79,13 @@ function createAggregateOps(context) {
|
|
|
70
79
|
// 如果启用 explain,直接返回执行计划(不缓存)
|
|
71
80
|
if (explain) {
|
|
72
81
|
const verbosity = typeof explain === 'string' ? explain : 'queryPlanner';
|
|
73
|
-
const cursor = collection.aggregate(
|
|
82
|
+
const cursor = collection.aggregate(convertedPipeline, aggOptions);
|
|
74
83
|
return cursor.explain(verbosity);
|
|
75
84
|
}
|
|
76
85
|
|
|
77
86
|
// 如果启用流式返回,直接返回 MongoDB 游标流
|
|
78
87
|
if (stream) {
|
|
79
|
-
const cursor = collection.aggregate(
|
|
88
|
+
const cursor = collection.aggregate(convertedPipeline, aggOptions);
|
|
80
89
|
const readableStream = cursor.stream();
|
|
81
90
|
|
|
82
91
|
// 添加慢查询日志支持
|
|
@@ -116,12 +125,12 @@ function createAggregateOps(context) {
|
|
|
116
125
|
const resultPromise = run(
|
|
117
126
|
'aggregate',
|
|
118
127
|
options,
|
|
119
|
-
async () => collection.aggregate(
|
|
128
|
+
async () => collection.aggregate(convertedPipeline, aggOptions).toArray()
|
|
120
129
|
);
|
|
121
130
|
|
|
122
131
|
// 添加 explain 方法支持链式调用(与原生 MongoDB 一致)
|
|
123
132
|
resultPromise.explain = async (verbosity = 'queryPlanner') => {
|
|
124
|
-
const cursor = collection.aggregate(
|
|
133
|
+
const cursor = collection.aggregate(convertedPipeline, aggOptions);
|
|
125
134
|
return cursor.explain(verbosity);
|
|
126
135
|
};
|
|
127
136
|
|