monsqlize 1.0.1 → 1.0.5
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 +204 -2464
- package/README.md +735 -1198
- package/index.d.ts +942 -18
- 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 +173 -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 +225 -29
- package/lib/model/features/soft-delete.js +348 -0
- package/lib/model/features/version.js +156 -0
- package/lib/model/index.js +756 -0
- package/lib/mongodb/common/accessor-helpers.js +17 -3
- package/lib/mongodb/connect.js +68 -13
- package/lib/mongodb/index.js +153 -6
- 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/watch.js +11 -2
- package/lib/mongodb/writes/common/batch-retry.js +64 -0
- package/lib/mongodb/writes/delete-batch.js +322 -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 +22 -7
- package/lib/mongodb/writes/index.js +17 -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-batch.js +358 -0
- 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 +18 -6
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
* 原子地查找并删除单个文档
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
const { createError, ErrorCodes } = require(
|
|
7
|
-
const CacheFactory = require(
|
|
8
|
-
const { handleFindOneAndResult, wasDocumentModified } = require(
|
|
9
|
-
const { isInTransaction, getTransactionFromSession } = require(
|
|
6
|
+
const { createError, ErrorCodes } = require('../../errors');
|
|
7
|
+
const CacheFactory = require('../../cache');
|
|
8
|
+
const { handleFindOneAndResult, wasDocumentModified } = require('./result-handler');
|
|
9
|
+
const { isInTransaction, getTransactionFromSession } = require('../common/transaction-aware');
|
|
10
|
+
const { convertObjectIdStrings } = require('../../utils/objectid-converter');
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* 创建 findOneAndDelete 操作
|
|
@@ -78,23 +79,31 @@ function createFindOneAndDeleteOps(context) {
|
|
|
78
79
|
const startTime = Date.now();
|
|
79
80
|
|
|
80
81
|
// 1. 参数验证
|
|
81
|
-
if (!filter || typeof filter !==
|
|
82
|
+
if (!filter || typeof filter !== 'object' || Array.isArray(filter)) {
|
|
82
83
|
throw createError(
|
|
83
84
|
ErrorCodes.INVALID_ARGUMENT,
|
|
84
|
-
|
|
85
|
-
[{ field:
|
|
85
|
+
'filter 必须是对象类型',
|
|
86
|
+
[{ field: 'filter', type: 'object.required', message: 'filter 是必需参数且必须是对象' }]
|
|
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
|
+
|
|
89
98
|
// 2. 构建操作上下文
|
|
90
|
-
const operation =
|
|
99
|
+
const operation = 'findOneAndDelete';
|
|
91
100
|
const ns = `${databaseName}.${collectionName}`;
|
|
92
101
|
|
|
93
102
|
try {
|
|
94
103
|
// 3. 执行查找并删除操作
|
|
95
104
|
// MongoDB 驱动 6.x: 默认返回文档,需要 includeResultMetadata=true 获取完整元数据
|
|
96
105
|
const driverOptions = { ...options, includeResultMetadata: true };
|
|
97
|
-
const result = await nativeCollection.findOneAndDelete(
|
|
106
|
+
const result = await nativeCollection.findOneAndDelete(convertedFilter, driverOptions);
|
|
98
107
|
|
|
99
108
|
// 4. 自动失效缓存(如果有文档被删除)
|
|
100
109
|
// 使用安全的修改判断函数
|
|
@@ -103,7 +112,7 @@ function createFindOneAndDeleteOps(context) {
|
|
|
103
112
|
try {
|
|
104
113
|
const ns = {
|
|
105
114
|
iid: instanceId,
|
|
106
|
-
type:
|
|
115
|
+
type: 'mongodb',
|
|
107
116
|
db: databaseName,
|
|
108
117
|
collection: collectionName
|
|
109
118
|
};
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
* 原子地查找并替换单个文档
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
const { createError, ErrorCodes } = require(
|
|
7
|
-
const CacheFactory = require(
|
|
8
|
-
const { handleFindOneAndResult, wasDocumentModified } = require(
|
|
9
|
-
const { isInTransaction, getTransactionFromSession } = require(
|
|
6
|
+
const { createError, ErrorCodes } = require('../../errors');
|
|
7
|
+
const CacheFactory = require('../../cache');
|
|
8
|
+
const { handleFindOneAndResult, wasDocumentModified } = require('./result-handler');
|
|
9
|
+
const { isInTransaction, getTransactionFromSession } = require('../common/transaction-aware');
|
|
10
|
+
const { convertObjectIdStrings } = require('../../utils/objectid-converter');
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* 创建 findOneAndReplace 操作
|
|
@@ -75,41 +76,56 @@ function createFindOneAndReplaceOps(context) {
|
|
|
75
76
|
const startTime = Date.now();
|
|
76
77
|
|
|
77
78
|
// 1. 参数验证
|
|
78
|
-
if (!filter || typeof filter !==
|
|
79
|
+
if (!filter || typeof filter !== 'object' || Array.isArray(filter)) {
|
|
79
80
|
throw createError(
|
|
80
81
|
ErrorCodes.INVALID_ARGUMENT,
|
|
81
|
-
|
|
82
|
-
[{ field:
|
|
82
|
+
'filter 必须是对象类型',
|
|
83
|
+
[{ field: 'filter', type: 'object.required', message: 'filter 是必需参数且必须是对象' }]
|
|
83
84
|
);
|
|
84
85
|
}
|
|
85
86
|
|
|
86
|
-
if (!replacement || typeof replacement !==
|
|
87
|
+
if (!replacement || typeof replacement !== 'object' || Array.isArray(replacement)) {
|
|
87
88
|
throw createError(
|
|
88
89
|
ErrorCodes.INVALID_ARGUMENT,
|
|
89
|
-
|
|
90
|
-
[{ field:
|
|
90
|
+
'replacement 必须是对象类型',
|
|
91
|
+
[{ field: 'replacement', type: 'object.required', message: 'replacement 是必需参数且必须是对象' }]
|
|
91
92
|
);
|
|
92
93
|
}
|
|
93
94
|
|
|
94
95
|
// 验证 replacement 不包含更新操作符
|
|
95
96
|
const replacementKeys = Object.keys(replacement);
|
|
96
|
-
if (replacementKeys.some(key => key.startsWith(
|
|
97
|
+
if (replacementKeys.some(key => key.startsWith('$'))) {
|
|
97
98
|
throw createError(
|
|
98
99
|
ErrorCodes.INVALID_ARGUMENT,
|
|
99
|
-
|
|
100
|
-
[{ field:
|
|
100
|
+
'replacement 不能包含更新操作符(如 $set, $inc 等)',
|
|
101
|
+
[{ field: 'replacement', type: 'object.invalid', message: 'findOneAndReplace 用于完整替换文档,请使用 findOneAndUpdate 进行部分更新' }]
|
|
101
102
|
);
|
|
102
103
|
}
|
|
103
104
|
|
|
105
|
+
// ✅ v1.3.0: 自动转换 ObjectId 字符串
|
|
106
|
+
const convertedFilter = convertObjectIdStrings(filter, 'filter', 0, new WeakSet(), {
|
|
107
|
+
logger: context.logger,
|
|
108
|
+
excludeFields: context.autoConvertConfig?.excludeFields,
|
|
109
|
+
customFieldPatterns: context.autoConvertConfig?.customFieldPatterns,
|
|
110
|
+
maxDepth: context.autoConvertConfig?.maxDepth
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const convertedReplacement = convertObjectIdStrings(replacement, 'document', 0, new WeakSet(), {
|
|
114
|
+
logger: context.logger,
|
|
115
|
+
excludeFields: context.autoConvertConfig?.excludeFields,
|
|
116
|
+
customFieldPatterns: context.autoConvertConfig?.customFieldPatterns,
|
|
117
|
+
maxDepth: context.autoConvertConfig?.maxDepth
|
|
118
|
+
});
|
|
119
|
+
|
|
104
120
|
// 2. 构建操作上下文
|
|
105
|
-
const operation =
|
|
121
|
+
const operation = 'findOneAndReplace';
|
|
106
122
|
const ns = `${databaseName}.${collectionName}`;
|
|
107
123
|
|
|
108
124
|
try {
|
|
109
125
|
// 3. 执行查找并替换操作
|
|
110
126
|
// MongoDB 驱动 6.x: 默认返回文档,需要 includeResultMetadata=true 获取完整元数据
|
|
111
127
|
const driverOptions = { ...options, includeResultMetadata: true };
|
|
112
|
-
const result = await nativeCollection.findOneAndReplace(
|
|
128
|
+
const result = await nativeCollection.findOneAndReplace(convertedFilter, convertedReplacement, driverOptions);
|
|
113
129
|
|
|
114
130
|
// 4. 自动失效缓存(如果有文档被修改)
|
|
115
131
|
// 使用安全的修改判断函数
|
|
@@ -118,7 +134,7 @@ function createFindOneAndReplaceOps(context) {
|
|
|
118
134
|
try {
|
|
119
135
|
const ns = {
|
|
120
136
|
iid: instanceId,
|
|
121
|
-
type:
|
|
137
|
+
type: 'mongodb',
|
|
122
138
|
db: databaseName,
|
|
123
139
|
collection: collectionName
|
|
124
140
|
};
|
|
@@ -166,7 +182,7 @@ function createFindOneAndReplaceOps(context) {
|
|
|
166
182
|
replacementKeys: Object.keys(replacement),
|
|
167
183
|
found: result && result.value !== null,
|
|
168
184
|
upserted: result?.lastErrorObject?.upserted ? true : false,
|
|
169
|
-
returnDocument: options.returnDocument ||
|
|
185
|
+
returnDocument: options.returnDocument || 'before',
|
|
170
186
|
comment: options.comment
|
|
171
187
|
});
|
|
172
188
|
} else {
|
|
@@ -175,7 +191,7 @@ function createFindOneAndReplaceOps(context) {
|
|
|
175
191
|
duration,
|
|
176
192
|
found: result && result.value !== null,
|
|
177
193
|
upserted: result?.lastErrorObject?.upserted ? true : false,
|
|
178
|
-
returnDocument: options.returnDocument ||
|
|
194
|
+
returnDocument: options.returnDocument || 'before'
|
|
179
195
|
});
|
|
180
196
|
}
|
|
181
197
|
|
|
@@ -199,8 +215,8 @@ function createFindOneAndReplaceOps(context) {
|
|
|
199
215
|
if (error.code === 11000) {
|
|
200
216
|
throw createError(
|
|
201
217
|
ErrorCodes.DUPLICATE_KEY,
|
|
202
|
-
|
|
203
|
-
[{ field:
|
|
218
|
+
'查找并替换失败:违反唯一性约束',
|
|
219
|
+
[{ field: '_id', message: error.message }],
|
|
204
220
|
error
|
|
205
221
|
);
|
|
206
222
|
}
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
* 原子地查找并更新单个文档
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
const { createError, ErrorCodes } = require(
|
|
7
|
-
const CacheFactory = require(
|
|
8
|
-
const { handleFindOneAndResult, wasDocumentModified } = require(
|
|
9
|
-
const { isInTransaction, getTransactionFromSession } = require(
|
|
6
|
+
const { createError, ErrorCodes } = require('../../errors');
|
|
7
|
+
const CacheFactory = require('../../cache');
|
|
8
|
+
const { handleFindOneAndResult, wasDocumentModified } = require('./result-handler');
|
|
9
|
+
const { isInTransaction, getTransactionFromSession } = require('../common/transaction-aware');
|
|
10
|
+
const { convertObjectIdStrings, convertUpdateDocument } = require('../../utils/objectid-converter');
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* 创建 findOneAndUpdate 操作
|
|
@@ -76,41 +77,56 @@ function createFindOneAndUpdateOps(context) {
|
|
|
76
77
|
const startTime = Date.now();
|
|
77
78
|
|
|
78
79
|
// 1. 参数验证
|
|
79
|
-
if (!filter || typeof filter !==
|
|
80
|
+
if (!filter || typeof filter !== 'object' || Array.isArray(filter)) {
|
|
80
81
|
throw createError(
|
|
81
82
|
ErrorCodes.INVALID_ARGUMENT,
|
|
82
|
-
|
|
83
|
-
[{ field:
|
|
83
|
+
'filter 必须是对象类型',
|
|
84
|
+
[{ field: 'filter', type: 'object.required', message: 'filter 是必需参数且必须是对象' }]
|
|
84
85
|
);
|
|
85
86
|
}
|
|
86
87
|
|
|
87
|
-
if (!update || typeof update !==
|
|
88
|
+
if (!update || typeof update !== 'object' || Array.isArray(update)) {
|
|
88
89
|
throw createError(
|
|
89
90
|
ErrorCodes.INVALID_ARGUMENT,
|
|
90
|
-
|
|
91
|
-
[{ field:
|
|
91
|
+
'update 必须是对象类型',
|
|
92
|
+
[{ field: 'update', type: 'object.required', message: 'update 是必需参数且必须是对象' }]
|
|
92
93
|
);
|
|
93
94
|
}
|
|
94
95
|
|
|
95
96
|
// 验证 update 包含更新操作符
|
|
96
97
|
const updateKeys = Object.keys(update);
|
|
97
|
-
if (updateKeys.length > 0 && !updateKeys.some(key => key.startsWith(
|
|
98
|
+
if (updateKeys.length > 0 && !updateKeys.some(key => key.startsWith('$'))) {
|
|
98
99
|
throw createError(
|
|
99
100
|
ErrorCodes.INVALID_ARGUMENT,
|
|
100
|
-
|
|
101
|
-
[{ field:
|
|
101
|
+
'update 必须使用更新操作符(如 $set, $inc 等)',
|
|
102
|
+
[{ field: 'update', type: 'object.invalid', message: '请使用 $set, $inc, $push 等更新操作符,或使用 findOneAndReplace 进行整体替换' }]
|
|
102
103
|
);
|
|
103
104
|
}
|
|
104
105
|
|
|
106
|
+
// ✅ v1.3.0: 自动转换 ObjectId 字符串
|
|
107
|
+
const convertedFilter = convertObjectIdStrings(filter, 'filter', 0, new WeakSet(), {
|
|
108
|
+
logger: context.logger,
|
|
109
|
+
excludeFields: context.autoConvertConfig?.excludeFields,
|
|
110
|
+
customFieldPatterns: context.autoConvertConfig?.customFieldPatterns,
|
|
111
|
+
maxDepth: context.autoConvertConfig?.maxDepth
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const convertedUpdate = convertUpdateDocument(update, {
|
|
115
|
+
logger: context.logger,
|
|
116
|
+
excludeFields: context.autoConvertConfig?.excludeFields,
|
|
117
|
+
customFieldPatterns: context.autoConvertConfig?.customFieldPatterns,
|
|
118
|
+
maxDepth: context.autoConvertConfig?.maxDepth
|
|
119
|
+
});
|
|
120
|
+
|
|
105
121
|
// 2. 构建操作上下文
|
|
106
|
-
const operation =
|
|
122
|
+
const operation = 'findOneAndUpdate';
|
|
107
123
|
const ns = `${databaseName}.${collectionName}`;
|
|
108
124
|
|
|
109
125
|
try {
|
|
110
126
|
// 3. 执行查找并更新操作
|
|
111
127
|
// MongoDB 驱动 6.x: 默认返回文档,需要 includeResultMetadata=true 获取完整元数据
|
|
112
128
|
const driverOptions = { ...options, includeResultMetadata: true };
|
|
113
|
-
const result = await nativeCollection.findOneAndUpdate(
|
|
129
|
+
const result = await nativeCollection.findOneAndUpdate(convertedFilter, convertedUpdate, driverOptions);
|
|
114
130
|
|
|
115
131
|
// 4. 自动失效缓存(如果有文档被修改)
|
|
116
132
|
// 使用安全的修改判断函数
|
|
@@ -119,7 +135,7 @@ function createFindOneAndUpdateOps(context) {
|
|
|
119
135
|
try {
|
|
120
136
|
const ns = {
|
|
121
137
|
iid: instanceId,
|
|
122
|
-
type:
|
|
138
|
+
type: 'mongodb',
|
|
123
139
|
db: databaseName,
|
|
124
140
|
collection: collectionName
|
|
125
141
|
};
|
|
@@ -167,7 +183,7 @@ function createFindOneAndUpdateOps(context) {
|
|
|
167
183
|
updateKeys: Object.keys(update),
|
|
168
184
|
found: result && result.value !== null,
|
|
169
185
|
upserted: result?.lastErrorObject?.upserted ? true : false,
|
|
170
|
-
returnDocument: options.returnDocument ||
|
|
186
|
+
returnDocument: options.returnDocument || 'before',
|
|
171
187
|
comment: options.comment
|
|
172
188
|
});
|
|
173
189
|
} else {
|
|
@@ -176,7 +192,7 @@ function createFindOneAndUpdateOps(context) {
|
|
|
176
192
|
duration,
|
|
177
193
|
found: result && result.value !== null,
|
|
178
194
|
upserted: result?.lastErrorObject?.upserted ? true : false,
|
|
179
|
-
returnDocument: options.returnDocument ||
|
|
195
|
+
returnDocument: options.returnDocument || 'before'
|
|
180
196
|
});
|
|
181
197
|
}
|
|
182
198
|
|
|
@@ -200,8 +216,8 @@ function createFindOneAndUpdateOps(context) {
|
|
|
200
216
|
if (error.code === 11000) {
|
|
201
217
|
throw createError(
|
|
202
218
|
ErrorCodes.DUPLICATE_KEY,
|
|
203
|
-
|
|
204
|
-
[{ field:
|
|
219
|
+
'查找并更新失败:违反唯一性约束',
|
|
220
|
+
[{ field: '_id', message: error.message }],
|
|
205
221
|
error
|
|
206
222
|
);
|
|
207
223
|
}
|
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
|
|
6
6
|
const { ObjectId } = require('mongodb');
|
|
7
7
|
const { createError, ErrorCodes } = require('../../errors');
|
|
8
|
-
const { isInTransaction, getTransactionFromSession } = require(
|
|
9
|
-
const { handleFindOneAndResult, wasDocumentModified } = require(
|
|
8
|
+
const { isInTransaction, getTransactionFromSession } = require('../common/transaction-aware');
|
|
9
|
+
const { handleFindOneAndResult, wasDocumentModified } = require('./result-handler');
|
|
10
|
+
const { convertObjectIdStrings } = require('../../utils/objectid-converter');
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* 创建 incrementOne 操作
|
|
@@ -91,6 +92,14 @@ function createIncrementOneOps(context) {
|
|
|
91
92
|
);
|
|
92
93
|
}
|
|
93
94
|
|
|
95
|
+
// ✅ v1.3.0: 自动转换 ObjectId 字符串
|
|
96
|
+
const convertedFilter = convertObjectIdStrings(filter, 'filter', 0, new WeakSet(), {
|
|
97
|
+
logger: context.logger,
|
|
98
|
+
excludeFields: context.autoConvertConfig?.excludeFields,
|
|
99
|
+
customFieldPatterns: context.autoConvertConfig?.customFieldPatterns,
|
|
100
|
+
maxDepth: context.autoConvertConfig?.maxDepth
|
|
101
|
+
});
|
|
102
|
+
|
|
94
103
|
// 2. 构建 $inc 更新对象
|
|
95
104
|
let incUpdate;
|
|
96
105
|
|
|
@@ -137,8 +146,14 @@ function createIncrementOneOps(context) {
|
|
|
137
146
|
const returnDocument = actualOptions.returnDocument || 'after';
|
|
138
147
|
const projection = actualOptions.projection;
|
|
139
148
|
|
|
149
|
+
// 🔧 Model 层 timestamps 支持:从 options.$set 读取额外的字段更新
|
|
150
|
+
if (actualOptions.$set && typeof actualOptions.$set === 'object') {
|
|
151
|
+
// 合并到 incUpdate
|
|
152
|
+
incUpdate.$set = actualOptions.$set;
|
|
153
|
+
}
|
|
154
|
+
|
|
140
155
|
const updateOptions = {
|
|
141
|
-
returnDocument
|
|
156
|
+
returnDocument,
|
|
142
157
|
includeResultMetadata: true,
|
|
143
158
|
maxTimeMS
|
|
144
159
|
};
|
|
@@ -148,7 +163,7 @@ function createIncrementOneOps(context) {
|
|
|
148
163
|
// 4. 执行 findOneAndUpdate 操作
|
|
149
164
|
let result;
|
|
150
165
|
try {
|
|
151
|
-
result = await collection.findOneAndUpdate(
|
|
166
|
+
result = await collection.findOneAndUpdate(convertedFilter, incUpdate, updateOptions);
|
|
152
167
|
} catch (error) {
|
|
153
168
|
throw error;
|
|
154
169
|
}
|
|
@@ -201,13 +216,13 @@ function createIncrementOneOps(context) {
|
|
|
201
216
|
operation: 'incrementOne',
|
|
202
217
|
durationMs: duration,
|
|
203
218
|
iid: instanceId,
|
|
204
|
-
type
|
|
219
|
+
type,
|
|
205
220
|
db: effectiveDbName,
|
|
206
221
|
collection: collection.collectionName,
|
|
207
222
|
found: result.value !== null,
|
|
208
223
|
filter: mongoSlowLogShaper?.sanitize ? mongoSlowLogShaper.sanitize(filter) : filter,
|
|
209
224
|
update: mongoSlowLogShaper?.sanitize ? mongoSlowLogShaper.sanitize(incUpdate) : incUpdate,
|
|
210
|
-
comment
|
|
225
|
+
comment
|
|
211
226
|
};
|
|
212
227
|
logger?.warn?.('🐌 Slow query: incrementOne', meta);
|
|
213
228
|
emit?.('slow-query', meta);
|
|
@@ -219,7 +234,7 @@ function createIncrementOneOps(context) {
|
|
|
219
234
|
// 7. 日志记录
|
|
220
235
|
logger?.debug?.('[incrementOne] 操作完成', {
|
|
221
236
|
ns: `${effectiveDbName}.${collection.collectionName}`,
|
|
222
|
-
duration
|
|
237
|
+
duration,
|
|
223
238
|
found: result && result.value !== null,
|
|
224
239
|
modified: wasDocumentModified(result)
|
|
225
240
|
});
|
|
@@ -3,19 +3,21 @@
|
|
|
3
3
|
* 统一导出所有写操作的工厂函数
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
const { createInsertOneOps } = require(
|
|
7
|
-
const { createInsertManyOps } = require(
|
|
8
|
-
const { createInsertBatchOps } = require(
|
|
9
|
-
const { createUpdateOneOps } = require(
|
|
10
|
-
const { createUpdateManyOps } = require(
|
|
11
|
-
const {
|
|
12
|
-
const {
|
|
13
|
-
const {
|
|
14
|
-
const {
|
|
15
|
-
const {
|
|
16
|
-
const {
|
|
17
|
-
const {
|
|
18
|
-
const {
|
|
6
|
+
const { createInsertOneOps } = require('./insert-one');
|
|
7
|
+
const { createInsertManyOps } = require('./insert-many');
|
|
8
|
+
const { createInsertBatchOps } = require('./insert-batch');
|
|
9
|
+
const { createUpdateOneOps } = require('./update-one');
|
|
10
|
+
const { createUpdateManyOps } = require('./update-many');
|
|
11
|
+
const { createUpdateBatchOps } = require('./update-batch'); // 🆕 批量更新
|
|
12
|
+
const { createReplaceOneOps } = require('./replace-one');
|
|
13
|
+
const { createUpsertOneOps } = require('./upsert-one'); // upsertOne 便利方法
|
|
14
|
+
const { createIncrementOneOps } = require('./increment-one'); // 新增:incrementOne 便利方法
|
|
15
|
+
const { createFindOneAndUpdateOps } = require('./find-one-and-update');
|
|
16
|
+
const { createFindOneAndReplaceOps } = require('./find-one-and-replace');
|
|
17
|
+
const { createDeleteOneOps } = require('./delete-one');
|
|
18
|
+
const { createDeleteManyOps } = require('./delete-many');
|
|
19
|
+
const { createDeleteBatchOps } = require('./delete-batch'); // 🆕 批量删除
|
|
20
|
+
const { createFindOneAndDeleteOps } = require('./find-one-and-delete');
|
|
19
21
|
|
|
20
22
|
module.exports = {
|
|
21
23
|
// Insert 操作
|
|
@@ -26,6 +28,7 @@ module.exports = {
|
|
|
26
28
|
// Update 操作
|
|
27
29
|
createUpdateOneOps,
|
|
28
30
|
createUpdateManyOps,
|
|
31
|
+
createUpdateBatchOps, // 🆕 批量更新
|
|
29
32
|
createReplaceOneOps,
|
|
30
33
|
createUpsertOneOps, // upsertOne 便利方法
|
|
31
34
|
createIncrementOneOps, // 新增:incrementOne 便利方法
|
|
@@ -37,5 +40,6 @@ module.exports = {
|
|
|
37
40
|
// Delete 操作
|
|
38
41
|
createDeleteOneOps,
|
|
39
42
|
createDeleteManyOps,
|
|
43
|
+
createDeleteBatchOps, // 🆕 批量删除
|
|
40
44
|
createFindOneAndDeleteOps,
|
|
41
45
|
};
|
|
@@ -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
|
}
|