Skip to content

Commit e443421

Browse files
lynne-wangMongoDB Bot
authored andcommitted
SERVER-78140 Allow updateOne to supply a sort option
GitOrigin-RevId: 879b5da
1 parent deb7cc6 commit e443421

32 files changed

+900
-61
lines changed

buildscripts/resmokeconfig/suites/timeseries_crud_jscore_passthrough.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ selector:
4848
- jstests/core/write/find_and_modify/find_and_modify_empty_update.js
4949
- jstests/core/write/find_and_modify/find_and_modify_invalid_query_params.js
5050
- jstests/core/write/find_and_modify/find_and_modify_server7660.js
51+
# updateOne sort.
52+
- jstests/core/write/update/updateOne_sort.js
5153
# Incompatible find command option.
5254
- jstests/core/min_max_hashed_index.js # min
5355
- jstests/core/query/expr/expr.js # divide
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/**
2+
* updateOne_update_with_sort.js
3+
*
4+
* Ensures that updateOne will not apply an update to a document which, due to a concurrent
5+
* modification, no longer matches the query predicate. At the end of the workload, the contents of
6+
* the database are checked for whether the correct number of documents were updated.
7+
*
8+
* @tags: [
9+
* # PM-1632 was delivered in 7.1, resolving the issue about assumes_unsharded_collection.
10+
* requires_fcv_80,
11+
* ]
12+
*/
13+
import {extendWorkload} from "jstests/concurrency/fsm_libs/extend_workload.js";
14+
import {isMongod} from "jstests/concurrency/fsm_workload_helpers/server_types.js";
15+
import {
16+
$config as $baseConfig
17+
} from "jstests/concurrency/fsm_workloads/updateOne_with_sort_update_queue.js";
18+
19+
export const $config = extendWorkload($baseConfig, function($config, $super) {
20+
// Use the same workload name as the database name, since the workload
21+
// name is assumed to be unique.
22+
$config.data.uniqueDBName = jsTestName();
23+
24+
$config.iterations = 1;
25+
26+
$config.states.updateOne = function(db, collName) {
27+
const updateCmd = {
28+
update: collName,
29+
updates: [{q: {a: 1, b: 1}, u: {$inc: {a: 1}}, multi: false, sort: {a: -1}}]
30+
};
31+
32+
// Update field 'a' to avoid matching the same document again.
33+
var res = db.runCommand(updateCmd);
34+
if (isMongod(db)) {
35+
assert.contains(res.nModified, [0, 1], tojson(res));
36+
}
37+
};
38+
39+
$config.setup = function(db, collName) {
40+
var bulk = db[collName].initializeUnorderedBulkOp();
41+
var doc = this.newDocForInsert(0);
42+
// Require that documents inserted by this workload use _id values that can be compared
43+
// using the default JS comparator.
44+
assert.neq(typeof doc._id,
45+
'object',
46+
'default comparator of' +
47+
' Array.prototype.sort() is not well-ordered for JS objects');
48+
bulk.insert(doc);
49+
var res = bulk.execute();
50+
assert.commandWorked(res);
51+
// Insert a single document into the collection.
52+
assert.eq(1, res.nInserted);
53+
54+
this.getIndexSpecs().forEach(function createIndex(indexSpec) {
55+
assert.commandWorked(db[collName].createIndex(indexSpec));
56+
});
57+
};
58+
59+
$config.teardown = function(db, collName) {
60+
var docs = db[collName].find().toArray();
61+
// Assert that while 10 threads attempted an updateOne on a single matching document, it was
62+
// only updated once with the correct update. All updateOne operations look for a document
63+
// with a==1, and then increment 'a' by 1. One should win the race and set a=2. The others
64+
// should fail to find a match. The assertion is that 'a' got incremented once (not zero
65+
// times and not twice).
66+
assert.eq(docs.length, 1);
67+
assert.eq(docs[0].a, 2);
68+
};
69+
70+
return $config;
71+
});
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/**
2+
* updateOne_update_with_sort_and_upsert.js
3+
*
4+
* Ensures that updateOne with a sort and {upsert: true} will perform an upsert when, due to a
5+
* concurrent modification, no documents match the query predicate. At the end of the workload, the
6+
* contents of the database are checked for whether the correct number of documents were updated.
7+
*
8+
* @tags: [
9+
* # PM-1632 was delivered in 7.1, resolving the issue about assumes_unsharded_collection.
10+
* requires_fcv_80,
11+
* ]
12+
*/
13+
import {extendWorkload} from "jstests/concurrency/fsm_libs/extend_workload.js";
14+
import {isMongod} from "jstests/concurrency/fsm_workload_helpers/server_types.js";
15+
import {
16+
$config as $baseConfig
17+
} from "jstests/concurrency/fsm_workloads/updateOne_with_sort_update_queue.js";
18+
19+
export const $config = extendWorkload($baseConfig, function($config, $super) {
20+
// Use the same workload name as the database name, since the workload
21+
// name is assumed to be unique.
22+
$config.data.uniqueDBName = jsTestName();
23+
24+
$config.iterations = 1;
25+
26+
$config.states.updateOne = function(db, collName) {
27+
const updateCmd = {
28+
update: collName,
29+
updates:
30+
[{q: {a: 1, b: 1}, u: {$inc: {a: 1}}, multi: false, sort: {a: -1}, upsert: true}]
31+
};
32+
33+
// Update field 'a' to avoid matching the same document again.
34+
var res = db.runCommand(updateCmd);
35+
if (isMongod(db)) {
36+
if (res.hasOwnProperty('upserted') && res.upserted.length != 0) {
37+
assert.eq(res.nModified, 0, tojson(res));
38+
} else {
39+
assert.eq(res.nModified, 1, tojson(res));
40+
}
41+
}
42+
};
43+
44+
$config.setup = function(db, collName) {
45+
this.numDocs = this.iterations * this.threadCount;
46+
47+
var bulk = db[collName].initializeUnorderedBulkOp();
48+
var doc = this.newDocForInsert(0);
49+
// Require that documents inserted by this workload use _id values that can be compared
50+
// using the default JS comparator.
51+
assert.neq(typeof doc._id,
52+
'object',
53+
'default comparator of' +
54+
' Array.prototype.sort() is not well-ordered for JS objects');
55+
bulk.insert(doc);
56+
var res = bulk.execute();
57+
assert.commandWorked(res);
58+
// Insert a single document into the collection.
59+
assert.eq(1, res.nInserted);
60+
61+
this.getIndexSpecs().forEach(function createIndex(indexSpec) {
62+
assert.commandWorked(db[collName].createIndex(indexSpec));
63+
});
64+
};
65+
66+
$config.teardown = function(db, collName) {
67+
var docs = db[collName].find().toArray();
68+
// Assert that while 10 threads attempted an updateOne on a single matching document, it was
69+
// only updated once with the correct update. All updateOne operations look for a document
70+
// with a==1, and then increment 'a' by 1. One should win the race and set a=2. The others
71+
// should fail to find a match and will insert a new document instead. The assertion is that
72+
// 'a' got incremented once in the original document that was in the collection (not zero
73+
// times and not twice). The additional documents are the product of upserts and have unique
74+
// _id values.
75+
assert.eq(docs.length, this.numDocs);
76+
77+
var seenIds = new Set();
78+
for (var i = 0; i < docs.length; i++) {
79+
assert.eq(docs[i].a, 2);
80+
assert.eq(seenIds.has(docs[i]._id), false);
81+
seenIds.add(docs[i]._id);
82+
}
83+
assert(seenIds.has(0));
84+
};
85+
86+
return $config;
87+
});
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/**
2+
* updateOne_update_with_sort_upsert_dupkey.js
3+
*
4+
* Ensures that updateOne with a sort, {upsert: true}, and a predicate on the same _id value will
5+
* perform an upsert when no documents match the query predicate. Verify that when there is a
6+
* WriteConflict, the update command is retried, and the document with the matching _id value is
7+
* updated. At the end of the workload, the contents of the database are checked for whether the
8+
* documents were updated correctly.
9+
*
10+
* @tags: [
11+
* # PM-1632 was delivered in 7.1, resolving the issue about assumes_unsharded_collection.
12+
* requires_fcv_80,
13+
* ]
14+
*/
15+
import {extendWorkload} from "jstests/concurrency/fsm_libs/extend_workload.js";
16+
import {isMongod} from "jstests/concurrency/fsm_workload_helpers/server_types.js";
17+
import {
18+
$config as $baseConfig
19+
} from "jstests/concurrency/fsm_workloads/updateOne_update_with_sort_and_upsert.js";
20+
21+
export const $config = extendWorkload($baseConfig, function($config, $super) {
22+
// Use the same workload name as the database name, since the workload
23+
// name is assumed to be unique.
24+
$config.data.uniqueDBName = jsTestName();
25+
26+
$config.data.getIndexSpecs = function getIndexSpecs() {
27+
return [];
28+
};
29+
30+
$config.threadCount = 2;
31+
32+
// The query predicate is on the _id unique index so that we
33+
$config.states.updateOne = function(db, collName) {
34+
const updateCmd = {
35+
update: collName,
36+
updates: [{q: {_id: 1}, u: {$inc: {a: 1}}, multi: false, sort: {a: -1}, upsert: true}]
37+
};
38+
39+
var res = db.runCommand(updateCmd);
40+
if (isMongod(db)) {
41+
if (res.hasOwnProperty('upserted') && res.upserted.length != 0) {
42+
// Case 1: The _id value is not yet present, so a new document is added to the
43+
// collection.
44+
assert.eq(res.nModified, 0, tojson(res));
45+
} else {
46+
// Case 2: The _id value matches an existing document in the collection, so it
47+
// modifies that document.
48+
assert.eq(res.nModified, 1, tojson(res));
49+
}
50+
}
51+
};
52+
53+
$config.teardown = function(db, collName) {
54+
var docs = db[collName].find().toArray();
55+
56+
// Assert that when 2 threads attempt an updateOne with an upsert on a query with the same
57+
// _id value (unique key), the earlier thread successfully upserts the document when it is
58+
// not found. Since the query predicate contains no initial value for 'a', it adds the
59+
// document {_id: 1, a: 1}. The second thread then finds the newly upserted document and
60+
// updates the field 'a', updating the document to {_id: 1, a: 2}.
61+
assert.eq(docs.length, 2);
62+
var modifiedDoc = db[collName].find({_id: 1}).toArray();
63+
assert.eq(modifiedDoc[0].a, this.numDocs);
64+
};
65+
66+
return $config;
67+
});
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/**
2+
* updateOne_update_queue.js
3+
*
4+
* A large number of documents are inserted during the workload setup. Each thread repeatedly
5+
* updates a document from the collection using the updateOne command with a sort. At the end of the
6+
* workload, the contents of the database are checked for whether the correct number of documents
7+
* were updated.
8+
*
9+
* This test is modeled off of findAndModify_update_queue.js, but instead of storing the _id field
10+
* of the updated document in another database and ensuring that every thread updated a different
11+
* document from the other threads, we check that the correct number of documents were updated
12+
* because updateOne doesn't return the modified document (and its _id value) unless upsert is true.
13+
*
14+
* @tags: [
15+
* # PM-1632 was delivered in 7.1, resolving the issue about assumes_unsharded_collection.
16+
* requires_fcv_80,
17+
* ]
18+
*/
19+
import {isMongod} from "jstests/concurrency/fsm_workload_helpers/server_types.js";
20+
21+
export const $config = (function() {
22+
var data = {
23+
// Use the workload name as the database name, since the workload name is assumed to be
24+
// unique.
25+
uniqueDBName: jsTestName(),
26+
27+
newDocForInsert: function newDocForInsert(i) {
28+
return {_id: i, a: 1, b: 1};
29+
},
30+
31+
getIndexSpecs: function getIndexSpecs() {
32+
return [{a: 1}, {b: 1}];
33+
},
34+
35+
opName: 'updated'
36+
};
37+
38+
var states = (function() {
39+
function updateOne(db, collName) {
40+
const updateCmd = {
41+
update: collName,
42+
updates: [{q: {a: 1, b: 1}, u: {$inc: {a: 1}}, multi: false, sort: {a: -1}}]
43+
};
44+
45+
// Update field 'a' to avoid matching the same document again.
46+
var res = db.runCommand(updateCmd);
47+
48+
if (isMongod(db)) {
49+
// Storage engines should automatically retry the operation, and thus should never
50+
// return 0.
51+
// In the rare case that the retry limit is exceeded, a document may not be matched.
52+
assert.neq(
53+
res.nModified,
54+
0,
55+
'updateOne should have found and updated a matching document, returned ' +
56+
tojson(res));
57+
}
58+
}
59+
60+
return {updateOne: updateOne};
61+
})();
62+
63+
var transitions = {updateOne: {updateOne: 1}};
64+
65+
function setup(db, collName) {
66+
// Each thread should update exactly one document per iteration.
67+
this.numDocs = this.iterations * this.threadCount;
68+
69+
var bulk = db[collName].initializeUnorderedBulkOp();
70+
for (var i = 0; i < this.numDocs; ++i) {
71+
var doc = this.newDocForInsert(i);
72+
// Require that documents inserted by this workload use _id values that can be compared
73+
// using the default JS comparator.
74+
assert.neq(typeof doc._id,
75+
'object',
76+
'default comparator of' +
77+
' Array.prototype.sort() is not well-ordered for JS objects');
78+
bulk.insert(doc);
79+
}
80+
var res = bulk.execute();
81+
assert.commandWorked(res);
82+
assert.eq(this.numDocs, res.nInserted);
83+
84+
this.getIndexSpecs().forEach(function createIndex(indexSpec) {
85+
assert.commandWorked(db[collName].createIndex(indexSpec));
86+
});
87+
}
88+
89+
function teardown(db, collName) {
90+
var numOldDocs = db[collName].countDocuments({a: 1, b: 1});
91+
assert.eq(numOldDocs, 0);
92+
var numNewDocs = db[collName].countDocuments({a: 2, b: 1});
93+
assert.eq(numNewDocs, this.numDocs);
94+
}
95+
96+
return {
97+
threadCount: 10,
98+
iterations: 100,
99+
data: data,
100+
startState: 'updateOne',
101+
states: states,
102+
transitions: transitions,
103+
setup: setup,
104+
teardown: teardown
105+
};
106+
})();
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/**
2+
* updateOne_update_queue_unindexed.js
3+
*
4+
* This is the same workload as updateOne_update_queue.js, but without the relevant index.
5+
*
6+
* The lack of an index that could satisfy the sort forces the updateOne operations to scan all
7+
* the matching documents in order to find the relevant document. This increases the amount of work
8+
* each updateOne operation has to do before getting to the matching document, and thus
9+
* increases the chance of a write conflict because each concurrent updateOne operation is
10+
* trying to update the same document from the queue.
11+
*
12+
* This test is modeled off of findAndModify_update_queue.js, but instead of storing the _id field
13+
* of the updated document in another database and ensuring that every thread updated a different
14+
* document from the other threads, we check that the correct number of documents were updated
15+
* because updateOne doesn't return the modified document (and its _id value) unless upsert is true.
16+
*
17+
* @tags: [
18+
* # PM-1632 was delivered in 7.1, resolving the issue about assumes_unsharded_collection.
19+
* requires_fcv_80,
20+
* ]
21+
*/
22+
import {extendWorkload} from "jstests/concurrency/fsm_libs/extend_workload.js";
23+
import {
24+
$config as $baseConfig
25+
} from "jstests/concurrency/fsm_workloads/updateOne_with_sort_update_queue.js";
26+
27+
export const $config = extendWorkload($baseConfig, function($config, $super) {
28+
// Use the same workload name as the database name, since the workload
29+
// name is assumed to be unique.
30+
$config.data.uniqueDBName = jsTestName();
31+
32+
$config.data.getIndexSpecs = function getIndexSpecs() {
33+
return [];
34+
};
35+
36+
return $config;
37+
});

0 commit comments

Comments
 (0)