diff options
author | Bernard Gorman <bernard.gorman@gmail.com> | 2018-10-15 14:13:53 -0400 |
---|---|---|
committer | Bernard Gorman <bernard.gorman@gmail.com> | 2018-10-23 00:19:01 +0100 |
commit | 1e19472175d9f8c26d2cc1a80e108a0a4a761213 (patch) | |
tree | ff798bd9801eb65585ee8096a86f0461373f1833 /src/mongo/db | |
parent | 10ec78a0ea0adca815df5a5cb9f3bf9f7d2221f6 (diff) | |
download | mongo-1e19472175d9f8c26d2cc1a80e108a0a4a761213.tar.gz |
SERVER-37566 Avoid recreating ProjectionExecAgg on each expansion of a wildcard index
Diffstat (limited to 'src/mongo/db')
-rw-r--r-- | src/mongo/db/catalog/collection_info_cache_impl.cpp | 6 | ||||
-rw-r--r-- | src/mongo/db/index/wildcard_access_method.h | 7 | ||||
-rw-r--r-- | src/mongo/db/index/wildcard_key_generator.h | 7 | ||||
-rw-r--r-- | src/mongo/db/query/get_executor.cpp | 12 | ||||
-rw-r--r-- | src/mongo/db/query/index_entry.h | 19 | ||||
-rw-r--r-- | src/mongo/db/query/plan_cache_indexability.cpp | 6 | ||||
-rw-r--r-- | src/mongo/db/query/plan_cache_indexability.h | 14 | ||||
-rw-r--r-- | src/mongo/db/query/plan_cache_indexability_test.cpp | 59 | ||||
-rw-r--r-- | src/mongo/db/query/plan_cache_test.cpp | 54 | ||||
-rw-r--r-- | src/mongo/db/query/planner_ixselect_test.cpp | 106 | ||||
-rw-r--r-- | src/mongo/db/query/planner_wildcard_helpers.cpp | 8 | ||||
-rw-r--r-- | src/mongo/db/query/query_planner_collation_test.cpp | 27 | ||||
-rw-r--r-- | src/mongo/db/query/query_planner_wildcard_index_test.cpp | 170 |
13 files changed, 275 insertions, 220 deletions
diff --git a/src/mongo/db/catalog/collection_info_cache_impl.cpp b/src/mongo/db/catalog/collection_info_cache_impl.cpp index c450398ec68..17dde1830fc 100644 --- a/src/mongo/db/catalog/collection_info_cache_impl.cpp +++ b/src/mongo/db/catalog/collection_info_cache_impl.cpp @@ -40,7 +40,7 @@ #include "mongo/db/concurrency/d_concurrency.h" #include "mongo/db/fts/fts_spec.h" #include "mongo/db/index/index_descriptor.h" -#include "mongo/db/index/wildcard_key_generator.h" +#include "mongo/db/index/wildcard_access_method.h" #include "mongo/db/index_legacy.h" #include "mongo/db/query/get_executor.h" #include "mongo/db/query/plan_cache.h" @@ -94,8 +94,8 @@ void CollectionInfoCacheImpl::computeIndexKeys(OperationContext* opCtx) { if (descriptor->getAccessMethodName() == IndexNames::WILDCARD) { // Obtain the projection used by the $** index's key generator. - auto pathProj = WildcardKeyGenerator::createProjectionExec( - descriptor->keyPattern(), descriptor->pathProjection()); + const auto* pathProj = + static_cast<WildcardAccessMethod*>(i.accessMethod(descriptor))->getProjectionExec(); // If the projection is an exclusion, then we must check the new document's keys on all // updates, since we do not exhaustively know the set of paths to be indexed. if (pathProj->getType() == ProjectionExecAgg::ProjectionType::kExclusionProjection) { diff --git a/src/mongo/db/index/wildcard_access_method.h b/src/mongo/db/index/wildcard_access_method.h index e18fca736a5..fb30ad9a1fb 100644 --- a/src/mongo/db/index/wildcard_access_method.h +++ b/src/mongo/db/index/wildcard_access_method.h @@ -59,6 +59,13 @@ public: const MultikeyPaths& multikeyPaths) const final; /** + * Returns a pointer to the ProjectionExecAgg owned by the underlying WildcardKeyGenerator. + */ + const ProjectionExecAgg* getProjectionExec() const { + return _keyGen.getProjectionExec(); + } + + /** * Returns the set of paths included in this $** index that could be multikey. */ std::set<FieldRef> getMultikeyPathSet(OperationContext*) const final; diff --git a/src/mongo/db/index/wildcard_key_generator.h b/src/mongo/db/index/wildcard_key_generator.h index 106e30ba8a4..7c17b929eaf 100644 --- a/src/mongo/db/index/wildcard_key_generator.h +++ b/src/mongo/db/index/wildcard_key_generator.h @@ -58,6 +58,13 @@ public: const CollatorInterface* collator); /** + * Returns a pointer to the key generator's underlying ProjectionExecAgg. + */ + const ProjectionExecAgg* getProjectionExec() const { + return _projExec.get(); + } + + /** * Applies the appropriate Wildcard projection to the input doc, and then adds one key-value * pair to the BSONObjSet 'keys' for each leaf node in the post-projection document: * { '': 'path.to.field', '': <collation-aware-field-value> } diff --git a/src/mongo/db/query/get_executor.cpp b/src/mongo/db/query/get_executor.cpp index 27a78db4caf..8e74fd72cbc 100644 --- a/src/mongo/db/query/get_executor.cpp +++ b/src/mongo/db/query/get_executor.cpp @@ -134,6 +134,11 @@ IndexEntry indexEntryFromIndexCatalogEntry(OperationContext* opCtx, const IndexC const bool isMultikey = desc->isMultikey(opCtx); + const auto* projExec = + (desc->getIndexType() == IndexType::INDEX_WILDCARD + ? static_cast<const WildcardAccessMethod*>(accessMethod)->getProjectionExec() + : nullptr); + return {desc->keyPattern(), desc->getIndexType(), isMultikey, @@ -149,7 +154,8 @@ IndexEntry indexEntryFromIndexCatalogEntry(OperationContext* opCtx, const IndexC IndexEntry::Identifier{desc->indexName()}, ice.getFilterExpression(), desc->infoObj(), - ice.getCollator()}; + ice.getCollator(), + projExec}; } void fillOutPlannerParams(OperationContext* opCtx, @@ -1473,8 +1479,8 @@ QueryPlannerParams fillOutPlannerParamsForDistinct(OperationContext* opCtx, plannerParams.indices.push_back(indexEntryFromIndexCatalogEntry(opCtx, *ice)); } else if (desc->getIndexType() == IndexType::INDEX_WILDCARD && !query.isEmpty()) { // Check whether the $** projection captures the field over which we are distinct-ing. - const auto proj = WildcardKeyGenerator::createProjectionExec(desc->keyPattern(), - desc->pathProjection()); + const auto* proj = + static_cast<WildcardAccessMethod*>(ii.accessMethod(desc))->getProjectionExec(); if (proj->applyProjectionToOneField(parsedDistinct.getKey())) { plannerParams.indices.push_back(indexEntryFromIndexCatalogEntry(opCtx, *ice)); } diff --git a/src/mongo/db/query/index_entry.h b/src/mongo/db/query/index_entry.h index bbfca41b969..a2c9bd555d1 100644 --- a/src/mongo/db/query/index_entry.h +++ b/src/mongo/db/query/index_entry.h @@ -33,6 +33,7 @@ #include <set> #include <string> +#include "mongo/db/exec/projection_exec_agg.h" #include "mongo/db/field_ref.h" #include "mongo/db/index/multikey_paths.h" #include "mongo/db/index_names.h" @@ -106,7 +107,8 @@ struct IndexEntry { Identifier ident, const MatchExpression* fe, const BSONObj& io, - const CollatorInterface* ci) + const CollatorInterface* ci, + const ProjectionExecAgg* projExec) : keyPattern(kp), multikey(mk), multikeyPaths(mkp), @@ -117,9 +119,12 @@ struct IndexEntry { filterExpr(fe), infoObj(io), type(type), - collator(ci) { + collator(ci), + wildcardProjection(projExec) { // The caller must not supply multikey metadata in two different formats. invariant(multikeyPaths.empty() || multikeyPathSet.empty()); + // We always expect a projection executor for $** indexes, and none otherwise. + invariant((type == IndexType::INDEX_WILDCARD) == (projExec != nullptr)); } /** @@ -131,14 +136,16 @@ struct IndexEntry { bool unq, Identifier ident, const MatchExpression* fe, - const BSONObj& io) + const BSONObj& io, + const ProjectionExecAgg* projExec = nullptr) : keyPattern(kp), multikey(mk), sparse(sp), unique(unq), identifier(std::move(ident)), filterExpr(fe), - infoObj(io) { + infoObj(io), + wildcardProjection(projExec) { type = IndexNames::nameToType(IndexNames::findPluginName(keyPattern)); } @@ -219,6 +226,10 @@ struct IndexEntry { // Null if this index orders strings according to the simple binary compare. If non-null, // represents the collator used to generate index keys for indexed strings. const CollatorInterface* collator = nullptr; + + // For $** indexes, a pointer to the projection executor owned by the index access method. Null + // unless this IndexEntry represents a wildcard index, in which case this is always non-null. + const ProjectionExecAgg* wildcardProjection = nullptr; }; std::ostream& operator<<(std::ostream& stream, const IndexEntry::Identifier& ident); diff --git a/src/mongo/db/query/plan_cache_indexability.cpp b/src/mongo/db/query/plan_cache_indexability.cpp index b1cbafc2b7f..488e40d7cc1 100644 --- a/src/mongo/db/query/plan_cache_indexability.cpp +++ b/src/mongo/db/query/plan_cache_indexability.cpp @@ -118,11 +118,7 @@ void PlanCacheIndexabilityState::processWildcardIndex(const IndexEntry& ie) { invariant(ie.type == IndexType::INDEX_WILDCARD); _wildcardIndexDiscriminators.emplace_back( - WildcardKeyGenerator::createProjectionExec(ie.keyPattern, - ie.infoObj.getObjectField("wildcardProjection")), - ie.identifier.catalogName, - ie.filterExpr, - ie.collator); + ie.wildcardProjection, ie.identifier.catalogName, ie.filterExpr, ie.collator); } void PlanCacheIndexabilityState::processIndexCollation(const std::string& indexName, diff --git a/src/mongo/db/query/plan_cache_indexability.h b/src/mongo/db/query/plan_cache_indexability.h index 80d357f524a..1c9991bd019 100644 --- a/src/mongo/db/query/plan_cache_indexability.h +++ b/src/mongo/db/query/plan_cache_indexability.h @@ -117,21 +117,21 @@ private: * index. */ struct WildcardIndexDiscriminatorContext { - WildcardIndexDiscriminatorContext(std::unique_ptr<ProjectionExecAgg> proj, + WildcardIndexDiscriminatorContext(const ProjectionExecAgg* proj, std::string name, const MatchExpression* filter, const CollatorInterface* coll) - : projectionExec(std::move(proj)), - catalogName(std::move(name)), + : projectionExec(proj), filterExpr(filter), - collator(coll) {} - - std::unique_ptr<ProjectionExecAgg> projectionExec; - std::string catalogName; + collator(coll), + catalogName(std::move(name)) {} // These are owned by the catalog. + const ProjectionExecAgg* projectionExec; const MatchExpression* filterExpr; // For partial indexes. const CollatorInterface* collator; + + std::string catalogName; }; /** diff --git a/src/mongo/db/query/plan_cache_indexability_test.cpp b/src/mongo/db/query/plan_cache_indexability_test.cpp index 4ab15f30ac1..dc67fd8c30a 100644 --- a/src/mongo/db/query/plan_cache_indexability_test.cpp +++ b/src/mongo/db/query/plan_cache_indexability_test.cpp @@ -30,6 +30,7 @@ #include "mongo/platform/basic.h" +#include "mongo/db/index/wildcard_key_generator.h" #include "mongo/db/json.h" #include "mongo/db/matcher/expression_parser.h" #include "mongo/db/pipeline/expression_context_for_test.h" @@ -53,6 +54,24 @@ std::unique_ptr<MatchExpression> parseMatchExpression(const BSONObj& obj, return std::move(status.getValue()); } +// Helper which constructs a $** IndexEntry and returns it along with an owned ProjectionExecAgg. +// The latter simulates the ProjectionExecAgg which, during normal operation, is owned and +// maintained by the $** index's IndexAccessMethod, and is required because the plan cache will +// obtain unowned pointers to it. +std::pair<IndexEntry, std::unique_ptr<ProjectionExecAgg>> makeWildcardEntry( + BSONObj keyPattern, const MatchExpression* filterExpr = nullptr) { + auto projExec = WildcardKeyGenerator::createProjectionExec(keyPattern, {}); + return {IndexEntry(keyPattern, + false, // multikey + false, // sparse + false, // unique + IndexEntry::Identifier{"indexName"}, + filterExpr, + BSONObj(), + projExec.get()), + std::move(projExec)}; +} + // Test sparse index discriminators for a simple sparse index. TEST(PlanCacheIndexabilityTest, SparseIndexSimple) { PlanCacheIndexabilityState state; @@ -413,13 +432,8 @@ TEST(PlanCacheIndexabilityTest, CompoundIndexCollationDiscriminator) { TEST(PlanCacheIndexabilityTest, WildcardDiscriminator) { PlanCacheIndexabilityState state; - state.updateDiscriminators({IndexEntry(BSON("a.$**" << 1), - false, // multikey - false, // sparse - false, // unique - IndexEntry::Identifier{"indexName"}, - nullptr, - BSONObj())}); + auto entryProjExecPair = makeWildcardEntry(BSON("a.$**" << 1)); + state.updateDiscriminators({entryProjExecPair.first}); const auto unindexedPathDiscriminators = state.buildWildcardDiscriminators("notIndexed"); ASSERT_EQ(0U, unindexedPathDiscriminators.size()); @@ -466,16 +480,10 @@ TEST(PlanCacheIndexabilityTest, WildcardDiscriminator) { TEST(PlanCacheIndexabilityTest, WildcardWithCollationDiscriminator) { PlanCacheIndexabilityState state; - IndexEntry entry(BSON("a.$**" << 1), - false, // multikey - false, // sparse - false, // unique - IndexEntry::Identifier{"indexName"}, - nullptr, - BSONObj()); + auto entryProjExecPair = makeWildcardEntry(BSON("a.$**" << 1)); CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); - entry.collator = &collator; - state.updateDiscriminators({entry}); + entryProjExecPair.first.collator = &collator; + state.updateDiscriminators({entryProjExecPair.first}); const auto unindexedPathDiscriminators = state.buildWildcardDiscriminators("notIndexed"); ASSERT_EQ(0U, unindexedPathDiscriminators.size()); @@ -501,14 +509,8 @@ TEST(PlanCacheIndexabilityTest, WildcardPartialIndexDiscriminator) { // expression will store (unowned) pointers into it. BSONObj filterObj = fromjson("{a: {$gt: 5}}"); auto filterExpr = parseMatchExpression(filterObj); - IndexEntry entry(BSON("$**" << 1), - false, // multikey - false, // sparse - false, // unique - IndexEntry::Identifier{"indexName"}, - filterExpr.get(), - BSONObj()); - state.updateDiscriminators({entry}); + auto entryProjExecPair = makeWildcardEntry(BSON("$**" << 1), filterExpr.get()); + state.updateDiscriminators({entryProjExecPair.first}); auto discriminatorsA = state.buildWildcardDiscriminators("a"); ASSERT_EQ(1U, discriminatorsA.size()); @@ -528,13 +530,8 @@ TEST(PlanCacheIndexabilityTest, WildcardPartialIndexDiscriminator) { TEST(PlanCacheIndexabilityTest, WildcardIndexDiscriminatesBetweenEqualityToEmptyObjAndOtherObjComparisons) { PlanCacheIndexabilityState state; - state.updateDiscriminators({IndexEntry(BSON("a.$**" << 1), - false, // multikey - false, // sparse - false, // unique - IndexEntry::Identifier{"indexName"}, - nullptr, - BSONObj())}); + auto entryProjExecPair = makeWildcardEntry(BSON("a.$**" << 1)); + state.updateDiscriminators({entryProjExecPair.first}); auto discriminatorsA = state.buildWildcardDiscriminators("a"); ASSERT_EQ(1U, discriminatorsA.size()); diff --git a/src/mongo/db/query/plan_cache_test.cpp b/src/mongo/db/query/plan_cache_test.cpp index 517211ab83f..1bca73d27cb 100644 --- a/src/mongo/db/query/plan_cache_test.cpp +++ b/src/mongo/db/query/plan_cache_test.cpp @@ -38,6 +38,7 @@ #include <memory> #include <ostream> +#include "mongo/db/index/wildcard_key_generator.h" #include "mongo/db/jsobj.h" #include "mongo/db/json.h" #include "mongo/db/matcher/extensions_callback_noop.h" @@ -227,6 +228,23 @@ void assertEquivalent(const char* queryStr, FAIL(ss); } +// Helper which constructs a $** IndexEntry and returns it along with an owned ProjectionExecAgg. +// The latter simulates the ProjectionExecAgg which, during normal operation, is owned and +// maintained by the $** index's IndexAccessMethod, and is required because the plan cache will +// obtain unowned pointers to it. +std::pair<IndexEntry, std::unique_ptr<ProjectionExecAgg>> makeWildcardEntry(BSONObj keyPattern) { + auto projExec = WildcardKeyGenerator::createProjectionExec(keyPattern, {}); + return {IndexEntry(keyPattern, + false, // multikey + false, // sparse + false, // unique + IndexEntry::Identifier{"indexName"}, + nullptr, + BSONObj(), + projExec.get()), + std::move(projExec)}; +} + // // Tests for CachedSolution // @@ -1282,17 +1300,9 @@ TEST_F(CachePlanSelectionTest, AndWithinPolygonWithinCenterSphere) { // $** index TEST_F(CachePlanSelectionTest, WildcardIxScan) { - params.indices.push_back(IndexEntry(BSON("$**" << 1), - IndexType::INDEX_WILDCARD, - false, // multikey - {}, // multikey paths - {}, // multikeyPathSet - true, // sparse - false, // unique - IndexEntry::Identifier{"anIndex"}, - nullptr, - BSONObj(), - nullptr)); + auto entryProjExecPair = makeWildcardEntry(BSON("$**" << 1)); + params.indices.push_back(entryProjExecPair.first); + BSONObj query = fromjson("{a: 1, b: 1}"); runQuery(query); @@ -2036,15 +2046,10 @@ TEST(PlanCacheTest, ComputeKeyCollationIndex) { } TEST(PlanCacheTest, ComputeKeyWildcardIndex) { + auto entryProjExecPair = makeWildcardEntry(BSON("a.$**" << 1)); + PlanCache planCache; - IndexEntry entry(BSON("a.$**" << 1), - false, // multikey - false, // sparse - false, // unique - IndexEntry::Identifier{""}, // name - nullptr, // filterExpr - BSONObj()); - planCache.notifyOfIndexEntries({entry}); + planCache.notifyOfIndexEntries({entryProjExecPair.first}); // Used to check that two queries have the same shape when no indexes are present. PlanCache planCacheWithNoIndexes; @@ -2096,15 +2101,10 @@ TEST(PlanCacheTest, ComputeKeyWildcardIndex) { } TEST(PlanCacheTest, ComputeKeyWildcardIndexDiscriminatesEqualityToEmptyObj) { + auto entryProjExecPair = makeWildcardEntry(BSON("a.$**" << 1)); + PlanCache planCache; - IndexEntry entry(BSON("a.$**" << 1), - false, // multikey - false, // sparse - false, // unique - IndexEntry::Identifier{""}, // name - nullptr, // filterExpr - BSONObj()); - planCache.notifyOfIndexEntries({entry}); + planCache.notifyOfIndexEntries({entryProjExecPair.first}); // Equality to empty obj and equality to non-empty obj have different plan cache keys. std::unique_ptr<CanonicalQuery> equalsEmptyObj(canonicalize("{a: {}}")); diff --git a/src/mongo/db/query/planner_ixselect_test.cpp b/src/mongo/db/query/planner_ixselect_test.cpp index ca9283826c0..d9acd0a2542 100644 --- a/src/mongo/db/query/planner_ixselect_test.cpp +++ b/src/mongo/db/query/planner_ixselect_test.cpp @@ -34,6 +34,7 @@ #include "mongo/db/query/planner_ixselect.h" +#include "mongo/db/index/wildcard_key_generator.h" #include "mongo/db/json.h" #include "mongo/db/matcher/expression_parser.h" #include "mongo/db/pipeline/expression_context_for_test.h" @@ -1032,9 +1033,19 @@ TEST(QueryPlannerIXSelectTest, NoStringComparisonType) { } } -IndexEntry makeIndexEntry(BSONObj keyPattern, - MultikeyPaths multiKeyPaths, - std::set<FieldRef> multikeyPathSet = {}) { +// Helper which constructs an IndexEntry and returns it along with an owned ProjectionExecAgg, which +// is non-null if the requested entry represents a wildcard index and null otherwise. When non-null, +// it simulates the ProjectionExecAgg that is owned by the $** IndexAccessMethod. +std::pair<IndexEntry, std::unique_ptr<ProjectionExecAgg>> makeIndexEntry( + BSONObj keyPattern, + MultikeyPaths multiKeyPaths, + std::set<FieldRef> multikeyPathSet = {}, + BSONObj infoObj = BSONObj()) { + auto projExec = (keyPattern.firstElement().fieldNameStringData().endsWith("$**"_sd) + ? WildcardKeyGenerator::createProjectionExec( + keyPattern, infoObj.getObjectField("wildcardProjection")) + : nullptr); + IndexEntry entry{std::move(keyPattern)}; entry.multikeyPaths = std::move(multiKeyPaths); entry.multikey = @@ -1042,30 +1053,24 @@ IndexEntry makeIndexEntry(BSONObj keyPattern, entry.multikeyPaths.cend(), [](const auto& entry) { return !entry.empty(); }); entry.multikeyPathSet = std::move(multikeyPathSet); - return entry; -} - -IndexEntry makeIndexEntryWithInfoObj(BSONObj keyPattern, - MultikeyPaths multiKeyPaths, - BSONObj infoObj) { - IndexEntry entry = makeIndexEntry(keyPattern, multiKeyPaths); + entry.wildcardProjection = projExec.get(); entry.infoObj = infoObj; - return entry; + return {entry, std::move(projExec)}; } TEST(QueryPlannerIXSelectTest, InternalExprEqCannotUseMultiKeyIndex) { - IndexEntry entry = makeIndexEntry(BSON("a" << 1), {{0U}}); + auto entry = makeIndexEntry(BSON("a" << 1), {{0U}}); std::vector<IndexEntry> indices; - indices.push_back(entry); + indices.push_back(entry.first); std::set<size_t> expectedIndices; testRateIndices( "{a: {$_internalExprEq: 1}}", "", kSimpleCollator, indices, "a", expectedIndices); } TEST(QueryPlannerIXSelectTest, InternalExprEqCanUseNonMultikeyFieldOfMultikeyIndex) { - IndexEntry entry = makeIndexEntry(BSON("a" << 1 << "b" << 1), {{0U}, {}}); + auto entry = makeIndexEntry(BSON("a" << 1 << "b" << 1), {{0U}, {}}); std::vector<IndexEntry> indices; - indices.push_back(entry); + indices.push_back(entry.first); std::set<size_t> expectedIndices = {0}; testRateIndices( "{b: {$_internalExprEq: 1}}", "", kSimpleCollator, indices, "b", expectedIndices); @@ -1295,18 +1300,18 @@ TEST(QueryPlannerIXSelectTest, ExpandWildcardIndices) { // Case where no fields are specified. std::vector<IndexEntry> result = - QueryPlannerIXSelect::expandIndexes(stdx::unordered_set<string>(), {indexEntry}); + QueryPlannerIXSelect::expandIndexes(stdx::unordered_set<string>(), {indexEntry.first}); ASSERT_TRUE(result.empty()); stdx::unordered_set<string> fields = {"fieldA", "fieldB"}; - result = QueryPlannerIXSelect::expandIndexes(fields, {indexEntry}); + result = QueryPlannerIXSelect::expandIndexes(fields, {indexEntry.first}); std::vector<BSONObj> expectedKeyPatterns = {BSON("fieldA" << 1), BSON("fieldB" << 1)}; ASSERT_TRUE(indexEntryKeyPatternsMatch(&expectedKeyPatterns, &result)); const auto wildcardIndexWithSubpath = makeIndexEntry(BSON("a.b.$**" << 1), {}); fields = {"a.b", "a.b.c", "a.d"}; - result = QueryPlannerIXSelect::expandIndexes(fields, {wildcardIndexWithSubpath}); + result = QueryPlannerIXSelect::expandIndexes(fields, {wildcardIndexWithSubpath.first}); expectedKeyPatterns = {BSON("a.b" << 1), BSON("a.b.c" << 1)}; ASSERT_TRUE(indexEntryKeyPatternsMatch(&expectedKeyPatterns, &result)); } @@ -1321,18 +1326,20 @@ TEST(QueryPlannerIXSelectTest, ExpandWildcardIndicesInPresenceOfOtherIndices) { std::vector<BSONObj> expectedKeyPatterns = { BSON("fieldA" << 1), BSON("fieldA" << 1), BSON("fieldB" << 1), BSON("fieldC" << 1)}; - auto result = QueryPlannerIXSelect::expandIndexes(fields, {aIndexEntry, wildcardIndexEntry}); + auto result = + QueryPlannerIXSelect::expandIndexes(fields, {aIndexEntry.first, wildcardIndexEntry.first}); ASSERT_TRUE(indexEntryKeyPatternsMatch(&expectedKeyPatterns, &result)); result.clear(); expectedKeyPatterns = { BSON("fieldB" << 1), BSON("fieldA" << 1), BSON("fieldB" << 1), BSON("fieldC" << 1)}; - result = QueryPlannerIXSelect::expandIndexes(fields, {bIndexEntry, wildcardIndexEntry}); + result = + QueryPlannerIXSelect::expandIndexes(fields, {bIndexEntry.first, wildcardIndexEntry.first}); ASSERT_TRUE(indexEntryKeyPatternsMatch(&expectedKeyPatterns, &result)); result.clear(); - result = - QueryPlannerIXSelect::expandIndexes(fields, {aIndexEntry, wildcardIndexEntry, bIndexEntry}); + result = QueryPlannerIXSelect::expandIndexes( + fields, {aIndexEntry.first, wildcardIndexEntry.first, bIndexEntry.first}); expectedKeyPatterns = {BSON("fieldA" << 1), BSON("fieldA" << 1), BSON("fieldB" << 1), @@ -1341,7 +1348,8 @@ TEST(QueryPlannerIXSelectTest, ExpandWildcardIndicesInPresenceOfOtherIndices) { ASSERT_TRUE(indexEntryKeyPatternsMatch(&expectedKeyPatterns, &result)); result.clear(); - result = QueryPlannerIXSelect::expandIndexes(fields, {wildcardIndexEntry, abIndexEntry}); + result = + QueryPlannerIXSelect::expandIndexes(fields, {wildcardIndexEntry.first, abIndexEntry.first}); expectedKeyPatterns = {BSON("fieldA" << 1), BSON("fieldB" << 1), BSON("fieldC" << 1), @@ -1355,7 +1363,7 @@ TEST(QueryPlannerIXSelectTest, ExpandedIndexEntriesAreCorrectlyMarkedAsMultikeyO const stdx::unordered_set<string> fields = {"a.b", "c.d"}; std::vector<BSONObj> expectedKeyPatterns = {BSON("a.b" << 1), BSON("c.d" << 1)}; - auto result = QueryPlannerIXSelect::expandIndexes(fields, {wildcardIndexEntry}); + auto result = QueryPlannerIXSelect::expandIndexes(fields, {wildcardIndexEntry.first}); ASSERT_TRUE(indexEntryKeyPatternsMatch(&expectedKeyPatterns, &result)); for (auto&& entry : result) { @@ -1378,19 +1386,20 @@ TEST(QueryPlannerIXSelectTest, WildcardIndexExpansionExcludesIdField) { stdx::unordered_set<string> fields = {"_id", "abc", "def"}; - std::vector<IndexEntry> result = QueryPlannerIXSelect::expandIndexes(fields, {indexEntry}); + std::vector<IndexEntry> result = + QueryPlannerIXSelect::expandIndexes(fields, {indexEntry.first}); std::vector<BSONObj> expectedKeyPatterns = {BSON("abc" << 1), BSON("def" << 1)}; ASSERT_TRUE(indexEntryKeyPatternsMatch(&expectedKeyPatterns, &result)); } TEST(QueryPlannerIXSelectTest, WildcardIndicesExpandedEntryHasCorrectProperties) { auto wildcardIndexEntry = makeIndexEntry(BSON("$**" << 1), {}); - wildcardIndexEntry.identifier = IndexEntry::Identifier("someIndex"); + wildcardIndexEntry.first.identifier = IndexEntry::Identifier("someIndex"); stdx::unordered_set<string> fields = {"abc", "def"}; std::vector<IndexEntry> result = - QueryPlannerIXSelect::expandIndexes(fields, {wildcardIndexEntry}); + QueryPlannerIXSelect::expandIndexes(fields, {wildcardIndexEntry.first}); std::vector<BSONObj> expectedKeyPatterns = {BSON("abc" << 1), BSON("def" << 1)}; ASSERT_TRUE(indexEntryKeyPatternsMatch(&expectedKeyPatterns, &result)); @@ -1409,7 +1418,7 @@ TEST(QueryPlannerIXSelectTest, WildcardIndicesExpandedEntryHasCorrectProperties) ASSERT_FALSE(ie.unique); ASSERT_EQ(ie.identifier, - IndexEntry::Identifier(wildcardIndexEntry.identifier.catalogName, + IndexEntry::Identifier(wildcardIndexEntry.first.identifier.catalogName, ie.keyPattern.firstElementFieldName())); } } @@ -1420,93 +1429,102 @@ TEST(QueryPlannerIXSelectTest, WildcardIndicesExcludeNonMatchingKeySubpath) { stdx::unordered_set<string> fields = {"abc", "def", "subpath.abc", "subpath.def", "subpath"}; std::vector<IndexEntry> result = - QueryPlannerIXSelect::expandIndexes(fields, {wildcardIndexEntry}); + QueryPlannerIXSelect::expandIndexes(fields, {wildcardIndexEntry.first}); std::vector<BSONObj> expectedKeyPatterns = { BSON("subpath.abc" << 1), BSON("subpath.def" << 1), BSON("subpath" << 1)}; ASSERT_TRUE(indexEntryKeyPatternsMatch(&expectedKeyPatterns, &result)); } TEST(QueryPlannerIXSelectTest, WildcardIndicesExcludeNonMatchingPathsWithInclusionProjection) { - auto wildcardIndexEntry = makeIndexEntryWithInfoObj( - BSON("$**" << 1), {}, BSON("wildcardProjection" << BSON("abc" << 1 << "subpath.abc" << 1))); + auto wildcardIndexEntry = + makeIndexEntry(BSON("$**" << 1), + {}, + {}, + BSON("wildcardProjection" << BSON("abc" << 1 << "subpath.abc" << 1))); stdx::unordered_set<string> fields = {"abc", "def", "subpath.abc", "subpath.def", "subpath"}; std::vector<IndexEntry> result = - QueryPlannerIXSelect::expandIndexes(fields, {wildcardIndexEntry}); + QueryPlannerIXSelect::expandIndexes(fields, {wildcardIndexEntry.first}); std::vector<BSONObj> expectedKeyPatterns = {BSON("abc" << 1), BSON("subpath.abc" << 1)}; ASSERT_TRUE(indexEntryKeyPatternsMatch(&expectedKeyPatterns, &result)); } TEST(QueryPlannerIXSelectTest, WildcardIndicesExcludeNonMatchingPathsWithExclusionProjection) { - auto wildcardIndexEntry = makeIndexEntryWithInfoObj( - BSON("$**" << 1), {}, BSON("wildcardProjection" << BSON("abc" << 0 << "subpath.abc" << 0))); + auto wildcardIndexEntry = + makeIndexEntry(BSON("$**" << 1), + {}, + {}, + BSON("wildcardProjection" << BSON("abc" << 0 << "subpath.abc" << 0))); stdx::unordered_set<string> fields = {"abc", "def", "subpath.abc", "subpath.def", "subpath"}; std::vector<IndexEntry> result = - QueryPlannerIXSelect::expandIndexes(fields, {wildcardIndexEntry}); + QueryPlannerIXSelect::expandIndexes(fields, {wildcardIndexEntry.first}); std::vector<BSONObj> expectedKeyPatterns = { BSON("def" << 1), BSON("subpath.def" << 1), BSON("subpath" << 1)}; ASSERT_TRUE(indexEntryKeyPatternsMatch(&expectedKeyPatterns, &result)); } TEST(QueryPlannerIXSelectTest, WildcardIndicesWithInclusionProjectionAllowIdExclusion) { - auto wildcardIndexEntry = makeIndexEntryWithInfoObj( + auto wildcardIndexEntry = makeIndexEntry( BSON("$**" << 1), {}, + {}, BSON("wildcardProjection" << BSON("_id" << 0 << "abc" << 1 << "subpath.abc" << 1))); stdx::unordered_set<string> fields = { "_id", "abc", "def", "subpath.abc", "subpath.def", "subpath"}; std::vector<IndexEntry> result = - QueryPlannerIXSelect::expandIndexes(fields, {wildcardIndexEntry}); + QueryPlannerIXSelect::expandIndexes(fields, {wildcardIndexEntry.first}); std::vector<BSONObj> expectedKeyPatterns = {BSON("abc" << 1), BSON("subpath.abc" << 1)}; ASSERT_TRUE(indexEntryKeyPatternsMatch(&expectedKeyPatterns, &result)); } TEST(QueryPlannerIXSelectTest, WildcardIndicesWithInclusionProjectionAllowIdInclusion) { - auto wildcardIndexEntry = makeIndexEntryWithInfoObj( + auto wildcardIndexEntry = makeIndexEntry( BSON("$**" << 1), {}, + {}, BSON("wildcardProjection" << BSON("_id" << 1 << "abc" << 1 << "subpath.abc" << 1))); stdx::unordered_set<string> fields = { "_id", "abc", "def", "subpath.abc", "subpath.def", "subpath"}; std::vector<IndexEntry> result = - QueryPlannerIXSelect::expandIndexes(fields, {wildcardIndexEntry}); + QueryPlannerIXSelect::expandIndexes(fields, {wildcardIndexEntry.first}); std::vector<BSONObj> expectedKeyPatterns = { BSON("_id" << 1), BSON("abc" << 1), BSON("subpath.abc" << 1)}; ASSERT_TRUE(indexEntryKeyPatternsMatch(&expectedKeyPatterns, &result)); } TEST(QueryPlannerIXSelectTest, WildcardIndicesWithExclusionProjectionAllowIdInclusion) { - auto wildcardIndexEntry = makeIndexEntryWithInfoObj( + auto wildcardIndexEntry = makeIndexEntry( BSON("$**" << 1), {}, + {}, BSON("wildcardProjection" << BSON("_id" << 1 << "abc" << 0 << "subpath.abc" << 0))); stdx::unordered_set<string> fields = { "_id", "abc", "def", "subpath.abc", "subpath.def", "subpath"}; std::vector<IndexEntry> result = - QueryPlannerIXSelect::expandIndexes(fields, {wildcardIndexEntry}); + QueryPlannerIXSelect::expandIndexes(fields, {wildcardIndexEntry.first}); std::vector<BSONObj> expectedKeyPatterns = { BSON("_id" << 1), BSON("def" << 1), BSON("subpath.def" << 1), BSON("subpath" << 1)}; ASSERT_TRUE(indexEntryKeyPatternsMatch(&expectedKeyPatterns, &result)); } TEST(QueryPlannerIXSelectTest, WildcardIndicesIncludeMatchingInternalNodes) { - auto wildcardIndexEntry = makeIndexEntryWithInfoObj( - BSON("$**" << 1), {}, BSON("wildcardProjection" << BSON("_id" << 1 << "subpath" << 1))); + auto wildcardIndexEntry = makeIndexEntry( + BSON("$**" << 1), {}, {}, BSON("wildcardProjection" << BSON("_id" << 1 << "subpath" << 1))); stdx::unordered_set<string> fields = { "_id", "abc", "def", "subpath.abc", "subpath.def", "subpath"}; std::vector<IndexEntry> result = - QueryPlannerIXSelect::expandIndexes(fields, {wildcardIndexEntry}); + QueryPlannerIXSelect::expandIndexes(fields, {wildcardIndexEntry.first}); std::vector<BSONObj> expectedKeyPatterns = { BSON("_id" << 1), BSON("subpath.abc" << 1), BSON("subpath.def" << 1), BSON("subpath" << 1)}; ASSERT_TRUE(indexEntryKeyPatternsMatch(&expectedKeyPatterns, &result)); diff --git a/src/mongo/db/query/planner_wildcard_helpers.cpp b/src/mongo/db/query/planner_wildcard_helpers.cpp index e5f9014eea8..63c128b5f82 100644 --- a/src/mongo/db/query/planner_wildcard_helpers.cpp +++ b/src/mongo/db/query/planner_wildcard_helpers.cpp @@ -305,8 +305,9 @@ void expandWildcardIndexEntry(const IndexEntry& wildcardIndex, // fixed-size vector of multikey metadata until after they are expanded. invariant(wildcardIndex.multikeyPaths.empty()); - const auto projExec = WildcardKeyGenerator::createProjectionExec( - wildcardIndex.keyPattern, wildcardIndex.infoObj.getObjectField("wildcardProjection")); + // Obtain the projection executor from the parent wildcard IndexEntry. + const auto* projExec = wildcardIndex.wildcardProjection; + invariant(projExec); const auto projectedFields = projExec->applyProjectionToFields(fields); @@ -351,7 +352,8 @@ void expandWildcardIndexEntry(const IndexEntry& wildcardIndex, {wildcardIndex.identifier.catalogName, fieldName}, wildcardIndex.filterExpr, wildcardIndex.infoObj, - wildcardIndex.collator); + wildcardIndex.collator, + wildcardIndex.wildcardProjection); invariant("$_path"_sd != fieldName); out->push_back(std::move(entry)); diff --git a/src/mongo/db/query/query_planner_collation_test.cpp b/src/mongo/db/query/query_planner_collation_test.cpp index a31468799aa..3f8a88912c0 100644 --- a/src/mongo/db/query/query_planner_collation_test.cpp +++ b/src/mongo/db/query/query_planner_collation_test.cpp @@ -561,31 +561,4 @@ TEST_F(QueryPlannerTest, NoSortStageWhenMinMaxIndexCollationDoesNotMatchButBound assertSolutionExists("{fetch: {node: {ixscan: {pattern: {a: 1, b: 1, c: 1}}}}}"); } -TEST_F(QueryPlannerTest, StringComparisonWithUnequalCollatorsAndWildcardIndexResultsInCollscan) { - CollatorInterfaceMock alwaysEqualCollator(CollatorInterfaceMock::MockType::kAlwaysEqual); - addIndex(fromjson("{'$**': 1}"), &alwaysEqualCollator); - - runQueryAsCommand( - fromjson("{find: 'testns', filter: {a: {$lt: 'foo'}}, collation: {locale: 'reverse'}}")); - - assertNumSolutions(1U); - assertSolutionExists("{cscan: {dir: 1}}"); -} - -TEST_F(QueryPlannerTest, StringComparisonWithEqualCollatorsAndWildcardIndexUsesIndex) { - params.options &= ~QueryPlannerParams::INCLUDE_COLLSCAN; - - CollatorInterfaceMock reverseStringCollator(CollatorInterfaceMock::MockType::kReverseString); - addIndex(fromjson("{'$**': 1}"), &reverseStringCollator); - - runQueryAsCommand( - fromjson("{find: 'testns', filter: {a: {$lt: 'foo'}}, collation: {locale: 'reverse'}}")); - - assertNumSolutions(1U); - assertSolutionExists( - "{fetch: {filter: null, collation: {locale: 'reverse'}, node: " - "{ixscan: {filter: null, pattern: {'$_path': 1, a: 1}," - "bounds: {'$_path': [['a','a',true,true]], 'a': [['','oof',true,false]]}}}}}"); -} - } // namespace diff --git a/src/mongo/db/query/query_planner_wildcard_index_test.cpp b/src/mongo/db/query/query_planner_wildcard_index_test.cpp index 1ebd3242ce1..2e0a5a09e13 100644 --- a/src/mongo/db/query/query_planner_wildcard_index_test.cpp +++ b/src/mongo/db/query/query_planner_wildcard_index_test.cpp @@ -30,6 +30,8 @@ #include "mongo/platform/basic.h" +#include "mongo/db/index/wildcard_key_generator.h" +#include "mongo/db/query/collation/collator_interface_mock.h" #include "mongo/db/query/planner_wildcard_helpers.h" #include "mongo/db/query/query_planner_test_fixture.h" #include "mongo/unittest/death_test.h" @@ -57,7 +59,9 @@ protected: void addWildcardIndex(BSONObj keyPattern, const std::set<std::string>& multikeyPathSet = {}, BSONObj wildcardProjection = BSONObj{}, - MatchExpression* partialFilterExpr = nullptr) { + MatchExpression* partialFilterExpr = nullptr, + CollatorInterface* collator = nullptr, + const std::string& indexName = kIndexName) { // Convert the set of std::string to a set of FieldRef. std::set<FieldRef> multikeyFieldRefs; for (auto&& path : multikeyPathSet) { @@ -68,18 +72,23 @@ protected: const bool isMultikey = !multikeyPathSet.empty(); BSONObj infoObj = BSON("wildcardProjection" << wildcardProjection); - params.indices.push_back(IndexEntry{std::move(keyPattern), - IndexType::INDEX_WILDCARD, - isMultikey, - {}, // multikeyPaths - std::move(multikeyFieldRefs), - false, // sparse - false, // unique - IndexEntry::Identifier{kIndexName}, - partialFilterExpr, - std::move(infoObj), - nullptr}); // collator + _projExec = WildcardKeyGenerator::createProjectionExec(keyPattern, wildcardProjection); + + params.indices.push_back({std::move(keyPattern), + IndexType::INDEX_WILDCARD, + isMultikey, + {}, // multikeyPaths + std::move(multikeyFieldRefs), + false, // sparse + false, // unique + IndexEntry::Identifier{indexName}, + partialFilterExpr, + std::move(infoObj), + collator, + _projExec.get()}); } + + std::unique_ptr<ProjectionExecAgg> _projExec; }; // @@ -87,7 +96,7 @@ protected: // DEATH_TEST_F(QueryPlannerWildcardTest, CannotExpandPreExpandedWildcardIndexEntry, "Invariant") { - addIndex(BSON("$**" << 1)); + addWildcardIndex(BSON("$**" << 1)); ASSERT_EQ(params.indices.size(), 2U); // Expand the $** index and add the expanded entry to the list of available indices. @@ -105,7 +114,7 @@ DEATH_TEST_F(QueryPlannerWildcardTest, CannotExpandPreExpandedWildcardIndexEntry // TEST_F(QueryPlannerWildcardTest, ExistsTrueQueriesUseWildcardIndexes) { - addIndex(BSON("$**" << 1)); + addWildcardIndex(BSON("$**" << 1)); runQuery(fromjson("{x: {$exists: true}}")); @@ -114,7 +123,7 @@ TEST_F(QueryPlannerWildcardTest, ExistsTrueQueriesUseWildcardIndexes) { } TEST_F(QueryPlannerWildcardTest, ExistsFalseQueriesDontUseWildcardIndexes) { - addIndex(BSON("$**" << 1)); + addWildcardIndex(BSON("$**" << 1)); runQuery(fromjson("{x: {$exists: false}}")); @@ -123,7 +132,7 @@ TEST_F(QueryPlannerWildcardTest, ExistsFalseQueriesDontUseWildcardIndexes) { } TEST_F(QueryPlannerWildcardTest, EqualsNullQueriesDontUseWildcardIndexes) { - addIndex(BSON("$**" << 1)); + addWildcardIndex(BSON("$**" << 1)); runQuery(fromjson("{x: {$eq: null}}")); @@ -132,7 +141,7 @@ TEST_F(QueryPlannerWildcardTest, EqualsNullQueriesDontUseWildcardIndexes) { } TEST_F(QueryPlannerWildcardTest, NotEqualsNullQueriesUseWildcardIndexes) { - addIndex(BSON("$**" << 1)); + addWildcardIndex(BSON("$**" << 1)); runQuery(fromjson("{x: {$ne: null}}")); @@ -187,7 +196,7 @@ TEST_F(QueryPlannerWildcardTest, NotEqualsNullInElemMatchObjectSparseMultiKeyBel } TEST_F(QueryPlannerWildcardTest, NotEqualsNullAndExistsQueriesUseWildcardIndexes) { - addIndex(BSON("$**" << 1)); + addWildcardIndex(BSON("$**" << 1)); runQuery(fromjson("{x: {$ne: null, $exists: true}}")); @@ -196,7 +205,7 @@ TEST_F(QueryPlannerWildcardTest, NotEqualsNullAndExistsQueriesUseWildcardIndexes } TEST_F(QueryPlannerWildcardTest, EqualsNullAndExistsQueriesUseWildcardIndexes) { - addIndex(BSON("$**" << 1)); + addWildcardIndex(BSON("$**" << 1)); runQuery(fromjson("{x: {$eq: null, $exists: true}}")); @@ -205,7 +214,7 @@ TEST_F(QueryPlannerWildcardTest, EqualsNullAndExistsQueriesUseWildcardIndexes) { } TEST_F(QueryPlannerWildcardTest, EmptyBoundsWithWildcardIndexes) { - addIndex(BSON("$**" << 1)); + addWildcardIndex(BSON("$**" << 1)); runQuery(fromjson("{x: {$lte: 5, $gte: 10}}")); @@ -431,7 +440,7 @@ TEST_F(QueryPlannerWildcardTest, ExprEqCanUseSparseIndexForEqualityToNull) { } TEST_F(QueryPlannerWildcardTest, PrefixRegex) { - addIndex(BSON("$**" << 1)); + addWildcardIndex(BSON("$**" << 1)); runQuery(fromjson("{a: /^foo/}")); assertNumSolutions(1U); @@ -442,7 +451,7 @@ TEST_F(QueryPlannerWildcardTest, PrefixRegex) { } TEST_F(QueryPlannerWildcardTest, NonPrefixRegex) { - addIndex(BSON("$**" << 1)); + addWildcardIndex(BSON("$**" << 1)); runQuery(fromjson("{a: /foo/}")); assertNumSolutions(1U); @@ -956,8 +965,8 @@ TEST_F(QueryPlannerWildcardTest, WildcardDoesNotSupportNegationPredicateInsideEl // Hinting with all paths index tests. // -TEST_F(QueryPlannerTest, ChooseWildcardIndexHint) { - addIndex(BSON("$**" << 1)); +TEST_F(QueryPlannerWildcardTest, ChooseWildcardIndexHint) { + addWildcardIndex(BSON("$**" << 1)); addIndex(BSON("x" << 1)); runQueryHint(fromjson("{x: {$eq: 1}}"), BSON("$**" << 1)); @@ -966,10 +975,10 @@ TEST_F(QueryPlannerTest, ChooseWildcardIndexHint) { assertSolutionExists("{fetch: {node: {ixscan: {pattern: {$_path: 1, x: 1}}}}}"); } -TEST_F(QueryPlannerTest, ChooseWildcardIndexHintByName) { - StringData wildcard = "wildcard"; +TEST_F(QueryPlannerWildcardTest, ChooseWildcardIndexHintByName) { + std::string wildcard = "wildcard"; CollatorInterface* nullCollator = nullptr; - addIndex(BSON("$**" << 1), nullCollator, wildcard); + addWildcardIndex(BSON("$**" << 1), {}, {}, {}, nullCollator, wildcard); addIndex(BSON("x" << 1)); runQueryHint(fromjson("{x: {$eq: 1}}"), @@ -980,8 +989,8 @@ TEST_F(QueryPlannerTest, ChooseWildcardIndexHintByName) { assertSolutionExists("{fetch: {node: {ixscan: {pattern: {$_path: 1, x: 1}}}}}"); } -TEST_F(QueryPlannerTest, ChooseWildcardIndexHintWithPath) { - addIndex(BSON("x.$**" << 1)); +TEST_F(QueryPlannerWildcardTest, ChooseWildcardIndexHintWithPath) { + addWildcardIndex(BSON("x.$**" << 1)); addIndex(BSON("x" << 1)); runQueryHint(fromjson("{x: {$eq: 1}}"), BSON("x.$**" << 1)); @@ -990,8 +999,8 @@ TEST_F(QueryPlannerTest, ChooseWildcardIndexHintWithPath) { assertSolutionExists("{fetch: {node: {ixscan: {pattern: {$_path: 1, x: 1}}}}}"); } -TEST_F(QueryPlannerTest, ChooseWildcardIndexHintWithOr) { - addIndex(BSON("$**" << 1)); +TEST_F(QueryPlannerWildcardTest, ChooseWildcardIndexHintWithOr) { + addWildcardIndex(BSON("$**" << 1)); addIndex(BSON("x" << 1 << "y" << 1)); runQueryHint(fromjson("{$or: [{x: 1}, {y: 1}]}"), BSON("$**" << 1)); @@ -1002,8 +1011,8 @@ TEST_F(QueryPlannerTest, ChooseWildcardIndexHintWithOr) { " {ixscan: {pattern: {$_path: 1, y: 1}}}]}}}}"); } -TEST_F(QueryPlannerTest, ChooseWildcardIndexHintWithCompoundIndex) { - addIndex(BSON("$**" << 1)); +TEST_F(QueryPlannerWildcardTest, ChooseWildcardIndexHintWithCompoundIndex) { + addWildcardIndex(BSON("$**" << 1)); addIndex(BSON("x" << 1 << "y" << 1)); runQueryHint(fromjson("{x: 1, y: 1}"), BSON("$**" << 1)); @@ -1013,33 +1022,33 @@ TEST_F(QueryPlannerTest, ChooseWildcardIndexHintWithCompoundIndex) { assertSolutionExists("{fetch: {node: {ixscan: {pattern: {$_path: 1, y: 1}}}}}"); } -TEST_F(QueryPlannerTest, QueryNotInWildcardIndexHint) { - addIndex(BSON("a.$**" << 1)); +TEST_F(QueryPlannerWildcardTest, QueryNotInWildcardIndexHint) { + addWildcardIndex(BSON("a.$**" << 1)); addIndex(BSON("x" << 1)); runQueryHint(fromjson("{x: {$eq: 1}}"), BSON("a.$**" << 1)); assertNumSolutions(0U); } -TEST_F(QueryPlannerTest, WildcardIndexDoesNotExist) { +TEST_F(QueryPlannerWildcardTest, WildcardIndexDoesNotExist) { addIndex(BSON("x" << 1)); runInvalidQueryHint(fromjson("{x: {$eq: 1}}"), BSON("$**" << 1)); } -TEST_F(QueryPlannerTest, WildcardIndexHintWithPartialFilter) { +TEST_F(QueryPlannerWildcardTest, WildcardIndexHintWithPartialFilter) { auto filterObj = fromjson("{a: {$gt: 100}}"); auto filterExpr = QueryPlannerTest::parseMatchExpression(filterObj); - addIndex(BSON("$**" << 1), filterExpr.get()); + addWildcardIndex(BSON("$**" << 1), {}, {}, filterExpr.get()); runQueryHint(fromjson("{a: {$eq: 1}}"), BSON("$**" << 1)); assertNumSolutions(0U); } -TEST_F(QueryPlannerTest, MultipleWildcardIndexesHintWithPartialFilter) { +TEST_F(QueryPlannerWildcardTest, MultipleWildcardIndexesHintWithPartialFilter) { auto filterObj = fromjson("{a: {$gt: 100}, b: {$gt: 100}}"); auto filterExpr = QueryPlannerTest::parseMatchExpression(filterObj); - addIndex(BSON("$**" << 1), filterExpr.get()); + addWildcardIndex(BSON("$**" << 1), {}, {}, filterExpr.get()); runQueryHint(fromjson("{a: {$eq: 1}, b: {$eq: 1}}"), BSON("$**" << 1)); assertNumSolutions(0U); @@ -1050,7 +1059,7 @@ TEST_F(QueryPlannerTest, MultipleWildcardIndexesHintWithPartialFilter) { // TEST_F(QueryPlannerWildcardTest, WildcardIndexesDoNotSupportObjectEquality) { - addIndex(BSON("$**" << 1)); + addWildcardIndex(BSON("$**" << 1)); runQuery(fromjson("{x: {abc: 1}}")); assertHasOnlyCollscan(); @@ -1065,7 +1074,7 @@ TEST_F(QueryPlannerWildcardTest, WildcardIndexesDoNotSupportObjectEquality) { } TEST_F(QueryPlannerWildcardTest, WildcardIndexesDoNotSupportObjectInequality) { - addIndex(BSON("$**" << 1)); + addWildcardIndex(BSON("$**" << 1)); runQuery(fromjson("{x: {$lt: {abc: 1}}}")); assertHasOnlyCollscan(); @@ -1102,7 +1111,7 @@ TEST_F(QueryPlannerWildcardTest, WildcardIndexesDoNotSupportObjectInequality) { // TEST_F(QueryPlannerWildcardTest, WildcardIndexesDoNotSupportInWithUnsupportedValues) { - addIndex(BSON("$**" << 1)); + addWildcardIndex(BSON("$**" << 1)); runQuery(fromjson("{x: {$in: [1, 2, 3, {abc: 1}]}}")); assertHasOnlyCollscan(); @@ -1113,7 +1122,7 @@ TEST_F(QueryPlannerWildcardTest, WildcardIndexesDoNotSupportInWithUnsupportedVal } TEST_F(QueryPlannerWildcardTest, WildcardIndexesSupportElemMatchWithNull) { - addIndex(BSON("$**" << 1)); + addWildcardIndex(BSON("$**" << 1)); // Simple case. runQuery(fromjson("{x: {$elemMatch: {$lt: 5, $gt: 0}}}")); @@ -1587,26 +1596,26 @@ TEST_F(QueryPlannerWildcardTest, ShouldDeclineToAnswerQueriesThatTraverseTooMany // Min/max with wildcard index. // -TEST_F(QueryPlannerTest, WildcardIndexDoesNotSupportMin) { - addIndex(BSON("$**" << 1)); +TEST_F(QueryPlannerWildcardTest, WildcardIndexDoesNotSupportMin) { + addWildcardIndex(BSON("$**" << 1)); runInvalidQueryHintMinMax(BSONObj(), BSON("$**" << 1), fromjson("{x: 1}"), BSONObj()); } -TEST_F(QueryPlannerTest, WildcardIndexDoesNotSupportMax) { - addIndex(BSON("$**" << 1)); +TEST_F(QueryPlannerWildcardTest, WildcardIndexDoesNotSupportMax) { + addWildcardIndex(BSON("$**" << 1)); runInvalidQueryHintMinMax(BSONObj(), BSON("$**" << 1), BSONObj(), fromjson("{x: 10}")); } -TEST_F(QueryPlannerTest, WildcardIndexDoesNotSupportMinMax) { - addIndex(BSON("$**" << 1)); +TEST_F(QueryPlannerWildcardTest, WildcardIndexDoesNotSupportMinMax) { + addWildcardIndex(BSON("$**" << 1)); runInvalidQueryHintMinMax(BSONObj(), BSON("$**" << 1), fromjson("{x: 1}"), fromjson("{x: 10}")); } -TEST_F(QueryPlannerTest, WildcardIndexDoesNotSupportCompoundMinMax) { - addIndex(BSON("$**" << 1)); +TEST_F(QueryPlannerWildcardTest, WildcardIndexDoesNotSupportCompoundMinMax) { + addWildcardIndex(BSON("$**" << 1)); runInvalidQueryHintMinMax( BSONObj(), BSON("$**" << 1), fromjson("{x: 1, y: 1}"), fromjson("{x: 10, y:10}")); @@ -1614,29 +1623,29 @@ TEST_F(QueryPlannerTest, WildcardIndexDoesNotSupportCompoundMinMax) { // Test with a query argument to check that the expanded indices are not used. -TEST_F(QueryPlannerTest, WildcardIndexDoesNotSupportMinWithFilter) { - addIndex(BSON("$**" << 1)); +TEST_F(QueryPlannerWildcardTest, WildcardIndexDoesNotSupportMinWithFilter) { + addWildcardIndex(BSON("$**" << 1)); runInvalidQueryHintMinMax( fromjson("{x: {$eq: 5}}"), BSON("$**" << 1), fromjson("{x: 1}"), BSONObj()); } -TEST_F(QueryPlannerTest, WildcardIndexDoesNotSupportMaxWithFilter) { - addIndex(BSON("$**" << 1)); +TEST_F(QueryPlannerWildcardTest, WildcardIndexDoesNotSupportMaxWithFilter) { + addWildcardIndex(BSON("$**" << 1)); runInvalidQueryHintMinMax( fromjson("{x: {$eq: 5}}"), BSON("$**" << 1), BSONObj(), fromjson("{x: 10}")); } -TEST_F(QueryPlannerTest, WildcardIndexDoesNotSupportMinMaxWithFilter) { - addIndex(BSON("$**" << 1)); +TEST_F(QueryPlannerWildcardTest, WildcardIndexDoesNotSupportMinMaxWithFilter) { + addWildcardIndex(BSON("$**" << 1)); runInvalidQueryHintMinMax( fromjson("{x: {$eq: 5}}"), BSON("$**" << 1), fromjson("{x: 1}"), fromjson("{x: 10}")); } -TEST_F(QueryPlannerTest, WildcardIndexDoesNotSupportCompoundMinMaxWithFilter) { - addIndex(BSON("$**" << 1)); +TEST_F(QueryPlannerWildcardTest, WildcardIndexDoesNotSupportCompoundMinMaxWithFilter) { + addWildcardIndex(BSON("$**" << 1)); runInvalidQueryHintMinMax(fromjson("{x: 5, y: 5}"), BSON("$**" << 1), @@ -1644,15 +1653,15 @@ TEST_F(QueryPlannerTest, WildcardIndexDoesNotSupportCompoundMinMaxWithFilter) { fromjson("{x: 10, y:10}")); } -TEST_F(QueryPlannerTest, PathSpecifiedWildcardIndexDoesNotSupportMinMax) { - addIndex(BSON("x.$**" << 1)); +TEST_F(QueryPlannerWildcardTest, PathSpecifiedWildcardIndexDoesNotSupportMinMax) { + addWildcardIndex(BSON("x.$**" << 1)); runInvalidQueryHintMinMax( fromjson("{x: {$eq: 5}}"), BSON("x.$**" << 1), fromjson("{x: 1}"), fromjson("{x: 10}")); } -TEST_F(QueryPlannerTest, WildcardIndexDoesNotEffectValidMinMax) { - addIndex(BSON("$**" << 1)); +TEST_F(QueryPlannerWildcardTest, WildcardIndexDoesNotEffectValidMinMax) { + addWildcardIndex(BSON("$**" << 1)); addIndex(BSON("x" << 1)); runQueryHintMinMax( @@ -1664,8 +1673,8 @@ TEST_F(QueryPlannerTest, WildcardIndexDoesNotEffectValidMinMax) { "node: {ixscan: {filter: null, pattern: {x: 1}}}}}"); } -TEST_F(QueryPlannerTest, WildcardIndexDoesNotSupportMinMaxWithoutHint) { - addIndex(BSON("$**" << 1)); +TEST_F(QueryPlannerWildcardTest, WildcardIndexDoesNotSupportMinMaxWithoutHint) { + addWildcardIndex(BSON("$**" << 1)); runInvalidQueryHintMinMax( fromjson("{x: {$eq: 5}}"), BSONObj(), fromjson("{x: 1}"), fromjson("{x: 10}")); @@ -1842,4 +1851,33 @@ TEST_F(QueryPlannerWildcardTest, TypeOfArrayWithWildcardIndex) { "{$_path: [['a','a',true,true], ['a.','a/', true, false]], " "a:[['MinKey','MaxKey',true,true]]}}}}}"); } + +TEST_F(QueryPlannerWildcardTest, + StringComparisonWithUnequalCollatorsAndWildcardIndexResultsInCollscan) { + CollatorInterfaceMock alwaysEqualCollator(CollatorInterfaceMock::MockType::kAlwaysEqual); + addWildcardIndex(fromjson("{'$**': 1}"), {}, {}, {}, &alwaysEqualCollator); + + runQueryAsCommand( + fromjson("{find: 'testns', filter: {a: {$lt: 'foo'}}, collation: {locale: 'reverse'}}")); + + assertNumSolutions(1U); + assertSolutionExists("{cscan: {dir: 1}}"); +} + +TEST_F(QueryPlannerWildcardTest, StringComparisonWithEqualCollatorsAndWildcardIndexUsesIndex) { + params.options &= ~QueryPlannerParams::INCLUDE_COLLSCAN; + + CollatorInterfaceMock reverseStringCollator(CollatorInterfaceMock::MockType::kReverseString); + addWildcardIndex(fromjson("{'$**': 1}"), {}, {}, {}, &reverseStringCollator); + + runQueryAsCommand( + fromjson("{find: 'testns', filter: {a: {$lt: 'foo'}}, collation: {locale: 'reverse'}}")); + + assertNumSolutions(1U); + assertSolutionExists( + "{fetch: {filter: null, collation: {locale: 'reverse'}, node: " + "{ixscan: {filter: null, pattern: {'$_path': 1, a: 1}," + "bounds: {'$_path': [['a','a',true,true]], 'a': [['','oof',true,false]]}}}}}"); +} + } // namespace mongo |