summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChi-I Huang <8468853+chiihuang@users.noreply.github.com>2024-07-30 14:13:52 -0700
committerMongoDB Bot <mongo-bot@mongodb.com>2024-07-30 22:19:16 +0000
commit8ee59a3af161ded4ff28c3215ec60d82f07aa6dd (patch)
tree13e5889a50c06a5ac7b58c43b941ea3f2140813c
parent606aba44e6c413e5abfea213ef38c2d8043cd9c5 (diff)
SERVER-85892 Fix incorrect output namespace in $merge spec (#19846) (#24833)
GitOrigin-RevId: 9f1eb0f38887e04f692e191be116c8035480df58
-rw-r--r--jstests/aggregation/documents_merge.js113
-rw-r--r--src/mongo/db/pipeline/document_source_merge.cpp2
2 files changed, 114 insertions, 1 deletions
diff --git a/jstests/aggregation/documents_merge.js b/jstests/aggregation/documents_merge.js
new file mode 100644
index 00000000000..99e9a8f1a41
--- /dev/null
+++ b/jstests/aggregation/documents_merge.js
@@ -0,0 +1,113 @@
+/**
+ * This is the test for $documents stage along with $merge stage in an aggregation pipeline,
+ * including verifying the bug in SERVER-85892 is addressed when the spec 'whenMatched' is not
+ * empty.
+ *
+ * @tags: [
+ * requires_fcv_80,
+ * assumes_against_mongod_not_mongos, # not yet supported until 7.2 with SERVER-65534
+ * ]
+ */
+
+load("jstests/aggregation/extras/merge_helpers.js"); // For withEachMergeMode and
+ // dropWithoutImplicitRecreate.
+
+const outColl = db[`${jsTest.name()}_out`];
+const outCollName = outColl.getName();
+const expectedTotalDocs = 100;
+
+function assertDocsInsertedCorrectly(docs, pipeline) {
+ const msg = `Failed with pipeline: ${JSON.stringify(pipeline, null, 2)}`;
+
+ assert.eq(expectedTotalDocs, docs.length, msg);
+ for (let i = 0; i < expectedTotalDocs; i++) {
+ assert.eq(docs[i].x, i, msg);
+ }
+}
+
+const documentsStage = {
+ $documents: {$map: {input: {$range: [0, expectedTotalDocs]}, in : {x: "$$this"}}}
+};
+
+function testFn(pipeline, assertFn) {
+ // Creates an index as $merge requires a unique index with the 'on' identifier field. Then
+ // inserts a document allowed to be matched.
+ dropWithoutImplicitRecreate(outCollName);
+ assert.commandWorked(outColl.createIndex({x: 1}, {unique: true}));
+ assert.commandWorked(outColl.insert({x: 10}));
+
+ assert.doesNotThrow(() => db.aggregate(pipeline));
+ let res = outColl.find({}, {_id: 0}).sort({x: 1}).toArray();
+ assertDocsInsertedCorrectly(res, pipeline);
+ assertFn(res);
+}
+
+{ // Tests $merge with non-empty pipeline along with let in whenMatched spec.
+ const pipeline = [
+ documentsStage,
+ {
+ $merge: {
+ into: outCollName,
+ let : {num: 123},
+ whenMatched: [{$set: {number: "$$num"}}],
+ on: "x"
+ }
+ }
+ ];
+
+ testFn(pipeline, res => {
+ assert.eq(res.filter(elem => elem.number === 123).length, 1);
+ });
+}
+
+{ // Tests $merge with non-empty pipeline in whenMatched spec.
+ const pipeline = [
+ documentsStage,
+ {$merge: {into: outCollName, whenMatched: [{$set: {new: true}}], on: "x"}}
+ ];
+
+ testFn(pipeline, res => {
+ assert.eq(res.filter(elem => elem.new === true).length, 1);
+ });
+}
+
+// Tests each combination of merge modes.
+withEachMergeMode(({whenMatchedMode, whenNotMatchedMode}) => {
+ const expectErrorCode = whenMatchedMode === "fail"
+ ? ErrorCodes.DuplicateKey
+ : whenNotMatchedMode === "fail" ? ErrorCodes.MergeStageNoMatchingDocument : null;
+
+ // Creates an index as $merge requires a unique index with the 'on' identifier field. Then
+ // inserts a document allowed to be matched.
+ dropWithoutImplicitRecreate(outCollName);
+ assert.commandWorked(outColl.createIndex({x: 1}, {unique: true}));
+ assert.commandWorked(outColl.insert({x: 10, old: true}));
+
+ const pipeline = [
+ documentsStage,
+ {
+ $merge: {
+ into: outCollName,
+ whenMatched: whenMatchedMode,
+ whenNotMatched: whenNotMatchedMode,
+ on: "x",
+ }
+ }
+ ];
+
+ if (expectErrorCode) {
+ assert.throwsWithCode(() => db.aggregate(pipeline), expectErrorCode);
+ return;
+ }
+
+ assert.doesNotThrow(() => db.aggregate(pipeline));
+ let res = outColl.find({}, {_id: 0}).sort({x: 1}).toArray();
+ if (whenNotMatchedMode == "discard") {
+ assert.eq(outColl.count(), 1);
+ } else {
+ assertDocsInsertedCorrectly(res, pipeline);
+ }
+
+ // Asserts if the old document is replaced when 'whenMatchedMode' is "replace".
+ assert.eq(res.filter(elem => elem.old === true).length, whenMatchedMode == "replace" ? 0 : 1);
+});
diff --git a/src/mongo/db/pipeline/document_source_merge.cpp b/src/mongo/db/pipeline/document_source_merge.cpp
index 33be917de08..c46cfa8c641 100644
--- a/src/mongo/db/pipeline/document_source_merge.cpp
+++ b/src/mongo/db/pipeline/document_source_merge.cpp
@@ -624,7 +624,7 @@ Value DocumentSourceMerge::serialize(const SerializationOptions& opts) const {
if (!_pipeline.has_value()) {
return boost::none;
}
- auto expCtxWithLetVariables = pExpCtx->copyWith(pExpCtx->ns);
+ auto expCtxWithLetVariables = pExpCtx->copyWith(getOutputNs());
if (spec.getLet()) {
BSONObjBuilder cleanLetSpecBuilder;
for (auto&& [name, expr] : *_letVariables) {