/** * Test readCommitted lookup/graphLookup. 'db' must be the test database for either the replica set * primary or mongos instance. 'secondary' is the shard/replica set secondary. If 'db' is backed * by a mongos instance then the associated cluster should have only a single shard. 'rst' is the * ReplSetTest instance associated with the replica set/shard. */ function testReadCommittedLookup(db, secondary, rst) { /** * Uses the 'rsSyncApplyStop' fail point to stop application of oplog entries on the given * secondary. */ function pauseReplication(sec) { assert.commandWorked( sec.adminCommand({configureFailPoint: "rsSyncApplyStop", mode: "alwaysOn"}), "failed to enable fail point on secondary"); } /** * Turns off the 'rsSyncApplyStop' fail point to resume application of oplog entries on the * given secondary. */ function resumeReplication(sec) { assert.commandWorked(sec.adminCommand({configureFailPoint: "rsSyncApplyStop", mode: "off"}), "failed to disable fail point on secondary"); } const aggCmdLookupObj = { aggregate: "local", pipeline: [ { $lookup: { from: "foreign", localField: "foreignKey", foreignField: "matchedField", as: "match", } }, ], cursor: {}, readConcern: { level: "majority", } }; const aggCmdGraphLookupObj = { aggregate: "local", pipeline: [{ $graphLookup: { from: "foreign", startWith: '$foreignKey', connectFromField: 'foreignKey', connectToField: "matchedField", as: "match" } }], cursor: {}, readConcern: { level: "majority", } }; // Seed matching data. const majorityWriteConcernObj = {writeConcern: {w: "majority", wtimeout: 60 * 1000}}; db.local.deleteMany({}, majorityWriteConcernObj); const localId = db.local.insertOne({foreignKey: "x"}, majorityWriteConcernObj).insertedId; db.foreign.deleteMany({}, majorityWriteConcernObj); const foreignId = db.foreign.insertOne({matchedField: "x"}, majorityWriteConcernObj).insertedId; const expectedMatchedResult = [{ _id: localId, foreignKey: "x", match: [ {_id: foreignId, matchedField: "x"}, ], }]; const expectedUnmatchedResult = [{ _id: localId, foreignKey: "x", match: [], }]; // Confirm lookup/graphLookup return the matched result. let result = db.runCommand(aggCmdLookupObj).cursor.firstBatch; assert.eq(result, expectedMatchedResult); result = db.runCommand(aggCmdGraphLookupObj).cursor.firstBatch; assert.eq(result, expectedMatchedResult); // Stop oplog application on the secondary so that it won't acknowledge updates. pauseReplication(secondary); // Update foreign data to no longer match, without a majority write concern. db.foreign.updateOne({_id: foreignId}, {$set: {matchedField: "non-match"}}); // lookup/graphLookup should not see the update, since it has not been acknowledged by the // secondary. result = db.runCommand(aggCmdLookupObj).cursor.firstBatch; assert.eq(result, expectedMatchedResult); result = db.runCommand(aggCmdGraphLookupObj).cursor.firstBatch; assert.eq(result, expectedMatchedResult); // Restart oplog application on the secondary and wait for it's snapshot to catch up. resumeReplication(secondary); rst.awaitLastOpCommitted(); // Now lookup/graphLookup should report that the documents don't match. result = db.runCommand(aggCmdLookupObj).cursor.firstBatch; assert.eq(result, expectedUnmatchedResult); result = db.runCommand(aggCmdGraphLookupObj).cursor.firstBatch; assert.eq(result, expectedUnmatchedResult); }