mirror of
https://github.com/mongodb/mongo.git
synced 2024-12-01 01:21:03 +01:00
216 lines
8.1 KiB
JavaScript
216 lines
8.1 KiB
JavaScript
/**
|
|
* Tests that $out and $merge with readConcern majority will only see committed data.
|
|
*
|
|
* Each operation is tested on a single node, and (if supported) through mongos on both sharded and
|
|
* unsharded collections. Mongos doesn't directly handle readConcern majority, but these tests
|
|
* should ensure that it correctly propagates the setting to the shards when running commands.
|
|
* @tags: [requires_sharding, requires_majority_read_concern]
|
|
*/
|
|
|
|
(function() {
|
|
'use strict';
|
|
|
|
// Skip this test if running with --nojournal and WiredTiger.
|
|
if (jsTest.options().noJournal &&
|
|
(!jsTest.options().storageEngine || jsTest.options().storageEngine === "wiredTiger")) {
|
|
print("Skipping test because running WiredTiger without journaling isn't a valid" +
|
|
" replica set configuration");
|
|
return;
|
|
}
|
|
|
|
const testServer = MongoRunner.runMongod();
|
|
const db = testServer.getDB("test");
|
|
if (!db.serverStatus().storageEngine.supportsCommittedReads) {
|
|
print("Skipping read_majority.js since storageEngine doesn't support it.");
|
|
MongoRunner.stopMongod(testServer);
|
|
return;
|
|
}
|
|
MongoRunner.stopMongod(testServer);
|
|
|
|
function runTests(sourceColl, mongodConnection) {
|
|
function makeSnapshot() {
|
|
return assert.commandWorked(mongodConnection.adminCommand("makeSnapshot")).name;
|
|
}
|
|
function setCommittedSnapshot(snapshot) {
|
|
assert.commandWorked(mongodConnection.adminCommand({"setCommittedSnapshot": snapshot}));
|
|
}
|
|
const db = sourceColl.getDB();
|
|
const targetColl = db.targetColl;
|
|
const targetReplaceDocsColl = db.targetReplaceDocsColl;
|
|
|
|
assert.commandWorked(sourceColl.remove({}));
|
|
assert.commandWorked(targetColl.remove({}));
|
|
assert.commandWorked(targetReplaceDocsColl.remove({}));
|
|
setCommittedSnapshot(makeSnapshot());
|
|
|
|
// Insert a single document and make it visible by advancing the snapshot.
|
|
assert.commandWorked(sourceColl.insert({_id: 1, state: 'before'}));
|
|
assert.commandWorked(targetReplaceDocsColl.insert({_id: 1, state: 'before'}));
|
|
setCommittedSnapshot(makeSnapshot());
|
|
|
|
// This insert will not be visible to $merge.
|
|
assert.commandWorked(sourceColl.insert({_id: 2, state: 'before'}));
|
|
assert.commandWorked(targetReplaceDocsColl.insert({_id: 2, state: 'before'}));
|
|
// Similarly this update will not be visible.
|
|
assert.commandWorked(sourceColl.update({_id: 1}, {state: 'after'}));
|
|
assert.commandWorked(targetReplaceDocsColl.update({_id: 1}, {state: 'after'}));
|
|
|
|
// Make sure we see only the first document.
|
|
let res = sourceColl.aggregate([], {readConcern: {level: 'majority'}});
|
|
assert.eq(res.itcount(), 1);
|
|
|
|
// Run $merge with whenNotMatched set to "insert". It will pick only the first document.
|
|
// Also it will not see the update ('after').
|
|
res = sourceColl.aggregate(
|
|
[
|
|
{$match: {state: 'before'}},
|
|
{$project: {state: 'merge'}},
|
|
{
|
|
$merge: {
|
|
into: {db: targetColl.getDB().getName(), coll: targetColl.getName()},
|
|
whenMatched: "fail",
|
|
whenNotMatched: "insert"
|
|
}
|
|
}
|
|
],
|
|
{readConcern: {level: 'majority'}});
|
|
|
|
assert.eq(res.itcount(), 0);
|
|
|
|
res = targetColl.find().sort({_id: 1});
|
|
// Only a single document is visible ($merge did not see the second insert).
|
|
assert.docEq(res.next(), {_id: 1, state: 'merge'});
|
|
assert(res.isExhausted());
|
|
|
|
// The same $merge but with whenMatched set to "replace".
|
|
res = sourceColl.aggregate(
|
|
[
|
|
{$match: {state: 'before'}},
|
|
{$project: {state: 'merge'}},
|
|
{
|
|
$merge: {
|
|
into: {
|
|
db: targetReplaceDocsColl.getDB().getName(),
|
|
coll: targetReplaceDocsColl.getName()
|
|
},
|
|
whenMatched: "replace",
|
|
whenNotMatched: "insert"
|
|
}
|
|
}
|
|
],
|
|
{readConcern: {level: 'majority'}});
|
|
assert.eq(res.itcount(), 0);
|
|
|
|
setCommittedSnapshot(makeSnapshot());
|
|
|
|
res = targetReplaceDocsColl.find().sort({_id: 1});
|
|
// The first document must overwrite the update that the read portion of $merge did not see.
|
|
assert.docEq(res.next(), {_id: 1, state: 'merge'});
|
|
// The second document is the result of the independent insert that $merge did not see.
|
|
assert.docEq(res.next(), {_id: 2, state: 'before'});
|
|
assert(res.isExhausted());
|
|
|
|
assert.commandWorked(targetColl.remove({}));
|
|
setCommittedSnapshot(makeSnapshot());
|
|
|
|
// Insert a document that will collide with $merge insert. The insert is not majority
|
|
// commited.
|
|
assert.commandWorked(targetColl.insert({_id: 1, state: 'collision'}));
|
|
|
|
res = db.runCommand({
|
|
aggregate: sourceColl.getName(),
|
|
pipeline: [
|
|
{$project: {state: 'merge'}},
|
|
{
|
|
$merge: {
|
|
into: {db: targetColl.getDB().getName(), coll: targetColl.getName()},
|
|
whenMatched: "fail",
|
|
whenNotMatched: "insert"
|
|
}
|
|
}
|
|
],
|
|
cursor: {},
|
|
readConcern: {level: 'majority'}
|
|
});
|
|
|
|
assert.commandFailedWithCode(res, ErrorCodes.DuplicateKey);
|
|
|
|
// Remove the documents (not majority).
|
|
assert.commandWorked(targetColl.remove({_id: 1}));
|
|
assert.commandWorked(targetColl.remove({_id: 2}));
|
|
|
|
// $merge should successfuly 'overwrite' the collection as it is 'empty' (not majority).
|
|
res = targetReplaceDocsColl.aggregate(
|
|
[
|
|
{$match: {state: 'before'}},
|
|
{$project: {state: 'merge'}},
|
|
{
|
|
$merge: {
|
|
into: {db: targetColl.getDB().getName(), coll: targetColl.getName()},
|
|
whenMatched: "fail",
|
|
whenNotMatched: "insert"
|
|
}
|
|
}
|
|
],
|
|
{readConcern: {level: 'majority'}});
|
|
|
|
assert.eq(res.itcount(), 0);
|
|
|
|
setCommittedSnapshot(makeSnapshot());
|
|
|
|
res = targetColl.find().sort({_id: 1});
|
|
// Only a single document is visible ($merge did not see the second insert).
|
|
assert.docEq(res.next(), {_id: 2, state: 'merge'});
|
|
assert(res.isExhausted());
|
|
}
|
|
|
|
const replTest = new ReplSetTest({
|
|
nodes: 1,
|
|
oplogSize: 2,
|
|
nodeOptions: {
|
|
setParameter: 'testingSnapshotBehaviorInIsolation=true',
|
|
enableMajorityReadConcern: '',
|
|
shardsvr: ''
|
|
}
|
|
});
|
|
replTest.startSet();
|
|
// Cannot wait for a stable recovery timestamp with 'testingSnapshotBehaviorInIsolation' set.
|
|
replTest.initiateWithAnyNodeAsPrimary(
|
|
null, "replSetInitiate", {doNotWaitForStableRecoveryTimestamp: true});
|
|
|
|
const mongod = replTest.getPrimary();
|
|
|
|
(function testSingleNode() {
|
|
const db = mongod.getDB("singleNode");
|
|
runTests(db.collection, mongod);
|
|
})();
|
|
|
|
const shardingTest = new ShardingTest({
|
|
shards: 0,
|
|
mongos: 1,
|
|
});
|
|
assert(shardingTest.adminCommand({addShard: replTest.getURL()}));
|
|
|
|
(function testUnshardedDBThroughMongos() {
|
|
const db = shardingTest.getDB("throughMongos");
|
|
runTests(db.unshardedDB, mongod);
|
|
})();
|
|
|
|
shardingTest.adminCommand({enableSharding: 'throughMongos'});
|
|
|
|
(function testUnshardedCollectionThroughMongos() {
|
|
const db = shardingTest.getDB("throughMongos");
|
|
runTests(db.unshardedCollection, mongod);
|
|
})();
|
|
|
|
(function testShardedCollectionThroughMongos() {
|
|
const db = shardingTest.getDB("throughMongos");
|
|
const collection = db.shardedCollection;
|
|
shardingTest.adminCommand({shardCollection: collection.getFullName(), key: {_id: 1}});
|
|
runTests(collection, mongod);
|
|
})();
|
|
|
|
shardingTest.stop();
|
|
replTest.stopSet();
|
|
})();
|