diff options
Diffstat (limited to 'src/mongo/db/query/plan_cache_test.cpp')
-rw-r--r-- | src/mongo/db/query/plan_cache_test.cpp | 738 |
1 files changed, 3 insertions, 735 deletions
diff --git a/src/mongo/db/query/plan_cache_test.cpp b/src/mongo/db/query/plan_cache_test.cpp index 4ec8387bb75..cf9ef5e9e6f 100644 --- a/src/mongo/db/query/plan_cache_test.cpp +++ b/src/mongo/db/query/plan_cache_test.cpp @@ -45,6 +45,7 @@ #include "mongo/db/matcher/extensions_callback_noop.h" #include "mongo/db/pipeline/expression_context_for_test.h" #include "mongo/db/query/canonical_query_encoder.h" +#include "mongo/db/query/canonical_query_test_util.h" #include "mongo/db/query/collation/collator_interface_mock.h" #include "mongo/db/query/plan_cache_key_factory.h" #include "mongo/db/query/plan_ranker.h" @@ -70,8 +71,6 @@ using std::string; using std::unique_ptr; using std::vector; -static const NamespaceString nss("test.collection"); - PlanCacheKey makeKey(const CanonicalQuery& cq, const std::vector<CoreIndexInfo>& indexCores = {}) { PlanCacheIndexabilityState indexabilityState; indexabilityState.updateDiscriminators(indexCores); @@ -79,210 +78,7 @@ PlanCacheKey makeKey(const CanonicalQuery& cq, const std::vector<CoreIndexInfo>& StringBuilder indexabilityKeyBuilder; plan_cache_detail::encodeIndexability(cq.root(), indexabilityState, &indexabilityKeyBuilder); - return {cq.encodeKey(), indexabilityKeyBuilder.str()}; -} - -/** - * Utility functions to create a CanonicalQuery - */ -unique_ptr<CanonicalQuery> canonicalize(const BSONObj& queryObj) { - QueryTestServiceContext serviceContext; - auto opCtx = serviceContext.makeOperationContext(); - - auto findCommand = std::make_unique<FindCommandRequest>(nss); - findCommand->setFilter(queryObj); - const boost::intrusive_ptr<ExpressionContext> expCtx; - auto statusWithCQ = - CanonicalQuery::canonicalize(opCtx.get(), - std::move(findCommand), - false, - expCtx, - ExtensionsCallbackNoop(), - MatchExpressionParser::kAllowAllSpecialFeatures); - ASSERT_OK(statusWithCQ.getStatus()); - return std::move(statusWithCQ.getValue()); -} - -unique_ptr<CanonicalQuery> canonicalize(StringData queryStr) { - BSONObj queryObj = fromjson(queryStr.toString()); - return canonicalize(queryObj); -} - -unique_ptr<CanonicalQuery> canonicalize(BSONObj query, - BSONObj sort, - BSONObj proj, - BSONObj collation) { - QueryTestServiceContext serviceContext; - auto opCtx = serviceContext.makeOperationContext(); - - auto findCommand = std::make_unique<FindCommandRequest>(nss); - findCommand->setFilter(query); - findCommand->setSort(sort); - findCommand->setProjection(proj); - findCommand->setCollation(collation); - const boost::intrusive_ptr<ExpressionContext> expCtx; - auto statusWithCQ = - CanonicalQuery::canonicalize(opCtx.get(), - std::move(findCommand), - false, - expCtx, - ExtensionsCallbackNoop(), - MatchExpressionParser::kAllowAllSpecialFeatures); - ASSERT_OK(statusWithCQ.getStatus()); - return std::move(statusWithCQ.getValue()); -} - -unique_ptr<CanonicalQuery> canonicalize(const char* queryStr, - const char* sortStr, - const char* projStr, - const char* collationStr) { - return canonicalize( - fromjson(queryStr), fromjson(sortStr), fromjson(projStr), fromjson(collationStr)); -} - -unique_ptr<CanonicalQuery> canonicalize(const char* queryStr, - const char* sortStr, - const char* projStr, - long long skip, - long long limit, - const char* hintStr, - const char* minStr, - const char* maxStr) { - QueryTestServiceContext serviceContext; - auto opCtx = serviceContext.makeOperationContext(); - - auto findCommand = std::make_unique<FindCommandRequest>(nss); - findCommand->setFilter(fromjson(queryStr)); - findCommand->setSort(fromjson(sortStr)); - findCommand->setProjection(fromjson(projStr)); - if (skip) { - findCommand->setSkip(skip); - } - if (limit) { - findCommand->setLimit(limit); - } - findCommand->setHint(fromjson(hintStr)); - findCommand->setMin(fromjson(minStr)); - findCommand->setMax(fromjson(maxStr)); - const boost::intrusive_ptr<ExpressionContext> expCtx; - auto statusWithCQ = - CanonicalQuery::canonicalize(opCtx.get(), - std::move(findCommand), - false, - expCtx, - ExtensionsCallbackNoop(), - MatchExpressionParser::kAllowAllSpecialFeatures); - ASSERT_OK(statusWithCQ.getStatus()); - return std::move(statusWithCQ.getValue()); -} - -unique_ptr<CanonicalQuery> canonicalize(const char* queryStr, - const char* sortStr, - const char* projStr, - long long skip, - long long limit, - const char* hintStr, - const char* minStr, - const char* maxStr, - bool explain) { - QueryTestServiceContext serviceContext; - auto opCtx = serviceContext.makeOperationContext(); - - auto findCommand = std::make_unique<FindCommandRequest>(nss); - findCommand->setFilter(fromjson(queryStr)); - findCommand->setSort(fromjson(sortStr)); - findCommand->setProjection(fromjson(projStr)); - if (skip) { - findCommand->setSkip(skip); - } - if (limit) { - findCommand->setLimit(limit); - } - findCommand->setHint(fromjson(hintStr)); - findCommand->setMin(fromjson(minStr)); - findCommand->setMax(fromjson(maxStr)); - const boost::intrusive_ptr<ExpressionContext> expCtx; - auto statusWithCQ = - CanonicalQuery::canonicalize(opCtx.get(), - std::move(findCommand), - explain, - expCtx, - ExtensionsCallbackNoop(), - MatchExpressionParser::kAllowAllSpecialFeatures); - ASSERT_OK(statusWithCQ.getStatus()); - return std::move(statusWithCQ.getValue()); -} - -/** - * Check that the stable keys of 'a' and 'b' are equal, but the index discriminators are not. - */ -void assertPlanCacheKeysUnequalDueToDiscriminators(const PlanCacheKey& a, const PlanCacheKey& b) { - ASSERT_EQ(a.getQueryShapeStringData(), b.getQueryShapeStringData()); - ASSERT_EQ(a.getIndexabilityDiscriminators().size(), b.getIndexabilityDiscriminators().size()); - ASSERT_NE(a.getIndexabilityDiscriminators(), b.getIndexabilityDiscriminators()); - - // Should always have the begin and end delimiters. - ASSERT_GTE(a.getIndexabilityDiscriminators().size(), 2u); -} - -/** - * Check that the stable keys of 'a' and 'b' are not equal because of the last character. - */ -void assertPlanCacheKeysUnequalDueToForceClassicEngineValue(const PlanCacheKey& a, - const PlanCacheKey& b) { - auto aUnstablePart = a.getIndexabilityDiscriminators(); - auto bUnstablePart = b.getIndexabilityDiscriminators(); - auto aStablePart = a.getQueryShape(); - auto bStablePart = b.getQueryShape(); - - ASSERT_EQ(aUnstablePart, bUnstablePart); - // The last character of the stable part encodes the engine that uses this PlanCacheKey. So the - // stable parts except for the last character should be identical. - ASSERT_EQ(aStablePart.substr(0, aStablePart.size() - 1), - bStablePart.substr(0, bStablePart.size() - 1)); - - // Should have at least 1 byte to represent whether we must use the classic engine. - ASSERT_GTE(aStablePart.size(), 1); - - // The indexability discriminators should match. - ASSERT_EQ(a.getIndexabilityDiscriminators(), b.getIndexabilityDiscriminators()); - - // The stable parts should not match because of the last character. - ASSERT_NE(aStablePart, bStablePart); - ASSERT_NE(aStablePart.back(), bStablePart.back()); -} - -/** - * Utility function to create MatchExpression - */ -unique_ptr<MatchExpression> parseMatchExpression(const BSONObj& obj) { - boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); - StatusWithMatchExpression status = - MatchExpressionParser::parse(obj, - std::move(expCtx), - ExtensionsCallbackNoop(), - MatchExpressionParser::kAllowAllSpecialFeatures); - if (!status.isOK()) { - str::stream ss; - ss << "failed to parse query: " << obj.toString() - << ". Reason: " << status.getStatus().toString(); - FAIL(ss); - } - - return std::move(status.getValue()); -} - -void assertEquivalent(const char* queryStr, - const MatchExpression* expected, - const MatchExpression* actual) { - if (actual->equivalent(expected)) { - return; - } - str::stream ss; - ss << "Match expressions are not equivalent." - << "\nOriginal query: " << queryStr << "\nExpected: " << expected->debugString() - << "\nActual: " << actual->debugString(); - FAIL(ss); + return {PlanCacheKeyInfo{cq.encodeKey(), indexabilityKeyBuilder.str()}}; } // Helper which constructs a $** IndexEntry and returns it along with an owned ProjectionExecutor. @@ -308,21 +104,6 @@ std::pair<IndexEntry, std::unique_ptr<WildcardProjection>> makeWildcardEntry(BSO std::move(wcProj)}; } -// A version of the above for CoreIndexInfo, used for plan cache update tests. -std::pair<CoreIndexInfo, std::unique_ptr<WildcardProjection>> makeWildcardUpdate( - BSONObj keyPattern) { - auto wcProj = std::make_unique<WildcardProjection>( - WildcardKeyGenerator::createProjectionExecutor(keyPattern, {})); - return {CoreIndexInfo(keyPattern, - IndexNames::nameToType(IndexNames::findPluginName(keyPattern)), - false, // sparse - IndexEntry::Identifier{"indexName"}, // name - nullptr, // filterExpr - nullptr, // collation - wcProj.get()), // wildcard - std::move(wcProj)}; -} - // // Tests for CachedSolution // @@ -1294,7 +1075,7 @@ protected: }; const std::string mockKey("mock_cache_key"); -const PlanCacheKey CachePlanSelectionTest::ck(mockKey, ""); +const PlanCacheKey CachePlanSelectionTest::ck{PlanCacheKeyInfo{mockKey, ""}}; // // Equality @@ -1863,495 +1644,6 @@ TEST_F(CachePlanSelectionTest, ContainedOrAndIntersection) { "]}}}}"); } -// When a sparse index is present, computeKey() should generate different keys depending on -// whether or not the predicates in the given query can use the index. -TEST(PlanCacheTest, ComputeKeySparseIndex) { - PlanCache planCache(5000); - const auto keyPattern = BSON("a" << 1); - const std::vector<CoreIndexInfo> indexCores = { - CoreIndexInfo(keyPattern, - IndexNames::nameToType(IndexNames::findPluginName(keyPattern)), - true, // sparse - IndexEntry::Identifier{""})}; // name - - unique_ptr<CanonicalQuery> cqEqNumber(canonicalize("{a: 0}}")); - unique_ptr<CanonicalQuery> cqEqString(canonicalize("{a: 'x'}}")); - unique_ptr<CanonicalQuery> cqEqNull(canonicalize("{a: null}}")); - - // 'cqEqNumber' and 'cqEqString' get the same key, since both are compatible with this - // index. - const auto eqNumberKey = makeKey(*cqEqNumber, indexCores); - const auto eqStringKey = makeKey(*cqEqString, indexCores); - ASSERT_EQ(eqNumberKey, eqStringKey); - - // 'cqEqNull' gets a different key, since it is not compatible with this index. - const auto eqNullKey = makeKey(*cqEqNull, indexCores); - ASSERT_NOT_EQUALS(eqNullKey, eqNumberKey); - - assertPlanCacheKeysUnequalDueToDiscriminators(eqNullKey, eqNumberKey); - assertPlanCacheKeysUnequalDueToDiscriminators(eqNullKey, eqStringKey); -} - -// When a partial index is present, computeKey() should generate different keys depending on -// whether or not the predicates in the given query "match" the predicates in the partial index -// filter. -TEST(PlanCacheTest, ComputeKeyPartialIndex) { - BSONObj filterObj = BSON("f" << BSON("$gt" << 0)); - unique_ptr<MatchExpression> filterExpr(parseMatchExpression(filterObj)); - - PlanCache planCache(5000); - const auto keyPattern = BSON("a" << 1); - const std::vector<CoreIndexInfo> indexCores = { - CoreIndexInfo(keyPattern, - IndexNames::nameToType(IndexNames::findPluginName(keyPattern)), - false, // sparse - IndexEntry::Identifier{""}, // name - filterExpr.get())}; // filterExpr - - unique_ptr<CanonicalQuery> cqGtNegativeFive(canonicalize("{f: {$gt: -5}}")); - unique_ptr<CanonicalQuery> cqGtZero(canonicalize("{f: {$gt: 0}}")); - unique_ptr<CanonicalQuery> cqGtFive(canonicalize("{f: {$gt: 5}}")); - - // 'cqGtZero' and 'cqGtFive' get the same key, since both are compatible with this index. - ASSERT_EQ(makeKey(*cqGtZero, indexCores), makeKey(*cqGtFive, indexCores)); - - // 'cqGtNegativeFive' gets a different key, since it is not compatible with this index. - assertPlanCacheKeysUnequalDueToDiscriminators(makeKey(*cqGtNegativeFive, indexCores), - makeKey(*cqGtZero, indexCores)); -} - -// Query shapes should get the same plan cache key if they have the same collation indexability. -TEST(PlanCacheTest, ComputeKeyCollationIndex) { - CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); - - PlanCache planCache(5000); - const auto keyPattern = BSON("a" << 1); - const std::vector<CoreIndexInfo> indexCores = { - CoreIndexInfo(keyPattern, - IndexNames::nameToType(IndexNames::findPluginName(keyPattern)), - false, // sparse - IndexEntry::Identifier{""}, // name - nullptr, // filterExpr - &collator)}; // collation - - unique_ptr<CanonicalQuery> containsString(canonicalize("{a: 'abc'}")); - unique_ptr<CanonicalQuery> containsObject(canonicalize("{a: {b: 'abc'}}")); - unique_ptr<CanonicalQuery> containsArray(canonicalize("{a: ['abc', 'xyz']}")); - unique_ptr<CanonicalQuery> noStrings(canonicalize("{a: 5}")); - unique_ptr<CanonicalQuery> containsStringHasCollation( - canonicalize("{a: 'abc'}", "{}", "{}", "{locale: 'mock_reverse_string'}")); - - // 'containsString', 'containsObject', and 'containsArray' have the same key, since none are - // compatible with the index. - ASSERT_EQ(makeKey(*containsString, indexCores), makeKey(*containsObject, indexCores)); - ASSERT_EQ(makeKey(*containsString, indexCores), makeKey(*containsArray, indexCores)); - - // 'noStrings' gets a different key since it is compatible with the index. - assertPlanCacheKeysUnequalDueToDiscriminators(makeKey(*containsString, indexCores), - makeKey(*noStrings, indexCores)); - ASSERT_EQ(makeKey(*containsString, indexCores).getIndexabilityDiscriminators(), "<0>"); - ASSERT_EQ(makeKey(*noStrings, indexCores).getIndexabilityDiscriminators(), "<1>"); - - // 'noStrings' and 'containsStringHasCollation' get different keys, since the collation - // specified in the query is considered part of its shape. However, they have the same index - // compatibility, so the unstable part of their PlanCacheKeys should be the same. - PlanCacheKey noStringKey = makeKey(*noStrings, indexCores); - PlanCacheKey withStringAndCollationKey = makeKey(*containsStringHasCollation, indexCores); - ASSERT_NE(noStringKey, withStringAndCollationKey); - ASSERT_EQ(noStringKey.getIndexabilityDiscriminators(), - withStringAndCollationKey.getIndexabilityDiscriminators()); - ASSERT_NE(noStringKey.getQueryShapeStringData(), - withStringAndCollationKey.getQueryShapeStringData()); - - unique_ptr<CanonicalQuery> inContainsString(canonicalize("{a: {$in: [1, 'abc', 2]}}")); - unique_ptr<CanonicalQuery> inContainsObject(canonicalize("{a: {$in: [1, {b: 'abc'}, 2]}}")); - unique_ptr<CanonicalQuery> inContainsArray(canonicalize("{a: {$in: [1, ['abc', 'xyz'], 2]}}")); - unique_ptr<CanonicalQuery> inNoStrings(canonicalize("{a: {$in: [1, 2]}}")); - unique_ptr<CanonicalQuery> inContainsStringHasCollation( - canonicalize("{a: {$in: [1, 'abc', 2]}}", "{}", "{}", "{locale: 'mock_reverse_string'}")); - - // 'inContainsString', 'inContainsObject', and 'inContainsArray' have the same key, since none - // are compatible with the index. - ASSERT_EQ(makeKey(*inContainsString, indexCores), makeKey(*inContainsObject, indexCores)); - ASSERT_EQ(makeKey(*inContainsString, indexCores), makeKey(*inContainsArray, indexCores)); - - // 'inNoStrings' gets a different key since it is compatible with the index. - assertPlanCacheKeysUnequalDueToDiscriminators(makeKey(*inContainsString, indexCores), - makeKey(*inNoStrings, indexCores)); - ASSERT_EQ(makeKey(*inContainsString, indexCores).getIndexabilityDiscriminators(), "<0>"); - ASSERT_EQ(makeKey(*inNoStrings, indexCores).getIndexabilityDiscriminators(), "<1>"); - - // 'inNoStrings' and 'inContainsStringHasCollation' get the same key since they compatible with - // the index. - ASSERT_NE(makeKey(*inNoStrings, indexCores), - makeKey(*inContainsStringHasCollation, indexCores)); - ASSERT_EQ(makeKey(*inNoStrings, indexCores).getIndexabilityDiscriminators(), - makeKey(*inContainsStringHasCollation, indexCores).getIndexabilityDiscriminators()); -} - -TEST(PlanCacheTest, ComputeKeyWildcardIndex) { - auto entryProjUpdatePair = makeWildcardUpdate(BSON("a.$**" << 1)); - - PlanCache planCache(5000); - const std::vector<CoreIndexInfo> indexCores = {entryProjUpdatePair.first}; - - // Used to check that two queries have the same shape when no indexes are present. - PlanCache planCacheWithNoIndexes(5000); - - // Compatible with index. - unique_ptr<CanonicalQuery> usesPathWithScalar(canonicalize("{a: 'abcdef'}")); - unique_ptr<CanonicalQuery> usesPathWithEmptyArray(canonicalize("{a: []}")); - - // Not compatible with index. - unique_ptr<CanonicalQuery> usesPathWithObject(canonicalize("{a: {b: 'abc'}}")); - unique_ptr<CanonicalQuery> usesPathWithArray(canonicalize("{a: [1, 2]}")); - unique_ptr<CanonicalQuery> usesPathWithArrayContainingObject(canonicalize("{a: [1, {b: 1}]}")); - unique_ptr<CanonicalQuery> usesPathWithEmptyObject(canonicalize("{a: {}}")); - unique_ptr<CanonicalQuery> doesNotUsePath(canonicalize("{b: 1234}")); - - // Check that the queries which are compatible with the index have the same key. - ASSERT_EQ(makeKey(*usesPathWithScalar, indexCores), - makeKey(*usesPathWithEmptyArray, indexCores)); - - // Check that the queries which have the same path as the index, but aren't supported, have - // different keys. - ASSERT_EQ(makeKey(*usesPathWithScalar), makeKey(*usesPathWithObject)); - assertPlanCacheKeysUnequalDueToDiscriminators(makeKey(*usesPathWithScalar, indexCores), - makeKey(*usesPathWithObject, indexCores)); - ASSERT_EQ(makeKey(*usesPathWithScalar, indexCores).getIndexabilityDiscriminators(), "<1>"); - ASSERT_EQ(makeKey(*usesPathWithObject, indexCores).getIndexabilityDiscriminators(), "<0>"); - - ASSERT_EQ(makeKey(*usesPathWithObject, indexCores), makeKey(*usesPathWithArray, indexCores)); - ASSERT_EQ(makeKey(*usesPathWithObject, indexCores), - makeKey(*usesPathWithArrayContainingObject, indexCores)); - - // The query on 'b' should have a completely different plan cache key (both with and without a - // wildcard index). - ASSERT_NE(makeKey(*usesPathWithScalar), makeKey(*doesNotUsePath)); - ASSERT_NE(makeKey(*usesPathWithScalar, indexCores), makeKey(*doesNotUsePath, indexCores)); - ASSERT_NE(makeKey(*usesPathWithObject), makeKey(*doesNotUsePath)); - ASSERT_NE(makeKey(*usesPathWithObject, indexCores), makeKey(*doesNotUsePath, indexCores)); - - // More complex queries with similar shapes. This is to ensure that plan cache key encoding - // correctly traverses the expression tree. - auto orQueryWithOneBranchAllowed = canonicalize("{$or: [{a: 3}, {a: {$gt: [1,2]}}]}"); - // Same shape except 'a' is compared to an object. - auto orQueryWithNoBranchesAllowed = - canonicalize("{$or: [{a: {someobject: 1}}, {a: {$gt: [1,2]}}]}"); - // The two queries should have the same shape when no indexes are present, but different shapes - // when a $** index is present. - ASSERT_EQ(makeKey(*orQueryWithOneBranchAllowed), makeKey(*orQueryWithNoBranchesAllowed)); - assertPlanCacheKeysUnequalDueToDiscriminators( - makeKey(*orQueryWithOneBranchAllowed, indexCores), - makeKey(*orQueryWithNoBranchesAllowed, indexCores)); - ASSERT_EQ(makeKey(*orQueryWithOneBranchAllowed, indexCores).getIndexabilityDiscriminators(), - "<1><0>"); - ASSERT_EQ(makeKey(*orQueryWithNoBranchesAllowed, indexCores).getIndexabilityDiscriminators(), - "<0><0>"); -} - -TEST(PlanCacheTest, ComputeKeyWildcardIndexDiscriminatesEqualityToEmptyObj) { - auto entryProjUpdatePair = makeWildcardUpdate(BSON("a.$**" << 1)); - - PlanCache planCache(5000); - const std::vector<CoreIndexInfo> indexCores = {entryProjUpdatePair.first}; - - // Equality to empty obj and equality to non-empty obj have different plan cache keys. - std::unique_ptr<CanonicalQuery> equalsEmptyObj(canonicalize("{a: {}}")); - std::unique_ptr<CanonicalQuery> equalsNonEmptyObj(canonicalize("{a: {b: 1}}")); - assertPlanCacheKeysUnequalDueToDiscriminators(makeKey(*equalsEmptyObj, indexCores), - makeKey(*equalsNonEmptyObj, indexCores)); - ASSERT_EQ(makeKey(*equalsNonEmptyObj, indexCores).getIndexabilityDiscriminators(), "<0>"); - ASSERT_EQ(makeKey(*equalsEmptyObj, indexCores).getIndexabilityDiscriminators(), "<1>"); - - // $in with empty obj and $in with non-empty obj have different plan cache keys. - std::unique_ptr<CanonicalQuery> inWithEmptyObj(canonicalize("{a: {$in: [{}]}}")); - std::unique_ptr<CanonicalQuery> inWithNonEmptyObj(canonicalize("{a: {$in: [{b: 1}]}}")); - assertPlanCacheKeysUnequalDueToDiscriminators(makeKey(*inWithEmptyObj, indexCores), - makeKey(*inWithNonEmptyObj, indexCores)); - ASSERT_EQ(makeKey(*inWithNonEmptyObj, indexCores).getIndexabilityDiscriminators(), "<0>"); - ASSERT_EQ(makeKey(*inWithEmptyObj, indexCores).getIndexabilityDiscriminators(), "<1>"); -} - -TEST(PlanCacheTest, ComputeKeyWildcardDiscriminatesCorrectlyBasedOnPartialFilterExpression) { - BSONObj filterObj = BSON("x" << BSON("$gt" << 0)); - std::unique_ptr<MatchExpression> filterExpr(parseMatchExpression(filterObj)); - - auto entryProjUpdatePair = makeWildcardUpdate(BSON("$**" << 1)); - auto indexInfo = std::move(entryProjUpdatePair.first); - indexInfo.filterExpr = filterExpr.get(); - - PlanCache planCache(5000); - const std::vector<CoreIndexInfo> indexCores = {indexInfo}; - - // Test that queries on field 'x' are discriminated based on their relationship with the partial - // filter expression. - { - auto compatibleWithFilter = canonicalize("{x: {$eq: 5}}"); - auto incompatibleWithFilter = canonicalize("{x: {$eq: -5}}"); - auto compatibleKey = makeKey(*compatibleWithFilter, indexCores); - auto incompatibleKey = makeKey(*incompatibleWithFilter, indexCores); - - assertPlanCacheKeysUnequalDueToDiscriminators(compatibleKey, incompatibleKey); - // The discriminator strings have the format "<xx>". That is, there are two discriminator - // bits for the "x" predicate, the first pertaining to the partialFilterExpression and the - // second around applicability to the wildcard index. - ASSERT_EQ(compatibleKey.getIndexabilityDiscriminators(), "<11>"); - ASSERT_EQ(incompatibleKey.getIndexabilityDiscriminators(), "<01>"); - } - - // The partialFilterExpression should lead to a discriminator over field 'x', but not over 'y'. - // (Separately, there are wildcard-related discriminator bits for both 'x' and 'y'.) - { - auto compatibleWithFilter = canonicalize("{x: {$eq: 5}, y: 1}"); - auto incompatibleWithFilter = canonicalize("{x: {$eq: -5}, y: 1}"); - auto compatibleKey = makeKey(*compatibleWithFilter, indexCores); - auto incompatibleKey = makeKey(*incompatibleWithFilter, indexCores); - - assertPlanCacheKeysUnequalDueToDiscriminators(compatibleKey, incompatibleKey); - // The discriminator strings have the format "<xx><y>". That is, there are two discriminator - // bits for the "x" predicate (the first pertaining to the partialFilterExpression, the - // second around applicability to the wildcard index) and one discriminator bit for "y". - ASSERT_EQ(compatibleKey.getIndexabilityDiscriminators(), "<11><1>"); - ASSERT_EQ(incompatibleKey.getIndexabilityDiscriminators(), "<01><1>"); - } - - // $eq:null predicates cannot be assigned to a wildcard index. Make sure that this is - // discrimated correctly. This test is designed to reproduce SERVER-48614. - { - auto compatibleQuery = canonicalize("{x: {$eq: 5}, y: 1}"); - auto incompatibleQuery = canonicalize("{x: {$eq: 5}, y: null}"); - auto compatibleKey = makeKey(*compatibleQuery, indexCores); - auto incompatibleKey = makeKey(*incompatibleQuery, indexCores); - - assertPlanCacheKeysUnequalDueToDiscriminators(compatibleKey, incompatibleKey); - // The discriminator strings have the format "<xx><y>". That is, there are two discriminator - // bits for the "x" predicate (the first pertaining to the partialFilterExpression, the - // second around applicability to the wildcard index) and one discriminator bit for "y". - ASSERT_EQ(compatibleKey.getIndexabilityDiscriminators(), "<11><1>"); - ASSERT_EQ(incompatibleKey.getIndexabilityDiscriminators(), "<11><0>"); - } - - // Test that the discriminators are correct for an $eq:null predicate on 'x'. This predicate is - // imcompatible for two reasons: null equality predicates cannot be answered by wildcard - // indexes, and the predicate is not compatible with the partial filter expression. This should - // result in two "0" bits inside the discriminator string. - { - auto key = makeKey(*canonicalize("{x: {$eq: null}}"), indexCores); - ASSERT_EQ(key.getIndexabilityDiscriminators(), "<00>"); - } -} - -TEST(PlanCacheTest, ComputeKeyWildcardDiscriminatesCorrectlyWithPartialFilterAndExpression) { - // Partial filter is an AND of multiple conditions. - BSONObj filterObj = BSON("x" << BSON("$gt" << 0) << "y" << BSON("$gt" << 0)); - std::unique_ptr<MatchExpression> filterExpr(parseMatchExpression(filterObj)); - - auto entryProjUpdatePair = makeWildcardUpdate(BSON("$**" << 1)); - auto indexInfo = std::move(entryProjUpdatePair.first); - indexInfo.filterExpr = filterExpr.get(); - - PlanCache planCache(5000); - const std::vector<CoreIndexInfo> indexCores = {indexInfo}; - - { - // The discriminators should have the format <xx><yy><z>. The 'z' predicate has just one - // discriminator because it is not referenced in the partial filter expression. All - // predicates are compatible. - auto key = makeKey(*canonicalize("{x: {$eq: 1}, y: {$eq: 2}, z: {$eq: 3}}"), indexCores); - ASSERT_EQ(key.getIndexabilityDiscriminators(), "<11><11><1>"); - } - - { - // The discriminators should have the format <xx><yy><z>. The 'y' predicate is not - // compatible with the partial filter expression, leading to one of the 'y' bits being set - // to zero. - auto key = makeKey(*canonicalize("{x: {$eq: 1}, y: {$eq: -2}, z: {$eq: 3}}"), indexCores); - ASSERT_EQ(key.getIndexabilityDiscriminators(), "<11><01><1>"); - } -} - -TEST(PlanCacheTest, ComputeKeyWildcardDiscriminatesCorrectlyWithPartialFilterOnNestedField) { - BSONObj filterObj = BSON("x.y" << BSON("$gt" << 0)); - std::unique_ptr<MatchExpression> filterExpr(parseMatchExpression(filterObj)); - - auto entryProjUpdatePair = makeWildcardUpdate(BSON("$**" << 1)); - auto indexInfo = std::move(entryProjUpdatePair.first); - indexInfo.filterExpr = filterExpr.get(); - - PlanCache planCache(5000); - const std::vector<CoreIndexInfo> indexCores = {indexInfo}; - - { - // The discriminators have the format <x><(x.y)(x.y)<y>. All predicates are compatible - auto key = - makeKey(*canonicalize("{x: {$eq: 1}, y: {$eq: 2}, 'x.y': {$eq: 3}}"), indexCores); - ASSERT_EQ(key.getIndexabilityDiscriminators(), "<1><11><1>"); - } - - { - // Here, the predicate on "x.y" is not compatible with the partial filter expression. - auto key = - makeKey(*canonicalize("{x: {$eq: 1}, y: {$eq: 2}, 'x.y': {$eq: -3}}"), indexCores); - ASSERT_EQ(key.getIndexabilityDiscriminators(), "<1><01><1>"); - } -} - -TEST(PlanCacheTest, ComputeKeyDiscriminatesCorrectlyWithPartialFilterAndWildcardProjection) { - BSONObj filterObj = BSON("x" << BSON("$gt" << 0)); - std::unique_ptr<MatchExpression> filterExpr(parseMatchExpression(filterObj)); - - auto entryProjUpdatePair = makeWildcardUpdate(BSON("y.$**" << 1)); - auto indexInfo = std::move(entryProjUpdatePair.first); - indexInfo.filterExpr = filterExpr.get(); - - PlanCache planCache(5000); - const std::vector<CoreIndexInfo> indexCores = {indexInfo}; - - { - // The discriminators have the format <x><y>. The discriminator for 'x' indicates whether - // the predicate is compatible with the partial filter expression, whereas the disciminator - // for 'y' is about compatibility with the wildcard index. - auto key = makeKey(*canonicalize("{x: {$eq: 1}, y: {$eq: 2}, z: {$eq: 3}}"), indexCores); - ASSERT_EQ(key.getIndexabilityDiscriminators(), "<1><1>"); - } - - { - // Similar to the previous case, except with an 'x' predicate that is incompatible with the - // partial filter expression. - auto key = makeKey(*canonicalize("{x: {$eq: -1}, y: {$eq: 2}, z: {$eq: 3}}"), indexCores); - ASSERT_EQ(key.getIndexabilityDiscriminators(), "<0><1>"); - } - - { - // Case where the 'y' predicate is not compatible with the wildcard index. - auto key = makeKey(*canonicalize("{x: {$eq: 1}, y: {$eq: null}, z: {$eq: 3}}"), indexCores); - ASSERT_EQ(key.getIndexabilityDiscriminators(), "<1><0>"); - } -} - -TEST(PlanCacheTest, StableKeyDoesNotChangeAcrossIndexCreation) { - PlanCache planCache(5000); - unique_ptr<CanonicalQuery> cq(canonicalize("{a: 0}}")); - const PlanCacheKey preIndexKey = makeKey(*cq); - const auto preIndexStableKey = preIndexKey.getQueryShape(); - ASSERT_EQ(preIndexKey.getIndexabilityDiscriminators(), ""); - - const auto keyPattern = BSON("a" << 1); - // Create a sparse index (which requires a discriminator). - const std::vector<CoreIndexInfo> indexCores = { - CoreIndexInfo(keyPattern, - IndexNames::nameToType(IndexNames::findPluginName(keyPattern)), - true, // sparse - IndexEntry::Identifier{""})}; // name - - const PlanCacheKey postIndexKey = makeKey(*cq, indexCores); - const auto postIndexStableKey = postIndexKey.getQueryShape(); - ASSERT_NE(preIndexKey, postIndexKey); - ASSERT_EQ(preIndexStableKey, postIndexStableKey); - ASSERT_EQ(postIndexKey.getIndexabilityDiscriminators(), "<1>"); -} - -TEST(PlanCacheTest, ComputeKeyNotEqualsArray) { - PlanCache planCache(5000); - unique_ptr<CanonicalQuery> cqNeArray(canonicalize("{a: {$ne: [1]}}")); - unique_ptr<CanonicalQuery> cqNeScalar(canonicalize("{a: {$ne: 123}}")); - - const PlanCacheKey noIndexNeArrayKey = makeKey(*cqNeArray); - const PlanCacheKey noIndexNeScalarKey = makeKey(*cqNeScalar); - ASSERT_EQ(noIndexNeArrayKey.getIndexabilityDiscriminators(), "<0>"); - ASSERT_EQ(noIndexNeScalarKey.getIndexabilityDiscriminators(), "<1>"); - ASSERT_EQ(noIndexNeScalarKey.getQueryShape(), noIndexNeArrayKey.getQueryShape()); - - const auto keyPattern = BSON("a" << 1); - // Create a normal btree index. It will have a discriminator. - const std::vector<CoreIndexInfo> indexCores = { - CoreIndexInfo(keyPattern, - IndexNames::nameToType(IndexNames::findPluginName(keyPattern)), - false, // sparse - IndexEntry::Identifier{""})}; // name*/ - - const PlanCacheKey withIndexNeArrayKey = makeKey(*cqNeArray, indexCores); - const PlanCacheKey withIndexNeScalarKey = makeKey(*cqNeScalar, indexCores); - - ASSERT_NE(noIndexNeArrayKey, withIndexNeArrayKey); - ASSERT_EQ(noIndexNeArrayKey.getQueryShape(), withIndexNeArrayKey.getQueryShape()); - - ASSERT_EQ(noIndexNeScalarKey.getQueryShape(), withIndexNeScalarKey.getQueryShape()); - // There will be one discriminator for the $not and another for the leaf node ({$eq: 123}). - ASSERT_EQ(withIndexNeScalarKey.getIndexabilityDiscriminators(), "<1><1>"); - // There will be one discriminator for the $not and another for the leaf node ({$eq: [1]}). - // Since the index can support equality to an array, the second discriminator will have a value - // of '1'. - ASSERT_EQ(withIndexNeArrayKey.getIndexabilityDiscriminators(), "<0><1>"); -} - -TEST(PlanCacheTest, ComputeKeyNinArray) { - PlanCache planCache(5000); - unique_ptr<CanonicalQuery> cqNinArray(canonicalize("{a: {$nin: [123, [1]]}}")); - unique_ptr<CanonicalQuery> cqNinScalar(canonicalize("{a: {$nin: [123, 456]}}")); - - const PlanCacheKey noIndexNinArrayKey = makeKey(*cqNinArray); - const PlanCacheKey noIndexNinScalarKey = makeKey(*cqNinScalar); - ASSERT_EQ(noIndexNinArrayKey.getIndexabilityDiscriminators(), "<0>"); - ASSERT_EQ(noIndexNinScalarKey.getIndexabilityDiscriminators(), "<1>"); - ASSERT_EQ(noIndexNinScalarKey.getQueryShape(), noIndexNinArrayKey.getQueryShape()); - - const auto keyPattern = BSON("a" << 1); - // Create a normal btree index. It will have a discriminator. - const std::vector<CoreIndexInfo> indexCores = { - CoreIndexInfo(keyPattern, - IndexNames::nameToType(IndexNames::findPluginName(keyPattern)), - false, // sparse - IndexEntry::Identifier{""})}; // name - - const PlanCacheKey withIndexNinArrayKey = makeKey(*cqNinArray, indexCores); - const PlanCacheKey withIndexNinScalarKey = makeKey(*cqNinScalar, indexCores); - - // The unstable part of the key for $nin: [<array>] should have changed. The stable part, - // however, should not. - ASSERT_EQ(noIndexNinArrayKey.getQueryShape(), withIndexNinArrayKey.getQueryShape()); - ASSERT_NE(noIndexNinArrayKey.getIndexabilityDiscriminators(), - withIndexNinArrayKey.getIndexabilityDiscriminators()); - - ASSERT_EQ(noIndexNinScalarKey.getQueryShape(), withIndexNinScalarKey.getQueryShape()); - ASSERT_EQ(withIndexNinArrayKey.getIndexabilityDiscriminators(), "<0><1>"); - ASSERT_EQ(withIndexNinScalarKey.getIndexabilityDiscriminators(), "<1><1>"); -} - -// Test for a bug which would be easy to introduce. If we only inserted discriminators for some -// nodes, we would have a problem. For example if our "stable" key was: -// (or[nt[eqa],nt[eqa]]) -// And there was just one discriminator: -// <0> - -// Whether the discriminator referred to the first not-eq node or the second would be -// ambiguous. This would make it possible for two queries with different shapes (and different -// plans) to get the same plan cache key. We test that this does not happen for a simple example. -TEST(PlanCacheTest, PlanCacheKeyCollision) { - PlanCache planCache(5000); - unique_ptr<CanonicalQuery> cqNeA(canonicalize("{$or: [{a: {$ne: 5}}, {a: {$ne: [12]}}]}")); - unique_ptr<CanonicalQuery> cqNeB(canonicalize("{$or: [{a: {$ne: [12]}}, {a: {$ne: 5}}]}")); - - const PlanCacheKey keyA = makeKey(*cqNeA); - const PlanCacheKey keyB = makeKey(*cqNeB); - ASSERT_EQ(keyA.getQueryShape(), keyB.getQueryShape()); - ASSERT_NE(keyA.getIndexabilityDiscriminators(), keyB.getIndexabilityDiscriminators()); - const auto keyPattern = BSON("a" << 1); - // Create a normal btree index. It will have a discriminator. - std::vector<CoreIndexInfo> indexCores = { - CoreIndexInfo(keyPattern, - IndexNames::nameToType(IndexNames::findPluginName(keyPattern)), - false, // sparse - IndexEntry::Identifier{""})}; // name - const PlanCacheKey keyAWithIndex = makeKey(*cqNeA, indexCores); - const PlanCacheKey keyBWithIndex = makeKey(*cqNeB, indexCores); - - ASSERT_EQ(keyAWithIndex.getQueryShape(), keyBWithIndex.getQueryShape()); - ASSERT_NE(keyAWithIndex.getIndexabilityDiscriminators(), - keyBWithIndex.getIndexabilityDiscriminators()); -} - TEST(PlanCacheTest, PlanCacheSizeWithCRUDOperations) { PlanCache planCache(5000); unique_ptr<CanonicalQuery> cq(canonicalize("{a: 1, b: 1}")); @@ -2594,28 +1886,4 @@ TEST(PlanCacheTest, PlanCacheSizeWithMultiplePlanCaches) { ASSERT_EQ(PlanCacheEntry::planCacheTotalSizeEstimateBytes.get(), originalSize); } -TEST(PlanCacheTest, DifferentQueryEngines) { - const auto keyPattern = BSON("a" << 1); - const std::vector<CoreIndexInfo> indexCores = { - CoreIndexInfo(keyPattern, - IndexNames::nameToType(IndexNames::findPluginName(keyPattern)), - false, // sparse - IndexEntry::Identifier{""})}; // name - - // Helper to construct a plan cache key given the 'forceClassicEngine' flag. - auto constructPlanCacheKey = [&](bool forceClassicEngine) { - RAIIServerParameterControllerForTest controller{"internalQueryForceClassicEngine", - forceClassicEngine}; - const auto queryStr = "{a: 0}"; - unique_ptr<CanonicalQuery> cq(canonicalize(queryStr)); - return makeKey(*cq, indexCores); - }; - - const auto classicEngineKey = constructPlanCacheKey(false); - const auto noClassicEngineKey = constructPlanCacheKey(true); - - // Check that the two plan cache keys are not equal because the plans were created under - // different engines. - assertPlanCacheKeysUnequalDueToForceClassicEngineValue(classicEngineKey, noClassicEngineKey); -} } // namespace |