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
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
* 分批批量插入大量文档到集合
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
const { createError, ErrorCodes } = require(
|
|
7
|
-
const CacheFactory = require(
|
|
8
|
-
const { isInTransaction, getTransactionFromSession } = require(
|
|
6
|
+
const { createError, ErrorCodes } = require('../../errors');
|
|
7
|
+
const CacheFactory = require('../../cache');
|
|
8
|
+
const { isInTransaction, getTransactionFromSession } = require('../common/transaction-aware');
|
|
9
|
+
const { convertObjectIdStrings } = require('../../utils/objectid-converter');
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* 创建 insertBatch 操作
|
|
@@ -46,26 +47,34 @@ function createInsertBatchOps(context) {
|
|
|
46
47
|
if (!Array.isArray(documents)) {
|
|
47
48
|
throw createError(
|
|
48
49
|
ErrorCodes.DOCUMENTS_REQUIRED,
|
|
49
|
-
|
|
50
|
-
[{ field:
|
|
50
|
+
'documents 必须是数组类型',
|
|
51
|
+
[{ field: 'documents', type: 'array.required', message: 'documents 是必需参数且必须是数组' }]
|
|
51
52
|
);
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
if (documents.length === 0) {
|
|
55
56
|
throw createError(
|
|
56
57
|
ErrorCodes.DOCUMENTS_REQUIRED,
|
|
57
|
-
|
|
58
|
-
[{ field:
|
|
58
|
+
'documents 数组不能为空',
|
|
59
|
+
[{ field: 'documents', type: 'array.min', message: 'documents 至少需要包含一个文档' }]
|
|
59
60
|
);
|
|
60
61
|
}
|
|
61
62
|
|
|
62
|
-
//
|
|
63
|
+
// ✅ v1.3.0: 自动转换 ObjectId 字符串
|
|
64
|
+
const convertedDocuments = documents.map(doc => convertObjectIdStrings(doc, 'document', 0, new WeakSet(), {
|
|
65
|
+
logger: context.logger,
|
|
66
|
+
excludeFields: context.autoConvertConfig?.excludeFields,
|
|
67
|
+
customFieldPatterns: context.autoConvertConfig?.customFieldPatterns,
|
|
68
|
+
maxDepth: context.autoConvertConfig?.maxDepth
|
|
69
|
+
}));
|
|
70
|
+
|
|
71
|
+
// 2. 提取选项
|
|
63
72
|
const {
|
|
64
73
|
batchSize = 1000,
|
|
65
74
|
concurrency = 1,
|
|
66
75
|
ordered = false,
|
|
67
76
|
onProgress,
|
|
68
|
-
onError =
|
|
77
|
+
onError = 'stop',
|
|
69
78
|
retryAttempts = 3,
|
|
70
79
|
retryDelay = 1000,
|
|
71
80
|
onRetry,
|
|
@@ -73,51 +82,51 @@ function createInsertBatchOps(context) {
|
|
|
73
82
|
} = options;
|
|
74
83
|
|
|
75
84
|
// 验证参数
|
|
76
|
-
if (typeof batchSize !==
|
|
85
|
+
if (typeof batchSize !== 'number' || batchSize < 1) {
|
|
77
86
|
throw createError(
|
|
78
87
|
ErrorCodes.INVALID_PARAMETER,
|
|
79
|
-
|
|
80
|
-
[{ field:
|
|
88
|
+
'batchSize 必须是大于 0 的数字',
|
|
89
|
+
[{ field: 'batchSize', type: 'number.min', message: 'batchSize 必须大于 0' }]
|
|
81
90
|
);
|
|
82
91
|
}
|
|
83
92
|
|
|
84
|
-
if (typeof concurrency !==
|
|
93
|
+
if (typeof concurrency !== 'number' || concurrency < 1) {
|
|
85
94
|
throw createError(
|
|
86
95
|
ErrorCodes.INVALID_PARAMETER,
|
|
87
|
-
|
|
88
|
-
[{ field:
|
|
96
|
+
'concurrency 必须是大于 0 的数字',
|
|
97
|
+
[{ field: 'concurrency', type: 'number.min', message: 'concurrency 必须大于 0' }]
|
|
89
98
|
);
|
|
90
99
|
}
|
|
91
100
|
|
|
92
|
-
const validErrorStrategies = [
|
|
101
|
+
const validErrorStrategies = ['stop', 'skip', 'collect', 'retry'];
|
|
93
102
|
if (!validErrorStrategies.includes(onError)) {
|
|
94
103
|
throw createError(
|
|
95
104
|
ErrorCodes.INVALID_PARAMETER,
|
|
96
|
-
`onError 必须是以下值之一: ${validErrorStrategies.join(
|
|
97
|
-
[{ field:
|
|
105
|
+
`onError 必须是以下值之一: ${validErrorStrategies.join(', ')}`,
|
|
106
|
+
[{ field: 'onError', type: 'enum', message: `有效值: ${validErrorStrategies.join(', ')}` }]
|
|
98
107
|
);
|
|
99
108
|
}
|
|
100
109
|
|
|
101
|
-
if (typeof retryAttempts !==
|
|
110
|
+
if (typeof retryAttempts !== 'number' || retryAttempts < 0) {
|
|
102
111
|
throw createError(
|
|
103
112
|
ErrorCodes.INVALID_PARAMETER,
|
|
104
|
-
|
|
105
|
-
[{ field:
|
|
113
|
+
'retryAttempts 必须是非负数',
|
|
114
|
+
[{ field: 'retryAttempts', type: 'number.min', message: 'retryAttempts 必须 >= 0' }]
|
|
106
115
|
);
|
|
107
116
|
}
|
|
108
117
|
|
|
109
|
-
if (typeof retryDelay !==
|
|
118
|
+
if (typeof retryDelay !== 'number' || retryDelay < 0) {
|
|
110
119
|
throw createError(
|
|
111
120
|
ErrorCodes.INVALID_PARAMETER,
|
|
112
|
-
|
|
113
|
-
[{ field:
|
|
121
|
+
'retryDelay 必须是非负数',
|
|
122
|
+
[{ field: 'retryDelay', type: 'number.min', message: 'retryDelay 必须 >= 0' }]
|
|
114
123
|
);
|
|
115
124
|
}
|
|
116
125
|
|
|
117
126
|
// 3. 构建操作上下文
|
|
118
|
-
const operation =
|
|
127
|
+
const operation = 'insertBatch';
|
|
119
128
|
const ns = `${databaseName}.${collectionName}`;
|
|
120
|
-
const totalCount =
|
|
129
|
+
const totalCount = convertedDocuments.length;
|
|
121
130
|
const totalBatches = Math.ceil(totalCount / batchSize);
|
|
122
131
|
|
|
123
132
|
// 结果统计
|
|
@@ -138,16 +147,16 @@ function createInsertBatchOps(context) {
|
|
|
138
147
|
totalBatches,
|
|
139
148
|
concurrency,
|
|
140
149
|
onError,
|
|
141
|
-
retryAttempts: onError ===
|
|
150
|
+
retryAttempts: onError === 'retry' ? retryAttempts : 0
|
|
142
151
|
});
|
|
143
152
|
|
|
144
153
|
try {
|
|
145
154
|
// 4. 分割文档为批次
|
|
146
155
|
const batches = [];
|
|
147
|
-
for (let i = 0; i <
|
|
156
|
+
for (let i = 0; i < convertedDocuments.length; i += batchSize) {
|
|
148
157
|
batches.push({
|
|
149
158
|
index: batches.length,
|
|
150
|
-
documents:
|
|
159
|
+
documents: convertedDocuments.slice(i, Math.min(i + batchSize, convertedDocuments.length)),
|
|
151
160
|
startIndex: i
|
|
152
161
|
});
|
|
153
162
|
}
|
|
@@ -178,7 +187,7 @@ function createInsertBatchOps(context) {
|
|
|
178
187
|
try {
|
|
179
188
|
const nsObj = {
|
|
180
189
|
iid: instanceId,
|
|
181
|
-
type:
|
|
190
|
+
type: 'mongodb',
|
|
182
191
|
db: databaseName,
|
|
183
192
|
collection: collectionName
|
|
184
193
|
};
|
|
@@ -222,7 +231,7 @@ function createInsertBatchOps(context) {
|
|
|
222
231
|
}
|
|
223
232
|
|
|
224
233
|
// 8. 如果有错误且策略是 stop,抛出错误
|
|
225
|
-
if (result.errors.length > 0 && onError ===
|
|
234
|
+
if (result.errors.length > 0 && onError === 'stop') {
|
|
226
235
|
throw createError(
|
|
227
236
|
ErrorCodes.WRITE_ERROR,
|
|
228
237
|
`insertBatch 操作失败: 在批次 ${result.errors[0].batchIndex + 1}/${totalBatches} 遇到错误`,
|
|
@@ -268,7 +277,7 @@ function createInsertBatchOps(context) {
|
|
|
268
277
|
|
|
269
278
|
let lastError = null;
|
|
270
279
|
let attempts = 0;
|
|
271
|
-
const maxAttempts = onError ===
|
|
280
|
+
const maxAttempts = onError === 'retry' ? retryAttempts + 1 : 1;
|
|
272
281
|
|
|
273
282
|
while (attempts < maxAttempts) {
|
|
274
283
|
try {
|
|
@@ -302,7 +311,7 @@ function createInsertBatchOps(context) {
|
|
|
302
311
|
batchIndex: batch.index,
|
|
303
312
|
attempt: attempts,
|
|
304
313
|
maxAttempts: retryAttempts,
|
|
305
|
-
error
|
|
314
|
+
error,
|
|
306
315
|
nextRetryDelay: retryDelay
|
|
307
316
|
});
|
|
308
317
|
}
|
|
@@ -382,11 +391,11 @@ function createInsertBatchOps(context) {
|
|
|
382
391
|
}
|
|
383
392
|
|
|
384
393
|
// 根据错误策略处理
|
|
385
|
-
if (retryContext.onError ===
|
|
394
|
+
if (retryContext.onError === 'stop') {
|
|
386
395
|
throw batchResult.error;
|
|
387
|
-
} else if (retryContext.onError ===
|
|
396
|
+
} else if (retryContext.onError === 'skip' || retryContext.onError === 'retry') {
|
|
388
397
|
retryContext.logger.warn(`[insertBatch] 跳过失败批次 ${batch.index + 1}/${batches.length}`, errorInfo);
|
|
389
|
-
} else if (retryContext.onError ===
|
|
398
|
+
} else if (retryContext.onError === 'collect') {
|
|
390
399
|
retryContext.logger.warn(`[insertBatch] 收集错误,继续执行 ${batch.index + 1}/${batches.length}`, errorInfo);
|
|
391
400
|
}
|
|
392
401
|
}
|
|
@@ -469,7 +478,7 @@ function createInsertBatchOps(context) {
|
|
|
469
478
|
});
|
|
470
479
|
}
|
|
471
480
|
|
|
472
|
-
if (retryContext.onError ===
|
|
481
|
+
if (retryContext.onError === 'stop') {
|
|
473
482
|
throw batchResult.error;
|
|
474
483
|
}
|
|
475
484
|
}
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
const { createError, ErrorCodes } = require('../../errors');
|
|
7
7
|
const CacheFactory = require('../../cache');
|
|
8
|
-
const { isInTransaction, getTransactionFromSession } = require(
|
|
8
|
+
const { isInTransaction, getTransactionFromSession } = require('../common/transaction-aware');
|
|
9
|
+
const { convertObjectIdStrings } = require('../../utils/objectid-converter');
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* 创建 insertMany 操作
|
|
@@ -36,7 +37,7 @@ function createInsertManyOps(context) {
|
|
|
36
37
|
* @param {string} [options.comment] - 操作注释(用于日志追踪)
|
|
37
38
|
* @returns {Promise<Object>} 插入结果 { acknowledged, insertedCount, insertedIds }
|
|
38
39
|
* @throws {Error} 当 documents 参数无效时
|
|
39
|
-
*
|
|
40
|
+
*
|
|
40
41
|
* @example
|
|
41
42
|
* const result = await collection('users').insertMany([
|
|
42
43
|
* { name: 'Alice', age: 25 },
|
|
@@ -62,36 +63,44 @@ function createInsertManyOps(context) {
|
|
|
62
63
|
if (!Array.isArray(documents)) {
|
|
63
64
|
throw createError(
|
|
64
65
|
ErrorCodes.DOCUMENTS_REQUIRED,
|
|
65
|
-
|
|
66
|
-
[{ field:
|
|
66
|
+
'documents 必须是数组类型',
|
|
67
|
+
[{ field: 'documents', type: 'array.required', message: 'documents 是必需参数且必须是数组' }]
|
|
67
68
|
);
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
if (documents.length === 0) {
|
|
71
72
|
throw createError(
|
|
72
73
|
ErrorCodes.DOCUMENTS_REQUIRED,
|
|
73
|
-
|
|
74
|
-
[{ field:
|
|
74
|
+
'documents 数组不能为空',
|
|
75
|
+
[{ field: 'documents', type: 'array.min', message: 'documents 至少需要包含一个文档' }]
|
|
75
76
|
);
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
// 验证每个文档都是对象
|
|
79
80
|
const invalidDocs = documents.filter((doc, index) => {
|
|
80
|
-
return !doc || typeof doc !==
|
|
81
|
+
return !doc || typeof doc !== 'object' || Array.isArray(doc);
|
|
81
82
|
});
|
|
82
83
|
|
|
83
84
|
if (invalidDocs.length > 0) {
|
|
84
85
|
throw createError(
|
|
85
86
|
ErrorCodes.DOCUMENTS_REQUIRED,
|
|
86
|
-
|
|
87
|
+
'documents 中的所有元素必须是对象类型',
|
|
87
88
|
invalidDocs.map((doc, idx) => ({
|
|
88
89
|
field: `documents[${idx}]`,
|
|
89
|
-
type:
|
|
90
|
-
message:
|
|
90
|
+
type: 'object.required',
|
|
91
|
+
message: '必须是对象类型'
|
|
91
92
|
}))
|
|
92
93
|
);
|
|
93
94
|
}
|
|
94
95
|
|
|
96
|
+
// ✅ v1.3.0: 自动转换 ObjectId 字符串
|
|
97
|
+
const convertedDocuments = documents.map(doc => convertObjectIdStrings(doc, 'document', 0, new WeakSet(), {
|
|
98
|
+
logger: context.logger,
|
|
99
|
+
excludeFields: context.autoConvertConfig?.excludeFields,
|
|
100
|
+
customFieldPatterns: context.autoConvertConfig?.customFieldPatterns,
|
|
101
|
+
maxDepth: context.autoConvertConfig?.maxDepth
|
|
102
|
+
}));
|
|
103
|
+
|
|
95
104
|
// 2. 构建操作上下文
|
|
96
105
|
const operation = 'insertMany';
|
|
97
106
|
const ns = `${databaseName}.${collectionName}`;
|
|
@@ -99,7 +108,7 @@ function createInsertManyOps(context) {
|
|
|
99
108
|
|
|
100
109
|
try {
|
|
101
110
|
// 3. 执行批量插入操作
|
|
102
|
-
const result = await nativeCollection.insertMany(
|
|
111
|
+
const result = await nativeCollection.insertMany(convertedDocuments, options);
|
|
103
112
|
|
|
104
113
|
// 4. 自动失效缓存
|
|
105
114
|
if (cache) {
|
|
@@ -107,7 +116,7 @@ function createInsertManyOps(context) {
|
|
|
107
116
|
// 使用标准命名空间模式删除该集合的所有缓存
|
|
108
117
|
const nsObj = {
|
|
109
118
|
iid: instanceId,
|
|
110
|
-
type:
|
|
119
|
+
type: 'mongodb',
|
|
111
120
|
db: databaseName,
|
|
112
121
|
collection: collectionName
|
|
113
122
|
};
|
|
@@ -186,7 +195,7 @@ function createInsertManyOps(context) {
|
|
|
186
195
|
// MongoDB 重复键错误
|
|
187
196
|
throw createError(
|
|
188
197
|
ErrorCodes.DUPLICATE_KEY,
|
|
189
|
-
|
|
198
|
+
'批量插入失败:违反唯一性约束',
|
|
190
199
|
[{ message: error.message, writeErrors: error.writeErrors }],
|
|
191
200
|
error
|
|
192
201
|
);
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
const { createError, ErrorCodes } = require('../../errors');
|
|
7
7
|
const CacheFactory = require('../../cache');
|
|
8
|
-
const { isInTransaction, getTransactionFromSession } = require(
|
|
8
|
+
const { isInTransaction, getTransactionFromSession } = require('../common/transaction-aware');
|
|
9
|
+
const { convertObjectIdStrings } = require('../../utils/objectid-converter');
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* 创建 insertOne 操作
|
|
@@ -35,7 +36,7 @@ function createInsertOneOps(context) {
|
|
|
35
36
|
* @param {string} [options.comment] - 操作注释(用于日志追踪)
|
|
36
37
|
* @returns {Promise<Object>} 插入结果 { acknowledged, insertedId }
|
|
37
38
|
* @throws {Error} 当 document 参数无效时
|
|
38
|
-
*
|
|
39
|
+
*
|
|
39
40
|
* @example
|
|
40
41
|
* const result = await collection('users').insertOne(
|
|
41
42
|
* { name: 'Alice', age: 25 }
|
|
@@ -53,21 +54,29 @@ function createInsertOneOps(context) {
|
|
|
53
54
|
const startTime = Date.now();
|
|
54
55
|
|
|
55
56
|
// 1. 参数验证
|
|
56
|
-
if (!document || typeof document !==
|
|
57
|
+
if (!document || typeof document !== 'object' || Array.isArray(document)) {
|
|
57
58
|
throw createError(
|
|
58
59
|
ErrorCodes.DOCUMENT_REQUIRED,
|
|
59
|
-
|
|
60
|
-
[{ field:
|
|
60
|
+
'document 必须是对象类型',
|
|
61
|
+
[{ field: 'document', type: 'object.required', message: 'document 是必需参数且必须是对象' }]
|
|
61
62
|
);
|
|
62
63
|
}
|
|
63
64
|
|
|
65
|
+
// ✅ v1.3.0: 自动转换 ObjectId 字符串
|
|
66
|
+
const convertedDocument = convertObjectIdStrings(document, 'document', 0, new WeakSet(), {
|
|
67
|
+
logger: context.logger,
|
|
68
|
+
excludeFields: context.autoConvertConfig?.excludeFields,
|
|
69
|
+
customFieldPatterns: context.autoConvertConfig?.customFieldPatterns,
|
|
70
|
+
maxDepth: context.autoConvertConfig?.maxDepth
|
|
71
|
+
});
|
|
72
|
+
|
|
64
73
|
// 2. 构建操作上下文
|
|
65
74
|
const operation = 'insertOne';
|
|
66
75
|
const ns = `${databaseName}.${collectionName}`;
|
|
67
76
|
|
|
68
77
|
try {
|
|
69
78
|
// 3. 执行插入操作
|
|
70
|
-
const result = await nativeCollection.insertOne(
|
|
79
|
+
const result = await nativeCollection.insertOne(convertedDocument, options);
|
|
71
80
|
|
|
72
81
|
// 4. 自动失效缓存
|
|
73
82
|
if (cache) {
|
|
@@ -75,7 +84,7 @@ function createInsertOneOps(context) {
|
|
|
75
84
|
// 使用标准命名空间模式删除该集合的所有缓存
|
|
76
85
|
const ns = {
|
|
77
86
|
iid: instanceId,
|
|
78
|
-
type:
|
|
87
|
+
type: 'mongodb',
|
|
79
88
|
db: databaseName,
|
|
80
89
|
collection: collectionName
|
|
81
90
|
};
|
|
@@ -151,8 +160,8 @@ function createInsertOneOps(context) {
|
|
|
151
160
|
// MongoDB 重复键错误
|
|
152
161
|
throw createError(
|
|
153
162
|
ErrorCodes.DUPLICATE_KEY,
|
|
154
|
-
|
|
155
|
-
[{ field:
|
|
163
|
+
'文档插入失败:违反唯一性约束',
|
|
164
|
+
[{ field: '_id', message: error.message }],
|
|
156
165
|
error
|
|
157
166
|
);
|
|
158
167
|
}
|
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
* 完整替换单个匹配的文档
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
const { createError, ErrorCodes } = require(
|
|
7
|
-
const CacheFactory = require(
|
|
8
|
-
const { isInTransaction, getTransactionFromSession } = require(
|
|
6
|
+
const { createError, ErrorCodes } = require('../../errors');
|
|
7
|
+
const CacheFactory = require('../../cache');
|
|
8
|
+
const { isInTransaction, getTransactionFromSession } = require('../common/transaction-aware');
|
|
9
|
+
const { convertObjectIdStrings } = require('../../utils/objectid-converter');
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* 创建 replaceOne 操作
|
|
@@ -59,39 +60,54 @@ function createReplaceOneOps(context) {
|
|
|
59
60
|
const startTime = Date.now();
|
|
60
61
|
|
|
61
62
|
// 1. 参数验证
|
|
62
|
-
if (!filter || typeof filter !==
|
|
63
|
+
if (!filter || typeof filter !== 'object' || Array.isArray(filter)) {
|
|
63
64
|
throw createError(
|
|
64
65
|
ErrorCodes.INVALID_ARGUMENT,
|
|
65
|
-
|
|
66
|
-
[{ field:
|
|
66
|
+
'filter 必须是对象类型',
|
|
67
|
+
[{ field: 'filter', type: 'object.required', message: 'filter 是必需参数且必须是对象' }]
|
|
67
68
|
);
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
if (!replacement || typeof replacement !==
|
|
71
|
+
if (!replacement || typeof replacement !== 'object' || Array.isArray(replacement)) {
|
|
71
72
|
throw createError(
|
|
72
73
|
ErrorCodes.INVALID_ARGUMENT,
|
|
73
|
-
|
|
74
|
-
[{ field:
|
|
74
|
+
'replacement 必须是对象类型',
|
|
75
|
+
[{ field: 'replacement', type: 'object.required', message: 'replacement 是必需参数且必须是对象' }]
|
|
75
76
|
);
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
// 验证 replacement 不包含更新操作符(防止误用)
|
|
79
80
|
const replacementKeys = Object.keys(replacement);
|
|
80
|
-
if (replacementKeys.some(key => key.startsWith(
|
|
81
|
+
if (replacementKeys.some(key => key.startsWith('$'))) {
|
|
81
82
|
throw createError(
|
|
82
83
|
ErrorCodes.INVALID_ARGUMENT,
|
|
83
|
-
|
|
84
|
-
[{ field:
|
|
84
|
+
'replacement 不能包含更新操作符(如 $set, $inc 等)',
|
|
85
|
+
[{ field: 'replacement', type: 'object.invalid', message: 'replaceOne 用于完整替换文档,请使用 updateOne 进行部分更新' }]
|
|
85
86
|
);
|
|
86
87
|
}
|
|
87
88
|
|
|
89
|
+
// ✅ v1.3.0: 自动转换 ObjectId 字符串
|
|
90
|
+
const convertedFilter = convertObjectIdStrings(filter, 'filter', 0, new WeakSet(), {
|
|
91
|
+
logger: context.logger,
|
|
92
|
+
excludeFields: context.autoConvertConfig?.excludeFields,
|
|
93
|
+
customFieldPatterns: context.autoConvertConfig?.customFieldPatterns,
|
|
94
|
+
maxDepth: context.autoConvertConfig?.maxDepth
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const convertedReplacement = convertObjectIdStrings(replacement, 'document', 0, new WeakSet(), {
|
|
98
|
+
logger: context.logger,
|
|
99
|
+
excludeFields: context.autoConvertConfig?.excludeFields,
|
|
100
|
+
customFieldPatterns: context.autoConvertConfig?.customFieldPatterns,
|
|
101
|
+
maxDepth: context.autoConvertConfig?.maxDepth
|
|
102
|
+
});
|
|
103
|
+
|
|
88
104
|
// 2. 构建操作上下文
|
|
89
|
-
const operation =
|
|
105
|
+
const operation = 'replaceOne';
|
|
90
106
|
const ns = `${databaseName}.${collectionName}`;
|
|
91
107
|
|
|
92
108
|
try {
|
|
93
109
|
// 3. 执行替换操作
|
|
94
|
-
const result = await nativeCollection.replaceOne(
|
|
110
|
+
const result = await nativeCollection.replaceOne(convertedFilter, convertedReplacement, options);
|
|
95
111
|
|
|
96
112
|
// 4. 自动失效缓存
|
|
97
113
|
if (cache && result.modifiedCount > 0) {
|
|
@@ -99,7 +115,7 @@ function createReplaceOneOps(context) {
|
|
|
99
115
|
// 使用标准命名空间模式删除该集合的所有缓存
|
|
100
116
|
const ns = {
|
|
101
117
|
iid: instanceId,
|
|
102
|
-
type:
|
|
118
|
+
type: 'mongodb',
|
|
103
119
|
db: databaseName,
|
|
104
120
|
collection: collectionName
|
|
105
121
|
};
|
|
@@ -176,8 +192,8 @@ function createReplaceOneOps(context) {
|
|
|
176
192
|
// MongoDB 重复键错误(可能在 upsert 时发生)
|
|
177
193
|
throw createError(
|
|
178
194
|
ErrorCodes.DUPLICATE_KEY,
|
|
179
|
-
|
|
180
|
-
[{ field:
|
|
195
|
+
'替换失败:违反唯一性约束',
|
|
196
|
+
[{ field: '_id', message: error.message }],
|
|
181
197
|
error
|
|
182
198
|
);
|
|
183
199
|
}
|
|
@@ -44,8 +44,8 @@ function detectDriverVersion() {
|
|
|
44
44
|
_driverVersionChecked = true;
|
|
45
45
|
|
|
46
46
|
try {
|
|
47
|
-
const mongodb = require(
|
|
48
|
-
const versionString = mongodb.version || require(
|
|
47
|
+
const mongodb = require('mongodb');
|
|
48
|
+
const versionString = mongodb.version || require('mongodb/package.json').version;
|
|
49
49
|
const match = versionString.match(/^(\d+)\./);
|
|
50
50
|
if (match) {
|
|
51
51
|
_detectedDriverMajorVersion = parseInt(match[1], 10);
|
|
@@ -74,21 +74,21 @@ function warnUnsupportedDriverVersion(logger) {
|
|
|
74
74
|
if (version < 6) {
|
|
75
75
|
// 驱动版本小于 6.x,可能不兼容
|
|
76
76
|
if (logger && logger.warn) {
|
|
77
|
-
logger.warn(
|
|
77
|
+
logger.warn('[result-handler] ⚠️ 检测到 MongoDB 驱动版本过旧', {
|
|
78
78
|
detectedVersion: version,
|
|
79
|
-
supportedVersion:
|
|
80
|
-
message:
|
|
81
|
-
recommendation:
|
|
79
|
+
supportedVersion: '6.x',
|
|
80
|
+
message: 'monSQLize 专为 MongoDB 驱动 6.x 设计,旧版本可能存在兼容性问题',
|
|
81
|
+
recommendation: '建议升级到 MongoDB Node.js 驱动 ^6.0.0'
|
|
82
82
|
});
|
|
83
83
|
}
|
|
84
84
|
} else if (version > 6) {
|
|
85
85
|
// 驱动版本大于 6.x,未经测试
|
|
86
86
|
if (logger && logger.warn) {
|
|
87
|
-
logger.warn(
|
|
87
|
+
logger.warn('[result-handler] ⚠️ 检测到 MongoDB 驱动版本未经测试', {
|
|
88
88
|
detectedVersion: version,
|
|
89
|
-
testedVersion:
|
|
90
|
-
message:
|
|
91
|
-
recommendation:
|
|
89
|
+
testedVersion: '6.x',
|
|
90
|
+
message: 'monSQLize 已针对 MongoDB 驱动 6.x 测试,新版本可能存在未知问题',
|
|
91
|
+
recommendation: '如遇问题,请查看技术分析报告或回退到驱动 6.x'
|
|
92
92
|
});
|
|
93
93
|
}
|
|
94
94
|
}
|
|
@@ -133,7 +133,7 @@ function handleFindOneAndResult(result, options = {}, logger = null) {
|
|
|
133
133
|
// 情况 1:result 为 null 或 undefined(驱动在某些边界情况下可能返回 null)
|
|
134
134
|
if (!result || result === null) {
|
|
135
135
|
if (logger && logger.debug) {
|
|
136
|
-
logger.debug(
|
|
136
|
+
logger.debug('[result-handler] Result is null/undefined, returning empty result', {
|
|
137
137
|
includeMetadata: options.includeResultMetadata
|
|
138
138
|
});
|
|
139
139
|
}
|
|
@@ -152,14 +152,14 @@ function handleFindOneAndResult(result, options = {}, logger = null) {
|
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
// 情况 2:result 是对象但缺少 lastErrorObject(驱动版本差异或异常情况)
|
|
155
|
-
if (typeof result ===
|
|
155
|
+
if (typeof result === 'object' && !result.lastErrorObject) {
|
|
156
156
|
if (logger && logger.warn) {
|
|
157
|
-
logger.warn(
|
|
157
|
+
logger.warn('[result-handler] ⚠️ Result missing lastErrorObject, possible driver version issue', {
|
|
158
158
|
hasValue: result.value !== undefined,
|
|
159
159
|
hasOk: result.ok !== undefined,
|
|
160
160
|
resultKeys: Object.keys(result),
|
|
161
161
|
driverVersion: detectDriverVersion(),
|
|
162
|
-
recommendation:
|
|
162
|
+
recommendation: '这可能表明 MongoDB 驱动返回了非预期的格式,请检查驱动版本'
|
|
163
163
|
});
|
|
164
164
|
}
|
|
165
165
|
|
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
* 更新所有匹配的文档
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
const { createError, ErrorCodes } = require(
|
|
7
|
-
const CacheFactory = require(
|
|
8
|
-
const { isInTransaction, getTransactionFromSession } = require(
|
|
6
|
+
const { createError, ErrorCodes } = require('../../errors');
|
|
7
|
+
const CacheFactory = require('../../cache');
|
|
8
|
+
const { isInTransaction, getTransactionFromSession } = require('../common/transaction-aware');
|
|
9
|
+
const { convertObjectIdStrings, convertUpdateDocument } = require('../../utils/objectid-converter');
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* 创建 updateMany 操作
|
|
@@ -60,39 +61,54 @@ function createUpdateManyOps(context) {
|
|
|
60
61
|
const startTime = Date.now();
|
|
61
62
|
|
|
62
63
|
// 1. 参数验证
|
|
63
|
-
if (!filter || typeof filter !==
|
|
64
|
+
if (!filter || typeof filter !== 'object' || Array.isArray(filter)) {
|
|
64
65
|
throw createError(
|
|
65
66
|
ErrorCodes.INVALID_ARGUMENT,
|
|
66
|
-
|
|
67
|
-
[{ field:
|
|
67
|
+
'filter 必须是对象类型',
|
|
68
|
+
[{ field: 'filter', type: 'object.required', message: 'filter 是必需参数且必须是对象' }]
|
|
68
69
|
);
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
if (!update || typeof update !==
|
|
72
|
+
if (!update || typeof update !== 'object' || Array.isArray(update)) {
|
|
72
73
|
throw createError(
|
|
73
74
|
ErrorCodes.INVALID_ARGUMENT,
|
|
74
|
-
|
|
75
|
-
[{ field:
|
|
75
|
+
'update 必须是对象类型',
|
|
76
|
+
[{ field: 'update', type: 'object.required', message: 'update 是必需参数且必须是对象' }]
|
|
76
77
|
);
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
// 验证 update 包含更新操作符(防止整体替换)
|
|
80
81
|
const updateKeys = Object.keys(update);
|
|
81
|
-
if (updateKeys.length > 0 && !updateKeys.some(key => key.startsWith(
|
|
82
|
+
if (updateKeys.length > 0 && !updateKeys.some(key => key.startsWith('$'))) {
|
|
82
83
|
throw createError(
|
|
83
84
|
ErrorCodes.INVALID_ARGUMENT,
|
|
84
|
-
|
|
85
|
-
[{ field:
|
|
85
|
+
'update 必须使用更新操作符(如 $set, $inc 等)',
|
|
86
|
+
[{ field: 'update', type: 'object.invalid', message: '请使用 $set, $inc, $push 等更新操作符' }]
|
|
86
87
|
);
|
|
87
88
|
}
|
|
88
89
|
|
|
90
|
+
// ✅ v1.3.0: 自动转换 ObjectId 字符串
|
|
91
|
+
const convertedFilter = convertObjectIdStrings(filter, 'filter', 0, new WeakSet(), {
|
|
92
|
+
logger: context.logger,
|
|
93
|
+
excludeFields: context.autoConvertConfig?.excludeFields,
|
|
94
|
+
customFieldPatterns: context.autoConvertConfig?.customFieldPatterns,
|
|
95
|
+
maxDepth: context.autoConvertConfig?.maxDepth
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const convertedUpdate = convertUpdateDocument(update, {
|
|
99
|
+
logger: context.logger,
|
|
100
|
+
excludeFields: context.autoConvertConfig?.excludeFields,
|
|
101
|
+
customFieldPatterns: context.autoConvertConfig?.customFieldPatterns,
|
|
102
|
+
maxDepth: context.autoConvertConfig?.maxDepth
|
|
103
|
+
});
|
|
104
|
+
|
|
89
105
|
// 2. 构建操作上下文
|
|
90
|
-
const operation =
|
|
106
|
+
const operation = 'updateMany';
|
|
91
107
|
const ns = `${databaseName}.${collectionName}`;
|
|
92
108
|
|
|
93
109
|
try {
|
|
94
|
-
// 3.
|
|
95
|
-
const result = await nativeCollection.updateMany(
|
|
110
|
+
// 3. 执行批量更新操作
|
|
111
|
+
const result = await nativeCollection.updateMany(convertedFilter, convertedUpdate, options);
|
|
96
112
|
|
|
97
113
|
// 4. 自动失效缓存(只要有匹配,就失效缓存)
|
|
98
114
|
if (cache && result.matchedCount > 0) {
|
|
@@ -100,7 +116,7 @@ function createUpdateManyOps(context) {
|
|
|
100
116
|
// 使用标准命名空间模式删除该集合的所有缓存
|
|
101
117
|
const ns = {
|
|
102
118
|
iid: instanceId,
|
|
103
|
-
type:
|
|
119
|
+
type: 'mongodb',
|
|
104
120
|
db: databaseName,
|
|
105
121
|
collection: collectionName
|
|
106
122
|
};
|
|
@@ -182,8 +198,8 @@ function createUpdateManyOps(context) {
|
|
|
182
198
|
// MongoDB 重复键错误(可能在 upsert 时发生)
|
|
183
199
|
throw createError(
|
|
184
200
|
ErrorCodes.DUPLICATE_KEY,
|
|
185
|
-
|
|
186
|
-
[{ field:
|
|
201
|
+
'批量更新失败:违反唯一性约束',
|
|
202
|
+
[{ field: '_id', message: error.message }],
|
|
187
203
|
error
|
|
188
204
|
);
|
|
189
205
|
}
|