summaryrefslogtreecommitdiff
path: root/jstests/libs/global_snapshot_reads_util.js
diff options
context:
space:
mode:
authorA. Jesse Jiryu Davis <jesse@mongodb.com>2020-05-07 19:48:22 -0400
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2020-05-13 15:59:44 +0000
commitb2384a85f62431d88ac01067ae4d1d3dbb4fede5 (patch)
treeb2036367f7a3a70d24381a5d605e0e957f9342ac /jstests/libs/global_snapshot_reads_util.js
parent84fd6eb9c0a700329dc0750261d05dd6741791cc (diff)
downloadmongo-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.js377
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
- });
-};
-})();