Skip to content

Commit 81e5695

Browse files
jsteemannMongoDB Bot
authored andcommitted
SERVER-97470 findAndModify: Mongos WriteConcernError Behavior Differs from Mongod (#30328)
GitOrigin-RevId: 049c7639d253141391ab612bbc293c85ba4e781e
1 parent bd7f829 commit 81e5695

File tree

6 files changed

+173
-134
lines changed

6 files changed

+173
-134
lines changed

jstests/libs/noop_write_commands.js

Lines changed: 130 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -11,83 +11,92 @@
1111
// // that it executed properly. Accepts the result of
1212
// // the noop request to validate it.
1313
// }
14-
function getNoopWriteCommands(coll) {
14+
function getNoopWriteCommands(coll, setupType) {
1515
const db = coll.getDB();
1616
const collName = coll.getName();
1717
const commands = [];
1818

19+
assert(setupType === "rs" || setupType === "sharding",
20+
`invalid setupType '${setupType}' used for tests`);
21+
1922
// 'applyOps' where the update has already been done.
2023
commands.push({
2124
req: {applyOps: [{op: "u", ns: coll.getFullName(), o: {_id: 1}, o2: {_id: 1}}]},
22-
setupFunc: function() {
25+
setupFunc: () => {
2326
assert.commandWorked(coll.insert({_id: 1}));
2427
},
25-
confirmFunc: function(res) {
28+
confirmFunc: (res) => {
2629
assert.commandWorkedIgnoringWriteConcernErrors(res);
2730
assert.eq(res.applied, 1);
2831
assert.eq(res.results[0], true);
2932
assert.eq(coll.find().itcount(), 1);
3033
assert.eq(coll.count({_id: 1}), 1);
31-
}
34+
},
3235
});
3336

3437
// 'update' where the document to update does not exist.
3538
commands.push({
3639
req: {update: collName, updates: [{q: {a: 1}, u: {b: 2}}]},
37-
setupFunc: function() {
40+
setupFunc: () => {
3841
assert.commandWorked(coll.insert({a: 1}));
3942
assert.commandWorked(coll.update({a: 1}, {b: 2}));
4043
},
41-
confirmFunc: function(res) {
44+
confirmFunc: (res) => {
4245
assert.commandWorkedIgnoringWriteConcernErrors(res);
4346
assert.eq(res.n, 0);
4447
assert.eq(res.nModified, 0);
4548
assert.eq(coll.find().itcount(), 1);
4649
assert.eq(coll.count({b: 2}), 1);
47-
}
50+
},
4851
});
4952

5053
// 'update' where the update has already been done.
5154
commands.push({
55+
checkWriteConcern: (res) => {
56+
assertWriteConcernError(res);
57+
},
5258
req: {update: collName, updates: [{q: {a: 1}, u: {$set: {b: 2}}}]},
53-
setupFunc: function() {
59+
setupFunc: () => {
5460
assert.commandWorked(coll.insert({a: 1}));
5561
assert.commandWorked(coll.update({a: 1}, {$set: {b: 2}}));
5662
},
57-
confirmFunc: function(res) {
63+
confirmFunc: (res) => {
5864
assert.commandWorkedIgnoringWriteConcernErrors(res);
5965
assert.eq(res.n, 1);
6066
assert.eq(res.nModified, 0);
6167
assert.eq(coll.find().itcount(), 1);
6268
assert.eq(coll.count({a: 1, b: 2}), 1);
63-
}
69+
},
6470
});
6571

6672
// 'update' with immutable field error.
6773
commands.push({
74+
checkWriteConcern: (res) => {
75+
assertWriteConcernError(res);
76+
},
6877
req: {update: collName, updates: [{q: {_id: 1}, u: {$set: {_id: 2}}}]},
69-
setupFunc: function() {
78+
setupFunc: () => {
7079
assert.commandWorked(coll.insert({_id: 1}));
7180
},
72-
confirmFunc: function(res) {
81+
confirmFunc: (res) => {
7382
assert.eq(res.n, 0);
7483
assert.eq(res.nModified, 0);
7584
assert.eq(coll.count({_id: 1}), 1);
76-
}
85+
},
7786
});
7887

7988
// 'delete' where the document to delete does not exist.
8089
commands.push({
8190
req: {delete: collName, deletes: [{q: {a: 1}, limit: 1}]},
82-
setupFunc: function() {
91+
setupFunc: () => {
8392
assert.commandWorked(coll.insert({a: 1}));
8493
assert.commandWorked(coll.remove({a: 1}));
8594
},
86-
confirmFunc: function(res) {
95+
confirmFunc: (res) => {
8796
assert.commandWorkedIgnoringWriteConcernErrors(res);
8897
assert.eq(res.n, 0);
8998
assert.eq(coll.count({a: 1}), 0);
90-
}
99+
},
91100
});
92101

93102
// 'createIndexes' where the index has already been created.
@@ -100,15 +109,15 @@ function getNoopWriteCommands(coll) {
100109
indexes: [{key: {a: 1}, name: "a_1"}],
101110
commitQuorum: "majority"
102111
},
103-
setupFunc: function() {
112+
setupFunc: () => {
104113
assert.commandWorked(coll.insert({a: 1}));
105114
assert.commandWorkedIgnoringWriteConcernErrors(db.runCommand({
106115
createIndexes: collName,
107116
indexes: [{key: {a: 1}, name: "a_1"}],
108117
commitQuorum: "majority"
109118
}));
110119
},
111-
confirmFunc: function(res) {
120+
confirmFunc: (res) => {
112121
assert.commandWorkedIgnoringWriteConcernErrors(res);
113122
let details = res;
114123
if ("raw" in details) {
@@ -117,95 +126,135 @@ function getNoopWriteCommands(coll) {
117126
}
118127
assert.eq(details.numIndexesBefore, details.numIndexesAfter);
119128
assert.eq(details.note, 'all indexes already exist');
120-
}
129+
},
121130
});
122131

123132
// 'findAndModify' where the document to update does not exist.
124133
commands.push({
125134
req: {findAndModify: collName, query: {a: 1}, update: {b: 2}},
126-
setupFunc: function() {
135+
setupFunc: () => {
127136
assert.commandWorked(coll.insert({a: 1}));
128137
assert.commandWorkedIgnoringWriteConcernErrors(
129138
db.runCommand({findAndModify: collName, query: {a: 1}, update: {b: 2}}));
130139
},
131-
confirmFunc: function(res) {
140+
confirmFunc: (res) => {
132141
assert.commandWorkedIgnoringWriteConcernErrors(res);
133142
assert.eq(res.lastErrorObject.updatedExisting, false);
134143
assert.eq(coll.find().itcount(), 1);
135144
assert.eq(coll.count({b: 2}), 1);
136-
}
145+
},
137146
});
138147

139148
// 'findAndModify' where the update has already been done.
140149
commands.push({
141150
req: {findAndModify: collName, query: {a: 1}, update: {$set: {b: 2}}},
142-
setupFunc: function() {
151+
setupFunc: () => {
143152
assert.commandWorked(coll.insert({a: 1}));
144153
assert.commandWorkedIgnoringWriteConcernErrors(
145154
db.runCommand({findAndModify: collName, query: {a: 1}, update: {$set: {b: 2}}}));
146155
},
147-
confirmFunc: function(res) {
156+
confirmFunc: (res) => {
148157
assert.commandWorkedIgnoringWriteConcernErrors(res);
149158
assert.eq(res.lastErrorObject.updatedExisting, true);
150159
assert.eq(coll.find().itcount(), 1);
151160
assert.eq(coll.count({a: 1, b: 2}), 1);
152-
}
161+
},
153162
});
154163

164+
// 'findAndModify' causing a unique index key violation. Only works in a replica set.
165+
if (setupType === "rs") {
166+
commands.push({
167+
req: {findAndModify: collName, query: {value: {$lt: 3}}, update: {$set: {value: 3}}},
168+
setupFunc: (shell) => {
169+
coll.createIndex({value: 1}, {unique: true});
170+
assert.commandWorked(coll.insert({value: 1}));
171+
assert.commandWorked(coll.insert({value: 2}));
172+
assert.commandWorked(coll.insert({value: 3}));
173+
},
174+
confirmFunc: (res) => {
175+
assert.commandFailedWithCode(res, ErrorCodes.DuplicateKey);
176+
assert.eq(coll.count({value: 1}), 1);
177+
assert.eq(coll.count({value: 2}), 1);
178+
assert.eq(coll.count({value: 3}), 1);
179+
},
180+
});
181+
}
182+
183+
// 'findAndModify' causing a unique index key violation. Only works in a sharded cluster.
184+
if (setupType === "sharding") {
185+
commands.push({
186+
req: {findAndModify: collName, query: {value: {$lt: 3}}, update: {$set: {value: 3}}},
187+
setupFunc: (shell) => {
188+
coll.createIndex({value: 1}, {unique: true});
189+
assert.commandWorked(
190+
shell.adminCommand({shardCollection: coll.getFullName(), key: {value: 1}}));
191+
assert.commandWorked(coll.insert({value: 1}));
192+
assert.commandWorked(coll.insert({value: 2}));
193+
assert.commandWorked(coll.insert({value: 3}));
194+
},
195+
confirmFunc: (res) => {
196+
assert.commandFailedWithCode(res, 31025);
197+
assert.eq(coll.count({value: 1}), 1);
198+
assert.eq(coll.count({value: 2}), 1);
199+
assert.eq(coll.count({value: 3}), 1);
200+
},
201+
});
202+
}
203+
155204
// 'findAndModify' with immutable field error.
156205
commands.push({
157206
req: {findAndModify: collName, query: {_id: 1}, update: {$set: {_id: 2}}},
158-
setupFunc: function() {
207+
setupFunc: () => {
159208
assert.commandWorked(coll.insert({_id: 1}));
160209
},
161-
confirmFunc: function(res) {
210+
confirmFunc: (res) => {
162211
assert.commandFailedWithCode(res, ErrorCodes.ImmutableField);
163212
assert.eq(coll.find().itcount(), 1);
164213
assert.eq(coll.count({_id: 1}), 1);
165-
}
214+
},
166215
});
167216

168217
// 'findAndModify' where the document to delete does not exist.
169218
commands.push({
170219
req: {findAndModify: collName, query: {a: 1}, remove: true},
171-
setupFunc: function() {
220+
setupFunc: () => {
172221
assert.commandWorked(coll.insert({a: 1}));
173222
assert.commandWorked(coll.remove({a: 1}));
174223
},
175-
confirmFunc: function(res) {
224+
confirmFunc: (res) => {
176225
assert.commandWorkedIgnoringWriteConcernErrors(res);
177226
assert.eq(res.lastErrorObject.n, 0);
178-
}
227+
},
179228
});
180229

181230
// 'dropDatabase' where the database has already been dropped.
182231
commands.push({
183232
req: {dropDatabase: 1},
184-
setupFunc: function() {
233+
setupFunc: () => {
185234
assert.commandWorked(coll.insert({a: 1}));
186235
assert.commandWorkedIgnoringWriteConcernErrors(db.runCommand({dropDatabase: 1}));
187236
},
188-
confirmFunc: function(res) {
237+
confirmFunc: (res) => {
189238
assert.commandWorkedIgnoringWriteConcernErrors(res);
190-
}
239+
},
191240
});
192241

193242
// 'drop' where the collection has already been dropped.
194243
commands.push({
195244
req: {drop: collName},
196-
setupFunc: function() {
245+
setupFunc: () => {
197246
assert.commandWorked(coll.insert({a: 1}));
198247
assert.commandWorkedIgnoringWriteConcernErrors(db.runCommand({drop: collName}));
199248
},
200-
confirmFunc: function(res) {
249+
confirmFunc: (res) => {
201250
assert.commandWorkedIgnoringWriteConcernErrors(res);
202-
}
251+
},
203252
});
204253

205254
// 'create' where the collection has already been created.
206255
commands.push({
207256
req: {create: collName},
208-
setupFunc: function() {
257+
setupFunc: () => {
209258
assert.commandWorkedIgnoringWriteConcernErrors(db.runCommand({create: collName}));
210259
},
211260
confirmFunc: function(res) {
@@ -223,15 +272,55 @@ function getNoopWriteCommands(coll) {
223272
// 'insert' where the document with the same _id has already been inserted.
224273
commands.push({
225274
req: {insert: collName, documents: [{_id: 1}]},
226-
setupFunc: function() {
275+
setupFunc: () => {
227276
assert.commandWorked(coll.insert({_id: 1}));
228277
},
229-
confirmFunc: function(res) {
278+
confirmFunc: (res) => {
230279
assert.commandWorkedIgnoringWriteErrorsAndWriteConcernErrors(res);
231280
assert.eq(res.n, 0);
232281
assert.eq(res.writeErrors[0].code, ErrorCodes.DuplicateKey);
233282
assert.eq(coll.count({_id: 1}), 1);
234-
}
283+
},
284+
});
285+
286+
commands.forEach((cmd) => {
287+
// Validate that all commands have proper definitions.
288+
['req', 'setupFunc', 'confirmFunc'].forEach((field) => {
289+
assert(cmd.hasOwnProperty(field),
290+
`command does not have required field '${field}': ${tojson(cmd)}`);
291+
});
292+
293+
// Attach a 'run' function to each command for the actual test execution.
294+
let self = cmd;
295+
cmd.run = (dbName, coll, shell) => {
296+
// Drop test collection.
297+
coll.drop();
298+
assert.eq(0, coll.find().itcount(), "test collection not empty");
299+
300+
jsTest.log("Testing command: " + tojson(self.req));
301+
302+
// Create environment for command to run in.
303+
self.setupFunc(shell);
304+
305+
// Provide a small wtimeout that we expect to time out.
306+
self.req.writeConcern = {w: 3, wtimeout: 1000};
307+
308+
// We check the error code of 'res' in the 'confirmFunc'.
309+
const res = "bulkWrite" in self.req ? shell.adminCommand(self.req)
310+
: shell.getDB(dbName).runCommand(self.req);
311+
312+
try {
313+
// Tests that the command receives a write concern error. If we don't wait for write
314+
// concern on noop writes then we won't get a write concern error.
315+
assertWriteConcernError(res);
316+
// Validate post-conditions of the commands.
317+
self.confirmFunc(res);
318+
} catch (e) {
319+
// Make sure that we print out the response.
320+
printjson(res);
321+
throw e;
322+
}
323+
};
235324
});
236325

237326
return commands;

0 commit comments

Comments
 (0)