diff options
author | A. Jesse Jiryu Davis <jesse@mongodb.com> | 2020-05-07 19:48:22 -0400 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2020-05-13 15:59:44 +0000 |
commit | b2384a85f62431d88ac01067ae4d1d3dbb4fede5 (patch) | |
tree | b2036367f7a3a70d24381a5d605e0e957f9342ac /jstests/libs/global_snapshot_reads_util.js | |
parent | 84fd6eb9c0a700329dc0750261d05dd6741791cc (diff) | |
download | mongo-b2384a85f62431d88ac01067ae4d1d3dbb4fede5.tar.gz |
SERVER-47996 Ban "distinct" on sharded collections
Diffstat (limited to 'jstests/libs/global_snapshot_reads_util.js')
-rw-r--r-- | jstests/libs/global_snapshot_reads_util.js | 377 |
1 files changed, 186 insertions, 191 deletions
diff --git a/jstests/libs/global_snapshot_reads_util.js b/jstests/libs/global_snapshot_reads_util.js index 87bcc52ae2d..6c3e5ada517 100644 --- a/jstests/libs/global_snapshot_reads_util.js +++ b/jstests/libs/global_snapshot_reads_util.js @@ -32,52 +32,182 @@ function verifyInvalidGetMoreAttempts(mainDb, collName, cursorId, lsid, txnNumbe ErrorCodes.NoSuchTransaction); } -var snapshotReadsTest; +/** + * Test non-transaction snapshot reads on primary and secondary. + * + * Pass two handles to the same database; either both connected to a mongos, or one connected to + * a replica set primary and the other connected to a replica set secondary. (The test will also + * pass $readPreference, so if the handles are connected to a mongos, then the reads will target + * primary/secondary shard servers.) + * + * For awaitCommittedFn, pass a function that waits for the last write to be committed on all + * secondaries. + * + * @param {primaryDB} Database handle connected to a primary or mongos + * @param {secondaryDB} Database handle connected to a secondary or mongos + * @param {collName} String + * @param {awaitCommittedFn} A function with no arguments or return value + */ +function SnapshotReadsTest({primaryDB, secondaryDB, awaitCommittedFn}) { + function _makeSnapshotReadConcern(atClusterTime) { + if (atClusterTime === undefined) { + return {level: "snapshot"}; + } -(function() { -function makeSnapshotReadConcern(atClusterTime) { - if (atClusterTime === undefined) { - return {level: "snapshot"}; + return {level: "snapshot", atClusterTime: atClusterTime}; } - return {level: "snapshot", atClusterTime: atClusterTime}; -} + /** + * Test non-transaction snapshot "find" and "aggregate". + * + * @param {testScenarioName} String used when logging progress + * @param {collName} String + */ + this.cursorTest = function({testScenarioName, collName}) { + const docs = [...Array(10).keys()].map((i) => ({"_id": i})); + + const commands = { + aggregate: (batchSize, readPreferenceMode, atClusterTime) => { + return { + aggregate: collName, + pipeline: [{$sort: {_id: 1}}], + cursor: {batchSize: batchSize}, + readConcern: _makeSnapshotReadConcern(atClusterTime), + $readPreference: {mode: readPreferenceMode} + }; + }, + find: (batchSize, readPreferenceMode, atClusterTime) => { + return { + find: collName, + sort: {_id: 1}, + batchSize: batchSize, + readConcern: _makeSnapshotReadConcern(atClusterTime), + $readPreference: {mode: readPreferenceMode} + }; + } + }; -function snapshotReadsCursorTest( - {testScenarioName, primaryDB, secondaryDB, collName, awaitCommittedFn}) { - const docs = [...Array(10).keys()].map((i) => ({"_id": i})); + for (let useCausalConsistency of [false, true]) { + for (let [db, readPreferenceMode] of [[primaryDB, "primary"], + [secondaryDB, "secondary"]]) { + jsTestLog(`Running the ${testScenarioName} scenario on collection ` + + `${collName} with read preference ${readPreferenceMode} and causal` + + ` consistency ${useCausalConsistency}`); + + for (let commandKey in commands) { + assert(commandKey); + jsTestLog("Testing the " + commandKey + " command."); + const command = commands[commandKey]; + + let res = assert.commandWorked( + primaryDB.runCommand({insert: collName, documents: docs})); + const insertTimestamp = res.operationTime; + assert(insertTimestamp); + + jsTestLog(`Inserted 10 documents at timestamp ${insertTimestamp}`); + awaitCommittedFn(db, insertTimestamp); + + // Create a session if useCausalConsistency is true. + let causalDb, sessionTimestamp; + + if (useCausalConsistency) { + let session = db.getMongo().startSession({causalConsistency: true}); + causalDb = session.getDatabase(db.getName()); + // Establish timestamp. + causalDb[collName].findOne({}); + sessionTimestamp = session.getOperationTime(); + } else { + causalDb = db; + } + + // Establish a snapshot cursor, fetching the first 5 documents. + res = assert.commandWorked(causalDb.runCommand(command(5, readPreferenceMode))); + assert.sameMembers(res.cursor.firstBatch, docs.slice(0, 5), res); + assert(res.cursor.hasOwnProperty("id")); + const cursorId = res.cursor.id; + assert.neq(cursorId, 0); + assert(res.cursor.hasOwnProperty("atClusterTime")); + let atClusterTime = res.cursor.atClusterTime; + assert.neq(atClusterTime, Timestamp(0, 0)); + if (useCausalConsistency) { + assert.gte(atClusterTime, sessionTimestamp); + } else { + assert.gte(atClusterTime, insertTimestamp); + } + + // This update is not visible to reads at insertTimestamp. + res = assert.commandWorked(primaryDB.runCommand( + {update: collName, updates: [{q: {}, u: {$set: {x: true}}, multi: true}]})); + + jsTestLog(`Updated collection "${collName}" at timestamp ${res.operationTime}`); + awaitCommittedFn(db, res.operationTime); + + // Retrieve the rest of the read command's result set. + res = assert.commandWorked( + causalDb.runCommand({getMore: cursorId, collection: collName})); + + // The cursor has been exhausted. The remaining docs don't show updated field. + assert.eq(0, res.cursor.id); + assert.eq(atClusterTime, res.cursor.atClusterTime); + assert.sameMembers(res.cursor.nextBatch, docs.slice(5), res); + + jsTestLog(`Starting new snapshot read`); + + // This read shows the updated docs. + res = + assert.commandWorked(causalDb.runCommand(command(20, readPreferenceMode))); + assert.eq(0, res.cursor.id); + assert(res.cursor.hasOwnProperty("atClusterTime")); + // Selected atClusterTime at or after first cursor's atClusterTime. + assert.gte(res.cursor.atClusterTime, atClusterTime); + assert.sameMembers(res.cursor.firstBatch, + [...Array(10).keys()].map((i) => ({"_id": i, "x": true})), + res); + + jsTestLog(`Reading with original insert timestamp ${insertTimestamp}`); + // Use non-causal database handle. + res = assert.commandWorked( + db.runCommand(command(20, readPreferenceMode, insertTimestamp))); + + assert.sameMembers(res.cursor.firstBatch, docs, res); + assert.eq(0, res.cursor.id); + assert(res.cursor.hasOwnProperty("atClusterTime")); + assert.eq(res.cursor.atClusterTime, insertTimestamp); + + // Reset. + assert.commandWorked( + primaryDB[collName].remove({}, {writeConcern: {w: "majority"}})); + } + } + } + }; - const commands = { - aggregate: (batchSize, readPreferenceMode, atClusterTime) => { + /** + * Test non-transaction snapshot "distinct" on primary and secondary. + * + * @param {testScenarioName} String used when logging progress + * @param {collName} String + */ + this.distinctTest = function({testScenarioName, collName}) { + // Note: this test sets documents' "x" field, whereas cursorTest uses "_id". + const docs = [...Array(10).keys()].map((i) => ({"x": i})); + + function distinctCommand(readPreferenceMode, atClusterTime) { return { - aggregate: collName, - pipeline: [{$sort: {_id: 1}}], - cursor: {batchSize: batchSize}, - readConcern: makeSnapshotReadConcern(atClusterTime), - $readPreference: {mode: readPreferenceMode} - }; - }, - find: (batchSize, readPreferenceMode, atClusterTime) => { - return { - find: collName, - sort: {_id: 1}, - batchSize: batchSize, - readConcern: makeSnapshotReadConcern(atClusterTime), + distinct: collName, + key: "x", + readConcern: _makeSnapshotReadConcern(atClusterTime), $readPreference: {mode: readPreferenceMode} }; } - }; - for (let useCausalConsistency of [false, true]) { - for (let [db, readPreferenceMode] of [[primaryDB, "primary"], [secondaryDB, "secondary"]]) { - jsTestLog(`Running the ${testScenarioName} scenario on collection ` + - `${collName} with read preference ${readPreferenceMode} and causal` + - ` consistency ${useCausalConsistency}`); - - for (let commandKey in commands) { - assert(commandKey); - jsTestLog("Testing the " + commandKey + " command."); - const command = commands[commandKey]; + for (let useCausalConsistency of [false, true]) { + for (let [db, readPreferenceMode] of [[primaryDB, "primary"], + [secondaryDB, "secondary"]]) { + jsTestLog(`Testing "distinct" with the ${testScenarioName} scenario on` + + ` collection ${collName} with read preference ${ + readPreferenceMode} and causal` + + ` consistency ${useCausalConsistency}`); let res = assert.commandWorked(primaryDB.runCommand({insert: collName, documents: docs})); @@ -100,14 +230,13 @@ function snapshotReadsCursorTest( causalDb = db; } - // Establish a snapshot cursor, fetching the first 5 documents. - res = assert.commandWorked(causalDb.runCommand(command(5, readPreferenceMode))); - assert.sameMembers(res.cursor.firstBatch, docs.slice(0, 5), res); - assert(res.cursor.hasOwnProperty("id")); - const cursorId = res.cursor.id; - assert.neq(cursorId, 0); - assert(res.cursor.hasOwnProperty("atClusterTime")); - let atClusterTime = res.cursor.atClusterTime; + // Execute "distinct". + res = + assert.commandWorked(causalDb.runCommand(distinctCommand(readPreferenceMode))); + const xs = [...Array(10).keys()]; + assert.sameMembers(xs, res.values); + assert(res.hasOwnProperty("atClusterTime")); + let atClusterTime = res.atClusterTime; assert.neq(atClusterTime, Timestamp(0, 0)); if (useCausalConsistency) { assert.gte(atClusterTime, sessionTimestamp); @@ -115,168 +244,34 @@ function snapshotReadsCursorTest( assert.gte(atClusterTime, insertTimestamp); } - // This update is not visible to reads at insertTimestamp. + // Set all "x" fields to 42. This update is not visible to reads at insertTimestamp. res = assert.commandWorked(primaryDB.runCommand( - {update: collName, updates: [{q: {}, u: {$set: {x: true}}, multi: true}]})); + {update: collName, updates: [{q: {}, u: {$set: {x: 42}}, multi: true}]})); jsTestLog(`Updated collection "${collName}" at timestamp ${res.operationTime}`); awaitCommittedFn(db, res.operationTime); - // Retrieve the rest of the read command's result set. - res = assert.commandWorked( - causalDb.runCommand({getMore: cursorId, collection: collName})); - - // The cursor has been exhausted. The remaining docs don't show updated field. - assert.eq(0, res.cursor.id); - assert.eq(atClusterTime, res.cursor.atClusterTime); - assert.sameMembers(res.cursor.nextBatch, docs.slice(5), res); - - jsTestLog(`Starting new snapshot read`); - // This read shows the updated docs. - res = assert.commandWorked(causalDb.runCommand(command(20, readPreferenceMode))); - assert.eq(0, res.cursor.id); - assert(res.cursor.hasOwnProperty("atClusterTime")); - // Selected atClusterTime at or after first cursor's atClusterTime. - assert.gte(res.cursor.atClusterTime, atClusterTime); - assert.sameMembers(res.cursor.firstBatch, - [...Array(10).keys()].map((i) => ({"_id": i, "x": true})), - res); + res = + assert.commandWorked(causalDb.runCommand(distinctCommand(readPreferenceMode))); + assert(res.hasOwnProperty("atClusterTime")); + // Selected atClusterTime at or after first read's atClusterTime. + assert.gte(res.atClusterTime, atClusterTime); + assert.sameMembers([42], res.values); jsTestLog(`Reading with original insert timestamp ${insertTimestamp}`); // Use non-causal database handle. res = assert.commandWorked( - db.runCommand(command(20, readPreferenceMode, insertTimestamp))); + db.runCommand(distinctCommand(readPreferenceMode, insertTimestamp))); - assert.sameMembers(res.cursor.firstBatch, docs, res); - assert.eq(0, res.cursor.id); - assert(res.cursor.hasOwnProperty("atClusterTime")); - assert.eq(res.cursor.atClusterTime, insertTimestamp); + assert.sameMembers(xs, res.values); + assert(res.hasOwnProperty("atClusterTime")); + assert.eq(res.atClusterTime, insertTimestamp); // Reset. assert.commandWorked( primaryDB[collName].remove({}, {writeConcern: {w: "majority"}})); } } - } -} - -function snapshotReadsDistinctTest( - {testScenarioName, primaryDB, secondaryDB, collName, awaitCommittedFn}) { - // Note: this test sets documents' "x" field, the test above uses "_id". - const docs = [...Array(10).keys()].map((i) => ({"x": i})); - - function distinctCommand(readPreferenceMode, atClusterTime) { - return { - distinct: collName, - key: "x", - readConcern: makeSnapshotReadConcern(atClusterTime), - $readPreference: {mode: readPreferenceMode} - }; - } - - for (let useCausalConsistency of [false, true]) { - for (let [db, readPreferenceMode] of [[primaryDB, "primary"], [secondaryDB, "secondary"]]) { - jsTestLog( - `Testing "distinct" with the ${testScenarioName} scenario on` + - ` collection ${collName} with read preference ${readPreferenceMode} and causal` + - ` consistency ${useCausalConsistency}`); - - let res = - assert.commandWorked(primaryDB.runCommand({insert: collName, documents: docs})); - const insertTimestamp = res.operationTime; - assert(insertTimestamp); - - jsTestLog(`Inserted 10 documents at timestamp ${insertTimestamp}`); - awaitCommittedFn(db, insertTimestamp); - - // Create a session if useCausalConsistency is true. - let causalDb, sessionTimestamp; - - if (useCausalConsistency) { - let session = db.getMongo().startSession({causalConsistency: true}); - causalDb = session.getDatabase(db.getName()); - // Establish timestamp. - causalDb[collName].findOne({}); - sessionTimestamp = session.getOperationTime(); - } else { - causalDb = db; - } - - // Execute "distinct". - res = assert.commandWorked(causalDb.runCommand(distinctCommand(readPreferenceMode))); - const xs = [...Array(10).keys()]; - assert.sameMembers(xs, res.values); - assert(res.hasOwnProperty("atClusterTime")); - let atClusterTime = res.atClusterTime; - assert.neq(atClusterTime, Timestamp(0, 0)); - if (useCausalConsistency) { - assert.gte(atClusterTime, sessionTimestamp); - } else { - assert.gte(atClusterTime, insertTimestamp); - } - - // Set all "x" fields to 42. This update is not visible to reads at insertTimestamp. - res = assert.commandWorked(primaryDB.runCommand( - {update: collName, updates: [{q: {}, u: {$set: {x: 42}}, multi: true}]})); - - jsTestLog(`Updated collection "${collName}" at timestamp ${res.operationTime}`); - awaitCommittedFn(db, res.operationTime); - - // This read shows the updated docs. - res = assert.commandWorked(causalDb.runCommand(distinctCommand(readPreferenceMode))); - assert(res.hasOwnProperty("atClusterTime")); - // Selected atClusterTime at or after first read's atClusterTime. - assert.gte(res.atClusterTime, atClusterTime); - assert.sameMembers([42], res.values); - - jsTestLog(`Reading with original insert timestamp ${insertTimestamp}`); - // Use non-causal database handle. - res = assert.commandWorked( - db.runCommand(distinctCommand(readPreferenceMode, insertTimestamp))); - - assert.sameMembers(xs, res.values); - assert(res.hasOwnProperty("atClusterTime")); - assert.eq(res.atClusterTime, insertTimestamp); - - // Reset. - assert.commandWorked(primaryDB[collName].remove({}, {writeConcern: {w: "majority"}})); - } - } + }; } - -/** - * Test non-transaction snapshot reads on primary and secondary. - * - * Pass two handles to the same database; either both connected to a mongos, or one connected to - * a replica set primary and the other connected to a replica set secondary. (The test will also - * pass $readPreference, so if the handles are connected to a mongos, then the reads will target - * primary/secondary shard servers.) - * - * For awaitCommittedFn, pass a function that waits for the last write to be committed on all - * secondaries. - * - * @param {testScenarioName} String used when logging progress - * @param {primaryDB} Database handle connected to a primary or mongos - * @param {secondaryDB} Database handle connected to a secondary or mongos - * @param {collName} String - * @param {awaitCommittedFn} A function with no arguments or return value - */ -snapshotReadsTest = function( - {testScenarioName, primaryDB, secondaryDB, collName, awaitCommittedFn}) { - snapshotReadsCursorTest({ - testScenarioName: testScenarioName, - primaryDB: primaryDB, - secondaryDB: secondaryDB, - collName: collName, - awaitCommittedFn: awaitCommittedFn - }); - snapshotReadsDistinctTest({ - testScenarioName: testScenarioName, - primaryDB: primaryDB, - secondaryDB: secondaryDB, - collName: collName, - awaitCommittedFn: awaitCommittedFn - }); -}; -})(); |