summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Zhang <jason.zhang@mongodb.com>2021-11-02 15:31:25 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2021-11-02 16:17:13 +0000
commitb47031ed346055373ff2c852c51afad2e6e5ee4e (patch)
tree0165ada99a6a2d081e11cffa7f9c390bf93fd5fe
parent6c45171b1bb84c272b6c7edd1e47c77db904e9f5 (diff)
downloadmongo-b47031ed346055373ff2c852c51afad2e6e5ee4e.tar.gz
SERVER-53335 Queries, updates, and deletes with non-"simple" collations may miss documents when using hashed sharding
-rw-r--r--etc/backports_required_for_multiversion_tests.yml2
-rw-r--r--jstests/libs/check_orphans_are_deleted_helpers.js8
-rw-r--r--jstests/sharding/query/collation_lookup.js3
-rw-r--r--jstests/sharding/query/collation_shard_targeting_hashed_shard_key.js416
-rw-r--r--jstests/sharding/query/collation_targeting.js3
-rw-r--r--jstests/sharding/query/collation_targeting_inherited.js3
-rw-r--r--src/mongo/s/chunk_manager.cpp17
-rw-r--r--src/mongo/s/chunk_manager.h4
-rw-r--r--src/mongo/s/commands/cluster_find_and_modify_cmd.cpp6
9 files changed, 448 insertions, 14 deletions
diff --git a/etc/backports_required_for_multiversion_tests.yml b/etc/backports_required_for_multiversion_tests.yml
index 656b120b325..3799d1e9334 100644
--- a/etc/backports_required_for_multiversion_tests.yml
+++ b/etc/backports_required_for_multiversion_tests.yml
@@ -112,6 +112,8 @@ last-lts:
test_file: jstests/sharding/database_versioning_all_commands.js
- ticket: SERVER-49988
test_file: jstests/sharding/hello_response_validation.js
+ - ticket: SERVER-53335
+ test_file: jstests/sharding/query/collation_shard_targeting_hashed_shard_key.js
- ticket: SERVER-50640
test_file: jstests/sharding/read_write_concern_defaults_application.js
- ticket: SERVER-50640
diff --git a/jstests/libs/check_orphans_are_deleted_helpers.js b/jstests/libs/check_orphans_are_deleted_helpers.js
index ce1597ee83a..c8308398091 100644
--- a/jstests/libs/check_orphans_are_deleted_helpers.js
+++ b/jstests/libs/check_orphans_are_deleted_helpers.js
@@ -72,7 +72,13 @@ var CheckOrphansAreDeletedHelpers = (function() {
mongosConn.getDB('config').chunks.find(chunksQuery).forEach(chunkDoc => {
// Use $min/$max so this will also work with hashed and compound shard keys.
const orphans =
- coll.find({}).hint(collDoc.key).min(chunkDoc.min).max(chunkDoc.max).toArray();
+ new DBCommandCursor(coll.getDB(), assert.commandWorked(coll.runCommand("find", {
+ collation: {locale: "simple"},
+ hint: collDoc.key,
+ min: chunkDoc.min,
+ max: chunkDoc.max
+ }))).toArray();
+
assert.eq(0,
orphans.length,
'found orphans @ ' + shardId + ' within chunk: ' + tojson(chunkDoc) +
diff --git a/jstests/sharding/query/collation_lookup.js b/jstests/sharding/query/collation_lookup.js
index 9fdae23e011..876aae0e5a1 100644
--- a/jstests/sharding/query/collation_lookup.js
+++ b/jstests/sharding/query/collation_lookup.js
@@ -17,9 +17,6 @@ load("jstests/aggregation/extras/utils.js"); // for arrayEq
load("jstests/noPassthrough/libs/server_parameter_helpers.js"); // For setParameterOnAllHosts.
load("jstests/libs/discover_topology.js"); // For findDataBearingNodes.
-// Shard key index has collation, which is not compatible with $min/$max
-TestData.skipCheckOrphans = true;
-
function runTests(withDefaultCollationColl, withoutDefaultCollationColl, collation) {
// Test that the $lookup stage respects the inherited collation.
let res = withDefaultCollationColl
diff --git a/jstests/sharding/query/collation_shard_targeting_hashed_shard_key.js b/jstests/sharding/query/collation_shard_targeting_hashed_shard_key.js
new file mode 100644
index 00000000000..9ae58763ab3
--- /dev/null
+++ b/jstests/sharding/query/collation_shard_targeting_hashed_shard_key.js
@@ -0,0 +1,416 @@
+
+/**
+ * Test shard targeting for queries on a collection with a non-simple collation and a hashed shard
+ * key.
+ * @tags: [
+ * requires_find_command
+ * ]
+ */
+(function() {
+const st = new ShardingTest({mongos: 1, config: 1, shards: 2, rs: {nodes: 1}});
+
+function shardCollectionWithSplitsAndMoves(
+ ns, shardKeyPattern, collation, splitPoints, chunksToMove) {
+ const collection = st.s.getCollection(ns);
+ const db = collection.getDB();
+
+ assert.commandWorked(db.runCommand({create: collection.getName(), collation: collation}));
+
+ st.ensurePrimaryShard(db.getName(), st.shard0.shardName);
+ assert.commandWorked(st.s.adminCommand({enableSharding: db.getName()}));
+
+ assert.commandWorked(st.s.adminCommand({
+ shardCollection: collection.getFullName(),
+ key: shardKeyPattern,
+ collation: {locale: "simple"}
+ }));
+
+ for (let splitPoint of splitPoints) {
+ assert.commandWorked(
+ st.s.adminCommand({split: collection.getFullName(), middle: splitPoint}));
+ }
+
+ for (let {query, shard} of chunksToMove) {
+ assert.commandWorked(st.s.adminCommand({
+ moveChunk: collection.getFullName(),
+ find: query,
+ to: shard,
+ }));
+ }
+
+ return collection;
+}
+
+function findQueryWithCollation(collection, query, collation) {
+ let cursor = collection.find(query);
+ if (collation) {
+ cursor = cursor.collation(collation);
+ }
+ return cursor.toArray();
+}
+
+{
+ jsTestLog(
+ "Test find command in an _id:hashed sharded collection with simple default collation.");
+
+ const collection = shardCollectionWithSplitsAndMoves(
+ "test.id_hashed_sharding_with_simple_collation",
+ {_id: "hashed"},
+ {locale: "simple"},
+ [{_id: convertShardKeyToHashed("A")}, {_id: convertShardKeyToHashed("a")}],
+ [
+ {query: {_id: "A"}, shard: st.shard0.shardName},
+ {query: {_id: "a"}, shard: st.shard1.shardName}
+ ]);
+
+ const docs = [{_id: "A"}];
+ assert.commandWorked(collection.insert(docs));
+
+ // Check default collation, simple collation, non-simple collation.
+ assert.eq([],
+ findQueryWithCollation(
+ st.s.getCollection(collection.getFullName()), {_id: "a"}, undefined));
+ assert.eq([],
+ findQueryWithCollation(
+ st.s.getCollection(collection.getFullName()), {_id: "a"}, {locale: "simple"}));
+ assert.eq(
+ docs,
+ findQueryWithCollation(
+ st.s.getCollection(collection.getFullName()), {_id: "a"}, {locale: "en", strength: 2}));
+}
+
+{
+ jsTestLog(
+ "Test find command in an _id:hashed sharded collection with non-simple default collation.");
+
+ const collection = shardCollectionWithSplitsAndMoves(
+ "test.id_hashed_sharding_with_default_collation",
+ {_id: "hashed"},
+ {locale: "en", strength: 2},
+ [{_id: convertShardKeyToHashed("A")}, {_id: convertShardKeyToHashed("a")}],
+ [
+ {query: {_id: "A"}, shard: st.shard0.shardName},
+ {query: {_id: "a"}, shard: st.shard1.shardName}
+ ]);
+
+ const docs = [{_id: "A"}];
+ assert.commandWorked(collection.insert(docs));
+
+ // Check default collation, simple collation, non-simple collation.
+ assert.eq(docs,
+ findQueryWithCollation(
+ st.s.getCollection(collection.getFullName()), {_id: "a"}, undefined));
+ assert.eq([],
+ findQueryWithCollation(
+ st.s.getCollection(collection.getFullName()), {_id: "a"}, {locale: "simple"}));
+ assert.eq(
+ docs,
+ findQueryWithCollation(
+ st.s.getCollection(collection.getFullName()), {_id: "a"}, {locale: "en", strength: 2}));
+}
+
+{
+ jsTestLog("Test an _id:1 sharded collection with non-simple default collation.");
+
+ const collection = st.s.getCollection("test.id_range_sharding_with_default_collation");
+ const db = collection.getDB();
+ assert.commandWorked(
+ db.runCommand({create: collection.getName(), collation: {locale: "en", strength: 2}}));
+
+ st.ensurePrimaryShard(db.getName(), st.shard0.shardName);
+ assert.commandWorked(st.s.adminCommand({enableSharding: db.getName()}));
+
+ const res = assert.commandFailedWithCode(st.s.adminCommand({
+ shardCollection: collection.getFullName(),
+ key: {_id: 1},
+ collation: {locale: "simple"}
+ }),
+ ErrorCodes.BadValue);
+ assert(/The _id index must have the same collation as the collection/.test(res.errmsg),
+ `expected shardCollection command to fail due to required collation for _id index: ${
+ tojson(res)}`);
+}
+
+{
+ jsTestLog("Test find command in a hashed sharded collection with simple default collation.");
+ const collection = shardCollectionWithSplitsAndMoves(
+ "test.non_id_hashed_sharding_with_simple_collation",
+ {notUnderscoreId: "hashed"},
+ {locale: "simple"},
+ [
+ {notUnderscoreId: convertShardKeyToHashed("A")},
+ {notUnderscoreId: convertShardKeyToHashed("a")}
+ ],
+ [
+ {query: {notUnderscoreId: "A"}, shard: st.shard0.shardName},
+ {query: {notUnderscoreId: "a"}, shard: st.shard1.shardName}
+ ]);
+
+ const docs = [{_id: 0, notUnderscoreId: "A"}];
+ assert.commandWorked(collection.insert(docs));
+
+ // Check default collation, simple collation, non-simple collation.
+ assert.eq([],
+ findQueryWithCollation(
+ st.s.getCollection(collection.getFullName()), {notUnderscoreId: "a"}, undefined));
+ assert.eq([],
+ findQueryWithCollation(st.s.getCollection(collection.getFullName()),
+ {notUnderscoreId: "a"},
+ {locale: "simple"}));
+ assert.eq(docs,
+ findQueryWithCollation(st.s.getCollection(collection.getFullName()),
+ {notUnderscoreId: "a"},
+ {locale: "en", strength: 2}));
+}
+
+{
+ jsTestLog(
+ "Test find command in a hashed sharded collection with non-simple default collation.");
+
+ const collection = shardCollectionWithSplitsAndMoves(
+ "test.non_id_hashed_sharding_with_non_simple_collation",
+ {notUnderscoreId: "hashed"},
+ {locale: "en", strength: 2},
+ [
+ {notUnderscoreId: convertShardKeyToHashed("A")},
+ {notUnderscoreId: convertShardKeyToHashed("a")}
+ ],
+ [
+ {query: {notUnderscoreId: "A"}, shard: st.shard0.shardName},
+ {query: {notUnderscoreId: "a"}, shard: st.shard1.shardName}
+ ]);
+
+ const docs = [{_id: 0, notUnderscoreId: "A"}];
+ assert.commandWorked(collection.insert(docs));
+
+ // Check default collation, simple collation, non-simple collation.
+ assert.eq(docs,
+ findQueryWithCollation(
+ st.s.getCollection(collection.getFullName()), {notUnderscoreId: "a"}, undefined));
+ assert.eq([],
+ findQueryWithCollation(st.s.getCollection(collection.getFullName()),
+ {notUnderscoreId: "a"},
+ {locale: "simple"}));
+ assert.eq(docs,
+ findQueryWithCollation(st.s.getCollection(collection.getFullName()),
+ {notUnderscoreId: "a"},
+ {locale: "en", strength: 2}));
+}
+
+{
+ jsTestLog(
+ "Test findAndModify command in an _id:hashed sharded collection with simple default collation.");
+
+ const collection = shardCollectionWithSplitsAndMoves(
+ "test.id_hashed_sharding_find_and_modify_simple_collation",
+ {_id: "hashed"},
+ {locale: "simple"},
+ [{_id: convertShardKeyToHashed("A")}, {_id: convertShardKeyToHashed("a")}],
+ [
+ {query: {_id: "A"}, shard: st.shard0.shardName},
+ {query: {_id: "a"}, shard: st.shard1.shardName}
+ ]);
+
+ const docs = [{_id: "A", count: 0}];
+ assert.commandWorked(collection.insert(docs));
+
+ const mongosCollection = st.s.getCollection(collection.getFullName());
+
+ // Check findAndModify results with the default, simple, and non-simple collation. Currently,
+ // due to findAndModify's assumption that _id is uniquely targetable, we do not do a scatter
+ // gather to check every shard for a match. findAndModify's current behavior will target the
+ // first shard in which the max key of a chunk is greater than the query's shard key. In this
+ // case, because we're using hashed sharding, hash('a') is less than hash('A'), which means when
+ // we query for {_id: "a"} we will target the shard containing the chunk for "a", likewise if we
+ // query for {_id: "A"} we will only target the shard containing the chunk for "A".
+ assert.lt(convertShardKeyToHashed("a"), convertShardKeyToHashed("A"));
+ assert.eq(null,
+ mongosCollection.findAndModify({query: {_id: "a"}, update: {$inc: {count: 1}}}));
+ assert.eq(null,
+ mongosCollection.findAndModify(
+ {query: {_id: "a"}, update: {$inc: {count: 1}}, collation: {locale: "simple"}}));
+ assert.eq(null, mongosCollection.findAndModify({
+ query: {_id: "a"},
+ update: {$inc: {count: 1}},
+ collation: {locale: "en", strength: 2}
+ }));
+ assert.eq({_id: "A", count: 0},
+ mongosCollection.findAndModify({query: {_id: "A"}, update: {$inc: {count: 1}}}));
+ assert.eq({_id: "A", count: 1},
+ mongosCollection.findAndModify(
+ {query: {_id: "A"}, update: {$inc: {count: 1}}, collation: {locale: "simple"}}));
+ assert.eq({_id: "A", count: 2}, mongosCollection.findAndModify({
+ query: {_id: "A"},
+ update: {$inc: {count: 1}},
+ collation: {locale: "en", strength: 2}
+ }));
+}
+
+{
+ jsTestLog(
+ "Test findAndModify command in an _id:hashed sharded collection with non-simple default collation.");
+
+ const collection = shardCollectionWithSplitsAndMoves(
+ "test.id_hashed_sharding_find_and_modify_with_non_simple_collation",
+ {_id: "hashed"},
+ {locale: "en", strength: 2},
+ [{_id: convertShardKeyToHashed("A")}, {_id: convertShardKeyToHashed("a")}],
+ [
+ {query: {_id: "A"}, shard: st.shard0.shardName},
+ {query: {_id: "a"}, shard: st.shard1.shardName}
+ ]);
+
+ const docs = [{_id: "A", count: 0}];
+ assert.commandWorked(collection.insert(docs));
+
+ const mongosCollection = st.s.getCollection(collection.getFullName());
+
+ // Check findAndModify results with the default, simple, and non-simple collation. Currently,
+ // due to findAndModify's assumption that _id is uniquely targetable, we do not do a scatter
+ // gather to check every shard for a match. findAndModify's current behavior will target the
+ // first shard in which the max key of a chunk is greater than the query's shard key. In this
+ // case, because we're using hashed sharding, hash('a') is less than hash('A'), which means when
+ // we query for {_id: "a"} we will target the shard containing the chunk for "a", likewise if we
+ // query for {_id: "A"} we will only target the shard containing the chunk for "A".
+ assert.lt(convertShardKeyToHashed("a"), convertShardKeyToHashed("A"));
+ assert.eq(null,
+ mongosCollection.findAndModify({query: {_id: "a"}, update: {$inc: {count: 1}}}));
+ assert.eq(null,
+ mongosCollection.findAndModify(
+ {query: {_id: "a"}, update: {$inc: {count: 1}}, collation: {locale: "simple"}}));
+ assert.eq(null, mongosCollection.findAndModify({
+ query: {_id: "a"},
+ update: {$inc: {count: 1}},
+ collation: {locale: "en", strength: 2}
+ }));
+ assert.eq({_id: "A", count: 0},
+ mongosCollection.findAndModify({query: {_id: "A"}, update: {$inc: {count: 1}}}));
+ assert.eq({_id: "A", count: 1},
+ mongosCollection.findAndModify(
+ {query: {_id: "A"}, update: {$inc: {count: 1}}, collation: {locale: "simple"}}));
+ assert.eq({_id: "A", count: 2}, mongosCollection.findAndModify({
+ query: {_id: "A"},
+ update: {$inc: {count: 1}},
+ collation: {locale: "en", strength: 2}
+ }));
+}
+
+{
+ jsTestLog(
+ "Test findAndModify command in a hashed sharded collection with simple default collation.");
+ const collection = shardCollectionWithSplitsAndMoves(
+ "test.non_id_hashed_sharding_find_and_modify_with_simple_collation",
+ {notUnderscoreId: "hashed"},
+ {locale: "simple"},
+ [
+ {notUnderscoreId: convertShardKeyToHashed("A")},
+ {notUnderscoreId: convertShardKeyToHashed("a")}
+ ],
+ [
+ {query: {notUnderscoreId: "A"}, shard: st.shard0.shardName},
+ {query: {notUnderscoreId: "a"}, shard: st.shard1.shardName}
+ ]);
+
+ const docs = [{_id: 0, notUnderscoreId: "A", count: 0}];
+ assert.commandWorked(collection.insert(docs));
+
+ const mongosCollection = st.s.getCollection(collection.getFullName());
+
+ // Check findAndModify results with the default, simple, and non-simple collation. Currently,
+ // due to findAndModify's assumption that _id is uniquely targetable, we do not do a scatter
+ // gather to check every shard for a match. findAndModify's current behavior will target the
+ // first shard in which the max key of a chunk is greater than the query's shard key. In this
+ // case, because we're using hashed sharding, hash('a') is less than hash('A'), which means when
+ // we query for {notUnderscoeId: "a"} we will target the shard containing the chunk for "a",
+ // likewise if we query for {notUnderscoreId: "A"} we will only target the shard containing the
+ // chunk for "A".
+ assert.lt(convertShardKeyToHashed("a"), convertShardKeyToHashed("A"));
+ assert.eq(null,
+ mongosCollection.findAndModify(
+ {query: {notUnderscoreId: "a"}, update: {$inc: {count: 1}}}));
+ assert.eq(null, mongosCollection.findAndModify({
+ query: {notUnderscoreId: "a"},
+ update: {$inc: {count: 1}},
+ collation: {locale: "simple"}
+ }));
+ assert.eq(null, mongosCollection.findAndModify({
+ query: {notUnderscoreId: "a"},
+ update: {$inc: {count: 1}},
+ collation: {locale: "en", strength: 2}
+ }));
+ assert.eq({_id: 0, notUnderscoreId: "A", count: 0},
+ mongosCollection.findAndModify(
+ {query: {notUnderscoreId: "A"}, update: {$inc: {count: 1}}}));
+ assert.eq({_id: 0, notUnderscoreId: "A", count: 1}, mongosCollection.findAndModify({
+ query: {notUnderscoreId: "A"},
+ update: {$inc: {count: 1}},
+ collation: {locale: "simple"}
+ }));
+ assert.eq({_id: 0, notUnderscoreId: "A", count: 2}, mongosCollection.findAndModify({
+ query: {notUnderscoreId: "A"},
+ update: {$inc: {count: 1}},
+ collation: {locale: "en", strength: 2}
+ }));
+}
+
+{
+ jsTestLog(
+ "Test findAndModify command in a hashed sharded collection with non-simple default collation.");
+
+ const collection = shardCollectionWithSplitsAndMoves(
+ "test.non_id_hashed_sharding_find_and_modify_with_non_simple_collation",
+ {notUnderscoreId: "hashed"},
+ {locale: "en", strength: 2},
+ [
+ {notUnderscoreId: convertShardKeyToHashed("A")},
+ {notUnderscoreId: convertShardKeyToHashed("a")}
+ ],
+ [
+ {query: {notUnderscoreId: "A"}, shard: st.shard0.shardName},
+ {query: {notUnderscoreId: "a"}, shard: st.shard1.shardName}
+ ]);
+
+ const docs = [{_id: 0, notUnderscoreId: "A", count: 0}];
+ assert.commandWorked(collection.insert(docs));
+
+ const mongosCollection = st.s.getCollection(collection.getFullName());
+
+ // Check findAndModify results with the default, simple, and non-simple collation. Currently,
+ // due to findAndModify's assumption that _id is uniquely targetable, we do not do a scatter
+ // gather to check every shard for a match. findAndModify's current behavior will target the
+ // first shard in which the max key of a chunk is greater than the query's shard key. In this
+ // case, because we're using hashed sharding, hash('a') is less than hash('A'), which means when
+ // we query for {notUnderscoreId: "a"} we will target the shard containing the chunk for "a",
+ // likewise if we query for {notUnderscoreId: "A"} we will only target the shard containing the
+ // chunk for "A".
+ assert.lt(convertShardKeyToHashed("a"), convertShardKeyToHashed("A"));
+ assert.eq(null,
+ mongosCollection.findAndModify(
+ {query: {notUnderscoreId: "a"}, update: {$inc: {count: 1}}}));
+ assert.eq(null, mongosCollection.findAndModify({
+ query: {notUnderscoreId: "a"},
+ update: {$inc: {count: 1}},
+ collation: {locale: "simple"}
+ }));
+ assert.eq(null, mongosCollection.findAndModify({
+ query: {notUnderscoreId: "a"},
+ update: {$inc: {count: 1}},
+ collation: {locale: "en", strength: 2}
+ }));
+ assert.eq({_id: 0, notUnderscoreId: "A", count: 0},
+ mongosCollection.findAndModify(
+ {query: {notUnderscoreId: "A"}, update: {$inc: {count: 1}}}));
+ assert.eq({_id: 0, notUnderscoreId: "A", count: 1}, mongosCollection.findAndModify({
+ query: {notUnderscoreId: "A"},
+ update: {$inc: {count: 1}},
+ collation: {locale: "simple"}
+ }));
+ assert.eq({_id: 0, notUnderscoreId: "A", count: 2}, mongosCollection.findAndModify({
+ query: {notUnderscoreId: "A"},
+ update: {$inc: {count: 1}},
+ collation: {locale: "en", strength: 2}
+ }));
+}
+
+st.stop();
+})(); \ No newline at end of file
diff --git a/jstests/sharding/query/collation_targeting.js b/jstests/sharding/query/collation_targeting.js
index b8172065f19..0883d18c430 100644
--- a/jstests/sharding/query/collation_targeting.js
+++ b/jstests/sharding/query/collation_targeting.js
@@ -2,9 +2,6 @@
(function() {
"use strict";
-// Shard key index has collation, which is not compatible with $min/$max
-TestData.skipCheckOrphans = true;
-
const caseInsensitive = {
locale: "en_US",
strength: 2
diff --git a/jstests/sharding/query/collation_targeting_inherited.js b/jstests/sharding/query/collation_targeting_inherited.js
index 6006c25a8c9..1ae0c4f32f4 100644
--- a/jstests/sharding/query/collation_targeting_inherited.js
+++ b/jstests/sharding/query/collation_targeting_inherited.js
@@ -2,9 +2,6 @@
(function() {
"use strict";
-// Shard key index has collation, which is not compatible with $min/$max
-TestData.skipCheckOrphans = true;
-
const caseInsensitive = {
locale: "en_US",
strength: 2
diff --git a/src/mongo/s/chunk_manager.cpp b/src/mongo/s/chunk_manager.cpp
index 29e1457122d..f565d29f7f5 100644
--- a/src/mongo/s/chunk_manager.cpp
+++ b/src/mongo/s/chunk_manager.cpp
@@ -350,16 +350,29 @@ void RoutingTableHistory::setAllShardsRefreshed() {
}
}
-Chunk ChunkManager::findIntersectingChunk(const BSONObj& shardKey, const BSONObj& collation) const {
+Chunk ChunkManager::findIntersectingChunk(const BSONObj& shardKey,
+ const BSONObj& collation,
+ bool bypassIsFieldHashedCheck) const {
const bool hasSimpleCollation = (collation.isEmpty() && !_rt->optRt->getDefaultCollator()) ||
SimpleBSONObjComparator::kInstance.evaluate(collation == CollationSpec::kSimpleSpec);
if (!hasSimpleCollation) {
for (BSONElement elt : shardKey) {
+ // We must assume that if the field is specified as "hashed" in the shard key pattern,
+ // then the hash value could have come from a collatable type.
+ const bool isFieldHashed =
+ (_rt->optRt->getShardKeyPattern().isHashedPattern() &&
+ _rt->optRt->getShardKeyPattern().getHashedField().fieldNameStringData() ==
+ elt.fieldNameStringData());
+
+ // If we want to skip the check in the special case where the _id field is hashed and
+ // used as the shard key, set bypassIsFieldHashedCheck. This assumes that a request with
+ // a query that contains an _id field can target a specific shard.
uassert(ErrorCodes::ShardKeyNotFound,
str::stream() << "Cannot target single shard due to collation of key "
<< elt.fieldNameStringData() << " for namespace "
<< _rt->optRt->nss(),
- !CollationIndexKey::isCollatableType(elt.type()));
+ !CollationIndexKey::isCollatableType(elt.type()) &&
+ (!isFieldHashed || bypassIsFieldHashedCheck));
}
}
diff --git a/src/mongo/s/chunk_manager.h b/src/mongo/s/chunk_manager.h
index 4179d38aa07..64f306626a0 100644
--- a/src/mongo/s/chunk_manager.h
+++ b/src/mongo/s/chunk_manager.h
@@ -613,7 +613,9 @@ public:
* Throws a DBException with the ShardKeyNotFound code if unable to target a single shard due to
* collation or due to the key not matching the shard key pattern.
*/
- Chunk findIntersectingChunk(const BSONObj& shardKey, const BSONObj& collation) const;
+ Chunk findIntersectingChunk(const BSONObj& shardKey,
+ const BSONObj& collation,
+ bool bypassIsFieldHashedCheck = false) const;
/**
* Same as findIntersectingChunk, but assumes the simple collation.
diff --git a/src/mongo/s/commands/cluster_find_and_modify_cmd.cpp b/src/mongo/s/commands/cluster_find_and_modify_cmd.cpp
index 8f7ed0e2ee2..532e3e12438 100644
--- a/src/mongo/s/commands/cluster_find_and_modify_cmd.cpp
+++ b/src/mongo/s/commands/cluster_find_and_modify_cmd.cpp
@@ -325,7 +325,11 @@ public:
const BSONObj shardKey =
getShardKey(opCtx, cm, nss, query, collation, boost::none, let, rc);
- auto chunk = cm.findIntersectingChunk(shardKey, collation);
+ // For now, set bypassIsFieldHashedCheck to be true in order to skip the
+ // isFieldHashedCheck in the special case where _id is hashed and used as the shard key.
+ // This means that we always assume that a findAndModify request using _id is targetable
+ // to a single shard.
+ auto chunk = cm.findIntersectingChunk(shardKey, collation, true);
_runCommand(opCtx,
chunk.getShardId(),