diff options
author | Svilen Mihaylov <svilen.mihaylov@mongodb.com> | 2022-06-22 20:27:30 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2022-06-22 22:37:28 +0000 |
commit | 3fa2961b6463ffa5a1ef1211ed2162965fa9cb19 (patch) | |
tree | 663ed41af0087cd2e21e7c970797ed93b76daf22 | |
parent | 41994ed8c080afb448b20c631a66bf782a9834c0 (diff) | |
download | mongo-3fa2961b6463ffa5a1ef1211ed2162965fa9cb19.tar.gz |
SERVER-67380 Improve handling of partial schema requirements
-rw-r--r-- | src/mongo/db/commands/cqf/cqf_aggregate.cpp | 10 | ||||
-rw-r--r-- | src/mongo/db/pipeline/abt/pipeline_test.cpp | 36 | ||||
-rw-r--r-- | src/mongo/db/query/optimizer/cascades/logical_rewriter.cpp | 39 | ||||
-rw-r--r-- | src/mongo/db/query/optimizer/physical_rewriter_optimizer_test.cpp | 54 | ||||
-rw-r--r-- | src/mongo/db/query/optimizer/rewrites/const_eval.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/query/optimizer/utils/utils.cpp | 183 | ||||
-rw-r--r-- | src/mongo/db/query/optimizer/utils/utils.h | 10 |
7 files changed, 173 insertions, 161 deletions
diff --git a/src/mongo/db/commands/cqf/cqf_aggregate.cpp b/src/mongo/db/commands/cqf/cqf_aggregate.cpp index 26f6bcf5cff..516a3f9ca2e 100644 --- a/src/mongo/db/commands/cqf/cqf_aggregate.cpp +++ b/src/mongo/db/commands/cqf/cqf_aggregate.cpp @@ -184,12 +184,16 @@ static opt::unordered_map<std::string, optimizer::IndexDefinition> buildIndexSpe // TODO: simplify expression. - PartialSchemaReqConversion conversion = convertExprToPartialSchemaReq(exprABT); - if (!conversion._success || conversion._hasEmptyInterval) { + auto conversion = convertExprToPartialSchemaReq(exprABT, true /*isFilterContext*/); + if (!conversion || conversion->_hasEmptyInterval) { // Unsatisfiable partial index filter? continue; } - partialIndexReqMap = std::move(conversion._reqMap); + tassert(6624257, + "Should not be seeing a partial index filter where we need to over-approximate", + !conversion->_retainPredicate); + + partialIndexReqMap = std::move(conversion->_reqMap); } // For now we assume distribution is Centralized. diff --git a/src/mongo/db/pipeline/abt/pipeline_test.cpp b/src/mongo/db/pipeline/abt/pipeline_test.cpp index 34f9d486d32..694047d6683 100644 --- a/src/mongo/db/pipeline/abt/pipeline_test.cpp +++ b/src/mongo/db/pipeline/abt/pipeline_test.cpp @@ -2326,13 +2326,15 @@ TEST(ABTTranslate, PartialIndex) { // The expression matches the pipeline. // By default the constant is translated as "int32". - auto conversionResult = convertExprToPartialSchemaReq(make<EvalFilter>( - make<PathGet>("b", - make<PathTraverse>(make<PathCompare>(Operations::Eq, Constant::int32(2)))), - make<Variable>(scanProjName))); - ASSERT_TRUE(conversionResult._success); - ASSERT_FALSE(conversionResult._hasEmptyInterval); - ASSERT_FALSE(conversionResult._retainPredicate); + auto conversionResult = convertExprToPartialSchemaReq( + make<EvalFilter>( + make<PathGet>( + "b", make<PathTraverse>(make<PathCompare>(Operations::Eq, Constant::int32(2)))), + make<Variable>(scanProjName)), + true /*isFilterContext*/); + ASSERT_TRUE(conversionResult.has_value()); + ASSERT_FALSE(conversionResult->_hasEmptyInterval); + ASSERT_FALSE(conversionResult->_retainPredicate); Metadata metadata = { {{scanDefName, @@ -2341,7 +2343,7 @@ TEST(ABTTranslate, PartialIndex) { IndexDefinition{{{makeIndexPath("a"), CollationOp::Ascending}}, true /*multiKey*/, {DistributionType::Centralized}, - std::move(conversionResult._reqMap)}}}}}}}; + std::move(conversionResult->_reqMap)}}}}}}}; ABT translated = translatePipeline( metadata, "[{$match: {'a': 3, 'b': 2}}]", scanProjName, scanDefName, prefixId); @@ -2394,13 +2396,15 @@ TEST(ABTTranslate, PartialIndexNegative) { ProjectionName scanProjName = prefixId.getNextId("scan"); // The expression does not match the pipeline. - auto conversionResult = convertExprToPartialSchemaReq(make<EvalFilter>( - make<PathGet>("b", - make<PathTraverse>(make<PathCompare>(Operations::Eq, Constant::int32(2)))), - make<Variable>(scanProjName))); - ASSERT_TRUE(conversionResult._success); - ASSERT_FALSE(conversionResult._hasEmptyInterval); - ASSERT_FALSE(conversionResult._retainPredicate); + auto conversionResult = convertExprToPartialSchemaReq( + make<EvalFilter>( + make<PathGet>( + "b", make<PathTraverse>(make<PathCompare>(Operations::Eq, Constant::int32(2)))), + make<Variable>(scanProjName)), + true /*isFilterContext*/); + ASSERT_TRUE(conversionResult.has_value()); + ASSERT_FALSE(conversionResult->_hasEmptyInterval); + ASSERT_FALSE(conversionResult->_retainPredicate); Metadata metadata = { {{scanDefName, @@ -2409,7 +2413,7 @@ TEST(ABTTranslate, PartialIndexNegative) { IndexDefinition{{{makeIndexPath("a"), CollationOp::Ascending}}, true /*multiKey*/, {DistributionType::Centralized}, - std::move(conversionResult._reqMap)}}}}}}}; + std::move(conversionResult->_reqMap)}}}}}}}; ABT translated = translatePipeline( metadata, "[{$match: {'a': 3, 'b': 3}}]", scanProjName, scanDefName, prefixId); diff --git a/src/mongo/db/query/optimizer/cascades/logical_rewriter.cpp b/src/mongo/db/query/optimizer/cascades/logical_rewriter.cpp index fd6bf9e1e40..4ecaf2c0795 100644 --- a/src/mongo/db/query/optimizer/cascades/logical_rewriter.cpp +++ b/src/mongo/db/query/optimizer/cascades/logical_rewriter.cpp @@ -624,16 +624,17 @@ static void convertFilterToSargableNode(ABT::reference_type node, return; } - PartialSchemaReqConversion conversion = convertExprToPartialSchemaReq(filterNode.getFilter()); - if (!conversion._success) { + auto conversion = + convertExprToPartialSchemaReq(filterNode.getFilter(), true /*isFilterContext*/); + if (!conversion) { return; } - if (conversion._hasEmptyInterval) { + if (conversion->_hasEmptyInterval) { addEmptyValueScanNode(ctx); return; } - for (const auto& entry : conversion._reqMap) { + for (const auto& entry : conversion->_reqMap) { uassert(6624111, "Filter partial schema requirement must contain a variable name.", !entry.first._projectionName.empty()); @@ -648,29 +649,29 @@ static void convertFilterToSargableNode(ABT::reference_type node, // If in substitution mode, disallow retaining original predicate. If in exploration mode, only // allow retaining the original predicate and if we have at least one index available. if constexpr (isSubstitution) { - if (conversion._retainPredicate) { + if (conversion->_retainPredicate) { return; } - } else if (!conversion._retainPredicate || scanDef.getIndexDefs().empty()) { + } else if (!conversion->_retainPredicate || scanDef.getIndexDefs().empty()) { return; } bool hasEmptyInterval = false; auto candidateIndexMap = computeCandidateIndexMap(ctx.getPrefixId(), indexingAvailability.getScanProjection(), - conversion._reqMap, + conversion->_reqMap, scanDef, hasEmptyInterval); if (hasEmptyInterval) { addEmptyValueScanNode(ctx); } else { - ABT sargableNode = make<SargableNode>(std::move(conversion._reqMap), + ABT sargableNode = make<SargableNode>(std::move(conversion->_reqMap), std::move(candidateIndexMap), IndexReqTarget::Complete, filterNode.getChild()); - if (conversion._retainPredicate) { + if (conversion->_retainPredicate) { const GroupIdType childGroupId = filterNode.getChild().cast<MemoLogicalDelegatorNode>()->getGroupId(); if (childGroupId == indexingAvailability.getScanGroupId()) { @@ -813,22 +814,24 @@ struct SubstituteConvert<EvaluationNode> { } // We still want to extract sargable nodes from EvalNode to use for PhysicalScans. - PartialSchemaReqConversion conversion = - convertExprToPartialSchemaReq(evalNode.getProjection()); + auto conversion = + convertExprToPartialSchemaReq(evalNode.getProjection(), false /*isFilterContext*/); + if (!conversion) { + return; + } uassert(6624165, "Should not be getting retainPredicate set for EvalNodes", - !conversion._retainPredicate); - - if (!conversion._success || conversion._reqMap.size() != 1) { + !conversion->_retainPredicate); + if (conversion->_reqMap.size() != 1) { // For evaluation nodes we expect to create a single entry. return; } - if (conversion._hasEmptyInterval) { + if (conversion->_hasEmptyInterval) { addEmptyValueScanNode(ctx); return; } - for (auto& entry : conversion._reqMap) { + for (auto& entry : conversion->_reqMap) { PartialSchemaRequirement& req = entry.second; req.setBoundProjectionName(evalNode.getProjectionName()); @@ -842,12 +845,12 @@ struct SubstituteConvert<EvaluationNode> { bool hasEmptyInterval = false; auto candidateIndexMap = computeCandidateIndexMap( - ctx.getPrefixId(), scanProjName, conversion._reqMap, scanDef, hasEmptyInterval); + ctx.getPrefixId(), scanProjName, conversion->_reqMap, scanDef, hasEmptyInterval); if (hasEmptyInterval) { addEmptyValueScanNode(ctx); } else { - ABT newNode = make<SargableNode>(std::move(conversion._reqMap), + ABT newNode = make<SargableNode>(std::move(conversion->_reqMap), std::move(candidateIndexMap), IndexReqTarget::Complete, evalNode.getChild()); diff --git a/src/mongo/db/query/optimizer/physical_rewriter_optimizer_test.cpp b/src/mongo/db/query/optimizer/physical_rewriter_optimizer_test.cpp index 6f6f6c743ed..58cbf9dee2b 100644 --- a/src/mongo/db/query/optimizer/physical_rewriter_optimizer_test.cpp +++ b/src/mongo/db/query/optimizer/physical_rewriter_optimizer_test.cpp @@ -4310,13 +4310,15 @@ TEST(PhysRewriter, PartialIndex1) { // TODO: Test cases where partial filter bound is a range which subsumes the query // requirement // TODO: (e.g. half open interval) - auto conversionResult = convertExprToPartialSchemaReq(make<EvalFilter>( - make<PathGet>("b", - make<PathTraverse>(make<PathCompare>(Operations::Eq, Constant::int64(2)))), - make<Variable>("root"))); - ASSERT_TRUE(conversionResult._success); - ASSERT_FALSE(conversionResult._hasEmptyInterval); - ASSERT_FALSE(conversionResult._retainPredicate); + auto conversionResult = convertExprToPartialSchemaReq( + make<EvalFilter>( + make<PathGet>( + "b", make<PathTraverse>(make<PathCompare>(Operations::Eq, Constant::int64(2)))), + make<Variable>("root")), + true /*isFilterContext*/); + ASSERT_TRUE(conversionResult.has_value()); + ASSERT_FALSE(conversionResult->_hasEmptyInterval); + ASSERT_FALSE(conversionResult->_retainPredicate); OptPhaseManager phaseManager( {OptPhaseManager::OptPhase::MemoSubstitutionPhase, @@ -4329,7 +4331,7 @@ TEST(PhysRewriter, PartialIndex1) { IndexDefinition{{{makeIndexPath("a"), CollationOp::Ascending}}, true /*isMultiKey*/, {DistributionType::Centralized}, - std::move(conversionResult._reqMap)}}}}}}}, + std::move(conversionResult->_reqMap)}}}}}}}, {true /*debugMode*/, 2 /*debugLevel*/, DebugInfo::kIterationLimitForTests}); ABT optimized = rootNode; @@ -4387,13 +4389,15 @@ TEST(PhysRewriter, PartialIndex2) { ABT rootNode = make<RootNode>(ProjectionRequirement{ProjectionNameVector{"root"}}, std::move(filterANode)); - auto conversionResult = convertExprToPartialSchemaReq(make<EvalFilter>( - make<PathGet>("a", - make<PathTraverse>(make<PathCompare>(Operations::Eq, Constant::int64(3)))), - make<Variable>("root"))); - ASSERT_TRUE(conversionResult._success); - ASSERT_FALSE(conversionResult._hasEmptyInterval); - ASSERT_FALSE(conversionResult._retainPredicate); + auto conversionResult = convertExprToPartialSchemaReq( + make<EvalFilter>( + make<PathGet>( + "a", make<PathTraverse>(make<PathCompare>(Operations::Eq, Constant::int64(3)))), + make<Variable>("root")), + true /*isFilterContext*/); + ASSERT_TRUE(conversionResult.has_value()); + ASSERT_FALSE(conversionResult->_hasEmptyInterval); + ASSERT_FALSE(conversionResult->_retainPredicate); OptPhaseManager phaseManager( {OptPhaseManager::OptPhase::MemoSubstitutionPhase, @@ -4406,7 +4410,7 @@ TEST(PhysRewriter, PartialIndex2) { IndexDefinition{{{makeIndexPath("a"), CollationOp::Ascending}}, true /*isMultiKey*/, {DistributionType::Centralized}, - std::move(conversionResult._reqMap)}}}}}}}, + std::move(conversionResult->_reqMap)}}}}}}}, {true /*debugMode*/, 2 /*debugLevel*/, DebugInfo::kIterationLimitForTests}); ABT optimized = rootNode; @@ -4462,13 +4466,15 @@ TEST(PhysRewriter, PartialIndexReject) { ABT rootNode = make<RootNode>(ProjectionRequirement{ProjectionNameVector{"root"}}, std::move(filterBNode)); - auto conversionResult = convertExprToPartialSchemaReq(make<EvalFilter>( - make<PathGet>("b", - make<PathTraverse>(make<PathCompare>(Operations::Eq, Constant::int64(4)))), - make<Variable>("root"))); - ASSERT_TRUE(conversionResult._success); - ASSERT_FALSE(conversionResult._hasEmptyInterval); - ASSERT_FALSE(conversionResult._retainPredicate); + auto conversionResult = convertExprToPartialSchemaReq( + make<EvalFilter>( + make<PathGet>( + "b", make<PathTraverse>(make<PathCompare>(Operations::Eq, Constant::int64(4)))), + make<Variable>("root")), + true /*isFilterContext*/); + ASSERT_TRUE(conversionResult.has_value()); + ASSERT_FALSE(conversionResult->_hasEmptyInterval); + ASSERT_FALSE(conversionResult->_retainPredicate); OptPhaseManager phaseManager( {OptPhaseManager::OptPhase::MemoSubstitutionPhase, @@ -4481,7 +4487,7 @@ TEST(PhysRewriter, PartialIndexReject) { IndexDefinition{{{makeIndexPath("a"), CollationOp::Ascending}}, true /*isMultiKey*/, {DistributionType::Centralized}, - std::move(conversionResult._reqMap)}}}}}}}, + std::move(conversionResult->_reqMap)}}}}}}}, {true /*debugMode*/, 2 /*debugLevel*/, DebugInfo::kIterationLimitForTests}); ABT optimized = rootNode; diff --git a/src/mongo/db/query/optimizer/rewrites/const_eval.cpp b/src/mongo/db/query/optimizer/rewrites/const_eval.cpp index 89bfe74551f..0278e20700e 100644 --- a/src/mongo/db/query/optimizer/rewrites/const_eval.cpp +++ b/src/mongo/db/query/optimizer/rewrites/const_eval.cpp @@ -86,7 +86,7 @@ void ConstEval::removeUnusedEvalNodes() { // TODO: consider caching. // TODO: consider deriving IndexingAvailability. if (!_disableSargableInlining || - !convertExprToPartialSchemaReq(k->getProjection())._success) { + !convertExprToPartialSchemaReq(k->getProjection(), false /*isFilterContext*/)) { // Schedule node inlining as there is exactly one reference. _singleRef.emplace(v.front()); _changed = true; diff --git a/src/mongo/db/query/optimizer/utils/utils.cpp b/src/mongo/db/query/optimizer/utils/utils.cpp index 42476964493..da4be863228 100644 --- a/src/mongo/db/query/optimizer/utils/utils.cpp +++ b/src/mongo/db/query/optimizer/utils/utils.cpp @@ -340,18 +340,8 @@ VariableNameSetType collectVariableReferences(const ABT& n) { return NodeVariableTracker::collect(n); } -PartialSchemaReqConversion::PartialSchemaReqConversion() - : _success(false), - _bound(), - _reqMap(), - _hasIntersected(false), - _hasTraversed(false), - _hasEmptyInterval(false), - _retainPredicate(false) {} - PartialSchemaReqConversion::PartialSchemaReqConversion(PartialSchemaRequirements reqMap) - : _success(true), - _bound(), + : _bound(), _reqMap(std::move(reqMap)), _hasIntersected(false), _hasTraversed(false), @@ -359,8 +349,7 @@ PartialSchemaReqConversion::PartialSchemaReqConversion(PartialSchemaRequirements _retainPredicate(false) {} PartialSchemaReqConversion::PartialSchemaReqConversion(ABT bound) - : _success(true), - _bound(std::move(bound)), + : _bound(std::move(bound)), _reqMap(), _hasIntersected(false), _hasTraversed(false), @@ -372,23 +361,24 @@ PartialSchemaReqConversion::PartialSchemaReqConversion(ABT bound) */ class PartialSchemaReqConverter { public: - PartialSchemaReqConverter() = default; + using ResultType = boost::optional<PartialSchemaReqConversion>; - PartialSchemaReqConversion handleEvalPathAndEvalFilter(PartialSchemaReqConversion pathResult, - PartialSchemaReqConversion inputResult) { - if (!pathResult._success || !inputResult._success) { + PartialSchemaReqConverter(const bool isFilterContext) : _isFilterContext(isFilterContext) {} + + ResultType handleEvalPathAndEvalFilter(ResultType pathResult, ResultType inputResult) { + if (!pathResult || !inputResult) { return {}; } - if (pathResult._bound.has_value() || !inputResult._bound.has_value() || - !inputResult._reqMap.empty()) { + if (pathResult->_bound.has_value() || !inputResult->_bound.has_value() || + !inputResult->_reqMap.empty()) { return {}; } - if (auto boundPtr = inputResult._bound->cast<Variable>(); boundPtr != nullptr) { + if (auto boundPtr = inputResult->_bound->cast<Variable>(); boundPtr != nullptr) { const ProjectionName& boundVarName = boundPtr->name(); PartialSchemaRequirements newMap; - for (auto& [key, req] : pathResult._reqMap) { + for (auto& [key, req] : pathResult->_reqMap) { if (!key._projectionName.empty()) { return {}; } @@ -396,40 +386,40 @@ public: } PartialSchemaReqConversion result{std::move(newMap)}; - result._hasEmptyInterval = pathResult._hasEmptyInterval; - result._retainPredicate = pathResult._retainPredicate; + result._hasEmptyInterval = pathResult->_hasEmptyInterval; + result._retainPredicate = pathResult->_retainPredicate; return result; } return {}; } - PartialSchemaReqConversion transport(const ABT& n, - const EvalPath& evalPath, - PartialSchemaReqConversion pathResult, - PartialSchemaReqConversion inputResult) { + ResultType transport(const ABT& n, + const EvalPath& evalPath, + ResultType pathResult, + ResultType inputResult) { return handleEvalPathAndEvalFilter(std::move(pathResult), std::move(inputResult)); } - PartialSchemaReqConversion transport(const ABT& n, - const EvalFilter& evalFilter, - PartialSchemaReqConversion pathResult, - PartialSchemaReqConversion inputResult) { + ResultType transport(const ABT& n, + const EvalFilter& evalFilter, + ResultType pathResult, + ResultType inputResult) { return handleEvalPathAndEvalFilter(std::move(pathResult), std::move(inputResult)); } - static PartialSchemaReqConversion handleComposition(const bool isMultiplicative, - PartialSchemaReqConversion leftResult, - PartialSchemaReqConversion rightResult) { - if (!leftResult._success || !rightResult._success) { + static ResultType handleComposition(const bool isMultiplicative, + ResultType leftResult, + ResultType rightResult) { + if (!leftResult || !rightResult) { return {}; } - if (leftResult._bound.has_value() || rightResult._bound.has_value()) { + if (leftResult->_bound.has_value() || rightResult->_bound.has_value()) { return {}; } - auto& leftReqMap = leftResult._reqMap; - auto& rightReqMap = rightResult._reqMap; + auto& leftReqMap = leftResult->_reqMap; + auto& rightReqMap = rightResult->_reqMap; if (isMultiplicative) { { ProjectionRenames projectionRenames; @@ -441,7 +431,7 @@ public: } } - if (!leftResult._hasTraversed && !rightResult._hasTraversed) { + if (!leftResult->_hasTraversed && !rightResult->_hasTraversed) { // Intersect intervals only if we have not traversed. E.g. (-inf, 90) ^ (70, +inf) // becomes (70, 90). for (auto& [key, req] : leftReqMap) { @@ -449,7 +439,7 @@ public: if (newIntervals) { req.getIntervals() = std::move(newIntervals.get()); } else { - leftResult._hasEmptyInterval = true; + leftResult->_hasEmptyInterval = true; break; } } @@ -458,7 +448,7 @@ public: return {}; } - leftResult._hasIntersected = true; + leftResult->_hasIntersected = true; return leftResult; } @@ -537,32 +527,40 @@ public: rightPath.is<PathIdentity>()) { // leftPath = Id, rightPath = Traverse Id. combineIntervalsDNF(false /*intersect*/, leftIntervals, newInterval); - leftResult._retainPredicate = true; + leftResult->_retainPredicate = true; return leftResult; } else if (const auto rightTraversePtr = rightPath.cast<PathTraverse>(); rightTraversePtr != nullptr && rightTraversePtr->getPath().is<PathIdentity>() && leftPath.is<PathIdentity>()) { // leftPath = Traverse Id, rightPath = Id. combineIntervalsDNF(false /*intersect*/, rightIntervals, newInterval); - rightResult._retainPredicate = true; + rightResult->_retainPredicate = true; return rightResult; } return {}; } - PartialSchemaReqConversion transport(const ABT& n, - const PathComposeM& pathComposeM, - PartialSchemaReqConversion leftResult, - PartialSchemaReqConversion rightResult) { + ResultType transport(const ABT& n, + const PathComposeM& pathComposeM, + ResultType leftResult, + ResultType rightResult) { + if (!_isFilterContext) { + return {}; + } + return handleComposition( true /*isMultiplicative*/, std::move(leftResult), std::move(rightResult)); } - PartialSchemaReqConversion transport(const ABT& n, - const PathComposeA& pathComposeA, - PartialSchemaReqConversion leftResult, - PartialSchemaReqConversion rightResult) { + ResultType transport(const ABT& n, + const PathComposeA& pathComposeA, + ResultType leftResult, + ResultType rightResult) { + if (!_isFilterContext) { + return {}; + } + const auto& path1 = pathComposeA.getPath1(); const auto& path2 = pathComposeA.getPath2(); const auto& eqNull = make<PathCompare>(Operations::Eq, Constant::null()); @@ -574,9 +572,9 @@ public: auto intervalExpr = IntervalReqExpr::makeSingularDNF(IntervalRequirement{ {true /*inclusive*/, Constant::null()}, {true /*inclusive*/, Constant::null()}}); - return {PartialSchemaRequirements{ + return {{PartialSchemaRequirements{ {PartialSchemaKey{}, - PartialSchemaRequirement{"" /*boundProjectionName*/, std::move(intervalExpr)}}}}; + PartialSchemaRequirement{"" /*boundProjectionName*/, std::move(intervalExpr)}}}}}; } return handleComposition( @@ -584,19 +582,18 @@ public: } template <class T> - static PartialSchemaReqConversion handleGetAndTraverse(const ABT& n, - PartialSchemaReqConversion inputResult) { - if (!inputResult._success) { + static ResultType handleGetAndTraverse(const ABT& n, ResultType inputResult) { + if (!inputResult) { return {}; } - if (inputResult._bound.has_value()) { + if (inputResult->_bound.has_value()) { return {}; } // New map has keys with appended paths. PartialSchemaRequirements newMap; - for (auto& entry : inputResult._reqMap) { + for (auto& entry : inputResult->_reqMap) { if (!entry.first._projectionName.empty()) { return {}; } @@ -611,41 +608,39 @@ public: newMap.emplace(PartialSchemaKey{"", std::move(path)}, std::move(entry.second)); } - inputResult._reqMap = std::move(newMap); + inputResult->_reqMap = std::move(newMap); return inputResult; } - PartialSchemaReqConversion transport(const ABT& n, - const PathGet& pathGet, - PartialSchemaReqConversion inputResult) { + ResultType transport(const ABT& n, const PathGet& pathGet, ResultType inputResult) { return handleGetAndTraverse<PathGet>(n, std::move(inputResult)); } - PartialSchemaReqConversion transport(const ABT& n, - const PathTraverse& pathTraverse, - PartialSchemaReqConversion inputResult) { - if (inputResult._reqMap.size() > 1) { + ResultType transport(const ABT& n, const PathTraverse& pathTraverse, ResultType inputResult) { + if (!inputResult) { + return {}; + } + if (inputResult->_reqMap.size() > 1) { // Cannot append traverse if we have more than one requirement. return {}; } - PartialSchemaReqConversion result = - handleGetAndTraverse<PathTraverse>(n, std::move(inputResult)); - result._hasTraversed = true; + auto result = handleGetAndTraverse<PathTraverse>(n, std::move(inputResult)); + if (result) { + result->_hasTraversed = true; + } return result; } - PartialSchemaReqConversion transport(const ABT& n, - const PathCompare& pathCompare, - PartialSchemaReqConversion inputResult) { - if (!inputResult._success) { + ResultType transport(const ABT& n, const PathCompare& pathCompare, ResultType inputResult) { + if (!inputResult) { return {}; } - if (!inputResult._bound.has_value() || !inputResult._reqMap.empty()) { + if (!inputResult->_bound.has_value() || !inputResult->_reqMap.empty()) { return {}; } - const auto& bound = inputResult._bound; + const auto& bound = inputResult->_bound; bool lowBoundInclusive = false; boost::optional<ABT> lowBound; bool highBoundInclusive = false; @@ -681,51 +676,53 @@ public: auto intervalExpr = IntervalReqExpr::makeSingularDNF(IntervalRequirement{ {lowBoundInclusive, std::move(lowBound)}, {highBoundInclusive, std::move(highBound)}}); - return {PartialSchemaRequirements{ + return {{PartialSchemaRequirements{ {PartialSchemaKey{}, - PartialSchemaRequirement{"" /*boundProjectionName*/, std::move(intervalExpr)}}}}; + PartialSchemaRequirement{"" /*boundProjectionName*/, std::move(intervalExpr)}}}}}; } - PartialSchemaReqConversion transport(const ABT& n, const PathIdentity& pathIdentity) { - return {PartialSchemaRequirements{{{}, {}}}}; + ResultType transport(const ABT& n, const PathIdentity& pathIdentity) { + return {{PartialSchemaRequirements{{{}, {}}}}}; } - PartialSchemaReqConversion transport(const ABT& n, const Constant& c) { + ResultType transport(const ABT& n, const Constant& c) { if (c.isNull()) { // Cannot create bounds with just NULL. return {}; } - return {n}; + return {{n}}; } template <typename T, typename... Ts> - PartialSchemaReqConversion transport(const ABT& n, const T& node, Ts&&...) { + ResultType transport(const ABT& n, const T& node, Ts&&...) { if constexpr (std::is_base_of_v<ExpressionSyntaxSort, T>) { // We allow expressions to participate in bounds. - return {n}; + return {{n}}; } // General case. Reject conversion. return {}; } - PartialSchemaReqConversion convert(const ABT& input) { + ResultType convert(const ABT& input) { return algebra::transport<true>(input, *this); } + +private: + const bool _isFilterContext; }; -PartialSchemaReqConversion convertExprToPartialSchemaReq(const ABT& expr) { - PartialSchemaReqConverter converter; - PartialSchemaReqConversion result = converter.convert(expr); - if (result._reqMap.empty()) { - result._success = false; - return result; +boost::optional<PartialSchemaReqConversion> convertExprToPartialSchemaReq( + const ABT& expr, const bool isFilterContext) { + PartialSchemaReqConverter converter(isFilterContext); + auto result = converter.convert(expr); + if (!result || result->_reqMap.empty()) { + return {}; } - for (const auto& entry : result._reqMap) { + for (const auto& entry : result->_reqMap) { if (entry.first.emptyPath() && isIntervalReqFullyOpenDNF(entry.second.getIntervals())) { // We need to determine either path or interval (or both). - result._success = false; - return result; + return {}; } } return result; diff --git a/src/mongo/db/query/optimizer/utils/utils.h b/src/mongo/db/query/optimizer/utils/utils.h index 473e2bbbd70..d3164d10db6 100644 --- a/src/mongo/db/query/optimizer/utils/utils.h +++ b/src/mongo/db/query/optimizer/utils/utils.h @@ -154,13 +154,9 @@ private: }; struct PartialSchemaReqConversion { - PartialSchemaReqConversion(); PartialSchemaReqConversion(PartialSchemaRequirements reqMap); PartialSchemaReqConversion(ABT bound); - // Is our current bottom-up conversion successful. If not shortcut to top. - bool _success; - // If set, contains a Constant or Variable bound of an (yet unknown) interval. boost::optional<ABT> _bound; @@ -185,9 +181,11 @@ struct PartialSchemaReqConversion { /** * Takes an expression that comes from an Filter or Evaluation node, and attempt to convert * to a PartialSchemaReqConversion. This is done independent of the availability of indexes. - * Essentially this means to extract intervals over paths whenever possible. + * Essentially this means to extract intervals over paths whenever possible. If the conversion is + * not possible, return empty result. */ -PartialSchemaReqConversion convertExprToPartialSchemaReq(const ABT& expr); +boost::optional<PartialSchemaReqConversion> convertExprToPartialSchemaReq(const ABT& expr, + bool isFilterContext); bool intersectPartialSchemaReq(PartialSchemaRequirements& target, const PartialSchemaRequirements& source, |