diff options
author | Cheahuychou Mao <mao.cheahuychou@gmail.com> | 2023-03-28 16:06:55 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2023-03-28 19:51:51 +0000 |
commit | 3e44409d23b9924ceca60bb2429382fbcc8fc0c3 (patch) | |
tree | 61cfcf4fc51e174817ae33653826463b2add0c58 | |
parent | b5953fbf51dc9b626e33c8fc62eded65f99c6b49 (diff) | |
download | mongo-3e44409d23b9924ceca60bb2429382fbcc8fc0c3.tar.gz |
SERVER-75312 Make sure analyzeShardKey command can handle queries with $expr equality
10 files changed, 293 insertions, 303 deletions
diff --git a/jstests/sharding/analyze_shard_key/read_and_write_distribution.js b/jstests/sharding/analyze_shard_key/read_and_write_distribution.js index 1de3a289960..1087ee97004 100644 --- a/jstests/sharding/analyze_shard_key/read_and_write_distribution.js +++ b/jstests/sharding/analyze_shard_key/read_and_write_distribution.js @@ -257,7 +257,7 @@ function makeTestCase(collName, {shardKeyField, isHashed, minVal, maxVal}) { update: collName, updates: [ {q: {[shardKeyField]: getNextVal()}, u: {$set: {z: 0}}}, - {q: {[shardKeyField]: getNextVal()}, u: {$set: {z: 0}}} + {q: {$expr: {$eq: ["$" + shardKeyField, getNextVal()]}}, u: [{$set: {z: 0}}]} ] }); writeDistribution.sampleSize.update += 2; diff --git a/src/mongo/db/s/analyze_shard_key_read_write_distribution.cpp b/src/mongo/db/s/analyze_shard_key_read_write_distribution.cpp index 55db53ef6ce..70bbeef8aab 100644 --- a/src/mongo/db/s/analyze_shard_key_read_write_distribution.cpp +++ b/src/mongo/db/s/analyze_shard_key_read_write_distribution.cpp @@ -48,40 +48,6 @@ namespace mongo { namespace analyze_shard_key { -namespace { - -/** - * Returns true if the query that specifies the given collation against the collection with the - * given default collator has simple collation. - */ -bool hasSimpleCollation(const CollatorInterface* defaultCollator, const BSONObj& collation) { - if (collation.isEmpty()) { - return !defaultCollator; - } - return SimpleBSONObjComparator::kInstance.evaluate(collation == CollationSpec::kSimpleSpec); -} - -/** - * Returns true if the given shard key contains any collatable fields (ones that can be affected in - * comparison or sort order by collation). - */ -bool shardKeyHasCollatableType(const ShardKeyPattern& shardKeyPattern, const BSONObj& shardKey) { - for (const BSONElement& elt : shardKey) { - if (CollationIndexKey::isCollatableType(elt.type())) { - return true; - } - if (shardKeyPattern.isHashedPattern() && - shardKeyPattern.getHashedField().fieldNameStringData() == elt.fieldNameStringData()) { - // If the field is specified as "hashed" in the shard key pattern, then the hash value - // could have come from a collatable type. - return true; - } - } - return false; -} - -} // namespace - template <typename DistributionMetricsType, typename SampleSizeType> DistributionMetricsType DistributionMetricsCalculator<DistributionMetricsType, SampleSizeType>::_getMetrics() const { @@ -106,23 +72,13 @@ DistributionMetricsCalculator<DistributionMetricsType, SampleSizeType>::_getMetr } template <typename DistributionMetricsType, typename SampleSizeType> -BSONObj -DistributionMetricsCalculator<DistributionMetricsType, SampleSizeType>::_incrementMetricsForQuery( +QueryTargetingInfo +DistributionMetricsCalculator<DistributionMetricsType, SampleSizeType>::_getTargetingInfoForQuery( OperationContext* opCtx, - const BSONObj& primaryFilter, + const BSONObj& filter, const BSONObj& collation, - const BSONObj& secondaryFilter, - const boost::optional<LegacyRuntimeConstants>& runtimeConstants, - const boost::optional<BSONObj>& letParameters) { - auto filter = primaryFilter; - auto shardKey = uassertStatusOK(extractShardKeyFromBasicQuery( - opCtx, _targeter.getNS(), _getShardKeyPattern(), primaryFilter)); - if (shardKey.isEmpty() && !secondaryFilter.isEmpty()) { - shardKey = _getShardKeyPattern().extractShardKeyFromDoc(secondaryFilter); - filter = shardKey; - } - - // Increment metrics about range targeting. + const boost::optional<BSONObj>& letParameters, + const boost::optional<LegacyRuntimeConstants>& runtimeConstants) { auto&& cif = [&]() { if (collation.isEmpty()) { return std::unique_ptr<CollatorInterface>{}; @@ -131,48 +87,51 @@ DistributionMetricsCalculator<DistributionMetricsType, SampleSizeType>::_increme CollatorFactoryInterface::get(opCtx->getServiceContext())->makeFromBSON(collation)); }(); auto expCtx = make_intrusive<ExpressionContext>( - opCtx, std::move(cif), _getChunkManager().getNss(), runtimeConstants, letParameters); + opCtx, + std::move(cif), + _getChunkManager().getNss(), + runtimeConstants.value_or(Variables::generateRuntimeConstants(opCtx)), + letParameters); std::set<ShardId> shardIds; // This is not used. - std::set<ChunkRange> chunkRanges; - bool targetMinkeyToMaxKey = false; - getShardIdsForQuery(expCtx, - filter, - collation, - _getChunkManager(), - &shardIds, - &chunkRanges, - &targetMinkeyToMaxKey); - _incrementNumByRanges(chunkRanges); + QueryTargetingInfo info; + getShardIdsForQuery(expCtx, filter, collation, _getChunkManager(), &shardIds, &info); + + return info; +} - // Increment metrics about sharding targeting. - if (!shardKey.isEmpty()) { - // This query filters by shard key equality. If the query has a simple collation or the - // shard key doesn't contain a collatable field, then there is only one matching shard key - // value so the query is guaranteed to target only one shard. Otherwise, the number of - // shards that it targets depend on how the matching shard key values are distributed among - // shards. Given this, pessimistically classify it as targeting to multiple shards. - invariant(!targetMinkeyToMaxKey); - if (hasSimpleCollation(_getDefaultCollator(), collation) || - !shardKeyHasCollatableType(_getShardKeyPattern(), shardKey)) { +template <typename DistributionMetricsType, typename SampleSizeType> +void DistributionMetricsCalculator<DistributionMetricsType, SampleSizeType>:: + _incrementMetricsForQuery(const QueryTargetingInfo& info) { + // Increment metrics about range targeting. + _incrementNumByRanges(info.chunkRanges); + + // Increment metrics about shard targeting. + switch (info.desc) { + case QueryTargetingInfo::Description::kSingleKey: { _incrementNumSingleShard(); - invariant(chunkRanges.size() == 1U); - } else { + tassert(7531200, + "Found a point query that targets multiple chunks", + info.chunkRanges.size() == 1U); + break; + } + case QueryTargetingInfo::Description::kMultipleKeys: { + // This query targets a subset of the shard key space. Therefore, the number of shards + // that it targets depends on how the matching shard key ranges are distributed among + // shards. Given this, pessimistically classify it as targeting to multiple shards. _incrementNumMultiShard(); + break; + } + case QueryTargetingInfo::Description::kMinKeyToMaxKey: { + // This query targets the entire shard key space. Therefore, it always targets all + // shards and chunks. + _incrementNumScatterGather(); + invariant((int)info.chunkRanges.size() == _getChunkManager().numChunks()); + break; } - } else if (targetMinkeyToMaxKey) { - // This query targets the entire shard key space. Therefore, it always targets all - // shards and chunks. - _incrementNumScatterGather(); - invariant((int)chunkRanges.size() == _getChunkManager().numChunks()); - } else { - // This query targets a subset of the shard key space. Therefore, the number of shards - // that it targets depends on how the matching shard key ranges are distributed among - // shards. Given this, pessimistically classify it as targeting to multiple shards. - _incrementNumMultiShard(); + default: + MONGO_UNREACHABLE; } - - return shardKey; } ReadSampleSize ReadDistributionMetricsCalculator::_getSampleSize() const { @@ -210,7 +169,8 @@ void ReadDistributionMetricsCalculator::addQuery(OperationContext* opCtx, auto cmd = SampledReadCommand::parse(IDLParserContext("ReadDistributionMetricsCalculator"), doc.getCmd()); - _incrementMetricsForQuery(opCtx, cmd.getFilter(), cmd.getCollation()); + auto info = _getTargetingInfoForQuery(opCtx, cmd.getFilter(), cmd.getCollation()); + _incrementMetricsForQuery(info); } WriteSampleSize WriteDistributionMetricsCalculator::_getSampleSize() const { @@ -269,16 +229,18 @@ void WriteDistributionMetricsCalculator::_addUpdateQuery( OperationContext* opCtx, const write_ops::UpdateCommandRequest& cmd) { for (const auto& updateOp : cmd.getUpdates()) { _numUpdate++; - auto primaryFilter = updateOp.getQ(); auto collation = write_ops::collationOf(updateOp); - // If this is a non-upsert replacement update, the replacement document can be used as a - // filter. - auto secondaryFilter = [&] { + auto info = _getTargetingInfoForQuery( + opCtx, updateOp.getQ(), collation, cmd.getLet(), cmd.getLegacyRuntimeConstants()); + + if (info.desc != QueryTargetingInfo::Description::kSingleKey) { + // If this is a non-upsert replacement update, the replacement document can be used as + // the filter. auto isReplacementUpdate = !updateOp.getUpsert() && updateOp.getU().type() == write_ops::UpdateModification::Type::kReplacement; auto isExactIdQuery = [&] { return CollectionRoutingInfoTargeter::isExactIdQuery( - opCtx, cmd.getNamespace(), primaryFilter, collation, _getChunkManager()); + opCtx, cmd.getNamespace(), updateOp.getQ(), collation, _getChunkManager()); }; // Currently, targeting by replacement document is only done when an updateOne without @@ -287,17 +249,13 @@ void WriteDistributionMetricsCalculator::_addUpdateQuery( (!feature_flags::gFeatureFlagUpdateOneWithoutShardKey.isEnabled( serverGlobalParams.featureCompatibility) || isExactIdQuery())) { - return updateOp.getU().getUpdateReplacement(); + auto filter = _getShardKeyPattern().extractShardKeyFromDoc( + updateOp.getU().getUpdateReplacement()); + info = _getTargetingInfoForQuery( + opCtx, filter, collation, cmd.getLet(), cmd.getLegacyRuntimeConstants()); } - return BSONObj(); - }(); - _incrementMetricsForQuery(opCtx, - primaryFilter, - secondaryFilter, - collation, - updateOp.getMulti(), - cmd.getLegacyRuntimeConstants(), - cmd.getLet()); + } + _incrementMetricsForQuery(info, updateOp.getMulti()); } } @@ -305,44 +263,30 @@ void WriteDistributionMetricsCalculator::_addDeleteQuery( OperationContext* opCtx, const write_ops::DeleteCommandRequest& cmd) { for (const auto& deleteOp : cmd.getDeletes()) { _numDelete++; - auto primaryFilter = deleteOp.getQ(); - auto secondaryFilter = BSONObj(); - _incrementMetricsForQuery(opCtx, - primaryFilter, - secondaryFilter, - write_ops::collationOf(deleteOp), - deleteOp.getMulti(), - cmd.getLegacyRuntimeConstants(), - cmd.getLet()); + auto info = _getTargetingInfoForQuery(opCtx, + deleteOp.getQ(), + write_ops::collationOf(deleteOp), + cmd.getLet(), + cmd.getLegacyRuntimeConstants()); + _incrementMetricsForQuery(info, deleteOp.getMulti()); } } void WriteDistributionMetricsCalculator::_addFindAndModifyQuery( OperationContext* opCtx, const write_ops::FindAndModifyCommandRequest& cmd) { _numFindAndModify++; - auto primaryFilter = cmd.getQuery(); - auto secondaryFilter = BSONObj(); - _incrementMetricsForQuery(opCtx, - primaryFilter, - secondaryFilter, - cmd.getCollation().value_or(BSONObj()), - false /* isMulti */, - cmd.getLegacyRuntimeConstants(), - cmd.getLet()); + auto info = _getTargetingInfoForQuery(opCtx, + cmd.getQuery(), + cmd.getCollation().value_or(BSONObj()), + cmd.getLet(), + cmd.getLegacyRuntimeConstants()); + _incrementMetricsForQuery(info, false /* isMulti */); } -void WriteDistributionMetricsCalculator::_incrementMetricsForQuery( - OperationContext* opCtx, - const BSONObj& primaryFilter, - const BSONObj& secondaryFilter, - const BSONObj& collation, - bool isMulti, - const boost::optional<LegacyRuntimeConstants>& runtimeConstants, - const boost::optional<BSONObj>& letParameters) { - auto shardKey = DistributionMetricsCalculator::_incrementMetricsForQuery( - opCtx, primaryFilter, collation, secondaryFilter, runtimeConstants, letParameters); - - if (shardKey.isEmpty()) { +void WriteDistributionMetricsCalculator::_incrementMetricsForQuery(const QueryTargetingInfo& info, + bool isMulti) { + DistributionMetricsCalculator::_incrementMetricsForQuery(info); + if (info.desc != QueryTargetingInfo::Description::kSingleKey) { // Increment metrics about writes without shard key. if (isMulti) { _incrementNumMultiWritesWithoutShardKey(); diff --git a/src/mongo/db/s/analyze_shard_key_read_write_distribution.h b/src/mongo/db/s/analyze_shard_key_read_write_distribution.h index 42eb5dd3d43..12a7109d8c7 100644 --- a/src/mongo/db/s/analyze_shard_key_read_write_distribution.h +++ b/src/mongo/db/s/analyze_shard_key_read_write_distribution.h @@ -36,10 +36,15 @@ #include "mongo/s/analyze_shard_key_documents_gen.h" #include "mongo/s/analyze_shard_key_util.h" #include "mongo/s/collection_routing_info_targeter.h" +#include "mongo/s/shard_key_pattern_query_util.h" namespace mongo { namespace analyze_shard_key { +namespace { +using QueryTargetingInfo = shard_key_pattern_query_util::QueryTargetingInfo; +} + /** * The utility class for calculating read or write distribution metrics for sampled queries against * the collection with the given routing info. @@ -98,20 +103,21 @@ protected: } /** - * The helper for 'addQuery'. Increments the metrics for the query with the given filter(s), - * collation, run-time contants and let parameters. The secondary filter is only applicable to - * non-upsert replacement updates, and the run-time constants and let parameters are only - * applicable to writes. - * - * If the query filters by shard key equality, returns the shard key value. + * The helper used by 'addQuery' to get the targeting info for a query with the given filter, + * collation, let parameters and runtime contants. */ - BSONObj _incrementMetricsForQuery( + QueryTargetingInfo _getTargetingInfoForQuery( OperationContext* opCtx, - const BSONObj& primaryfilter, + const BSONObj& filter, const BSONObj& collation, - const BSONObj& secondaryFilter = BSONObj(), - const boost::optional<LegacyRuntimeConstants>& runtimeConstants = boost::none, - const boost::optional<BSONObj>& letParameters = boost::none); + const boost::optional<BSONObj>& letParameters = boost::none, + const boost::optional<LegacyRuntimeConstants>& runtimeConstants = boost::none); + + /** + * The helper used by 'addQuery' to increment the metrics for a query with the given targeting + * info. + */ + void _incrementMetricsForQuery(const QueryTargetingInfo& info); const ChunkManager& _getChunkManager() const { return _targeter.getRoutingInfo().cm; @@ -199,13 +205,7 @@ private: _numMultiWritesWithoutShardKey++; } - void _incrementMetricsForQuery(OperationContext* opCtx, - const BSONObj& primaryFilter, - const BSONObj& secondaryFilter, - const BSONObj& collation, - bool isMulti, - const boost::optional<LegacyRuntimeConstants>& runtimeConstants, - const boost::optional<BSONObj>& letParameters); + void _incrementMetricsForQuery(const QueryTargetingInfo& info, bool isMulti); int64_t _numUpdate = 0; int64_t _numDelete = 0; diff --git a/src/mongo/db/s/analyze_shard_key_read_write_distribution_test.cpp b/src/mongo/db/s/analyze_shard_key_read_write_distribution_test.cpp index 8af7634343e..276b5991868 100644 --- a/src/mongo/db/s/analyze_shard_key_read_write_distribution_test.cpp +++ b/src/mongo/db/s/analyze_shard_key_read_write_distribution_test.cpp @@ -159,9 +159,11 @@ protected: } SampledQueryDocument makeSampledUpdateQueryDocument( - const std::vector<write_ops::UpdateOpEntry>& updateOps) const { + const std::vector<write_ops::UpdateOpEntry>& updateOps, + const boost::optional<BSONObj>& letParameters = boost::none) const { write_ops::UpdateCommandRequest cmd(nss); cmd.setUpdates(updateOps); + cmd.setLet(letParameters); return {UUID::gen(), nss, collUuid, @@ -173,9 +175,11 @@ protected: } SampledQueryDocument makeSampledDeleteQueryDocument( - const std::vector<write_ops::DeleteOpEntry>& deleteOps) const { + const std::vector<write_ops::DeleteOpEntry>& deleteOps, + const boost::optional<BSONObj>& letParameters = boost::none) const { write_ops::DeleteCommandRequest cmd(nss); cmd.setDeletes(deleteOps); + cmd.setLet(letParameters); return {UUID::gen(), nss, collUuid, @@ -191,13 +195,15 @@ protected: const write_ops::UpdateModification& update, bool upsert, bool remove, - const BSONObj& collation = BSONObj()) const { + const BSONObj& collation = BSONObj(), + const boost::optional<BSONObj>& letParameters = boost::none) const { write_ops::FindAndModifyCommandRequest cmd(nss); cmd.setQuery(filter); cmd.setUpdate(update); cmd.setUpsert(upsert); cmd.setRemove(remove); cmd.setCollation(collation); + cmd.setLet(letParameters); return {UUID::gen(), nss, collUuid, @@ -821,6 +827,21 @@ TEST_F(ReadDistributionFilterByShardKeyEqualityTest, ShardKeyEqualityHashed) { hasCollatableType); } +TEST_F(ReadDistributionFilterByShardKeyEqualityTest, ShardKeyEqualityExpression) { + auto targeter = makeCollectionRoutingInfoTargeter(chunkSplitInfoRangeSharding0); + auto filter = BSON("$expr" << BSON("$and" << BSON_ARRAY(BSON("$eq" << BSON_ARRAY("$a.x" << 100)) + << BSON("$eq" << BSON_ARRAY("$b.y" + << "A"))))); + auto numByRange = std::vector<int64_t>({0, 0, 1}); + auto hasSimpleCollation = true; + auto hasCollatableType = true; + assertTargetMetrics(targeter, + makeSampledReadQueryDocument(getRandomSampledReadCommandName(), filter), + numByRange, + hasSimpleCollation, + hasCollatableType); +} + class ReadDistributionFilterByShardKeyRangeTest : public ReadWriteDistributionTest { protected: void assertTargetMetrics(const CollectionRoutingInfoTargeter& targeter, @@ -972,12 +993,18 @@ protected: const SampledQueryDocument& queryDoc, const std::vector<int64_t>& numByRange, bool hasSimpleCollation, - bool hasCollatableType) const { + bool hasCollatableType, + bool multi = false) const { WriteMetrics metrics; if (hasSimpleCollation || !hasCollatableType) { metrics.numSingleShard = 1; } else { metrics.numMultiShard = 1; + if (multi) { + metrics.numMultiWritesWithoutShardKey = 1; + } else { + metrics.numSingleWritesWithoutShardKey = 1; + } } metrics.numByRange = numByRange; assertMetricsForWriteQuery(targeter, queryDoc, metrics); @@ -989,32 +1016,38 @@ protected: SampledQueryDocument makeSampledUpdateQueryDocument( const BSONObj& filter, const BSONObj& updateMod, - const BSONObj& collation = BSONObj()) const { + const BSONObj& collation = BSONObj(), + bool multi = false, + const boost::optional<BSONObj>& letParameters = boost::none) const { auto updateOp = write_ops::UpdateOpEntry(filter, write_ops::UpdateModification(updateMod)); - updateOp.setMulti(getRandomBool()); + updateOp.setMulti(multi); updateOp.setUpsert(getRandomBool()); updateOp.setCollation(collation); - return ReadWriteDistributionTest::makeSampledUpdateQueryDocument({updateOp}); + return ReadWriteDistributionTest::makeSampledUpdateQueryDocument({updateOp}, letParameters); } SampledQueryDocument makeSampledDeleteQueryDocument( - const BSONObj& filter, const BSONObj& collation = BSONObj()) const { - auto deleteOp = write_ops::DeleteOpEntry(filter, getRandomBool() /* multi */); + const BSONObj& filter, + const BSONObj& collation = BSONObj(), + bool multi = false, + const boost::optional<BSONObj>& letParameters = boost::none) const { + auto deleteOp = write_ops::DeleteOpEntry(filter, multi); deleteOp.setCollation(collation); - return ReadWriteDistributionTest::makeSampledDeleteQueryDocument({deleteOp}); + return ReadWriteDistributionTest::makeSampledDeleteQueryDocument({deleteOp}, letParameters); } SampledQueryDocument makeSampledFindAndModifyQueryDocument( const BSONObj& filter, const BSONObj& updateMod, - const BSONObj& collation = BSONObj()) const { - + const BSONObj& collation = BSONObj(), + const boost::optional<BSONObj>& letParameters = boost::none) const { return ReadWriteDistributionTest::makeSampledFindAndModifyQueryDocument( filter, updateMod, getRandomBool() /* upsert */, getRandomBool() /* remove */, - collation); + collation, + letParameters); } }; @@ -1116,29 +1149,35 @@ TEST_F(WriteDistributionFilterByShardKeyEqualityTest, const BSONObj& filter, const BSONObj& updateMod, const std::vector<int64_t>& numByRange) { - // The collection has a non-simple default collation and the query specifies an empty - // collation. - auto targeter0 = makeCollectionRoutingInfoTargeter( - chunkSplitInfo, - uassertStatusOK(CollatorFactoryInterface::get(getServiceContext()) - ->makeFromBSON(caseInsensitiveCollation))); - assertTargetMetrics(targeter0, - makeSampledUpdateQueryDocument(filter, updateMod, emptyCollation), - numByRange, - hasSimpleCollation, - hasCollatableType); - - // The collection has a simple default collation and the query specifies a non-simple - // collation. - auto targeter1 = makeCollectionRoutingInfoTargeter( - chunkSplitInfo, - uassertStatusOK( - CollatorFactoryInterface::get(getServiceContext())->makeFromBSON(simpleCollation))); - assertTargetMetrics(targeter1, - makeSampledDeleteQueryDocument(filter, caseInsensitiveCollation), - numByRange, - hasSimpleCollation, - hasCollatableType); + for (auto& multi : {true, false}) { + // The collection has a non-simple default collation and the query specifies an empty + // collation. + auto targeter0 = makeCollectionRoutingInfoTargeter( + chunkSplitInfo, + uassertStatusOK(CollatorFactoryInterface::get(getServiceContext()) + ->makeFromBSON(caseInsensitiveCollation))); + assertTargetMetrics( + targeter0, + makeSampledUpdateQueryDocument(filter, updateMod, emptyCollation, multi), + numByRange, + hasSimpleCollation, + hasCollatableType, + multi); + + // The collection has a simple default collation and the query specifies a non-simple + // collation. + auto targeter1 = makeCollectionRoutingInfoTargeter( + chunkSplitInfo, + uassertStatusOK(CollatorFactoryInterface::get(getServiceContext()) + ->makeFromBSON(simpleCollation))); + assertTargetMetrics( + targeter1, + makeSampledDeleteQueryDocument(filter, caseInsensitiveCollation, multi), + numByRange, + hasSimpleCollation, + hasCollatableType, + multi); + } // The collection doesn't have a default collation and the query specifies a non-simple // collation. @@ -1348,6 +1387,43 @@ TEST_F(WriteDistributionFilterByShardKeyEqualityTest, ShardKeyEqualitySuffixFiel hasCollatableType); } +TEST_F(WriteDistributionFilterByShardKeyEqualityTest, ShardKeyEqualityExpressionWithLetParameters) { + auto targeter = makeCollectionRoutingInfoTargeter(chunkSplitInfoRangeSharding0); + auto filter = BSON("$expr" << BSON("$and" << BSON_ARRAY(BSON("$eq" << BSON_ARRAY("$a.x" + << "$$value")) + << BSON("$eq" << BSON_ARRAY("$b.y" + << "A"))))); + auto updateMod = BSON("$set" << BSON("c" << 100)); + auto collation = BSONObj(); + auto letParameters = BSON("value" << 100); + + auto numByRange = std::vector<int64_t>({0, 0, 1}); + auto hasSimpleCollation = true; + auto hasCollatableType = true; + + for (auto& multi : {true, false}) { + assertTargetMetrics( + targeter, + makeSampledUpdateQueryDocument(filter, updateMod, collation, multi, letParameters), + numByRange, + hasSimpleCollation, + hasCollatableType, + multi); + assertTargetMetrics(targeter, + makeSampledDeleteQueryDocument(filter, collation, multi, letParameters), + numByRange, + hasSimpleCollation, + hasCollatableType, + multi); + } + assertTargetMetrics( + targeter, + makeSampledFindAndModifyQueryDocument(filter, updateMod, collation, letParameters), + numByRange, + hasSimpleCollation, + hasCollatableType); +} + class WriteDistributionFilterByShardKeyRangeTest : public ReadWriteDistributionTest { protected: void assertTargetMetrics(const CollectionRoutingInfoTargeter& targeter, diff --git a/src/mongo/s/chunk_manager_query_test.cpp b/src/mongo/s/chunk_manager_query_test.cpp index bc545d68557..9b47635c0b6 100644 --- a/src/mongo/s/chunk_manager_query_test.cpp +++ b/src/mongo/s/chunk_manager_query_test.cpp @@ -43,6 +43,8 @@ namespace mongo { namespace { +using shard_key_pattern_query_util::QueryTargetingInfo; + const NamespaceString kNss = NamespaceString::createNamespaceString_forTest("TestDB", "TestColl"); class ChunkManagerQueryTest : public CatalogCacheTestFixture { @@ -70,7 +72,7 @@ protected: const BSONObj& query, const BSONObj& queryCollation, const std::set<ShardId>& expectedShardIds, - bool expectTargetMinKeyToMaxKey) { + QueryTargetingInfo expectedQueryTargetingInfo) { const ShardKeyPattern shardKeyPattern(shardKey); auto chunkManager = makeCollectionRoutingInfo( @@ -78,8 +80,7 @@ protected: .cm; std::set<ShardId> shardIds; - std::set<ChunkRange> chunkRanges; - bool targetMinKeyToMaxKey = false; + QueryTargetingInfo info; auto&& cif = [&]() { if (queryCollation.isEmpty()) { @@ -91,15 +92,10 @@ protected: }(); auto expCtx = make_intrusive<ExpressionContextForTest>(operationContext(), kNss, std::move(cif)); - getShardIdsForQuery(expCtx, - query, - queryCollation, - chunkManager, - &shardIds, - &chunkRanges, - &targetMinKeyToMaxKey); + getShardIdsForQuery(expCtx, query, queryCollation, chunkManager, &shardIds, &info); _assertShardIdsMatch(expectedShardIds, shardIds); - ASSERT_EQ(expectTargetMinKeyToMaxKey, targetMinKeyToMaxKey); + // The test coverage for chunk ranges is in CollectionRoutingInfoTargeterTest. + ASSERT_EQ(expectedQueryTargetingInfo.desc, info.desc); } private: @@ -147,7 +143,6 @@ TEST_F(ChunkManagerQueryTest, GetShardIdsForRangeMinAndMaxAreTheSameAtLastChunkM } TEST_F(ChunkManagerQueryTest, EmptyQuerySingleShard) { - bool expectTargetMinKeyToMaxKey = true; runQueryTest(BSON("a" << 1), nullptr, false, @@ -155,11 +150,10 @@ TEST_F(ChunkManagerQueryTest, EmptyQuerySingleShard) { BSONObj(), BSONObj(), {ShardId("0")}, - expectTargetMinKeyToMaxKey); + {QueryTargetingInfo::Description::kMinKeyToMaxKey, {}}); } TEST_F(ChunkManagerQueryTest, EmptyQueryMultiShard) { - bool expectTargetMinKeyToMaxKey = true; runQueryTest(BSON("a" << 1), nullptr, false, @@ -172,11 +166,10 @@ TEST_F(ChunkManagerQueryTest, EmptyQueryMultiShard) { BSONObj(), BSONObj(), {ShardId("0"), ShardId("1"), ShardId("2"), ShardId("3")}, - expectTargetMinKeyToMaxKey); + {QueryTargetingInfo::Description::kMinKeyToMaxKey, {}}); } TEST_F(ChunkManagerQueryTest, UniversalRangeMultiShard) { - bool expectTargetMinKeyToMaxKey = true; runQueryTest(BSON("a" << 1), nullptr, false, @@ -189,11 +182,10 @@ TEST_F(ChunkManagerQueryTest, UniversalRangeMultiShard) { BSON("b" << 1), BSONObj(), {ShardId("0"), ShardId("1"), ShardId("2"), ShardId("3")}, - expectTargetMinKeyToMaxKey); + {QueryTargetingInfo::Description::kMinKeyToMaxKey, {}}); } TEST_F(ChunkManagerQueryTest, EqualityRangeSingleShard) { - bool expectTargetMinKeyToMaxKey = false; runQueryTest(BSON("a" << 1), nullptr, false, @@ -202,11 +194,10 @@ TEST_F(ChunkManagerQueryTest, EqualityRangeSingleShard) { << "x"), BSONObj(), {ShardId("0")}, - expectTargetMinKeyToMaxKey); + {QueryTargetingInfo::Description::kSingleKey, {}}); } TEST_F(ChunkManagerQueryTest, EqualityRangeMultiShard) { - bool expectTargetMinKeyToMaxKey = false; runQueryTest(BSON("a" << 1), nullptr, false, @@ -220,11 +211,10 @@ TEST_F(ChunkManagerQueryTest, EqualityRangeMultiShard) { << "y"), BSONObj(), {ShardId("2")}, - expectTargetMinKeyToMaxKey); + {QueryTargetingInfo::Description::kSingleKey, {}}); } TEST_F(ChunkManagerQueryTest, SetRangeMultiShard) { - bool expectTargetMinKeyToMaxKey = false; runQueryTest(BSON("a" << 1), nullptr, false, @@ -237,11 +227,10 @@ TEST_F(ChunkManagerQueryTest, SetRangeMultiShard) { fromjson("{a:{$in:['u','y']}}"), BSONObj(), {ShardId("0"), ShardId("2")}, - expectTargetMinKeyToMaxKey); + {QueryTargetingInfo::Description::kMultipleKeys, {}}); } TEST_F(ChunkManagerQueryTest, GTRangeMultiShard) { - bool expectTargetMinKeyToMaxKey = false; runQueryTest(BSON("a" << 1), nullptr, false, @@ -254,11 +243,10 @@ TEST_F(ChunkManagerQueryTest, GTRangeMultiShard) { BSON("a" << GT << "x"), BSONObj(), {ShardId("1"), ShardId("2"), ShardId("3")}, - expectTargetMinKeyToMaxKey); + {QueryTargetingInfo::Description::kMultipleKeys, {}}); } TEST_F(ChunkManagerQueryTest, GTERangeMultiShard) { - bool expectTargetMinKeyToMaxKey = false; runQueryTest(BSON("a" << 1), nullptr, false, @@ -271,13 +259,12 @@ TEST_F(ChunkManagerQueryTest, GTERangeMultiShard) { BSON("a" << GTE << "x"), BSONObj(), {ShardId("1"), ShardId("2"), ShardId("3")}, - expectTargetMinKeyToMaxKey); + {QueryTargetingInfo::Description::kMultipleKeys, {}}); } TEST_F(ChunkManagerQueryTest, LTRangeMultiShard) { // NOTE (SERVER-4791): It isn't actually necessary to return shard 2 because its lowest key is // "y", which is excluded from the query - bool expectTargetMinKeyToMaxKey = false; runQueryTest(BSON("a" << 1), nullptr, false, @@ -290,11 +277,10 @@ TEST_F(ChunkManagerQueryTest, LTRangeMultiShard) { BSON("a" << LT << "y"), BSONObj(), {ShardId("0"), ShardId("1"), ShardId("2")}, - expectTargetMinKeyToMaxKey); + {QueryTargetingInfo::Description::kMultipleKeys, {}}); } TEST_F(ChunkManagerQueryTest, LTERangeMultiShard) { - bool expectTargetMinKeyToMaxKey = false; runQueryTest(BSON("a" << 1), nullptr, false, @@ -307,11 +293,10 @@ TEST_F(ChunkManagerQueryTest, LTERangeMultiShard) { BSON("a" << LTE << "y"), BSONObj(), {ShardId("0"), ShardId("1"), ShardId("2")}, - expectTargetMinKeyToMaxKey); + {QueryTargetingInfo::Description::kMultipleKeys, {}}); } TEST_F(ChunkManagerQueryTest, OrEqualities) { - bool expectTargetMinKeyToMaxKey = false; runQueryTest(BSON("a" << 1), nullptr, false, @@ -324,11 +309,10 @@ TEST_F(ChunkManagerQueryTest, OrEqualities) { fromjson("{$or:[{a:'u'},{a:'y'}]}"), BSONObj(), {ShardId("0"), ShardId("2")}, - expectTargetMinKeyToMaxKey); + {QueryTargetingInfo::Description::kMultipleKeys, {}}); } TEST_F(ChunkManagerQueryTest, OrEqualityInequality) { - bool expectTargetMinKeyToMaxKey = false; runQueryTest(BSON("a" << 1), nullptr, false, @@ -341,11 +325,10 @@ TEST_F(ChunkManagerQueryTest, OrEqualityInequality) { fromjson("{$or:[{a:'u'},{a:{$gte:'y'}}]}"), BSONObj(), {ShardId("0"), ShardId("2"), ShardId("3")}, - expectTargetMinKeyToMaxKey); + {QueryTargetingInfo::Description::kMultipleKeys, {}}); } TEST_F(ChunkManagerQueryTest, OrEqualityInequalityUnhelpful) { - bool expectTargetMinKeyToMaxKey = true; runQueryTest(BSON("a" << 1), nullptr, false, @@ -358,11 +341,10 @@ TEST_F(ChunkManagerQueryTest, OrEqualityInequalityUnhelpful) { fromjson("{$or:[{a:'u'},{a:{$gte:'zz'}},{}]}"), BSONObj(), {ShardId("0"), ShardId("1"), ShardId("2"), ShardId("3")}, - expectTargetMinKeyToMaxKey); + {QueryTargetingInfo::Description::kMinKeyToMaxKey, {}}); } TEST_F(ChunkManagerQueryTest, UnsatisfiableRangeSingleShard) { - bool expectTargetMinKeyToMaxKey = false; runQueryTest(BSON("a" << 1), nullptr, false, @@ -370,11 +352,10 @@ TEST_F(ChunkManagerQueryTest, UnsatisfiableRangeSingleShard) { BSON("a" << GT << "x" << LT << "x"), BSONObj(), {ShardId("0")}, - expectTargetMinKeyToMaxKey); + {QueryTargetingInfo::Description::kMultipleKeys, {}}); } TEST_F(ChunkManagerQueryTest, UnsatisfiableRangeMultiShard) { - bool expectTargetMinKeyToMaxKey = false; runQueryTest(BSON("a" << 1), nullptr, false, @@ -387,11 +368,10 @@ TEST_F(ChunkManagerQueryTest, UnsatisfiableRangeMultiShard) { BSON("a" << GT << "x" << LT << "x"), BSONObj(), {ShardId("0")}, - expectTargetMinKeyToMaxKey); + {QueryTargetingInfo::Description::kMultipleKeys, {}}); } TEST_F(ChunkManagerQueryTest, EqualityThenUnsatisfiable) { - bool expectTargetMinKeyToMaxKey = false; runQueryTest(BSON("a" << 1 << "b" << 1), nullptr, false, @@ -404,11 +384,10 @@ TEST_F(ChunkManagerQueryTest, EqualityThenUnsatisfiable) { BSON("a" << 1 << "b" << GT << 4 << LT << 4), BSONObj(), {ShardId("0")}, - expectTargetMinKeyToMaxKey); + {QueryTargetingInfo::Description::kMultipleKeys, {}}); } TEST_F(ChunkManagerQueryTest, InequalityThenUnsatisfiable) { - bool expectTargetMinKeyToMaxKey = false; runQueryTest(BSON("a" << 1 << "b" << 1), nullptr, false, @@ -421,11 +400,10 @@ TEST_F(ChunkManagerQueryTest, InequalityThenUnsatisfiable) { BSON("a" << GT << 1 << "b" << GT << 4 << LT << 4), BSONObj(), {ShardId("0")}, - expectTargetMinKeyToMaxKey); + {QueryTargetingInfo::Description::kMultipleKeys, {}}); } TEST_F(ChunkManagerQueryTest, OrEqualityUnsatisfiableInequality) { - bool expectTargetMinKeyToMaxKey = false; runQueryTest(BSON("a" << 1), nullptr, false, @@ -438,11 +416,10 @@ TEST_F(ChunkManagerQueryTest, OrEqualityUnsatisfiableInequality) { fromjson("{$or:[{a:'x'},{a:{$gt:'u',$lt:'u'}},{a:{$gte:'y'}}]}"), BSONObj(), {ShardId("1"), ShardId("2"), ShardId("3")}, - expectTargetMinKeyToMaxKey); + {QueryTargetingInfo::Description::kMultipleKeys, {}}); } TEST_F(ChunkManagerQueryTest, InMultiShard) { - bool expectTargetMinKeyToMaxKey = false; runQueryTest(BSON("a" << 1 << "b" << 1), nullptr, false, @@ -451,11 +428,10 @@ TEST_F(ChunkManagerQueryTest, InMultiShard) { << BSON("$in" << BSON_ARRAY(0 << 5 << 25))), BSONObj(), {ShardId("0"), ShardId("1"), ShardId("2")}, - expectTargetMinKeyToMaxKey); + {QueryTargetingInfo::Description::kMultipleKeys, {}}); } TEST_F(ChunkManagerQueryTest, CollationStringsMultiShard) { - bool expectTargetMinKeyToMaxKey = true; runQueryTest(BSON("a" << 1), nullptr, false, @@ -470,11 +446,10 @@ TEST_F(ChunkManagerQueryTest, CollationStringsMultiShard) { BSON("locale" << "mock_reverse_string"), {ShardId("0"), ShardId("1"), ShardId("2"), ShardId("3")}, - expectTargetMinKeyToMaxKey); + {QueryTargetingInfo::Description::kMinKeyToMaxKey, {}}); } TEST_F(ChunkManagerQueryTest, DefaultCollationStringsMultiShard) { - bool expectTargetMinKeyToMaxKey = true; runQueryTest( BSON("a" << 1), std::make_unique<CollatorInterfaceMock>(CollatorInterfaceMock::MockType::kReverseString), @@ -490,11 +465,10 @@ TEST_F(ChunkManagerQueryTest, DefaultCollationStringsMultiShard) { BSON("locale" << "mock_reverse_string"), {ShardId("0"), ShardId("1"), ShardId("2"), ShardId("3")}, - expectTargetMinKeyToMaxKey); + {QueryTargetingInfo::Description::kMinKeyToMaxKey, {}}); } TEST_F(ChunkManagerQueryTest, SimpleCollationStringsMultiShard) { - bool expectTargetMinKeyToMaxKey = false; runQueryTest( BSON("a" << 1), std::make_unique<CollatorInterfaceMock>(CollatorInterfaceMock::MockType::kReverseString), @@ -510,11 +484,10 @@ TEST_F(ChunkManagerQueryTest, SimpleCollationStringsMultiShard) { BSON("locale" << "simple"), {ShardId("2")}, - expectTargetMinKeyToMaxKey); + {QueryTargetingInfo::Description::kSingleKey, {}}); } TEST_F(ChunkManagerQueryTest, CollationNumbersMultiShard) { - bool expectTargetMinKeyToMaxKey = false; runQueryTest( BSON("a" << 1), std::make_unique<CollatorInterfaceMock>(CollatorInterfaceMock::MockType::kReverseString), @@ -529,11 +502,10 @@ TEST_F(ChunkManagerQueryTest, CollationNumbersMultiShard) { BSON("locale" << "mock_reverse_string"), {ShardId("0")}, - expectTargetMinKeyToMaxKey); + {QueryTargetingInfo::Description::kSingleKey, {}}); } TEST_F(ChunkManagerQueryTest, DefaultCollationNumbersMultiShard) { - bool expectTargetMinKeyToMaxKey = false; runQueryTest( BSON("a" << 1), std::make_unique<CollatorInterfaceMock>(CollatorInterfaceMock::MockType::kReverseString), @@ -547,11 +519,10 @@ TEST_F(ChunkManagerQueryTest, DefaultCollationNumbersMultiShard) { BSON("a" << 5), BSONObj(), {ShardId("0")}, - expectTargetMinKeyToMaxKey); + {QueryTargetingInfo::Description::kSingleKey, {}}); } TEST_F(ChunkManagerQueryTest, SimpleCollationNumbersMultiShard) { - bool expectTargetMinKeyToMaxKey = false; runQueryTest( BSON("a" << 1), std::make_unique<CollatorInterfaceMock>(CollatorInterfaceMock::MockType::kReverseString), @@ -566,7 +537,7 @@ TEST_F(ChunkManagerQueryTest, SimpleCollationNumbersMultiShard) { BSON("locale" << "simple"), {ShardId("0")}, - expectTargetMinKeyToMaxKey); + {QueryTargetingInfo::Description::kSingleKey, {}}); } TEST_F(ChunkManagerQueryTest, SnapshotQueryWithMoreShardsThanLatestMetadata) { @@ -615,13 +586,8 @@ TEST_F(ChunkManagerQueryTest, SnapshotQueryWithMoreShardsThanLatestMetadata) { const auto expCtx = make_intrusive<ExpressionContextForTest>(); shardIds.clear(); - getShardIdsForQuery(expCtx, - BSON("x" << BSON("$gt" << -20)), - {}, - chunkManager, - &shardIds, - nullptr /* chunkRanges */, - nullptr /* targetMinKeyToMaxKey */); + getShardIdsForQuery( + expCtx, BSON("x" << BSON("$gt" << -20)), {}, chunkManager, &shardIds, nullptr /* info */); ASSERT_EQ(2, shardIds.size()); } diff --git a/src/mongo/s/cluster_commands_helpers.cpp b/src/mongo/s/cluster_commands_helpers.cpp index 11c2e3b68d5..345c4eae3f7 100644 --- a/src/mongo/s/cluster_commands_helpers.cpp +++ b/src/mongo/s/cluster_commands_helpers.cpp @@ -181,13 +181,7 @@ std::vector<AsyncRequestsSender::Request> buildVersionedRequestsForTargetedShard } auto expCtx = make_intrusive<ExpressionContext>(opCtx, std::move(collator), nss); - getShardIdsForQuery(expCtx, - query, - collation, - cm, - &shardIds, - nullptr /* chunkRanges */, - nullptr /* targetMinKeyToMaxKey */); + getShardIdsForQuery(expCtx, query, collation, cm, &shardIds, nullptr /* info */); const auto targetedSampleId = eligibleForSampling ? analyze_shard_key::tryGenerateTargetedSampleId( @@ -662,13 +656,7 @@ std::set<ShardId> getTargetedShardsForQuery(boost::intrusive_ptr<ExpressionConte // The collection is sharded. Use the routing table to decide which shards to target based // on the query and collation. std::set<ShardId> shardIds; - getShardIdsForQuery(expCtx, - query, - collation, - cm, - &shardIds, - nullptr /* chunkRanges */, - nullptr /* targetMinKeyToMaxKey */); + getShardIdsForQuery(expCtx, query, collation, cm, &shardIds, nullptr /* info */); return shardIds; } diff --git a/src/mongo/s/collection_routing_info_targeter.cpp b/src/mongo/s/collection_routing_info_targeter.cpp index 4204e355da8..af6713a6e5e 100644 --- a/src/mongo/s/collection_routing_info_targeter.cpp +++ b/src/mongo/s/collection_routing_info_targeter.cpp @@ -68,6 +68,7 @@ constexpr auto kIdFieldName = "_id"_sd; const ShardKeyPattern kVirtualIdShardKey(BSON(kIdFieldName << 1)); using UpdateType = write_ops::UpdateModification::Type; +using shard_key_pattern_query_util::QueryTargetingInfo; // Tracks the number of {multi:false} updates with an exact match on _id that are broadcasted to // multiple shards. @@ -667,8 +668,12 @@ StatusWith<std::vector<ShardEndpoint>> CollectionRoutingInfoTargeter::_targetQue } std::set<ShardId> shardIds; + QueryTargetingInfo info; try { - getShardIdsForQuery(expCtx, query, collation, _cri.cm, &shardIds, chunkRanges); + getShardIdsForQuery(expCtx, query, collation, _cri.cm, &shardIds, &info); + if (chunkRanges) { + chunkRanges->swap(info.chunkRanges); + } } catch (const DBException& ex) { return ex.toStatus(); } diff --git a/src/mongo/s/commands/cluster_query_without_shard_key_cmd.cpp b/src/mongo/s/commands/cluster_query_without_shard_key_cmd.cpp index b4af18f9f74..65bdc43f1d3 100644 --- a/src/mongo/s/commands/cluster_query_without_shard_key_cmd.cpp +++ b/src/mongo/s/commands/cluster_query_without_shard_key_cmd.cpp @@ -89,13 +89,8 @@ std::set<ShardId> getShardsToTarget(OperationContext* opCtx, CollatorFactoryInterface::get(opCtx->getServiceContext())->makeFromBSON(collation)); } auto expCtx = make_intrusive<ExpressionContext>(opCtx, std::move(collator), nss); - getShardIdsForQuery(expCtx, - query, - collation, - cm, - &allShardsContainingChunksForNs, - nullptr /* chunkRanges */, - nullptr /* targetMinKeyToMaxKey */); + getShardIdsForQuery( + expCtx, query, collation, cm, &allShardsContainingChunksForNs, nullptr /* info */); // We must either get a subset of shards to target in the case of a partial shard key or we must // target all shards. diff --git a/src/mongo/s/shard_key_pattern_query_util.cpp b/src/mongo/s/shard_key_pattern_query_util.cpp index 227a53bc4cc..37d27d23e10 100644 --- a/src/mongo/s/shard_key_pattern_query_util.cpp +++ b/src/mongo/s/shard_key_pattern_query_util.cpp @@ -44,6 +44,7 @@ namespace mongo { namespace { using pathsupport::EqualityMatches; +using shard_key_pattern_query_util::QueryTargetingInfo; // Maximum number of intervals produced by $in queries constexpr size_t kMaxFlattenedInCombinations = 4000000; @@ -404,10 +405,9 @@ void getShardIdsForQuery(boost::intrusive_ptr<ExpressionContext> expCtx, const BSONObj& collation, const ChunkManager& cm, std::set<ShardId>* shardIds, - std::set<ChunkRange>* chunkRanges, - bool* targetMinKeyToMaxKey) { - if (chunkRanges) { - invariant(chunkRanges->empty()); + QueryTargetingInfo* info) { + if (info) { + invariant(info->chunkRanges.empty()); } auto findCommand = std::make_unique<FindCommandRequest>(cm.getNss()); @@ -437,11 +437,9 @@ void getShardIdsForQuery(boost::intrusive_ptr<ExpressionContext> expCtx, try { auto chunk = cm.findIntersectingChunk(shardKeyToFind, collation); shardIds->insert(chunk.getShardId()); - if (chunkRanges) { - chunkRanges->insert(chunk.getRange()); - } - if (targetMinKeyToMaxKey) { - *targetMinKeyToMaxKey = false; + if (info) { + info->desc = QueryTargetingInfo::Description::kSingleKey; + info->chunkRanges.insert(chunk.getRange()); } return; } catch (const DBException&) { @@ -468,12 +466,7 @@ void getShardIdsForQuery(boost::intrusive_ptr<ExpressionContext> expCtx, const auto& min = it->first; const auto& max = it->second; - cm.getShardIdsForRange(min, max, shardIds, chunkRanges); - - if (targetMinKeyToMaxKey && ChunkMap::allElementsAreOfType(MinKey, min) && - ChunkMap::allElementsAreOfType(MaxKey, max)) { - *targetMinKeyToMaxKey = true; - } + cm.getShardIdsForRange(min, max, shardIds, info ? &info->chunkRanges : nullptr); // Once we know we need to visit all shards no need to keep looping. // However, this optimization does not apply when we are reading from a snapshot @@ -491,15 +484,29 @@ void getShardIdsForQuery(boost::intrusive_ptr<ExpressionContext> expCtx, if (shardIds->empty()) { cm.forEachChunk([&](const Chunk& chunk) { shardIds->insert(chunk.getShardId()); - if (chunkRanges) { - chunkRanges->insert(chunk.getRange()); - } - if (targetMinKeyToMaxKey) { - *targetMinKeyToMaxKey = false; + if (info) { + info->chunkRanges.insert(chunk.getRange()); } return false; }); } + + if (info) { + info->desc = [&] { + if (ranges.size() == 1) { + auto min = ranges.begin()->first; + auto max = ranges.begin()->second; + if (SimpleBSONObjComparator::kInstance.evaluate(min == max)) { + return QueryTargetingInfo::Description::kSingleKey; + } + if (ChunkMap::allElementsAreOfType(MinKey, min) && + ChunkMap::allElementsAreOfType(MaxKey, max)) { + return QueryTargetingInfo::Description::kMinKeyToMaxKey; + } + } + return QueryTargetingInfo::Description::kMultipleKeys; + }(); + } } } // namespace mongo diff --git a/src/mongo/s/shard_key_pattern_query_util.h b/src/mongo/s/shard_key_pattern_query_util.h index 07c3757370c..5696f444e20 100644 --- a/src/mongo/s/shard_key_pattern_query_util.h +++ b/src/mongo/s/shard_key_pattern_query_util.h @@ -108,20 +108,29 @@ BoundList flattenBounds(const ShardKeyPattern& shardKeyPattern, const IndexBound */ IndexBounds getIndexBoundsForQuery(const BSONObj& key, const CanonicalQuery& canonicalQuery); +namespace shard_key_pattern_query_util { + +struct QueryTargetingInfo { + enum Description { kSingleKey, kMultipleKeys, kMinKeyToMaxKey }; + + Description desc; + std::set<ChunkRange> chunkRanges; +}; + +} // namespace shard_key_pattern_query_util + /** - * Finds the shard IDs for a given filter and collation. If collation is empty, we use the - * collection default collation for targeting. - * - * If 'chunkRanges' is not null, populates it with ChunkRanges that would be targeted by the query. - * If 'targetMinKeyToMaxKey' is not null, sets it to true if the query targets the entire shard key - * space. + * Populates 'shardIds' with the shard ids for a query with given filter and collation. If the + * collation is empty, it uses the collection default collation for targeting. If 'info' is not + * null, populates it with the ChunkRanges that the query targets and a description about whether + * the query targets a single shard key value, multiple but not all shard key values or all shard + * key values. */ void getShardIdsForQuery(boost::intrusive_ptr<ExpressionContext> expCtx, const BSONObj& query, const BSONObj& collation, const ChunkManager& cm, std::set<ShardId>* shardIds, - std::set<ChunkRange>* chunkRanges = nullptr, - bool* targetMinKeyToMaxKey = nullptr); + shard_key_pattern_query_util::QueryTargetingInfo* info = nullptr); } // namespace mongo |