summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBernard Gorman <bernard.gorman@gmail.com>2018-10-15 14:13:53 -0400
committerBernard Gorman <bernard.gorman@gmail.com>2018-10-23 00:19:01 +0100
commit1e19472175d9f8c26d2cc1a80e108a0a4a761213 (patch)
treeff798bd9801eb65585ee8096a86f0461373f1833
parent10ec78a0ea0adca815df5a5cb9f3bf9f7d2221f6 (diff)
downloadmongo-1e19472175d9f8c26d2cc1a80e108a0a4a761213.tar.gz
SERVER-37566 Avoid recreating ProjectionExecAgg on each expansion of a wildcard index
-rw-r--r--src/mongo/db/catalog/collection_info_cache_impl.cpp6
-rw-r--r--src/mongo/db/index/wildcard_access_method.h7
-rw-r--r--src/mongo/db/index/wildcard_key_generator.h7
-rw-r--r--src/mongo/db/query/get_executor.cpp12
-rw-r--r--src/mongo/db/query/index_entry.h19
-rw-r--r--src/mongo/db/query/plan_cache_indexability.cpp6
-rw-r--r--src/mongo/db/query/plan_cache_indexability.h14
-rw-r--r--src/mongo/db/query/plan_cache_indexability_test.cpp59
-rw-r--r--src/mongo/db/query/plan_cache_test.cpp54
-rw-r--r--src/mongo/db/query/planner_ixselect_test.cpp106
-rw-r--r--src/mongo/db/query/planner_wildcard_helpers.cpp8
-rw-r--r--src/mongo/db/query/query_planner_collation_test.cpp27
-rw-r--r--src/mongo/db/query/query_planner_wildcard_index_test.cpp170
-rw-r--r--src/mongo/s/chunk_manager.cpp5
14 files changed, 278 insertions, 222 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
diff --git a/src/mongo/s/chunk_manager.cpp b/src/mongo/s/chunk_manager.cpp
index e5841bd3863..e9fb081d505 100644
--- a/src/mongo/s/chunk_manager.cpp
+++ b/src/mongo/s/chunk_manager.cpp
@@ -288,9 +288,10 @@ IndexBounds ChunkManager::getIndexBoundsForQuery(const BSONObj& key,
false /* sparse */,
false /* unique */,
IndexEntry::Identifier{"shardkey"},
- NULL /* filterExpr */,
+ nullptr /* filterExpr */,
BSONObj(),
- NULL /* collator */);
+ nullptr, /* collator */
+ nullptr /* projExec */);
plannerParams.indices.push_back(indexEntry);
auto solutions = uassertStatusOK(QueryPlanner::plan(canonicalQuery, plannerParams));