diff options
author | auto-revert-processor <dev-prod-dag@mongodb.com> | 2023-04-26 02:43:09 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2023-04-26 03:55:20 +0000 |
commit | 5c1f588bfa4ed2edbeb3abbb26e952e08641da14 (patch) | |
tree | 06518ba42a4cb8aa5d3f30439405f949d86fd142 /src/mongo/db/query/optimizer | |
parent | a199d5f7b81b303f0eb155469593889db5d8c4ef (diff) | |
download | mongo-5c1f588bfa4ed2edbeb3abbb26e952e08641da14.tar.gz |
Revert "SERVER-69026 [CQF] Rewrite Sargable disjunction to RIDUnion"
This reverts commit f55584c091541a27c0ccf3c1eceb5d58a4a9c896.
Diffstat (limited to 'src/mongo/db/query/optimizer')
14 files changed, 198 insertions, 606 deletions
diff --git a/src/mongo/db/query/optimizer/bool_expression.h b/src/mongo/db/query/optimizer/bool_expression.h index c83d014e744..fb5a851781c 100644 --- a/src/mongo/db/query/optimizer/bool_expression.h +++ b/src/mongo/db/query/optimizer/bool_expression.h @@ -154,7 +154,6 @@ struct BoolExpr { using ChildVisitorConst = std::function<void(const Node& child, const size_t childIndex)>; using AtomVisitor = std::function<void(T& expr)>; using AtomVisitorConst = std::function<void(const T& expr)>; - using AtomPredConst = std::function<bool(const T& expr)>; static size_t visitConjuncts(const Node& node, const ChildVisitorConst& visitor) { size_t index = 0; @@ -225,18 +224,6 @@ struct BoolExpr { algebra::transport<false>(node, impl); } - static bool any(const Node& node, const AtomPredConst& atomPred) { - bool result = false; - visitAnyShape(node, [&](const T& atom) { result = result || atomPred(atom); }); - return result; - } - - static bool all(const Node& node, const AtomPredConst& atomPred) { - bool result = true; - visitAnyShape(node, [&](const T& atom) { result = result && atomPred(atom); }); - return result; - } - static void visitCNF(Node& node, const AtomVisitor& visitor) { visitConjuncts(node, [&](Node& child, const size_t) { visitDisjuncts(child, @@ -264,6 +251,7 @@ struct BoolExpr { algebra::transport<false>(node, impl); } + static bool isCNF(const Node& n) { if (n.template is<Conjunction>()) { bool disjunctions = true; @@ -343,15 +331,6 @@ struct BoolExpr { return *this; } - Builder& subtree(BoolExpr::Node expr) { - tassert(6902603, - "BoolExpr::Builder::subtree does not support negation", - !isCurrentlyNegated()); - _result = std::move(expr); - maybeAddToParent(); - return *this; - } - Builder& push(const bool isConjunction) { const bool negated = isCurrentlyNegated(); _stack.push_back({(negated == isConjunction) ? NodeType::Disj : NodeType::Conj, diff --git a/src/mongo/db/query/optimizer/cascades/implementers.cpp b/src/mongo/db/query/optimizer/cascades/implementers.cpp index a98ef1e1d39..834066e6040 100644 --- a/src/mongo/db/query/optimizer/cascades/implementers.cpp +++ b/src/mongo/db/query/optimizer/cascades/implementers.cpp @@ -416,7 +416,7 @@ public: if (_hints._disableScan) { return; } - if (_hints._forceIndexScanForPredicates && hasProperIntervals(reqMap.getRoot())) { + if (_hints._forceIndexScanForPredicates && hasProperIntervals(reqMap)) { return; } break; @@ -473,7 +473,7 @@ public: requiresRootProjection = projectionsLeftToSatisfy.erase(scanProjectionName); } - for (const auto& [key, boundProjName] : getBoundProjections(reqMap)) { + for (const auto& [key, boundProjName] : getBoundProjections(reqMap.getRoot())) { projectionsLeftToSatisfy.erase(boundProjName); } if (!projectionsLeftToSatisfy.getVector().empty()) { @@ -973,7 +973,8 @@ public: } void operator()(const ABT& /*n*/, const RIDUnionNode& node) { - // TODO SERVER-75587 should implement this. + // TODO SERVER-69026 should implement this. + tasserted(7016300, "RIDUnionNode not implemented yet."); return; } diff --git a/src/mongo/db/query/optimizer/cascades/logical_props_derivation.cpp b/src/mongo/db/query/optimizer/cascades/logical_props_derivation.cpp index 83215119727..7afbaa04bb5 100644 --- a/src/mongo/db/query/optimizer/cascades/logical_props_derivation.cpp +++ b/src/mongo/db/query/optimizer/cascades/logical_props_derivation.cpp @@ -249,7 +249,7 @@ public: } } - indexingAvailability.setHasProperInterval(hasProperIntervals(node.getReqMap().getRoot())); + indexingAvailability.setHasProperInterval(hasProperIntervals(node.getReqMap())); return maybeUpdateNodePropsMap(node, std::move(result)); } diff --git a/src/mongo/db/query/optimizer/cascades/logical_rewriter.cpp b/src/mongo/db/query/optimizer/cascades/logical_rewriter.cpp index e68098c1eb1..d9ede8e5f82 100644 --- a/src/mongo/db/query/optimizer/cascades/logical_rewriter.cpp +++ b/src/mongo/db/query/optimizer/cascades/logical_rewriter.cpp @@ -34,6 +34,7 @@ #include "mongo/db/query/optimizer/utils/path_utils.h" #include "mongo/db/query/optimizer/utils/reftracker_utils.h" + namespace mongo::optimizer::cascades { LogicalRewriter::RewriteSet LogicalRewriter::_explorationSet = { @@ -1161,6 +1162,7 @@ struct ExploreConvert { * Used to pre-compute properties of a PSR. */ struct RequirementProps { + bool _isFullyOpen; bool _mayReturnNull; }; @@ -1176,216 +1178,99 @@ struct SplitRequirementsResult { }; /** - * Helper transport for 'splitRequirementsFetch': adds a PSRExpr::Node to a builder. The caller can - * specify whether to keep only predicates, only projections, or both. Implicitly it handles - * perfOnly predicates: either dropping them (on the fetch side) or converting them to non-perfOnly - * (on the index side). + * Used to split requirements into left and right side. If "isIndex" is false, this is a separation + * between "index" and "fetch" predicates, otherwise it is a separation between the two sides of + * index intersection. The separation handles cases where we may have intervals which include Null + * and return the value, in which case instead of moving the requirement on the left, we insert a + * copy on the right side which will fetch the value from the collection. We convert perf-only + * requirements to non-perf when inserting on the left under "isIndex", otherwise we drop them. The + * mask parameter represents a bitmask indicating which requirements go on the left (bit is 1) and + * which go on the right. */ -struct SplitRequirementsFetchTransport { - enum class Keep { - kBoth, - kPredicateOnly, - kProjectionOnly, - }; - static void addReq(const bool left, - const PSRExpr::Node& expr, - const Keep keep, - const boost::optional<FieldNameSet>& indexFieldPrefixMap, - PSRExprBuilder& leftReqs, - PSRExprBuilder& rightReqs, - bool& hasFieldCoverage) { - auto& builder = left ? leftReqs : rightReqs; - - SplitRequirementsFetchTransport impl{ - left, - keep, - indexFieldPrefixMap, - builder, - hasFieldCoverage, - }; - algebra::transport<false>(expr, impl); - } - - void prepare(const PSRExpr::Conjunction&) { - builder.pushConj(); - } - void transport(const PSRExpr::Conjunction&, const PSRExpr::NodeVector&) { - builder.pop(); - } - void prepare(const PSRExpr::Disjunction&) { - builder.pushDisj(); - } - void transport(const PSRExpr::Disjunction&, const PSRExpr::NodeVector&) { - builder.pop(); - } - void transport(const PSRExpr::Atom& node) { - const bool keepPred = keep != Keep::kProjectionOnly; - const bool keepProj = keep != Keep::kPredicateOnly; - - const auto& [key, req] = node.getExpr(); - const bool perfOnly = req.getIsPerfOnly(); - const auto outputBinding = keepProj ? req.getBoundProjectionName() : boost::none; - // perfOnly predicates on the fetch side become trivially true. - const auto intervals = ((perfOnly && !left) || !keepPred) - ? IntervalReqExpr::makeSingularDNF() - : req.getIntervals(); - - if (outputBinding || !isIntervalReqFullyOpenDNF(intervals)) { - builder.atom(key, - PartialSchemaRequirement{ - std::move(outputBinding), std::move(intervals), false /*isPerfOnly*/}); - - if (left && indexFieldPrefixMap) { - if (auto pathPtr = key._path.cast<PathGet>(); - pathPtr != nullptr && indexFieldPrefixMap->count(pathPtr->name()) == 0) { - // We have found a left requirement which cannot be covered with an - // index. - hasFieldCoverage = false; - } - } - } else { - // The whole predicate/projection is trivial and its indexability doesn't - // matter. - } - } - - const bool left; - const Keep keep; - const boost::optional<FieldNameSet>& indexFieldPrefixMap; - - PSRExprBuilder& builder; - bool& hasFieldCoverage; -}; - -/** - * Takes a vector of PSRExpr, 'conjuncts', and splits them into an index side (on the left) and a - * fetch side (on the right). - * - * The bitfield 'mask' says how to split: each corresponding bit is 1 for left or 0 for right. - * - * 'perfOnly' predicates are preserved and converted to non-perfOnly when they go on the index side. - * On the fetch side they are dropped, by converting them to trivially-true. - * - * If yielding-tolerant plans are requested (by 'hints._disableYieldingTolerantPlans == false') then - * any predicate that should go on the left, we actually put on both sides. - * - * Some special cases apply when we attempt to put a predicate on the index side: - * - If yielding-tolerant plans are requested (by 'hints._disableYieldingTolerantPlans == false') - * then we put the predicate on both sides. - * - If correct null handling is requested (by 'hints._fastIndexNullHandling == false') and the - * predicate may contain null, we satisfy its output projection (if any) on the fetch side - * instead. - */ -static SplitRequirementsResult splitRequirementsFetch( +static SplitRequirementsResult splitRequirements( const size_t mask, + const bool isIndex, const QueryHints& hints, const std::vector<RequirementProps>& reqProps, - const boost::optional<FieldNameSet>& indexFieldPrefixMap, - const PSRExpr::NodeVector& conjuncts) { - - bool hasFieldCoverage = true; - PSRExprBuilder leftReqs; - PSRExprBuilder rightReqs; - leftReqs.pushConj(); - rightReqs.pushConj(); - - // Adds a PSRExpr 'expr' to the left or right, as specified by 'left'. - // When adding to the right, replaces any 'perfOnly' atoms with trivially-true. - // When adding to the left, keeps 'perfOnly' atoms and marks them non-perfOnly. - // - // 'keep' specifies whether to keep only the predicate, only the projection, or both. - // It defaults to both. - // - // If we end up adding an unindexed path (one we know does not appear in any index), - // set 'hasFieldCoverage' to false as a signal to bail out. - using Keep = SplitRequirementsFetchTransport::Keep; - const auto addReq = - [&](const bool left, const PSRExpr::Node& expr, const Keep keep = Keep::kBoth) { - SplitRequirementsFetchTransport::addReq( - left, expr, keep, indexFieldPrefixMap, leftReqs, rightReqs, hasFieldCoverage); - }; + const boost::optional<FieldNameSet>& indexFieldPrefixMapForScanDef, + const PartialSchemaRequirements& reqMap) { + SplitRequirementsResult result; + + result._leftReqsBuilder.pushDisj().pushConj(); + result._rightReqsBuilder.pushDisj().pushConj(); + + const auto addRequirement = [&](const bool left, + PartialSchemaKey key, + boost::optional<ProjectionName> boundProjectionName, + IntervalReqExpr::Node intervals) { + // We always strip out the perf-only flag. + auto& builder = left ? result._leftReqsBuilder : result._rightReqsBuilder; + builder.atom(std::move(key), + PartialSchemaRequirement{std::move(boundProjectionName), + std::move(intervals), + false /*isPerfOnly*/}); + }; size_t index = 0; - - for (const auto& conjunct : conjuncts) { + for (const auto& [key, req] : reqMap.conjuncts()) { + const auto& intervals = req.getIntervals(); + const auto& outputBinding = req.getBoundProjectionName(); + const bool perfOnly = req.getIsPerfOnly(); const auto& reqProp = reqProps.at(index); - const bool left = ((1ull << index) & mask); - if (!left) { + if (((1ull << index) & mask) == 0) { // Predicate should go on the right side. - addReq(false /*left*/, conjunct); + if (isIndex || !perfOnly) { + addRequirement(false /*left*/, key, outputBinding, intervals); + } index++; continue; } - // Predicate should go on the left side. However: - // - Correct null handling requires moving the projection to the fetch side. - // - Yield-safe plans require duplicating the predicate to both sides. - // - Except that 'perfOnly' predicates become true on the fetch side. - - if (hints._fastIndexNullHandling || !reqProp._mayReturnNull) { + // Predicate should go on the left side. + bool addedToLeft = false; + if (isIndex || hints._fastIndexNullHandling || !reqProp._mayReturnNull) { // We can never return Null values from the requirement. - if (hints._disableYieldingTolerantPlans) { + if (isIndex || hints._disableYieldingTolerantPlans || perfOnly) { // Insert into left side unchanged. - addReq(true /*left*/, conjunct); - + addRequirement(true /*left*/, key, outputBinding, intervals); } else { // Insert a requirement on the right side too, left side is non-binding. - addReq(true /*left*/, conjunct, Keep::kPredicateOnly); - addReq(false /*left*/, conjunct); + addRequirement(true /*left*/, key, boost::none /*boundProjectionName*/, intervals); + addRequirement(false /*left*/, key, outputBinding, intervals); } + addedToLeft = true; } else { // At this point we should not be seeing perf-only predicates. + invariant(!perfOnly); - // We cannot return index values, since the interval can possibly contain Null. Instead, + // We cannot return index values if our interval can possibly contain Null. Instead, // we remove the output binding for the left side, and return the value from the // right (seek) side. - addReq(true /*left*/, conjunct, Keep::kPredicateOnly); - addReq(false /*left*/, - conjunct, - // Yield-safe plans keep both the predicate and projection on the fetch side. - // Yield-unsafe plans only need the projection. - hints._disableYieldingTolerantPlans ? Keep::kProjectionOnly : Keep::kBoth); - } - - if (!hasFieldCoverage) { - break; + if (!reqProp._isFullyOpen) { + addRequirement(true /*left*/, key, boost::none /*boundProjectionName*/, intervals); + addedToLeft = true; + } + addRequirement(false /*left*/, + key, + outputBinding, + hints._disableYieldingTolerantPlans ? IntervalReqExpr::makeSingularDNF() + : intervals); } - index++; - } - - return { - std::move(leftReqs), - std::move(rightReqs), - hasFieldCoverage, - }; -} - -static SplitRequirementsResult splitRequirementsIndex(const size_t mask, - const PSRExpr::NodeVector& reqs, - const bool disjunctive) { - PSRExprBuilder leftReqs; - PSRExprBuilder rightReqs; - if (disjunctive) { - leftReqs.pushDisj(); - rightReqs.pushDisj(); - } else { - leftReqs.pushConj(); - rightReqs.pushConj(); - } - size_t index = 0; - for (const auto& req : reqs) { - if ((1ull << index) & mask) { - leftReqs.subtree(req); - } else { - rightReqs.subtree(req); + if (addedToLeft && indexFieldPrefixMapForScanDef) { + if (auto pathPtr = key._path.cast<PathGet>(); + pathPtr != nullptr && indexFieldPrefixMapForScanDef->count(pathPtr->name()) == 0) { + // We have found a left requirement which cannot be covered with an + // index. + result._hasFieldCoverage = false; + break; + } } - index++; } - return {std::move(leftReqs), std::move(rightReqs)}; + return result; } template <> @@ -1409,6 +1294,11 @@ struct ExploreConvert<SargableNode> { return; } + // SERVER-69026: Support "splitting" a top-level disjunction for index ORing. + if (!PSRExpr::isSingletonDisjunction(sargableNode.getReqMap().getRoot())) { + return; + } + const std::string& scanDefName = indexingAvailability.getScanDefName(); const ScanDefinition& scanDef = ctx.getMetadata()._scanDefs.at(scanDefName); if (scanDef.getIndexDefs().empty()) { @@ -1432,42 +1322,6 @@ struct ExploreConvert<SargableNode> { const bool isIndex = target == IndexReqTarget::Index; - // Decide whether to do a conjunctive or disjunctive split. - // Rearrange the predicates so that the top-level node is the one we want to split: - // - DNF if we want a disjunctive split. - // - CNF if we want a conjunctive split. - boost::optional<PSRExpr::Node> splittable; - { - const auto& reqMap = sargableNode.getReqMap().getRoot(); - if (isIndex) { - // When targeting an index, do a disjunctive split if possible. - if (PSRExpr::isSingletonDisjunction(reqMap)) { - // Trivial disjunction means we can only do a conjunctive split. - splittable = PSRExpr::convertToCNF(reqMap, SargableNode::kMaxPartialSchemaReqs); - tassert(6902602, - "converting DNF with only trivial disjunction to CNF should never fail", - splittable); - } else { - splittable = reqMap; - } - } else { - // When targeting 'Complete', the only split we allow is index/fetch, - // because we want to do all union/intersection of record IDs within the index side, - // to avoid redundant fetching. - - // Index/fetch is a conjunctive split. - splittable = PSRExpr::convertToCNF(reqMap); - } - } - if (!splittable) { - // Conversion between DNF/CNF can fail if the result would be too big. - return; - } - const bool disjunctive = splittable->is<PSRExpr::Disjunction>(); - const PSRExpr::NodeVector& reqs = disjunctive - ? splittable->cast<PSRExpr::Disjunction>()->nodes() - : splittable->cast<PSRExpr::Conjunction>()->nodes(); - const auto& indexFieldPrefixMap = ctx.getIndexFieldPrefixMap(); boost::optional<FieldNameSet> indexFieldPrefixMapForScanDef; if (auto it = indexFieldPrefixMap.find(scanDefName); @@ -1475,27 +1329,21 @@ struct ExploreConvert<SargableNode> { indexFieldPrefixMapForScanDef = it->second; } + const auto& reqMap = sargableNode.getReqMap(); const auto& hints = ctx.getHints(); // Pre-computed properties of the requirements. - // We only need these for the index/fetch split. std::vector<RequirementProps> reqProps; - if (!isIndex) { - reqProps.reserve(reqs.size()); - for (const auto& conjunct : reqs) { - // Pre-compute if a requirement's interval is fully open. - - // Pre-compute if a requirement's interval may contain nulls, and also has an output - // binding. Do use constant folding if we do not have to. - const bool mayReturnNull = !hints._fastIndexNullHandling && !isIndex && - PSRExpr::any(conjunct, [&](const PartialSchemaEntry& entry) { - return entry.second.mayReturnNull(ctx.getConstFold()); - }); - - reqProps.push_back({ - mayReturnNull, - }); - } + for (const auto& [key, req] : reqMap.conjuncts()) { + // Pre-compute if a requirement's interval is fully open. + const bool fullyOpen = isIntervalReqFullyOpenDNF(req.getIntervals()); + + // Pre-compute if a requirement's interval may contain nulls, and also has an output + // binding. Do use constant folding if we do not have to. + const bool mayReturnNull = + !hints._fastIndexNullHandling && !isIndex && req.mayReturnNull(ctx.getConstFold()); + + reqProps.push_back({fullyOpen, mayReturnNull}); } // We iterate over the possible ways to split N predicates into 2^N subsets, one goes to the @@ -1503,16 +1351,12 @@ struct ExploreConvert<SargableNode> { // try having at least one predicate on the left (mask = 1), and we try all possible // subsets. For index intersection however (isIndex = true), we try symmetric partitioning // (thus the high bound is 2^(N-1)). - const size_t highMask = isIndex ? (1ull << (reqs.size() - 1)) : (1ull << reqs.size()); + const size_t reqSize = PSRExpr::numLeaves(reqMap.getRoot()); // assumes singular DNF + const size_t highMask = isIndex ? (1ull << (reqSize - 1)) : (1ull << reqSize); for (size_t mask = 1; mask < highMask; mask++) { - auto splitResult = isIndex - ? splitRequirementsIndex(mask, reqs, disjunctive) - : splitRequirementsFetch( - mask, hints, reqProps, indexFieldPrefixMapForScanDef, reqs); - if (!splitResult._hasFieldCoverage) { - // Reject rewrite. No suitable indexes. - continue; - } + SplitRequirementsResult splitResult = splitRequirements( + mask, isIndex, hints, reqProps, indexFieldPrefixMapForScanDef, reqMap); + auto leftReqs = splitResult._leftReqsBuilder.finish(); auto rightReqs = splitResult._rightReqsBuilder.finish(); @@ -1521,37 +1365,6 @@ struct ExploreConvert<SargableNode> { invariant(!hints._fastIndexNullHandling && !isIndex); continue; } - // Convert everything back to DNF. - if (!PSRExpr::isDNF(*leftReqs)) { - leftReqs = PSRExpr::convertToDNF(std::move(*leftReqs)); - if (!leftReqs) { - continue; - } - } - if (rightReqs && !PSRExpr::isDNF(*rightReqs)) { - rightReqs = PSRExpr::convertToDNF(std::move(*rightReqs)); - if (!rightReqs) { - continue; - } - } - // DNF / CNF conversions can create redundant predicates; try to simplify. - // If the reqs are too big, even after simplification, creating a SargableNode will - // fail, so bail out. - const auto isTooBig = [&](const PSRExpr::Node& reqs) -> bool { - return PSRExpr::numLeaves(reqs) > SargableNode::kMaxPartialSchemaReqs; - }; - if (leftReqs) { - PartialSchemaRequirements::simplifyRedundantDNF(*leftReqs); - if (isTooBig(*leftReqs)) { - continue; - } - } - if (rightReqs) { - PartialSchemaRequirements::simplifyRedundantDNF(*rightReqs); - if (isTooBig(*rightReqs)) { - continue; - } - } const bool hasLeftintervals = hasProperIntervals(*leftReqs); const bool hasRightIntervals = rightReqs && hasProperIntervals(*rightReqs); @@ -1565,40 +1378,39 @@ struct ExploreConvert<SargableNode> { continue; } - auto leftCandidateIndexes = - computeCandidateIndexes(ctx.getPrefixId(), - scanProjectionName, - PartialSchemaRequirements{*leftReqs}, - scanDef, - hints, - ctx.getConstFold()); - if (isIndex && leftCandidateIndexes.empty() && - PSRExpr::isSingletonDisjunction(*leftReqs)) { - // Reject rewrite, because further splitting can only be conjunctive, - // which does not increase the set of candidate indexes. + if (!splitResult._hasFieldCoverage) { + // Reject rewrite. No suitable indexes. + continue; + } + + auto leftCandidateIndexes = computeCandidateIndexes(ctx.getPrefixId(), + scanProjectionName, + *leftReqs, + scanDef, + hints, + ctx.getConstFold()); + if (isIndex && leftCandidateIndexes.empty()) { + // Reject rewrite. continue; } CandidateIndexes rightCandidateIndexes; if (rightReqs) { - rightCandidateIndexes = - computeCandidateIndexes(ctx.getPrefixId(), - scanProjectionName, - PartialSchemaRequirements{*rightReqs}, - scanDef, - hints, - ctx.getConstFold()); + rightCandidateIndexes = computeCandidateIndexes(ctx.getPrefixId(), + scanProjectionName, + *rightReqs, + scanDef, + hints, + ctx.getConstFold()); } - if (isIndex && rightCandidateIndexes.empty() && - PSRExpr::isSingletonDisjunction(*rightReqs)) { - // Reject rewrite, because further splitting can only be conjunctive, - // which does not increase the set of candidate indexes. + if (isIndex && rightCandidateIndexes.empty()) { + // With empty candidate map, reject only if we cannot implement as Seek. continue; } ABT scanDelegator = make<MemoLogicalDelegatorNode>(scanGroupId); - ABT leftChild = make<SargableNode>(PartialSchemaRequirements{std::move(*leftReqs)}, + ABT leftChild = make<SargableNode>(std::move(*leftReqs), std::move(leftCandidateIndexes), boost::none, IndexReqTarget::Index, @@ -1606,23 +1418,20 @@ struct ExploreConvert<SargableNode> { boost::optional<ScanParams> rightScanParams; if (rightReqs) { - rightScanParams = computeScanParams( - ctx.getPrefixId(), PartialSchemaRequirements{*rightReqs}, scanProjectionName); + rightScanParams = + computeScanParams(ctx.getPrefixId(), *rightReqs, scanProjectionName); } ABT rightChild = rightReqs - ? make<SargableNode>(PartialSchemaRequirements{std::move(*rightReqs)}, + ? make<SargableNode>(std::move(*rightReqs), std::move(rightCandidateIndexes), std::move(rightScanParams), isIndex ? IndexReqTarget::Index : IndexReqTarget::Seek, scanDelegator) : scanDelegator; - ABT newRoot = disjunctive - ? make<RIDUnionNode>( - scanProjectionName, std::move(leftChild), std::move(rightChild)) - : make<RIDIntersectNode>( - scanProjectionName, std::move(leftChild), std::move(rightChild)); + ABT newRoot = make<RIDIntersectNode>( + scanProjectionName, std::move(leftChild), std::move(rightChild)); const auto& result = ctx.addNode(newRoot, false /*substitute*/); for (const MemoLogicalNodeId nodeId : result.second) { diff --git a/src/mongo/db/query/optimizer/index_bounds.cpp b/src/mongo/db/query/optimizer/index_bounds.cpp index 295ca6cc6a4..72de5613a18 100644 --- a/src/mongo/db/query/optimizer/index_bounds.cpp +++ b/src/mongo/db/query/optimizer/index_bounds.cpp @@ -180,80 +180,27 @@ bool PartialSchemaRequirement::mayReturnNull(const ConstFoldFn& constFold) const return _boundProjectionName && checkMaybeHasNull(getIntervals(), constFold); }; -bool IndexPathLessComparator::operator()(const ABT& path1, const ABT& path2) const { +bool IndexPath3WComparator::operator()(const ABT& path1, const ABT& path2) const { return compareExprAndPaths(path1, path2) < 0; } -int PartialSchemaKeyComparator::Cmp3W::operator()(const PartialSchemaKey& k1, - const PartialSchemaKey& k2) const { +bool PartialSchemaKeyLessComparator::operator()(const PartialSchemaKey& k1, + const PartialSchemaKey& k2) const { if (const auto& p1 = k1._projectionName) { if (const auto& p2 = k2._projectionName) { const int projCmp = p1->compare(*p2); if (projCmp != 0) { - return projCmp; + return projCmp < 0; } // Fallthrough to comparison below. } else { - // p1 > p2 because nonempty > empty. - return 1; + return false; } } else if (k2._projectionName) { - // p1 < p2 because empty < nonempty - return -1; - } - // p1 == p2 so compare paths. - return compareExprAndPaths(k1._path, k2._path); -} -bool PartialSchemaKeyComparator::Less::operator()(const PartialSchemaKey& k1, - const PartialSchemaKey& k2) const { - return PartialSchemaKeyComparator::Cmp3W{}(k1, k2) < 0; -} - -int PartialSchemaRequirementComparator::Cmp3W::operator()( - const PartialSchemaRequirement& req1, const PartialSchemaRequirement& req2) const { - - int intervalCmp = compareIntervalExpr(req1.getIntervals(), req2.getIntervals()); - if (intervalCmp != 0) { - return intervalCmp; - } - - // Intervals are equal: compare the output bindings. - auto b1 = req1.getBoundProjectionName(); - auto b2 = req2.getBoundProjectionName(); - if (b1 && b2) { - return b1->compare(*b2); - } else if (b1) { - // b1 > b2 because nonempty > empty. - return 1; - } else if (b2) { - // b1 < b2 because empty < nonempty. - return -1; - } else { - // empty == empty. - return 0; - } -} - -bool PartialSchemaRequirementComparator::Less::operator()( - const PartialSchemaRequirement& req1, const PartialSchemaRequirement& req2) const { - - int intervalCmp = compareIntervalExpr(req1.getIntervals(), req2.getIntervals()); - if (intervalCmp < 0) { - return true; - } else if (intervalCmp > 0) { return false; } - // Intervals are equal: compare the output bindings. - auto b1 = req1.getBoundProjectionName(); - auto b2 = req2.getBoundProjectionName(); - if (b1 && b2) { - return b1->compare(*b2) < 0; - } else { - // If either or both optionals are empty, then the only way for - // 'b1 < b2' is '{} < {anything}'. - return !b1 && b2; - } + return compareExprAndPaths(k1._path, k2._path) < 0; } ResidualRequirement::ResidualRequirement(PartialSchemaKey key, diff --git a/src/mongo/db/query/optimizer/index_bounds.h b/src/mongo/db/query/optimizer/index_bounds.h index 84b24f5e327..b23ae574a48 100644 --- a/src/mongo/db/query/optimizer/index_bounds.h +++ b/src/mongo/db/query/optimizer/index_bounds.h @@ -237,41 +237,23 @@ private: /** * This comparator is used to compare paths with Get, Traverse, and Id. */ -struct IndexPathLessComparator { +struct IndexPath3WComparator { bool operator()(const ABT& path1, const ABT& path2) const; }; -using IndexPathSet = std::set<ABT, IndexPathLessComparator>; +using IndexPathSet = std::set<ABT, IndexPath3WComparator>; -struct PartialSchemaKeyComparator { - struct Less { - bool operator()(const PartialSchemaKey& k1, const PartialSchemaKey& k2) const; - }; - - struct Cmp3W { - int operator()(const PartialSchemaKey& k1, const PartialSchemaKey& k2) const; - }; -}; -struct PartialSchemaRequirementComparator { - struct Less { - bool operator()(const PartialSchemaRequirement& req1, - const PartialSchemaRequirement& req2) const; - }; - - struct Cmp3W { - int operator()(const PartialSchemaRequirement& req1, - const PartialSchemaRequirement& req2) const; - }; +struct PartialSchemaKeyLessComparator { + bool operator()(const PartialSchemaKey& k1, const PartialSchemaKey& k2) const; }; - /** * Used to track cardinality estimates per predicate inside a PartialSchemaRequirement. The order of * estimates is the same as the leaf order in the primary PartialSchemaRequirements. */ using PartialSchemaKeyCE = std::vector<std::pair<PartialSchemaKey, CEType>>; -using PartialSchemaKeySet = std::set<PartialSchemaKey, PartialSchemaKeyComparator::Less>; +using PartialSchemaKeySet = std::set<PartialSchemaKey, PartialSchemaKeyLessComparator>; // Requirements which are not satisfied directly by an IndexScan, PhysicalScan or Seek (e.g. using // an index field, or scan field). The index refers to the underlying entry in the diff --git a/src/mongo/db/query/optimizer/index_union_optimizer_test.cpp b/src/mongo/db/query/optimizer/index_union_optimizer_test.cpp index ebba5611135..06592638760 100644 --- a/src/mongo/db/query/optimizer/index_union_optimizer_test.cpp +++ b/src/mongo/db/query/optimizer/index_union_optimizer_test.cpp @@ -335,6 +335,7 @@ TEST(LogicalRewriter, DisjunctionConversionDedup) { // We should see everything get reordered and deduped, // so each of the leaf predicates appears once. + // TODO SERVER-73827 We should get 2 leaf predicates instead of 3 here. ASSERT_EXPLAIN_V2_AUTO( "Root [{scan_0}]\n" "Sargable [Complete]\n" @@ -342,6 +343,8 @@ TEST(LogicalRewriter, DisjunctionConversionDedup) { "| | {\n" "| | {{scan_0, 'PathGet [a] PathIdentity []', {{{=Const [0]}}}}}\n" "| | U \n" + "| | {{scan_0, 'PathGet [a] PathIdentity []', {{{=Const [0]}}}}}\n" + "| | U \n" "| | {{scan_0, 'PathGet [b] PathIdentity []', {{{=Const [1]}}}}}\n" "| | }\n" "| scanParams: \n" @@ -350,7 +353,9 @@ TEST(LogicalRewriter, DisjunctionConversionDedup) { "| {\n" "| {{evalTemp_0, 'PathIdentity []', {{{=Const [0]}}}, entryIndex: 0}}\n" "| U \n" - "| {{evalTemp_1, 'PathIdentity []', {{{=Const [1]}}}, entryIndex: 1}}\n" + "| {{evalTemp_0, 'PathIdentity []', {{{=Const [0]}}}, entryIndex: 1}}\n" + "| U \n" + "| {{evalTemp_1, 'PathIdentity []', {{{=Const [1]}}}, entryIndex: 2}}\n" "| }\n" "Scan [coll, {scan_0}]\n", optimized); @@ -473,11 +478,11 @@ TEST(PhysRewriter, OptimizeSargableNodeWithTopLevelDisjunction) { ABT scanNode = make<ScanNode>("ptest", "test"); ABT sargableNode1 = make<SargableNode>( - reqs1, CandidateIndexes(), scanParams, IndexReqTarget::Complete, std::move(scanNode)); + reqs1, CandidateIndexes(), scanParams, IndexReqTarget::Index, std::move(scanNode)); ABT sargableNode2 = make<SargableNode>( - reqs2, CandidateIndexes(), boost::none, IndexReqTarget::Complete, std::move(sargableNode1)); + reqs2, CandidateIndexes(), boost::none, IndexReqTarget::Index, std::move(sargableNode1)); ABT sargableNode3 = make<SargableNode>( - reqs3, CandidateIndexes(), boost::none, IndexReqTarget::Complete, std::move(sargableNode2)); + reqs3, CandidateIndexes(), boost::none, IndexReqTarget::Index, std::move(sargableNode2)); ABT rootNode = make<RootNode>(properties::ProjectionRequirement{ProjectionNameVector{"ptest"}}, std::move(sargableNode3)); @@ -485,24 +490,25 @@ TEST(PhysRewriter, OptimizeSargableNodeWithTopLevelDisjunction) { // SargableNodes are correctly lowered to FilterNodes. auto prefixId = PrefixId::createForTests(); auto phaseManager = makePhaseManager( - { - OptPhase::MemoSubstitutionPhase, OptPhase::MemoExplorationPhase, - // OptPhase::MemoImplementationPhase, - }, + {OptPhase::MemoSubstitutionPhase, + OptPhase::MemoExplorationPhase, + OptPhase::MemoImplementationPhase}, prefixId, {{{"test", createScanDef( {}, { + // For now, verify that we do not get an indexed plan even when there + // are indexes available on the queried fields. {"ab", - IndexDefinition{{{makeNonMultikeyIndexPath("a"), CollationOp::Ascending}, - {makeNonMultikeyIndexPath("b"), CollationOp::Ascending}}, + IndexDefinition{{{makeIndexPath("a"), CollationOp::Ascending}, + {makeIndexPath("b"), CollationOp::Ascending}}, false /*isMultiKey*/, {DistributionType::Centralized}, {}}}, {"cd", - IndexDefinition{{{makeNonMultikeyIndexPath("c"), CollationOp::Ascending}, - {makeNonMultikeyIndexPath("d"), CollationOp::Ascending}}, + IndexDefinition{{{makeIndexPath("c"), CollationOp::Ascending}, + {makeIndexPath("d"), CollationOp::Ascending}}, false /*isMultiKey*/, {DistributionType::Centralized}, {}}}, @@ -511,17 +517,9 @@ TEST(PhysRewriter, OptimizeSargableNodeWithTopLevelDisjunction) { {"g", makeIndexDefinition("g", CollationOp::Ascending, false /*isMultiKey*/)}, })}}}, boost::none /*costModel*/, - DebugInfo::kDefaultForTests, - QueryHints{ - ._disableScan = true, - }); + DebugInfo::kDefaultForTests); phaseManager.optimize(rootNode); - // We should get an index union between 'ab' and 'cd'. - // TODO SERVER-75587 In the logical plan, we get an alternative where 'ab' and 'cd' are each - // satisfied by an intersection. Instead we should get a single scan of the compound indexes - // 'ab' and 'cd'. Hopefully that will work out once we enable the physical phase, which makes it - // possible to cost the alternatives. ASSERT_EXPLAIN_V2Compact_AUTO( "Root [{ptest}]\n" "Filter []\n" @@ -536,55 +534,23 @@ TEST(PhysRewriter, OptimizeSargableNodeWithTopLevelDisjunction) { "| EvalFilter []\n" "| | Variable [ptest]\n" "| PathGet [e] PathCompare [Eq] Const [1]\n" - "RIDIntersect [ptest]\n" - "| Scan [test, {ptest}]\n" - "RIDUnion [ptest]\n" - "| RIDIntersect [ptest]\n" - "| | Sargable [Index]\n" - "| | | | | requirements: \n" - "| | | | | {{{ptest, 'PathGet [d] PathIdentity []', {{{=Const [1]}}}}}}\n" - "| | | | candidateIndexes: \n" - "| | | | candidateId: 1, cd, {'<indexKey> 1': evalTemp_49}, {Unbound, " - "Unbound}, {{{<fully open>}}}\n" - "| | | | residualReqs: \n" - "| | | | {{{evalTemp_49, 'PathIdentity []', {{{=Const [1]}}}, " - "entryIndex: 0}}}\n" - "| | | scanParams: \n" - "| | | {'d': evalTemp_50}\n" - "| | | residualReqs: \n" - "| | | {{{evalTemp_50, 'PathIdentity []', {{{=Const [1]}}}, entryIndex: " - "0}}}\n" - "| | Scan [test, {ptest}]\n" - "| Sargable [Index]\n" - "| | | | requirements: \n" - "| | | | {{{ptest, 'PathGet [c] PathIdentity []', {{{=Const [1]}}}}}}\n" - "| | | candidateIndexes: \n" - "| | | candidateId: 1, cd, {}, {SimpleEquality, Unbound}, {{{[Const [1 | " - "minKey], Const [1 | maxKey]]}}}\n" - "| | scanParams: \n" - "| | {'c': evalTemp_44}\n" - "| | residualReqs: \n" - "| | {{{evalTemp_44, 'PathIdentity []', {{{=Const [1]}}}, entryIndex: " - "0}}}\n" - "| Scan [test, {ptest}]\n" - "RIDIntersect [ptest]\n" - "| Sargable [Index]\n" - "| | | requirements: \n" - "| | | {{{ptest, 'PathGet [b] PathIdentity []', {{{=Const [1]}}}}}}\n" - "| | candidateIndexes: \n" - "| | candidateId: 1, ab, {'<indexKey> 1': evalTemp_45}, {Unbound, Unbound}, " - "{{{<fully open>}}}\n" - "| | residualReqs: \n" - "| | {{{evalTemp_45, 'PathIdentity []', {{{=Const [1]}}}, entryIndex: " - "0}}}\n" - "| Scan [test, {ptest}]\n" - "Sargable [Index]\n" - "| | requirements: \n" - "| | {{{ptest, 'PathGet [a] PathIdentity []', {{{=Const [1]}}}}}}\n" - "| candidateIndexes: \n" - "| candidateId: 1, ab, {}, {SimpleEquality, Unbound}, {{{[Const [1 | minKey], Const " - "[1 | maxKey]]}}}\n" - "Scan [test, {ptest}]\n", + "Filter []\n" + "| BinaryOp [Or]\n" + "| | BinaryOp [And]\n" + "| | | EvalFilter []\n" + "| | | | Variable [ptest]\n" + "| | | PathGet [d] PathCompare [Eq] Const [1]\n" + "| | EvalFilter []\n" + "| | | Variable [ptest]\n" + "| | PathGet [c] PathCompare [Eq] Const [1]\n" + "| BinaryOp [And]\n" + "| | EvalFilter []\n" + "| | | Variable [ptest]\n" + "| | PathGet [b] PathCompare [Eq] Const [1]\n" + "| EvalFilter []\n" + "| | Variable [ptest]\n" + "| PathGet [a] PathCompare [Eq] Const [1]\n" + "PhysicalScan [{'<root>': ptest}, test]\n", rootNode); } diff --git a/src/mongo/db/query/optimizer/partial_schema_requirements.cpp b/src/mongo/db/query/optimizer/partial_schema_requirements.cpp index 60d8145bdb0..40b54d4d61d 100644 --- a/src/mongo/db/query/optimizer/partial_schema_requirements.cpp +++ b/src/mongo/db/query/optimizer/partial_schema_requirements.cpp @@ -42,11 +42,11 @@ public: } void transport(PSRExpr::Conjunction& node, std::vector<PSRExpr::Node>& children) { - sortAndDedupChildren(children); + sortChildren(children); } void transport(PSRExpr::Disjunction& node, std::vector<PSRExpr::Node>& children) { - sortAndDedupChildren(children); + sortChildren(children); } void normalize(PSRExpr::Node& node) { @@ -54,16 +54,13 @@ public: } private: - void sortAndDedupChildren(std::vector<PSRExpr::Node>& children) { + void sortChildren(std::vector<PSRExpr::Node>& children) { struct Comparator { bool operator()(const PSRExpr::Node& i1, const PSRExpr::Node& i2) const { return comparePartialSchemaRequirementsExpr(i1, i2) < 0; } }; std::sort(children.begin(), children.end(), Comparator{}); - - auto end = std::unique(children.begin(), children.end()); - children.erase(end, children.end()); } }; @@ -77,11 +74,8 @@ PartialSchemaEntry makeNoopPartialSchemaEntry() { } } // namespace -void PartialSchemaRequirements::normalize(PSRExpr::Node& expr) { - PSRNormalizeTransporter{}.normalize(expr); -} void PartialSchemaRequirements::normalize() { - normalize(_expr); + PSRNormalizeTransporter{}.normalize(_expr); } PartialSchemaRequirements::PartialSchemaRequirements(PSRExpr::Node requirements) @@ -158,6 +152,7 @@ PartialSchemaRequirements::findFirstConjunct(const PartialSchemaKey& key) const void PartialSchemaRequirements::add(PartialSchemaKey key, PartialSchemaRequirement req) { tassert(7016406, "Expected a PartialSchemaRequirements in DNF form", PSRExpr::isDNF(_expr)); + // TODO SERVER-69026 Consider applying the distributive law. tassert(7453912, "Expected a singleton disjunction", PSRExpr::isSingletonDisjunction(_expr)); // Add an entry to the first conjunction @@ -233,82 +228,12 @@ static bool simplifyExpr( bool PartialSchemaRequirements::simplify( std::function<bool(const PartialSchemaKey&, PartialSchemaRequirement&)> func) { - return simplify(_expr, func); -} -bool PartialSchemaRequirements::simplify( - PSRExpr::Node& expr, - std::function<bool(const PartialSchemaKey&, PartialSchemaRequirement&)> func) { - if (PSRExpr::isCNF(expr)) { - return simplifyExpr<true /*isCNF*/>(expr, func); - } - return simplifyExpr<false /*isCNF*/>(expr, func); -} - -void PartialSchemaRequirements::simplifyRedundantDNF(PSRExpr::Node& expr) { - tassert(6902601, "simplifyRedundantDNF expects DNF", PSRExpr::isDNF(expr)); - - // Normalizing ensures: - // - Each term has no duplicate atoms. - // - The overall expression has no duplicate terms. - // - The terms are sorted in increasing length. - PSRNormalizeTransporter{}.normalize(expr); - - // Now remove terms that are subsumed by some other term. This means try to remove terms whose - // atoms are a superset of some other term: (a^b) subsumes (a^b^c), so remove (a^b^c). Since - // there are no duplicate atoms, we're looking to remove terms whose 'nodes().size()' is large. - PSRExpr::NodeVector& terms = expr.cast<PSRExpr::Disjunction>()->nodes(); - - // First give each unique atom a label. - // Store each atom by value because 'remove_if' can move-from a 'PSRExpr::Node', which deletes - // the heap-allocated 'Atom'. - std::vector<PSRExpr::Atom> atoms; - const auto atomLabel = [&](const PSRExpr::Atom& atom) -> size_t { - size_t i = 0; - for (const auto& seen : atoms) { - if (atom == seen) { - return i; - } - ++i; - } - atoms.emplace_back(atom); - return i; - }; - using Mask = size_t; - static constexpr size_t maxAtoms = sizeof(Mask) * CHAR_BIT; - for (const PSRExpr::Node& termNode : terms) { - for (const PSRExpr::Node& atomNode : termNode.cast<PSRExpr::Conjunction>()->nodes()) { - const PSRExpr::Atom& atom = *atomNode.cast<PSRExpr::Atom>(); - atomLabel(atom); - if (atoms.size() > maxAtoms) { - return; - } - } + if (PSRExpr::isCNF(_expr)) { + return simplifyExpr<true /*isCNF*/>(_expr, func); } - - std::vector<Mask> seen; - seen.reserve(terms.size()); - auto last = std::remove_if(terms.begin(), terms.end(), [&](const PSRExpr::Node& term) -> bool { - Mask mask = 0; - for (const PSRExpr::Node& atomNode : term.cast<PSRExpr::Conjunction>()->nodes()) { - const PSRExpr::Atom& atom = *atomNode.cast<PSRExpr::Atom>(); - mask |= Mask{1} << atomLabel(atom); - } - - // Does any previously-seen mask subsume this one? - for (Mask prev : seen) { - const bool isSuperset = (prev & mask) == prev; - if (isSuperset) { - return true; - } - } - - seen.push_back(mask); - return false; - }); - terms.erase(last, terms.end()); + return simplifyExpr<false /*isCNF*/>(_expr, func); } - /** * Returns a vector of ((input binding, path), output binding). The output binding names * are unique and you can think of the vector as a product: every row has all the projections diff --git a/src/mongo/db/query/optimizer/partial_schema_requirements.h b/src/mongo/db/query/optimizer/partial_schema_requirements.h index 55f03d149c3..47feba141e8 100644 --- a/src/mongo/db/query/optimizer/partial_schema_requirements.h +++ b/src/mongo/db/query/optimizer/partial_schema_requirements.h @@ -52,7 +52,7 @@ class PartialSchemaRequirements { public: using Entry = std::pair<PartialSchemaKey, PartialSchemaRequirement>; - // TODO SERVER-73828: Remove these iterator constructs. + // TODO SERVER-69026: Remove these iterator constructs. using ConstNodeVecIter = std::vector<PSRExpr::Node>::const_iterator; using NodeVecIter = std::vector<PSRExpr::Node>::iterator; @@ -121,7 +121,7 @@ public: // fully-open PartialSchemaRequirement which does not bind. PartialSchemaRequirements(); - explicit PartialSchemaRequirements(PSRExpr::Node requirements); + PartialSchemaRequirements(PSRExpr::Node requirements); bool operator==(const PartialSchemaRequirements& other) const; @@ -145,7 +145,7 @@ public: boost::optional<std::pair<size_t, PartialSchemaRequirement>> findFirstConjunct( const PartialSchemaKey&) const; - // TODO SERVER-73828: Remove these methods in favor of visitDis/Conjuncts(). + // TODO SERVER-69026: Remove these methods in favor of visitDis/Conjuncts(). Range<true> conjuncts() const { tassert(7453905, "Expected PartialSchemaRequirement to be a singleton disjunction", @@ -176,6 +176,9 @@ public: * * For now, we assert that we have only one disjunct. This means we avoid applying * the distributive law, which would duplicate the new requirement into each disjunct. + * + * TODO SERVER-69026 Consider applying the distributive law to allow contained-OR in + * SargableNode. */ void add(PartialSchemaKey, PartialSchemaRequirement); @@ -193,19 +196,6 @@ public: * TODO SERVER-73827: Consider applying this simplification during BoolExpr building. */ bool simplify(std::function<bool(const PartialSchemaKey&, PartialSchemaRequirement&)>); - static bool simplify(PSRExpr::Node& expr, - std::function<bool(const PartialSchemaKey&, PartialSchemaRequirement&)>); - static void normalize(PSRExpr::Node& expr); - - /** - * Given a DNF, try to detect and remove redundant terms. - * - * For example, in ((a ^ b) U (z) U (a ^ b ^ c)) the (a ^ b) is redundant because - * (a ^ b ^ c) implies (a ^ b). - * - * TODO SERVER-73827 Consider doing this simplification as part of BoolExpr::Builder. - */ - static void simplifyRedundantDNF(PSRExpr::Node& expr); const auto& getRoot() const { return _expr; 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 d503c4b69cb..0c8ecd80db3 100644 --- a/src/mongo/db/query/optimizer/physical_rewriter_optimizer_test.cpp +++ b/src/mongo/db/query/optimizer/physical_rewriter_optimizer_test.cpp @@ -1806,14 +1806,14 @@ TEST(PhysRewriter, SargableProjectionRenames) { // projections. ASSERT_EXPLAIN_V2_AUTO( "Root [{root}]\n" - "Evaluation [{pa} = Variable [pa1]]\n" + "Evaluation [{pa1} = Variable [pa]]\n" "Sargable [Complete]\n" "| | requirements: \n" - "| | {{{root, 'PathGet [a] PathIdentity []', pa1, {{{=Const [1]}}}}}}\n" + "| | {{{root, 'PathGet [a] PathIdentity []', pa, {{{=Const [1]}}}}}}\n" "| scanParams: \n" - "| {'a': pa1}\n" + "| {'a': pa}\n" "| residualReqs: \n" - "| {{{pa1, 'PathIdentity []', {{{=Const [1]}}}, entryIndex: 0}}}\n" + "| {{{pa, 'PathIdentity []', {{{=Const [1]}}}, entryIndex: 0}}}\n" "Scan [c1, {root}]\n", optimized); } @@ -1981,10 +1981,7 @@ TEST(PhysRewriter, CoveredScan) { ABT optimized = std::move(rootNode); phaseManager.optimize(optimized); - ASSERT_BETWEEN_AUTO( // - 3, - 6, - phaseManager.getMemo().getStats()._physPlanExplorationCount); + ASSERT_EQ(5, phaseManager.getMemo().getStats()._physPlanExplorationCount); // Since we do not optimize with fast null handling, we need to split the predicate between the // index scan and fetch in order to handle null. @@ -1994,8 +1991,8 @@ TEST(PhysRewriter, CoveredScan) { "| | Const [true]\n" "| LimitSkip [limit: 1, skip: 0]\n" "| Seek [ridProjection: rid_0, {'a': pa}, c1]\n" - "IndexScan [{'<rid>': rid_0}, scanDefName: c1, indexDefName: index1, interval: {<Const " - "[1]}]\n", + "IndexScan [{'<rid>': rid_0}, scanDefName: c1, indexDefName: index1, interval: {<Const [1" + "]}]\n", optimized); } diff --git a/src/mongo/db/query/optimizer/reference_tracker.cpp b/src/mongo/db/query/optimizer/reference_tracker.cpp index 6d8bdfc5582..79c2d7bda35 100644 --- a/src/mongo/db/query/optimizer/reference_tracker.cpp +++ b/src/mongo/db/query/optimizer/reference_tracker.cpp @@ -510,7 +510,7 @@ struct Collector { const RIDUnionNode& node, CollectedInfo leftChildResult, CollectedInfo rightChildResult) { - // TODO SERVER-75587 should determine how the reference tracker for RIDUnionNode will work. + // TODO SERVER-69026 should determine how the reference tracker for RIDUnionNode will work. return handleRIDNodeReferences( node, std::move(leftChildResult), std::move(rightChildResult)); } diff --git a/src/mongo/db/query/optimizer/utils/abt_compare.cpp b/src/mongo/db/query/optimizer/utils/abt_compare.cpp index 12e4fcf1fe4..c321add3efa 100644 --- a/src/mongo/db/query/optimizer/utils/abt_compare.cpp +++ b/src/mongo/db/query/optimizer/utils/abt_compare.cpp @@ -411,14 +411,9 @@ public: private: int compare(const PSRExpr::Atom& node, const PSRExpr::Atom& other) { - const auto& [key1, req1] = node.getExpr(); - const auto& [key2, req2] = other.getExpr(); - - int keyCmp = PartialSchemaKeyComparator::Cmp3W{}(key1, key2); - if (keyCmp != 0) { - return keyCmp; - } - return PartialSchemaRequirementComparator::Cmp3W{}(req1, req2); + auto isSorted = + PartialSchemaKeyLessComparator{}(node.getExpr().first, other.getExpr().first); + return isSorted ? -1 : 1; } int compare(const PSRExpr::Conjunction& node, const PSRExpr::Conjunction& other) { diff --git a/src/mongo/db/query/optimizer/utils/utils.cpp b/src/mongo/db/query/optimizer/utils/utils.cpp index 68b21a1b422..881cad5c5f4 100644 --- a/src/mongo/db/query/optimizer/utils/utils.cpp +++ b/src/mongo/db/query/optimizer/utils/utils.cpp @@ -377,7 +377,7 @@ public: } } - leftReqMap = PartialSchemaRequirements{std::move(*resultReqs.finish())}; + leftReqMap = std::move(*resultReqs.finish()); return leftResult; } // Left and right don't all use the same key. @@ -979,6 +979,7 @@ bool isSubsetOfPartialSchemaReq(const PartialSchemaRequirements& lhs, bool intersectPartialSchemaReq(PartialSchemaRequirements& target, const PartialSchemaRequirements& source) { + // TODO SERVER-69026 Consider implementing intersect for top-level disjunctions. if (!PSRExpr::isSingletonDisjunction(target.getRoot()) || !PSRExpr::isSingletonDisjunction(source.getRoot())) { return false; @@ -1006,7 +1007,7 @@ PartialSchemaRequirements unionPartialSchemaReq(PartialSchemaRequirements&& left resultNodes.insert(resultNodes.end(), std::make_move_iterator(rightDisj.nodes().begin()), std::make_move_iterator(rightDisj.nodes().end())); - return PartialSchemaRequirements{PSRExpr::make<PSRExpr::Disjunction>(std::move(resultNodes))}; + return PSRExpr::make<PSRExpr::Disjunction>(std::move(resultNodes)); } std::string encodeIndexKeyName(const size_t indexField) { @@ -2658,10 +2659,10 @@ PhysPlanBuilder lowerEqPrefixes(PrefixId& prefixId, return lowerTransport.lower(eqPrefixes.at(eqPrefixIndex)._interval); } -bool hasProperIntervals(const PSRExpr::Node& reqs) { +bool hasProperIntervals(const PartialSchemaRequirements& reqMap) { // Compute if this node has any proper (not fully open) intervals. bool hasProperIntervals = false; - PSRExpr::visitAnyShape(reqs, [&](const PartialSchemaEntry& e) { + PSRExpr::visitDNF(reqMap.getRoot(), [&](const PartialSchemaEntry& e) { hasProperIntervals |= !isIntervalReqFullyOpenDNF(e.second.getIntervals()); }); return hasProperIntervals; diff --git a/src/mongo/db/query/optimizer/utils/utils.h b/src/mongo/db/query/optimizer/utils/utils.h index 6653a0f308a..8fb3602e31d 100644 --- a/src/mongo/db/query/optimizer/utils/utils.h +++ b/src/mongo/db/query/optimizer/utils/utils.h @@ -458,5 +458,5 @@ PhysPlanBuilder lowerEqPrefixes(PrefixId& prefixId, CEType scanGroupCE, bool useSortedMerge); -bool hasProperIntervals(const PSRExpr::Node& reqs); +bool hasProperIntervals(const PartialSchemaRequirements& reqMap); } // namespace mongo::optimizer |