diff options
author | Svilen Mihaylov <svilen.mihaylov@mongodb.com> | 2023-02-03 18:44:40 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2023-02-03 21:15:53 +0000 |
commit | dd107e8f21b626df74a96db84d0a95cc0db05a75 (patch) | |
tree | c026b143c81eca0c84c2893526d9241fbc858dca | |
parent | 56616080c12298229fc6e3cc71ace4c85ac973f4 (diff) | |
download | mongo-dd107e8f21b626df74a96db84d0a95cc0db05a75.tar.gz |
SERVER-72784 [CQF] Update representation of compound index bounds
18 files changed, 477 insertions, 333 deletions
diff --git a/jstests/cqf/array_match.js b/jstests/cqf/array_match.js index 324275b477f..1f330d2874a 100644 --- a/jstests/cqf/array_match.js +++ b/jstests/cqf/array_match.js @@ -48,9 +48,9 @@ assert.commandWorked(bulk.execute()); const indexUnionNode = navigateToPlanPath(res, "child.child.leftChild.child"); assertValueOnPath("Union", indexUnionNode, "nodeType"); assertValueOnPath("IndexScan", indexUnionNode, "children.0.nodeType"); - assertValueOnPath([2], indexUnionNode, "children.0.interval.0.lowBound.bound.value"); + assertValueOnPath([2], indexUnionNode, "children.0.interval.lowBound.bound.0.value"); assertValueOnPath("IndexScan", indexUnionNode, "children.1.nodeType"); - assertValueOnPath(2, indexUnionNode, "children.1.interval.0.lowBound.bound.value"); + assertValueOnPath(2, indexUnionNode, "children.1.interval.lowBound.bound.0.value"); } { @@ -59,9 +59,9 @@ assert.commandWorked(bulk.execute()); const indexUnionNode = navigateToPlanPath(res, "child.child.leftChild.child"); assertValueOnPath("Union", indexUnionNode, "nodeType"); assertValueOnPath("IndexScan", indexUnionNode, "children.0.nodeType"); - assertValueOnPath(undefined, indexUnionNode, "children.0.interval.0.lowBound.bound.value"); + assertValueOnPath(undefined, indexUnionNode, "children.0.interval.lowBound.bound.0.value"); assertValueOnPath("IndexScan", indexUnionNode, "children.1.nodeType"); - assertValueOnPath([], indexUnionNode, "children.1.interval.0.lowBound.bound.value"); + assertValueOnPath([], indexUnionNode, "children.1.interval.lowBound.bound.0.value"); } assert.commandWorked(t.dropIndex({a: 1})); @@ -75,8 +75,8 @@ assert.commandWorked(t.createIndex({b: 1, a: 1})); const indexUnionNode = navigateToPlanPath(res, "child.child.leftChild.child"); assertValueOnPath("Union", indexUnionNode, "nodeType"); assertValueOnPath("IndexScan", indexUnionNode, "children.0.nodeType"); - assertValueOnPath([2], indexUnionNode, "children.0.interval.1.lowBound.bound.value"); + assertValueOnPath([2], indexUnionNode, "children.0.interval.lowBound.bound.1.value"); assertValueOnPath("IndexScan", indexUnionNode, "children.1.nodeType"); - assertValueOnPath(2, indexUnionNode, "children.1.interval.1.lowBound.bound.value"); + assertValueOnPath(2, indexUnionNode, "children.1.interval.lowBound.bound.1.value"); } }()); diff --git a/jstests/cqf/find_sort.js b/jstests/cqf/find_sort.js index d82e8228f43..53d281dba0d 100644 --- a/jstests/cqf/find_sort.js +++ b/jstests/cqf/find_sort.js @@ -37,5 +37,5 @@ assert.eq(numResults, res.executionStats.nReturned); const indexScanNode = navigateToPlanPath(res, "child.child.child.leftChild.child.child"); assertValueOnPath("IndexScan", indexScanNode, "nodeType"); -assertValueOnPath(5, indexScanNode, "interval.0.highBound.bound.value"); +assertValueOnPath(5, indexScanNode, "interval.highBound.bound.0.value"); }()); diff --git a/jstests/libs/optimizer_utils.js b/jstests/libs/optimizer_utils.js index 88378cfd5c3..2d0a39efadb 100644 --- a/jstests/libs/optimizer_utils.js +++ b/jstests/libs/optimizer_utils.js @@ -133,30 +133,57 @@ function findSubtrees(tree, predicate) { return result; } +function printBound(bound) { + if (!Array.isArray(bound.bound)) { + return [false, ""]; + } + + let result = ""; + let first = true; + for (const element of bound.bound) { + if (element.nodeType !== "Const") { + return [false, ""]; + } + + result += tojson(element.value); + if (first) { + first = false; + } else { + result += " | "; + } + } + + return [true, result]; +} + function prettyInterval(compoundInterval) { // Takes an array of intervals, each one applying to one component of a compound index key. // Try to format it as a string. // If either bound is not Constant, return the original JSON unchanged. - if (!Array.isArray(compoundInterval)) { - return compoundInterval; - } + + const lowBound = compoundInterval.lowBound; + const highBound = compoundInterval.highBound; + const lowInclusive = lowBound.inclusive; + const highInclusive = highBound.inclusive; + assert.eq(typeof lowInclusive, 'boolean'); + assert.eq(typeof highInclusive, 'boolean'); let result = ''; - for (const {lowBound, highBound} of compoundInterval) { - if (lowBound.bound.nodeType !== 'Const' || highBound.bound.nodeType !== 'Const') { + { + const res = printBound(lowBound); + if (!res[0]) { return compoundInterval; } - - const lowInclusive = lowBound.inclusive; - const highInclusive = highBound.inclusive; - assert.eq(typeof lowInclusive, 'boolean'); - assert.eq(typeof highInclusive, 'boolean'); - - result += ' '; result += lowInclusive ? '[ ' : '( '; - result += tojson(lowBound.bound.value); - result += ', '; - result += tojson(highBound.bound.value); + result += res[1]; + } + result += ", "; + { + const res = printBound(highBound); + if (!res[0]) { + return compoundInterval; + } + result += res[1]; result += highInclusive ? ' ]' : ' )'; } return result.trim(); diff --git a/src/mongo/db/exec/sbe/abt/abt_lower.cpp b/src/mongo/db/exec/sbe/abt/abt_lower.cpp index 784e8280ee0..5358b304edc 100644 --- a/src/mongo/db/exec/sbe/abt/abt_lower.cpp +++ b/src/mongo/db/exec/sbe/abt/abt_lower.cpp @@ -1107,7 +1107,7 @@ std::unique_ptr<sbe::EExpression> SBENodeLowering::convertBoundsToExpr( SlotVarMap& slotMap, const bool isLower, const IndexDefinition& indexDef, - const CompoundIntervalRequirement& interval) { + const CompoundBoundRequirement& bound) { std::vector<std::unique_ptr<sbe::EExpression>> ksFnArgs; ksFnArgs.emplace_back( sbe::makeE<sbe::EConstant>(sbe::value::TypeTags::NumberInt64, @@ -1119,29 +1119,21 @@ std::unique_ptr<sbe::EExpression> SBENodeLowering::convertBoundsToExpr( sbe::value::bitcastFrom<uint32_t>(indexDef.getOrdering()))); auto exprLower = getExpressionLowering(slotMap); - bool inclusive = true; - bool fullyInfinite = true; - for (const auto& entry : interval) { - const BoundRequirement& entryBound = isLower ? entry.getLowBound() : entry.getHighBound(); - if (!entryBound.isMinusInf() && !entryBound.isPlusInf()) { - fullyInfinite = false; - if (!entryBound.isInclusive()) { - inclusive = false; - } - } - - auto boundExpr = exprLower.optimize(entryBound.getBound()); - ksFnArgs.emplace_back(std::move(boundExpr)); + for (const auto& expr : bound.getBound()) { + ksFnArgs.emplace_back(exprLower.optimize(expr)); } - if (fullyInfinite && !isLower) { + if (!isLower && (bound.isMinusInf() || bound.isPlusInf())) { // We can skip if fully infinite only for upper bound. For lower bound we need to generate // minkeys. return nullptr; }; + const auto discriminator = (isLower == bound.isInclusive()) + ? KeyString::Discriminator::kExclusiveBefore + : KeyString::Discriminator::kExclusiveAfter; ksFnArgs.emplace_back(sbe::makeE<sbe::EConstant>( sbe::value::TypeTags::NumberInt64, - sbe::value::bitcastFrom<int64_t>((isLower == inclusive) ? 1 : 2))); + sbe::value::bitcastFrom<int64_t>(static_cast<int64_t>(discriminator)))); return sbe::makeE<sbe::EFunction>("ks", toInlinedVector(std::move(ksFnArgs))); } @@ -1182,15 +1174,16 @@ std::unique_ptr<sbe::PlanStage> SBENodeLowering::walk(const IndexScanNode& n, } const auto& interval = n.getIndexInterval(); - auto lowerBoundExpr = convertBoundsToExpr(slotMap, true /*isLower*/, indexDef, interval); - auto upperBoundExpr = convertBoundsToExpr(slotMap, false /*isLower*/, indexDef, interval); + auto lowerBoundExpr = + convertBoundsToExpr(slotMap, true /*isLower*/, indexDef, interval.getLowBound()); + auto upperBoundExpr = + convertBoundsToExpr(slotMap, false /*isLower*/, indexDef, interval.getHighBound()); tassert(6624234, "Invalid bounds combination", lowerBoundExpr != nullptr || upperBoundExpr == nullptr); const bool reverse = n.isIndexReverseOrder(); if (reverse) { - // TODO: SERVER-72784: implement bounds as vector of values, move reversal to impl. phase. std::swap(lowerBoundExpr, upperBoundExpr); } diff --git a/src/mongo/db/exec/sbe/abt/abt_lower.h b/src/mongo/db/exec/sbe/abt/abt_lower.h index 51a37aa75d8..0d37d2c274c 100644 --- a/src/mongo/db/exec/sbe/abt/abt_lower.h +++ b/src/mongo/db/exec/sbe/abt/abt_lower.h @@ -275,11 +275,10 @@ private: const NodeProps& props, const sbe::value::SlotVector& toExclude = {}); - std::unique_ptr<sbe::EExpression> convertBoundsToExpr( - SlotVarMap& slotMap, - bool isLower, - const IndexDefinition& indexDef, - const CompoundIntervalRequirement& interval); + std::unique_ptr<sbe::EExpression> convertBoundsToExpr(SlotVarMap& slotMap, + bool isLower, + const IndexDefinition& indexDef, + const CompoundBoundRequirement& bound); std::unique_ptr<sbe::PlanStage> generateInternal(const ABT& n, SlotVarMap& slotMap, diff --git a/src/mongo/db/exec/sbe/abt/abt_lower_bm.cpp b/src/mongo/db/exec/sbe/abt/abt_lower_bm.cpp index ee7e653073d..20e17f83a32 100644 --- a/src/mongo/db/exec/sbe/abt/abt_lower_bm.cpp +++ b/src/mongo/db/exec/sbe/abt/abt_lower_bm.cpp @@ -147,14 +147,13 @@ BENCHMARK_F(ABTNodeLoweringFixture, BM_LowerPhysicalScan)(benchmark::State& stat } BENCHMARK_F(ABTNodeLoweringFixture, BM_LowerIndexScanAndSeek)(benchmark::State& state) { - auto indexScan = - _node(make<IndexScanNode>(FieldProjectionMap{{ProjectionName{"rid"}}, {}, {}}, - "collName", - "idx", - CompoundIntervalRequirement{IntervalRequirement( - BoundRequirement(false, Constant::fromDouble(23)), - BoundRequirement(true, Constant::fromDouble(35)))}, - false)); + auto indexScan = _node( + make<IndexScanNode>(FieldProjectionMap{{ProjectionName{"rid"}}, {}, {}}, + "collName", + "idx", + CompoundIntervalRequirement{{false, makeSeq(Constant::fromDouble(23))}, + {true, makeSeq(Constant::fromDouble(35))}}, + false)); auto seek = _node(make<LimitSkipNode>( properties::LimitSkipRequirement(1, 0), diff --git a/src/mongo/db/exec/sbe/abt/abt_lower_test.cpp b/src/mongo/db/exec/sbe/abt/abt_lower_test.cpp index a4a496baa9a..4a9f299f6cb 100644 --- a/src/mongo/db/exec/sbe/abt/abt_lower_test.cpp +++ b/src/mongo/db/exec/sbe/abt/abt_lower_test.cpp @@ -530,17 +530,17 @@ TEST_F(ABTPlanGeneration, LowerIndexScanNode) { bool isReversed = i == 1; auto reversedString = isReversed ? "reverse" : "forward"; // Basic index scan with RID - runNodeVariation(ctx, - str::stream() << "Basic " << reversedString << " index scan with RID", - _node(make<IndexScanNode>( - FieldProjectionMap{{ProjectionName{"rid"}}, {}, {}}, - "collName", - "index0", - CompoundIntervalRequirement{IntervalRequirement( - BoundRequirement(i > 0, Constant::fromDouble(23 + i * 4)), - BoundRequirement(i == 0, Constant::fromDouble(35 + i * 100)))}, - isReversed)), - indexDefs); + runNodeVariation( + ctx, + str::stream() << "Basic " << reversedString << " index scan with RID", + _node(make<IndexScanNode>( + FieldProjectionMap{{ProjectionName{"rid"}}, {}, {}}, + "collName", + "index0", + CompoundIntervalRequirement{{i > 0, makeSeq(Constant::fromDouble(23 + i * 4))}, + {i == 0, makeSeq(Constant::fromDouble(35 + i * 100))}}, + isReversed)), + indexDefs); // Covering index scan with one field @@ -551,9 +551,9 @@ TEST_F(ABTPlanGeneration, LowerIndexScanNode) { FieldProjectionMap{{}, {}, {{"<indexKey> 0", ProjectionName{"proj0"}}}}, "collName", "index0", - CompoundIntervalRequirement{IntervalRequirement( - BoundRequirement(i >= 0, Constant::fromDouble(23 + (i + 1) * 3)), - BoundRequirement(i > 0, Constant::fromDouble(35 + ((i * 3) * (i * 4)))))}, + CompoundIntervalRequirement{ + {i >= 0, makeSeq(Constant::fromDouble(23 + (i + 1) * 3))}, + {i > 0, makeSeq(Constant::fromDouble(35 + ((i * 3) * (i * 4))))}}, isReversed)), indexDefs); } @@ -742,14 +742,13 @@ TEST_F(ABTPlanGeneration, LowerSeekNode) { GoldenTestContext ctx(&goldenTestConfig); ctx.printTestHeader(GoldenTestContext::HeaderFormat::Text); - auto indexScan = - _node(make<IndexScanNode>(FieldProjectionMap{{ProjectionName{"rid"}}, {}, {}}, - "collName", - "index0", - CompoundIntervalRequirement{IntervalRequirement( - BoundRequirement(false, Constant::fromDouble(23)), - BoundRequirement(true, Constant::fromDouble(35)))}, - false)); + auto indexScan = _node( + make<IndexScanNode>(FieldProjectionMap{{ProjectionName{"rid"}}, {}, {}}, + "collName", + "index0", + CompoundIntervalRequirement{{false, makeSeq(Constant::fromDouble(23))}, + {true, makeSeq(Constant::fromDouble(35))}}, + false)); auto seek = _node(make<LimitSkipNode>( properties::LimitSkipRequirement(1, 0), diff --git a/src/mongo/db/query/ce/bound_utils.cpp b/src/mongo/db/query/ce/bound_utils.cpp index 13961f8e2bb..3374a550c74 100644 --- a/src/mongo/db/query/ce/bound_utils.cpp +++ b/src/mongo/db/query/ce/bound_utils.cpp @@ -88,12 +88,12 @@ IntervalRequirement getMinMaxIntervalForType(sbe::value::TypeTags type) { bool isIntervalSubsetOfType(const IntervalRequirement& interval, sbe::value::TypeTags type) { // Create a conjunction of the interval and the min-max interval for the type as input for the // intersection function. - auto intervals = - IntervalReqExpr::make<IntervalReqExpr::Disjunction>(IntervalReqExpr::NodeVector{ - IntervalReqExpr::make<IntervalReqExpr::Conjunction>(IntervalReqExpr::NodeVector{ - IntervalReqExpr::make<IntervalReqExpr::Atom>(interval), - IntervalReqExpr::make<IntervalReqExpr::Atom>(getMinMaxIntervalForType(type))})}); - + auto intervals = std::move(*IntervalReqExpr::Builder{} + .pushDisj() + .pushConj() + .atom(interval) + .atom(getMinMaxIntervalForType(type)) + .finish()); return intersectDNFIntervals(intervals, ConstEval::constFold).has_value(); } diff --git a/src/mongo/db/query/optimizer/explain.cpp b/src/mongo/db/query/optimizer/explain.cpp index fb9af129873..af36021acbf 100644 --- a/src/mongo/db/query/optimizer/explain.cpp +++ b/src/mongo/db/query/optimizer/explain.cpp @@ -893,91 +893,115 @@ public: return printer; } - void printInterval(ExplainPrinter& printer, const IntervalRequirement& interval) { - const BoundRequirement& lowBound = interval.getLowBound(); - const BoundRequirement& highBound = interval.getHighBound(); + void printBound(ExplainPrinter& printer, const BoundRequirement& bound) { + if constexpr (version < ExplainVersion::V3) { + // Since we are printing on a single level, use V1 printer in order to avoid children + // being reversed. Also note that we are specifically not printing inclusive flag here. + // The inclusion is explained by the caller. + ExplainGeneratorTransporter<ExplainVersion::V1> gen; + auto boundPrinter = gen.generate(bound.getBound()); + printer.printSingleLevel(boundPrinter); + } else if constexpr (version == ExplainVersion::V3) { + printer.fieldName("inclusive").print(bound.isInclusive()); + { + ExplainPrinter boundPrinter = generate(bound.getBound()); + printer.fieldName("bound").print(boundPrinter); + } + } else { + MONGO_UNREACHABLE; + } + } + + void printBound(ExplainPrinter& printer, const CompoundBoundRequirement& bound) { if constexpr (version < ExplainVersion::V3) { - const auto printBoundFn = [](ExplainPrinter& printer, const ABT& bound) { - // Since we are printing on a single level, use V1 printer in order to avoid - // children being reversed. - ExplainGeneratorTransporter<ExplainVersion::V1> gen; - auto boundPrinter = gen.generate(bound); - printer.printSingleLevel(boundPrinter); - }; + const bool manyConstants = bound.size() > 1 && bound.isConstant(); + if (manyConstants) { + printer.print("Const ["); + } + + bool first = true; + for (const auto& entry : bound.getBound()) { + if (first) { + first = false; + } else { + printer.print(" | "); + } + + if (manyConstants) { + std::ostringstream os; + os << entry.cast<Constant>()->get(); + printer.print(os.str()); + } else { + ExplainGeneratorTransporter<ExplainVersion::V1> gen; + auto boundPrinter = gen.generate(entry); + printer.printSingleLevel(boundPrinter); + } + } + + if (manyConstants) { + printer.print("]"); + } + } else if constexpr (version == ExplainVersion::V3) { + printer.fieldName("inclusive").print(bound.isInclusive()); + + std::vector<ExplainPrinter> printers; + for (const auto& entry : bound.getBound()) { + printers.push_back(generate(entry)); + } + printer.fieldName("bound").print(printers); + } else { + MONGO_UNREACHABLE; + } + } + + template <class T> + void printInterval(ExplainPrinter& printer, const T& interval) { + const auto& lowBound = interval.getLowBound(); + const auto& highBound = interval.getHighBound(); + if constexpr (version < ExplainVersion::V3) { // Shortened output for half-open, fully open and point intervals. if (interval.isFullyOpen()) { printer.print("<fully open>"); } else if (interval.isEquality()) { printer.print("="); - printBoundFn(printer, lowBound.getBound()); + printBound(printer, lowBound); } else if (lowBound.isMinusInf()) { printer.print("<"); if (highBound.isInclusive()) { printer.print("="); } - printBoundFn(printer, highBound.getBound()); + printBound(printer, highBound); } else if (highBound.isPlusInf()) { printer.print(">"); if (lowBound.isInclusive()) { printer.print("="); } - printBoundFn(printer, lowBound.getBound()); + printBound(printer, lowBound); } else { // Output for a generic interval. printer.print(lowBound.isInclusive() ? "[" : "("); - printBoundFn(printer, lowBound.getBound()); + printBound(printer, lowBound); printer.print(", "); - printBoundFn(printer, highBound.getBound()); + printBound(printer, highBound); printer.print(highBound.isInclusive() ? "]" : ")"); } } else if constexpr (version == ExplainVersion::V3) { ExplainPrinter lowBoundPrinter; - lowBoundPrinter.fieldName("inclusive").print(lowBound.isInclusive()); - { - ExplainPrinter boundPrinter = generate(lowBound.getBound()); - lowBoundPrinter.fieldName("bound").print(boundPrinter); - } - + printBound(lowBoundPrinter, lowBound); ExplainPrinter highBoundPrinter; - highBoundPrinter.fieldName("inclusive").print(highBound.isInclusive()); - { - ExplainPrinter boundPrinter = generate(highBound.getBound()); - highBoundPrinter.fieldName("bound").print(boundPrinter); - } + printBound(highBoundPrinter, highBound); - printer.fieldName("lowBound") + ExplainPrinter local; + local.fieldName("lowBound") .print(lowBoundPrinter) .fieldName("highBound") .print(highBoundPrinter); - } else { - MONGO_UNREACHABLE; - } - } - - void printInterval(ExplainPrinter& printer, const CompoundIntervalRequirement& interval) { - if constexpr (version < ExplainVersion::V3) { - bool first = true; - for (const auto& entry : interval) { - if (first) { - first = false; - } else { - printer.print(", "); - } - printInterval(printer, entry); - } - } else if constexpr (version == ExplainVersion::V3) { - std::vector<ExplainPrinter> printers; - for (const auto& entry : interval) { - ExplainPrinter local; - printInterval(local, entry); - printers.push_back(std::move(local)); - } - printer.print(printers); + printer.print(local); } else { MONGO_UNREACHABLE; } @@ -2862,7 +2886,7 @@ std::string ExplainGenerator::explainInterval(const IntervalRequirement& interva return gen.printInterval(interval); } -std::string explainInterval(const CompoundIntervalRequirement& interval) { +std::string ExplainGenerator::explainInterval(const CompoundIntervalRequirement& interval) { ExplainGeneratorV2 gen; return gen.printInterval(interval); } diff --git a/src/mongo/db/query/optimizer/index_bounds.cpp b/src/mongo/db/query/optimizer/index_bounds.cpp index 3135126a57b..f6421d0e0d0 100644 --- a/src/mongo/db/query/optimizer/index_bounds.cpp +++ b/src/mongo/db/query/optimizer/index_bounds.cpp @@ -44,19 +44,10 @@ BoundRequirement BoundRequirement::makePlusInf() { return {true /*inclusive*/, Constant::maxKey()}; } -BoundRequirement::BoundRequirement(bool inclusive, ABT bound) - : _inclusive(inclusive), _bound(std::move(bound)) { +BoundRequirement::BoundRequirement(bool inclusive, ABT bound) : Base(inclusive, std::move(bound)) { assertExprSort(_bound); } -bool BoundRequirement::operator==(const BoundRequirement& other) const { - return _inclusive == other._inclusive && _bound == other._bound; -} - -bool BoundRequirement::isInclusive() const { - return _inclusive; -} - bool BoundRequirement::isMinusInf() const { return _inclusive && _bound == Constant::minKey(); } @@ -65,50 +56,78 @@ bool BoundRequirement::isPlusInf() const { return _inclusive && _bound == Constant::maxKey(); } -const ABT& BoundRequirement::getBound() const { - return _bound; -} - IntervalRequirement::IntervalRequirement() : IntervalRequirement(BoundRequirement::makeMinusInf(), BoundRequirement::makePlusInf()) {} IntervalRequirement::IntervalRequirement(BoundRequirement lowBound, BoundRequirement highBound) - : _lowBound(std::move(lowBound)), _highBound(std::move(highBound)) {} - -bool IntervalRequirement::operator==(const IntervalRequirement& other) const { - return _lowBound == other._lowBound && _highBound == other._highBound; -} + : Base(std::move(lowBound), std::move(highBound)) {} bool IntervalRequirement::isFullyOpen() const { return _lowBound.isMinusInf() && _highBound.isPlusInf(); } -bool IntervalRequirement::isEquality() const { - return _lowBound.isInclusive() && _highBound.isInclusive() && _lowBound == _highBound; +bool IntervalRequirement::isConstant() const { + return getLowBound().getBound().is<Constant>() && getHighBound().getBound().is<Constant>(); } -const BoundRequirement& IntervalRequirement::getLowBound() const { - return _lowBound; +bool isIntervalReqFullyOpenDNF(const IntervalReqExpr::Node& n) { + if (auto singular = IntervalReqExpr::getSingularDNF(n); singular && singular->isFullyOpen()) { + return true; + } + return false; } -BoundRequirement& IntervalRequirement::getLowBound() { - return _lowBound; +CompoundBoundRequirement::CompoundBoundRequirement(bool inclusive, ABTVector bound) + : Base(inclusive, std::move(bound)) { + for (const auto& expr : _bound) { + assertExprSort(expr); + } } -const BoundRequirement& IntervalRequirement::getHighBound() const { - return _highBound; +bool CompoundBoundRequirement::isMinusInf() const { + return _inclusive && std::all_of(_bound.cbegin(), _bound.cend(), [](const ABT& element) { + return element == Constant::minKey(); + }); } -BoundRequirement& IntervalRequirement::getHighBound() { - return _highBound; +bool CompoundBoundRequirement::isPlusInf() const { + return _inclusive && std::all_of(_bound.cbegin(), _bound.cend(), [](const ABT& element) { + return element == Constant::maxKey(); + }); } -void IntervalRequirement::reverse() { - std::swap(_lowBound, _highBound); +bool CompoundBoundRequirement::isConstant() const { + return std::all_of( + _bound.cbegin(), _bound.cend(), [](const ABT& element) { return element.is<Constant>(); }); } -bool IntervalRequirement::isConstant() const { - return getLowBound().getBound().is<Constant>() && getHighBound().getBound().is<Constant>(); +size_t CompoundBoundRequirement::size() const { + return _bound.size(); +} + +void CompoundBoundRequirement::push_back(BoundRequirement bound) { + _inclusive &= bound.isInclusive(); + _bound.push_back(std::move(bound.getBound())); +} + +CompoundIntervalRequirement::CompoundIntervalRequirement() + : CompoundIntervalRequirement({true /*inclusive*/, {}}, {true /*inclusive*/, {}}) {} + +CompoundIntervalRequirement::CompoundIntervalRequirement(CompoundBoundRequirement lowBound, + CompoundBoundRequirement highBound) + : Base(std::move(lowBound), std::move(highBound)) {} + +bool CompoundIntervalRequirement::isFullyOpen() const { + return _lowBound.isMinusInf() && _highBound.isPlusInf(); +} + +size_t CompoundIntervalRequirement::size() const { + return _lowBound.size(); +} + +void CompoundIntervalRequirement::push_back(IntervalRequirement interval) { + _lowBound.push_back(std::move(interval.getLowBound())); + _highBound.push_back(std::move(interval.getHighBound())); } PartialSchemaKey::PartialSchemaKey(ABT path) : PartialSchemaKey(boost::none, std::move(path)) {} @@ -126,13 +145,6 @@ bool PartialSchemaKey::operator==(const PartialSchemaKey& other) const { return _projectionName == other._projectionName && _path == other._path; } -bool isIntervalReqFullyOpenDNF(const IntervalReqExpr::Node& n) { - if (auto singular = IntervalReqExpr::getSingularDNF(n); singular && singular->isFullyOpen()) { - return true; - } - return false; -} - PartialSchemaRequirement::PartialSchemaRequirement( boost::optional<ProjectionName> boundProjectionName, IntervalReqExpr::Node intervals, diff --git a/src/mongo/db/query/optimizer/index_bounds.h b/src/mongo/db/query/optimizer/index_bounds.h index f26f88b0aff..3ecae65f19f 100644 --- a/src/mongo/db/query/optimizer/index_bounds.h +++ b/src/mongo/db/query/optimizer/index_bounds.h @@ -37,49 +37,155 @@ namespace mongo::optimizer { -class BoundRequirement { +/** + * Generic bound. + */ +template <class T> +class Bound { +public: + Bound(bool inclusive, T bound) : _inclusive(inclusive), _bound(std::move(bound)) {} + + bool operator==(const Bound& other) const { + return _inclusive == other._inclusive && _bound == other._bound; + } + + bool isInclusive() const { + return _inclusive; + } + + const T& getBound() const { + return _bound; + } + T& getBound() { + return _bound; + } + +protected: + bool _inclusive; + T _bound; +}; + +/** + * Generic interval. + */ +template <class T> +class Interval { +public: + Interval(T lowBound, T highBound) + : _lowBound(std::move(lowBound)), _highBound(std::move(highBound)) {} + + bool operator==(const Interval& other) const { + return _lowBound == other._lowBound && _highBound == other._highBound; + } + + bool isEquality() const { + return _lowBound.isInclusive() && _highBound.isInclusive() && _lowBound == _highBound; + } + + void reverse() { + std::swap(_lowBound, _highBound); + } + + const T& getLowBound() const { + return _lowBound; + } + T& getLowBound() { + return _lowBound; + } + + const T& getHighBound() const { + return _highBound; + } + T& getHighBound() { + return _highBound; + } + +protected: + T _lowBound; + T _highBound; +}; + +/** + * Represents a bound in an simple interval (interval over one projection). The bound can be a + * constant or an expression (e.g. a formula). This is a logical abstraction. + */ +class BoundRequirement : public Bound<ABT> { + using Base = Bound<ABT>; + public: static BoundRequirement makeMinusInf(); static BoundRequirement makePlusInf(); BoundRequirement(bool inclusive, ABT bound); - bool operator==(const BoundRequirement& other) const; - - bool isInclusive() const; - bool isMinusInf() const; bool isPlusInf() const; - - const ABT& getBound() const; - -private: - bool _inclusive; - ABT _bound; }; -class IntervalRequirement { +/** + * Represents a simple interval (interval over one projection). This is a logical abstraction. It + * counts low and high bounds which may be inclusive or exclusive. + */ +class IntervalRequirement : public Interval<BoundRequirement> { + using Base = Interval<BoundRequirement>; + public: IntervalRequirement(); IntervalRequirement(BoundRequirement lowBound, BoundRequirement highBound); - bool operator==(const IntervalRequirement& other) const; - bool isFullyOpen() const; - bool isEquality() const; - void reverse(); bool isConstant() const; +}; - const BoundRequirement& getLowBound() const; - BoundRequirement& getLowBound(); - const BoundRequirement& getHighBound() const; - BoundRequirement& getHighBound(); +/** + * Represents an expression (consisting of possibly nested unions and intersections) over an + * interval. + */ +using IntervalReqExpr = BoolExpr<IntervalRequirement>; +bool isIntervalReqFullyOpenDNF(const IntervalReqExpr::Node& n); -private: - BoundRequirement _lowBound; - BoundRequirement _highBound; +/** + * Represents a bound in a compound interval, which encodes an equality prefix. It consists of a + * vector of expressions, which represents an index bound. This is a physical abstraction. + */ +class CompoundBoundRequirement : public Bound<ABTVector> { + using Base = Bound<ABTVector>; + +public: + CompoundBoundRequirement(bool inclusive, ABTVector bound); + + bool isMinusInf() const; + bool isPlusInf() const; + bool isConstant() const; + + size_t size() const; + + // Extend the current compound bound with a simple bound. It is the caller's responsibility to + // ensure we confirm to an equality prefix. + void push_back(BoundRequirement bound); }; +/** + * An interval of compound keys: each endpoint is a compound key, with one expression per index key. + * This is a physical primitive tied to a specific index. + */ +class CompoundIntervalRequirement : public Interval<CompoundBoundRequirement> { + using Base = Interval<CompoundBoundRequirement>; + +public: + CompoundIntervalRequirement(); + CompoundIntervalRequirement(CompoundBoundRequirement lowBound, + CompoundBoundRequirement highBound); + + bool isFullyOpen() const; + + size_t size() const; + void push_back(IntervalRequirement interval); +}; + +// Unions and conjunctions of individual compound intervals. +using CompoundIntervalReqExpr = BoolExpr<CompoundIntervalRequirement>; + struct PartialSchemaKey { PartialSchemaKey(ABT path); PartialSchemaKey(ProjectionName projectionName, ABT path); @@ -97,9 +203,6 @@ struct PartialSchemaKey { ABT _path; }; -using IntervalReqExpr = BoolExpr<IntervalRequirement>; -bool isIntervalReqFullyOpenDNF(const IntervalReqExpr::Node& n); - class PartialSchemaRequirement { public: PartialSchemaRequirement(boost::optional<ProjectionName> boundProjectionName, @@ -290,21 +393,6 @@ struct ResidualRequirementWithCE { }; using ResidualRequirementsWithCE = std::vector<ResidualRequirementWithCE>; - -// A sequence of intervals corresponding to a compound bound, with one entry for each index key. -// This is a physical primitive as it is tied to a specific index. It contains a sequence of simple -// bounds which form a single equality prefix. As such the first 0+ entries are inclusive -// (equalities), followed by 0/1 possibly exclusive ranges, followed by 0+ unconstrained entries -// (MinKey to MaxKey). When the IndexNode is lowered we need to compute a global inclusion/exclusion -// for the entire compound interval, and we do so by determining if there are ANY exclusive simple -// low bounds or high bounds. In this case the compound bound is exclusive (on the low or the high -// side respectively). -// TODO: SERVER-72784: Update representation of compound index bounds. -using CompoundIntervalRequirement = std::vector<IntervalRequirement>; - -// Unions and conjunctions of individual compound intervals. -using CompoundIntervalReqExpr = BoolExpr<CompoundIntervalRequirement>; - struct EqualityPrefixEntry { EqualityPrefixEntry(size_t startPos); diff --git a/src/mongo/db/query/optimizer/logical_rewriter_optimizer_test.cpp b/src/mongo/db/query/optimizer/logical_rewriter_optimizer_test.cpp index 9288904a1d6..c50787889b1 100644 --- a/src/mongo/db/query/optimizer/logical_rewriter_optimizer_test.cpp +++ b/src/mongo/db/query/optimizer/logical_rewriter_optimizer_test.cpp @@ -2070,13 +2070,14 @@ TEST(LogicalRewriter, SargableNodeRIN) { ASSERT_EQ(1, ci.at(0)._eqPrefixes.size()); // The first index field ("a") is constrained to 1, the remaining fields are not constrained. - ASSERT_EQ( + ASSERT_INTERVAL_AUTO( // NOLINT "{\n" " {\n" - " {=Const [1], <fully open>, <fully open>, <fully open>, <fully open>}\n" + " {[Const [1 | minKey | minKey | minKey | minKey], Const [1 | maxKey | maxKey | " + "maxKey | maxKey]]}\n" " }\n" "}\n", - ExplainGenerator::explainIntervalExpr(ci.at(0)._eqPrefixes.front()._interval)); + ci.at(0)._eqPrefixes.front()._interval); // No correlated projections. ASSERT_EQ(0, ci.at(0)._correlatedProjNames.getVector().size()); @@ -2098,27 +2099,29 @@ TEST(LogicalRewriter, SargableNodeRIN) { ASSERT_EQ(2, ci.at(1)._eqPrefixes.size()); // The first index field ("a") is again constrained to 1, and the remaining ones are not. - ASSERT_EQ( + ASSERT_INTERVAL_AUTO( // NOLINT "{\n" " {\n" - " {=Const [1], <fully open>, <fully open>, <fully open>, <fully open>}\n" + " {[Const [1 | minKey | minKey | minKey | minKey], Const [1 | maxKey | maxKey | ma" + "xKey | maxKey]]}\n" " }\n" "}\n", - ExplainGenerator::explainIntervalExpr(ci.at(1)._eqPrefixes.at(0)._interval)); + ci.at(1)._eqPrefixes.at(0)._interval); // Second eq prefix begins at index field 2. ASSERT_EQ(2, ci.at(1)._eqPrefixes.at(1)._startPos); // The first two index fields are constrained to variables obtained from the first scan, the // third one ("c") is bound to "2". The last two fields are unconstrained. - ASSERT_EQ( + ASSERT_INTERVAL_AUTO( // NOLINT "{\n" " {\n" - " {=Variable [evalTemp_26], =Variable [evalTemp_27], =Const [2], <fully open>, " - "<fully open>}\n" + " {[Variable [evalTemp_26] | Variable [evalTemp_27] | Const [2] | Const [minKey] |" + " Const [minKey], Variable [evalTemp_26] | Variable [evalTemp_27] | Const [2] | Const [ma" + "xKey] | Const [maxKey]]}\n" " }\n" "}\n", - ExplainGenerator::explainIntervalExpr(ci.at(1)._eqPrefixes.at(1)._interval)); + ci.at(1)._eqPrefixes.at(1)._interval); // Two correlated projections. ASSERT_EQ(2, ci.at(1)._correlatedProjNames.getVector().size()); @@ -2138,35 +2141,37 @@ TEST(LogicalRewriter, SargableNodeRIN) { ASSERT_EQ(4, ci.at(2)._correlatedProjNames.getVector().size()); // The first index field ("a") is again constrained to 1. - ASSERT_EQ( + ASSERT_INTERVAL_AUTO( // NOLINT "{\n" " {\n" - " {=Const [1], <fully open>, <fully open>, <fully open>, <fully open>}\n" + " {[Const [1 | minKey | minKey | minKey | minKey], Const [1 | maxKey | maxKey | ma" + "xKey | maxKey]]}\n" " }\n" "}\n", - ExplainGenerator::explainIntervalExpr(ci.at(2)._eqPrefixes.at(0)._interval)); + ci.at(2)._eqPrefixes.at(0)._interval); // The first two index fields are constrained to variables obtained from the first scan, the // third one ("c") is bound to "2". The last two fields are unconstrained. - ASSERT_EQ( + ASSERT_INTERVAL_AUTO( // NOLINT "{\n" " {\n" - " {=Variable [evalTemp_29], =Variable [evalTemp_30], =Const [2], <fully open>, " - "<fully open>}\n" + " {[Variable [evalTemp_29] | Variable [evalTemp_30] | Const [2] | Const [minKey] |" + " Const [minKey], Variable [evalTemp_29] | Variable [evalTemp_30] | Const [2] | Const [ma" + "xKey] | Const [maxKey]]}\n" " }\n" "}\n", - ExplainGenerator::explainIntervalExpr(ci.at(2)._eqPrefixes.at(1)._interval)); + ci.at(2)._eqPrefixes.at(1)._interval); // The first 4 index fields are constrained to variables from the second scan, and the last one // to 4. - ASSERT_EQ( + ASSERT_INTERVAL_AUTO( // NOLINT "{\n" " {\n" - " {=Variable [evalTemp_29], =Variable [evalTemp_30], =Variable [evalTemp_31], " - "=Variable [evalTemp_32], =Const [3]}\n" + " {=Variable [evalTemp_29] | Variable [evalTemp_30] | Variable [evalTemp_31] | Var" + "iable [evalTemp_32] | Const [3]}\n" " }\n" "}\n", - ExplainGenerator::explainIntervalExpr(ci.at(2)._eqPrefixes.at(2)._interval)); + ci.at(2)._eqPrefixes.at(2)._interval); } TEST(LogicalRewriter, EmptyArrayIndexBounds) { 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 6d9ad94b866..25f35a160a6 100644 --- a/src/mongo/db/query/optimizer/physical_rewriter_optimizer_test.cpp +++ b/src/mongo/db/query/optimizer/physical_rewriter_optimizer_test.cpp @@ -1084,8 +1084,8 @@ TEST(PhysRewriter, FilterIndexing3) { "| | pa\n" "| RefBlock: \n" "| Variable [pa]\n" - "IndexScan [{'<indexKey> 0': pa}, scanDefName: c1, indexDefName: index1, interval: {=Cons" - "t [1], <fully open>}]\n", + "IndexScan [{'<indexKey> 0': pa}, scanDefName: c1, indexDefName: index1, interval: {[Const " + "[1 | minKey], Const [1 | maxKey]]}]\n", optimized); } @@ -1148,8 +1148,8 @@ TEST(PhysRewriter, FilterIndexing3MultiKey) { "Unique []\n" "| projections: \n" "| rid_0\n" - "IndexScan [{'<rid>': rid_0}, scanDefName: c1, indexDefName: index1, interval: {=Const [1" - "], <fully open>}]\n", + "IndexScan [{'<rid>': rid_0}, scanDefName: c1, indexDefName: index1, interval: {[Const [1" + " | minKey], Const [1 | maxKey]]}]\n", optimized); } @@ -1259,7 +1259,7 @@ TEST(PhysRewriter, FilterIndexing4) { "| Const [1]\n" "IndexScan [{'<indexKey> 0': pa, '<indexKey> 1': evalTemp_12, '<indexKey> 2': evalTemp_13" ", '<indexKey> 3': evalTemp_14}, scanDefName: c1, indexDefName: index1, interval: {>Const" - " [1], >Const [maxKey], >Const [maxKey], >Const [maxKey]}]\n", + " [1 | maxKey | maxKey | maxKey]}]\n", optimized); } @@ -1342,7 +1342,7 @@ TEST(PhysRewriter, FilterIndexing5) { "| | Variable [evalTemp_0]\n" "| PathIdentity []\n" "IndexScan [{'<indexKey> 0': pa, '<indexKey> 1': evalTemp_0}, scanDefName: c1, indexDefNa" - "me: index1, interval: {>Const [0], >Const [maxKey]}]\n", + "me: index1, interval: {>Const [0 | maxKey]}]\n", optimized); } @@ -1411,7 +1411,7 @@ TEST(PhysRewriter, FilterIndexing6) { "| Variable [pa]\n" "| Variable [pb]\n" "IndexScan [{'<indexKey> 0': pa, '<indexKey> 1': pb}, scanDefName: c1, indexDefName: inde" - "x1, interval: {=Const [0], >Const [0]}]\n", + "x1, interval: {(Const [0 | 0], Const [0 | maxKey]]}]\n", optimized); } @@ -1727,8 +1727,8 @@ TEST(PhysRewriter, FilterIndexingRIN) { "evalTemp_60}]\n" "| | | Const [true]\n" "| | IndexScan [{'<rid>': rid_0}, scanDefName: c1, indexDefName: index1, interval: " - "{=Variable [evalTemp_57], =Variable [evalTemp_58], =Variable [evalTemp_59], =Variable " - "[evalTemp_60], =Const [3]}]\n" + "{=Variable [evalTemp_57] | Variable [evalTemp_58] | Variable [evalTemp_59] | Variable " + "[evalTemp_60] | Const [3]}]\n" "| SpoolProducer [Lazy, id: 0, {evalTemp_59, evalTemp_60}]\n" "| | | Const [true]\n" "| Union [{evalTemp_59, evalTemp_60}]\n" @@ -1739,17 +1739,19 @@ TEST(PhysRewriter, FilterIndexingRIN) { "| | | | limit: 1\n" "| | | | skip: 0\n" "| | | IndexScan [{'<indexKey> 2': evalTemp_59, '<indexKey> 3': evalTemp_60}, " - "scanDefName: c1, indexDefName: index1, interval: {(Variable [evalTemp_57], Variable " - "[evalTemp_57]], (Variable [evalTemp_58], Variable [evalTemp_58]], >Variable [rinInner_2], " - ">Variable [rinInner_3], =Const [maxKey]}]\n" + "scanDefName: c1, indexDefName: index1, interval: {(Variable [evalTemp_57] | Variable " + "[evalTemp_58] | Variable [rinInner_2] | Variable [rinInner_3] | Const [maxKey], Variable " + "[evalTemp_57] | Variable [evalTemp_58] | Const [maxKey] | Const [maxKey] | Const " + "[maxKey])}]\n" "| | SpoolConsumer [Stack, id: 0, {rinInner_2, rinInner_3}]\n" "| LimitSkip []\n" "| | limitSkip:\n" "| | limit: 1\n" "| | skip: 0\n" "| IndexScan [{'<indexKey> 2': evalTemp_59, '<indexKey> 3': evalTemp_60}, scanDefName: " - "c1, indexDefName: index1, interval: {=Variable [evalTemp_57], =Variable [evalTemp_58], " - ">Const [2], >Const [maxKey], >Const [maxKey]}]\n" + "c1, indexDefName: index1, interval: {(Variable [evalTemp_57] | Variable [evalTemp_58] | " + "Const [2] | Const [maxKey] | Const [maxKey], Variable [evalTemp_57] | Variable " + "[evalTemp_58] | Const [maxKey] | Const [maxKey] | Const [maxKey]]}]\n" "SpoolProducer [Lazy, id: 1, {evalTemp_57, evalTemp_58}]\n" "| | Const [true]\n" "Union [{evalTemp_57, evalTemp_58}]\n" @@ -1760,16 +1762,16 @@ TEST(PhysRewriter, FilterIndexingRIN) { "| | | limit: 1\n" "| | | skip: 0\n" "| | IndexScan [{'<indexKey> 0': evalTemp_57, '<indexKey> 1': evalTemp_58}, " - "scanDefName: c1, indexDefName: index1, interval: {>Variable [rinInner_0], >Variable " - "[rinInner_1], =Const [maxKey], =Const [maxKey], =Const [maxKey]}]\n" + "scanDefName: c1, indexDefName: index1, interval: {(Variable [rinInner_0] | Variable " + "[rinInner_1] | Const [maxKey] | Const [maxKey] | Const [maxKey], Const [maxKey | maxKey | " + "maxKey | maxKey | maxKey])}]\n" "| SpoolConsumer [Stack, id: 1, {rinInner_0, rinInner_1}]\n" "LimitSkip []\n" "| limitSkip:\n" "| limit: 1\n" "| skip: 0\n" "IndexScan [{'<indexKey> 0': evalTemp_57, '<indexKey> 1': evalTemp_58}, scanDefName: c1, " - "indexDefName: index1, interval: {>Const [1], >Const [maxKey], >Const [maxKey], >Const [m" - "axKey], >Const [maxKey]}]\n", + "indexDefName: index1, interval: {>Const [1 | maxKey | maxKey | maxKey | maxKey]}]\n", optimized); } @@ -1840,7 +1842,7 @@ TEST(PhysRewriter, FilterIndexingRIN1) { "NestedLoopJoin [joinType: Inner, {pa}]\n" "| | Const [true]\n" "| IndexScan [{'<indexKey> 1': pb, '<rid>': rid_0}, scanDefName: c1, indexDefName: " - "index1, interval: {=Variable [pa], >Const [2]}]\n" + "index1, interval: {(Variable [pa] | Const [2], Variable [pa] | Const [maxKey]]}]\n" "SpoolProducer [Lazy, id: 1, {pa}]\n" "| | Const [true]\n" "Union [{pa}]\n" @@ -1851,14 +1853,14 @@ TEST(PhysRewriter, FilterIndexingRIN1) { "| | | limit: 1\n" "| | | skip: 0\n" "| | IndexScan [{'<indexKey> 0': pa}, scanDefName: c1, indexDefName: index1, interval: " - "{(Const [1], Variable [rinInner_1]), (Const [maxKey], Const [minKey]]}, reversed]\n" + "{(Const [1 | maxKey], Variable [rinInner_1] | Const [minKey])}, reversed]\n" "| SpoolConsumer [Stack, id: 1, {rinInner_1}]\n" "LimitSkip []\n" "| limitSkip:\n" "| limit: 1\n" "| skip: 0\n" "IndexScan [{'<indexKey> 0': pa}, scanDefName: c1, indexDefName: index1, interval: {>Const " - "[1], >Const [maxKey]}, reversed]\n", + "[1 | maxKey]}, reversed]\n", optimized); } @@ -1951,9 +1953,11 @@ TEST(PhysRewriter, FilterIndexingRIN2) { "| | | Variable [disjunction_3]\n" "| | Union [{disjunction_3, rid_0}]\n" "| | | IndexScan [{'<indexKey> 1': disjunction_3, '<rid>': rid_0}, scanDefName: c1, " - "indexDefName: index1, interval: {=Variable [disjunction_0], [Const [7], Const [8]]}]\n" + "indexDefName: index1, interval: {[Variable [disjunction_0] | Const [7], Variable " + "[disjunction_0] | Const [8]]}]\n" "| | IndexScan [{'<indexKey> 1': disjunction_3, '<rid>': rid_0}, scanDefName: c1, " - "indexDefName: index1, interval: {=Variable [disjunction_0], [Const [5], Const [6]]}]\n" + "indexDefName: index1, interval: {[Variable [disjunction_0] | Const [5], Variable " + "[disjunction_0] | Const [6]]}]\n" "| SpoolProducer [Lazy, id: 1, {disjunction_0}]\n" "| | | Const [true]\n" "| Union [{disjunction_0}]\n" @@ -1964,14 +1968,14 @@ TEST(PhysRewriter, FilterIndexingRIN2) { "| | | | limit: 1\n" "| | | | skip: 0\n" "| | | IndexScan [{'<indexKey> 0': disjunction_0}, scanDefName: c1, indexDefName: " - "index1, interval: {(Variable [rinInner_1], Const [4]], =Const [maxKey]}]\n" + "index1, interval: {(Variable [rinInner_1] | Const [maxKey], Const [4 | maxKey])}]\n" "| | SpoolConsumer [Stack, id: 1, {rinInner_1}]\n" "| LimitSkip []\n" "| | limitSkip:\n" "| | limit: 1\n" "| | skip: 0\n" "| IndexScan [{'<indexKey> 0': disjunction_0}, scanDefName: c1, indexDefName: index1, " - "interval: {[Const [3], Const [4]], <fully open>}]\n" + "interval: {[Const [3 | minKey], Const [4 | maxKey]]}]\n" "NestedLoopJoin [joinType: Inner, {disjunction_0}]\n" "| | Const [true]\n" "| GroupBy []\n" @@ -1984,9 +1988,11 @@ TEST(PhysRewriter, FilterIndexingRIN2) { "| | Variable [disjunction_2]\n" "| Union [{disjunction_2, rid_0}]\n" "| | IndexScan [{'<indexKey> 1': disjunction_2, '<rid>': rid_0}, scanDefName: c1, " - "indexDefName: index1, interval: {=Variable [disjunction_0], [Const [7], Const [8]]}]\n" + "indexDefName: index1, interval: {[Variable [disjunction_0] | Const [7], Variable " + "[disjunction_0] | Const [8]]}]\n" "| IndexScan [{'<indexKey> 1': disjunction_2, '<rid>': rid_0}, scanDefName: c1, " - "indexDefName: index1, interval: {=Variable [disjunction_0], [Const [5], Const [6]]}]\n" + "indexDefName: index1, interval: {[Variable [disjunction_0] | Const [5], Variable " + "[disjunction_0] | Const [6]]}]\n" "SpoolProducer [Lazy, id: 0, {disjunction_0}]\n" "| | Const [true]\n" "Union [{disjunction_0}]\n" @@ -1997,14 +2003,14 @@ TEST(PhysRewriter, FilterIndexingRIN2) { "| | | limit: 1\n" "| | | skip: 0\n" "| | IndexScan [{'<indexKey> 0': disjunction_0}, scanDefName: c1, indexDefName: " - "index1, interval: {(Variable [rinInner_0], Const [2]], =Const [maxKey]}]\n" + "index1, interval: {(Variable [rinInner_0] | Const [maxKey], Const [2 | maxKey])}]\n" "| SpoolConsumer [Stack, id: 0, {rinInner_0}]\n" "LimitSkip []\n" "| limitSkip:\n" "| limit: 1\n" "| skip: 0\n" "IndexScan [{'<indexKey> 0': disjunction_0}, scanDefName: c1, indexDefName: index1, " - "interval: {[Const [1], Const [2]], <fully open>}]\n", + "interval: {[Const [1 | minKey], Const [2 | maxKey]]}]\n", optimized); } @@ -2921,19 +2927,19 @@ TEST(PhysRewriter, CompoundIndex3) { explainRoot, "child.child.leftChild.rightChild.children.0.child") .Obj(); ASSERT_BSON_PATH("\"IndexScan\"", explainIndex1, "nodeType"); - ASSERT_BSON_PATH("2", explainIndex1, "interval.0.lowBound.bound.value"); - ASSERT_BSON_PATH("2", explainIndex1, "interval.0.highBound.bound.value"); - ASSERT_BSON_PATH("4", explainIndex1, "interval.1.lowBound.bound.value"); - ASSERT_BSON_PATH("4", explainIndex1, "interval.1.highBound.bound.value"); + ASSERT_BSON_PATH("2", explainIndex1, "interval.lowBound.bound.0.value"); + ASSERT_BSON_PATH("2", explainIndex1, "interval.highBound.bound.0.value"); + ASSERT_BSON_PATH("4", explainIndex1, "interval.lowBound.bound.1.value"); + ASSERT_BSON_PATH("4", explainIndex1, "interval.highBound.bound.1.value"); const BSONObj& explainIndex2 = dotted_path_support::extractElementAtPath(explainRoot, "child.child.leftChild.leftChild") .Obj(); ASSERT_BSON_PATH("\"IndexScan\"", explainIndex2, "nodeType"); - ASSERT_BSON_PATH("1", explainIndex2, "interval.0.lowBound.bound.value"); - ASSERT_BSON_PATH("1", explainIndex2, "interval.0.highBound.bound.value"); - ASSERT_BSON_PATH("3", explainIndex2, "interval.1.lowBound.bound.value"); - ASSERT_BSON_PATH("3", explainIndex2, "interval.1.highBound.bound.value"); + ASSERT_BSON_PATH("1", explainIndex2, "interval.lowBound.bound.0.value"); + ASSERT_BSON_PATH("1", explainIndex2, "interval.highBound.bound.0.value"); + ASSERT_BSON_PATH("3", explainIndex2, "interval.lowBound.bound.1.value"); + ASSERT_BSON_PATH("3", explainIndex2, "interval.highBound.bound.1.value"); } TEST(PhysRewriter, CompoundIndex4Negative) { @@ -3012,8 +3018,8 @@ TEST(PhysRewriter, CompoundIndex4Negative) { ASSERT_BSON_PATH("\"Seek\"", explainRoot, "child.rightChild.child.child.nodeType"); ASSERT_BSON_PATH("\"IndexScan\"", explainRoot, "child.leftChild.nodeType"); - ASSERT_BSON_PATH("1", explainRoot, "child.leftChild.interval.0.lowBound.bound.value"); - ASSERT_BSON_PATH("1", explainRoot, "child.leftChild.interval.0.highBound.bound.value"); + ASSERT_BSON_PATH("1", explainRoot, "child.leftChild.interval.lowBound.bound.0.value"); + ASSERT_BSON_PATH("1", explainRoot, "child.leftChild.interval.highBound.bound.0.value"); } TEST(PhysRewriter, CompoundIndex5) { @@ -3079,13 +3085,13 @@ TEST(PhysRewriter, CompoundIndex5) { "| aggregations: \n" "Union [{rid_0}]\n" "| | | IndexScan [{'<rid>': rid_0}, scanDefName: c1, indexDefName: index1, interval" - ": {=Const [1], =Const [3]}]\n" + ": {=Const [1 | 3]}]\n" "| | IndexScan [{'<rid>': rid_0}, scanDefName: c1, indexDefName: index1, interval: {=" - "Const [0], =Const [3]}]\n" + "Const [0 | 3]}]\n" "| IndexScan [{'<rid>': rid_0}, scanDefName: c1, indexDefName: index1, interval: {=Cons" - "t [1], =Const [2]}]\n" + "t [1 | 2]}]\n" "IndexScan [{'<rid>': rid_0}, scanDefName: c1, indexDefName: index1, interval: {=Const [0" - "], =Const [2]}]\n", + " | 2]}]\n", optimized); } @@ -3156,14 +3162,14 @@ TEST(PhysRewriter, IndexBoundsIntersect) { const BSONObj& explainIndexScan = dotted_path_support::extractElementAtPath(explainRoot, "child.leftChild.child").Obj(); ASSERT_BSON_PATH("\"IndexScan\"", explainIndexScan, "nodeType"); - ASSERT_BSON_PATH("1", explainIndexScan, "interval.0.lowBound.bound.value"); - ASSERT_BSON_PATH("1", explainIndexScan, "interval.0.highBound.bound.value"); + ASSERT_BSON_PATH("1", explainIndexScan, "interval.lowBound.bound.0.value"); + ASSERT_BSON_PATH("1", explainIndexScan, "interval.highBound.bound.0.value"); const std::string lowBound = dotted_path_support::extractElementAtPath( - explainIndexScan, "interval.1.lowBound.bound.value") + explainIndexScan, "interval.lowBound.bound.1.value") .toString(false /*includeFieldName*/); const std::string highBound = dotted_path_support::extractElementAtPath( - explainIndexScan, "interval.1.highBound.bound.value") + explainIndexScan, "interval.highBound.bound.1.value") .toString(false /*includeFieldName*/); ASSERT_TRUE((filterVal == "70" && lowBound == "MinKey" && highBound == "90") || (filterVal == "90" && lowBound == "70" && highBound == "MaxKey")); @@ -3490,7 +3496,7 @@ TEST(PhysRewriter, IndexResidualReq) { "| PathCompare [Gt]\n" "| Const [0]\n" "IndexScan [{'<indexKey> 0': pa, '<indexKey> 1': evalTemp_2}, scanDefName: c1, indexDefNa" - "me: index1, interval: {>Const [0], >Const [maxKey]}]\n", + "me: index1, interval: {>Const [0 | maxKey]}]\n", phaseManager); } @@ -3573,8 +3579,8 @@ TEST(PhysRewriter, IndexResidualReq1) { "| Seek [ridProjection: rid_0, {'<root>': root}, c1]\n" "| RefBlock: \n" "| Variable [rid_0]\n" - "IndexScan [{'<rid>': rid_0}, scanDefName: c1, indexDefName: index1, interval: {=Const [0" - "], =Const [0], =Const [0], <fully open>}]\n", + "IndexScan [{'<rid>': rid_0}, scanDefName: c1, indexDefName: index1, interval: {[Const [0" + " | 0 | 0 | minKey], Const [0 | 0 | 0 | maxKey]]}]\n", optimized); } @@ -3647,7 +3653,7 @@ TEST(PhysRewriter, IndexResidualReq2) { "| PathCompare [Eq]\n" "| Const [0]\n" "IndexScan [{'<indexKey> 2': evalTemp_10, '<rid>': rid_0}, scanDefName: c1, indexDefName:" - " index1, interval: {=Const [0], <fully open>, <fully open>}]\n", + " index1, interval: {[Const [0 | minKey | minKey], Const [0 | maxKey | maxKey]]}]\n", optimized); } @@ -3788,8 +3794,8 @@ TEST(PhysRewriter, ElemMatchIndex1) { "Unique []\n" "| projections: \n" "| rid_0\n" - "IndexScan [{'<rid>': rid_0}, scanDefName: c1, indexDefName: index1, interval: {=Const [1" - "], (Const [70], Const [90])}]\n", + "IndexScan [{'<rid>': rid_0}, scanDefName: c1, indexDefName: index1, interval: {(Const [1" + " | 70], Const [1 | 90])}]\n", optimized); } @@ -3946,7 +3952,7 @@ TEST(PhysRewriter, ObjectElemMatchResidual) { "| | Variable [evalTemp_8]\n" "| PathGet [c] PathTraverse [1] PathCompare [Eq] Const [1]\n" "IndexScan [{'<indexKey> 1': evalTemp_8, '<rid>': rid_0}, scanDefName: c1, indexDefName: " - "index1, interval: {<fully open>, <fully open>}]\n", + "index1, interval: {<fully open>}]\n", optimized); } @@ -4190,7 +4196,7 @@ TEST(PhysRewriter, PathObj) { "| projections: \n" "| rid_0\n" "IndexScan [{'<rid>': rid_0}, scanDefName: c1, indexDefName: index1, interval: {[Const [{" - "}], Const [[]]), <Const [minKey]}]\n", + "} | minKey], Const [[] | minKey])}]\n", optimized); } @@ -4282,9 +4288,9 @@ TEST(PhysRewriter, ArrayConstantIndex) { "| aggregations: \n" "Union [{rid_0}]\n" "| IndexScan [{'<rid>': rid_0}, scanDefName: c1, indexDefName: index1, interval: {=Cons" - "t [0], =Const [[1, 2, 3]]}]\n" + "t [0 | [1, 2, 3]]}]\n" "IndexScan [{'<rid>': rid_0}, scanDefName: c1, indexDefName: index1, interval: {=Const [0" - "], =Const [1]}]\n", + " | 1]}]\n", optimized); } @@ -6155,8 +6161,8 @@ TEST(PhysRewriter, PerfOnlyPreds1) { "| Seek [ridProjection: rid_0, {'a': pa, 'b': evalTemp_3}, c1]\n" "| RefBlock: \n" "| Variable [rid_0]\n" - "IndexScan [{'<rid>': rid_0}, scanDefName: c1, indexDefName: index1, interval: {=Const [2" - "], <Const [1]}]\n", + "IndexScan [{'<rid>': rid_0}, scanDefName: c1, indexDefName: index1, interval: {[Const [2" + " | minKey], Const [2 | 1])}]\n", optimized); } diff --git a/src/mongo/db/query/optimizer/utils/abt_hash.cpp b/src/mongo/db/query/optimizer/utils/abt_hash.cpp index 2b9454cfc84..7344f58491d 100644 --- a/src/mongo/db/query/optimizer/utils/abt_hash.cpp +++ b/src/mongo/db/query/optimizer/utils/abt_hash.cpp @@ -75,6 +75,13 @@ static void updateBoundHash(size_t& result, const BoundRequirement& bound) { updateHash(result, ABTHashGenerator::generate(bound.getBound())); }; +static void updateCompoundBoundHash(size_t& result, const CompoundBoundRequirement& bound) { + updateHash(result, std::hash<bool>()(bound.isInclusive())); + for (const auto& expr : bound.getBound()) { + updateHash(result, ABTHashGenerator::generate(expr)); + } +} + template <class T> class IntervalHasher { public: @@ -87,9 +94,8 @@ public: size_t computeHash(const CompoundIntervalRequirement& req) { size_t result = 19; - for (const auto& interval : req) { - updateHash(result, computeHash(interval)); - } + updateCompoundBoundHash(result, req.getLowBound()); + updateCompoundBoundHash(result, req.getHighBound()); return result; } diff --git a/src/mongo/db/query/optimizer/utils/interval_utils.cpp b/src/mongo/db/query/optimizer/utils/interval_utils.cpp index 850dcb42efe..875e3adeec5 100644 --- a/src/mongo/db/query/optimizer/utils/interval_utils.cpp +++ b/src/mongo/db/query/optimizer/utils/interval_utils.cpp @@ -667,8 +667,7 @@ bool combineCompoundIntervalsDNF(CompoundIntervalReqExpr::Node& targetIntervals, targetConjunction.cast<CompoundIntervalReqExpr::Conjunction>()->nodes()) { const auto& targetInterval = targetConjunct.cast<CompoundIntervalReqExpr::Atom>()->getExpr(); - if (!targetInterval.empty() && !targetInterval.back().isEquality() && - !sourceInterval.isFullyOpen()) { + if (!targetInterval.isEquality() && !sourceInterval.isFullyOpen()) { // We do not have an equality prefix. Reject. return false; } @@ -708,14 +707,11 @@ void padCompoundIntervalsDNF(CompoundIntervalReqExpr::Node& targetIntervals, targetConjunct.cast<CompoundIntervalReqExpr::Atom>()->getExpr(); IntervalRequirement sourceInterval; - if (!targetInterval.empty()) { - const auto& lastInterval = targetInterval.back(); - if (!lastInterval.getLowBound().isInclusive()) { - sourceInterval.getLowBound() = {false /*inclusive*/, Constant::maxKey()}; - } - if (!lastInterval.getHighBound().isInclusive()) { - sourceInterval.getHighBound() = {false /*inclusive*/, Constant::minKey()}; - } + if (!targetInterval.getLowBound().isInclusive()) { + sourceInterval.getLowBound() = {false /*inclusive*/, Constant::maxKey()}; + } + if (!targetInterval.getHighBound().isInclusive()) { + sourceInterval.getHighBound() = {false /*inclusive*/, Constant::minKey()}; } auto newInterval = targetInterval; @@ -826,18 +822,9 @@ boost::optional<ABT> coerceIntervalToPathCompareEqMember(const IntervalReqExpr:: return boost::none; } -bool areCompoundIntervalsEqualities(const CompoundIntervalRequirement& intervals) { - for (const auto& interval : intervals) { - if (!interval.isEquality()) { - return false; - } - } - return true; -} - bool isSimpleRange(const CompoundIntervalReqExpr::Node& interval) { if (const auto singularInterval = CompoundIntervalReqExpr::getSingularDNF(interval); - singularInterval && !areCompoundIntervalsEqualities(*singularInterval)) { + singularInterval && !singularInterval->isEquality()) { return true; } return false; diff --git a/src/mongo/db/query/optimizer/utils/interval_utils.h b/src/mongo/db/query/optimizer/utils/interval_utils.h index 9bb8afc5903..ccaeb574e0f 100644 --- a/src/mongo/db/query/optimizer/utils/interval_utils.h +++ b/src/mongo/db/query/optimizer/utils/interval_utils.h @@ -130,11 +130,6 @@ boost::optional<ABT> coerceIntervalToPathCompareEqMember(const IntervalReqExpr:: /** - * Returns true if all components of the compound interval are equalities. - */ -bool areCompoundIntervalsEqualities(const CompoundIntervalRequirement& intervals); - -/** * Returns true if the interval corresponds to a simple range (e.g >10 as opposed to a point * equality or complex boolean expression of intervals). */ diff --git a/src/mongo/db/query/optimizer/utils/utils.cpp b/src/mongo/db/query/optimizer/utils/utils.cpp index 24ab7aeddd4..eb87415b20d 100644 --- a/src/mongo/db/query/optimizer/utils/utils.cpp +++ b/src/mongo/db/query/optimizer/utils/utils.cpp @@ -2248,9 +2248,9 @@ public: const auto& currentCorrelatedProjNames = params._correlatedProjNames; // Update interval with current correlations. for (size_t i = 0; i < startPos; i++) { - BoundRequirement bound{true /*inclusive*/, - make<Variable>(currentCorrelatedProjNames.at(i))}; - interval.at(i) = {bound, bound}; + auto& lowBound = interval.getLowBound().getBound().at(i); + lowBound = make<Variable>(currentCorrelatedProjNames.at(i)); + interval.getHighBound().getBound().at(i) = lowBound; } if (_currentEqPrefixIndex + 1 == _eqPrefixes.size()) { @@ -2276,11 +2276,15 @@ public: FieldProjectionMap innerFPM; const auto addInnerBound = [&](BoundRequirement bound) { - const auto& req = interval.at(innerInterval.size()); + const size_t size = innerInterval.size(); if (reverse) { - innerInterval.emplace_back(req.getLowBound(), std::move(bound)); + BoundRequirement lowBound{false /*inclusive*/, + interval.getLowBound().getBound().at(size)}; + innerInterval.push_back({std::move(lowBound), std::move(bound)}); } else { - innerInterval.emplace_back(std::move(bound), req.getHighBound()); + BoundRequirement highBound{false /*inclusive*/, + interval.getHighBound().getBound().at(size)}; + innerInterval.push_back({std::move(bound), std::move(highBound)}); } }; diff --git a/src/mongo/db/test_output/pipeline/abt/a_b_t_optimization_test/optimize_pipeline_tests.txt b/src/mongo/db/test_output/pipeline/abt/a_b_t_optimization_test/optimize_pipeline_tests.txt index 8f2dd57ff17..9c7307461f1 100644 --- a/src/mongo/db/test_output/pipeline/abt/a_b_t_optimization_test/optimize_pipeline_tests.txt +++ b/src/mongo/db/test_output/pipeline/abt/a_b_t_optimization_test/optimize_pipeline_tests.txt @@ -566,7 +566,7 @@ Evaluation [{combinedProjection_0}] | PathField [a] | PathConstant [] | Variable [fieldProj_0] -IndexScan [{'<indexKey> 0': fieldProj_0}, scanDefName: collection, indexDefName: index1, interval: {=Const [10], =Const [20]}] +IndexScan [{'<indexKey> 0': fieldProj_0}, scanDefName: collection, indexDefName: index1, interval: {=Const [10 | 20]}] ==== VARIATION: optimized $match index covered, match on three indexed keys then project ==== @@ -635,7 +635,7 @@ Evaluation [{combinedProjection_0}] | PathField [a] | PathConstant [] | Variable [fieldProj_0] -IndexScan [{'<indexKey> 0': fieldProj_0, '<indexKey> 1': fieldProj_1, '<indexKey> 2': fieldProj_2}, scanDefName: collection, indexDefName: index1, interval: {=Const [10], =Const [20], =Const [30]}] +IndexScan [{'<indexKey> 0': fieldProj_0, '<indexKey> 1': fieldProj_1, '<indexKey> 2': fieldProj_2}, scanDefName: collection, indexDefName: index1, interval: {=Const [10 | 20 | 30]}] ==== VARIATION: optimized $match index covered, inclusion project then match on three indexed keys ==== @@ -704,7 +704,7 @@ Evaluation [{combinedProjection_0}] | PathField [a] | PathConstant [] | Variable [fieldProj_0] -IndexScan [{'<indexKey> 0': fieldProj_0, '<indexKey> 1': fieldProj_1, '<indexKey> 2': fieldProj_2}, scanDefName: collection, indexDefName: index1, interval: {=Const [10], =Const [20], =Const [30]}] +IndexScan [{'<indexKey> 0': fieldProj_0, '<indexKey> 1': fieldProj_1, '<indexKey> 2': fieldProj_2}, scanDefName: collection, indexDefName: index1, interval: {=Const [10 | 20 | 30]}] ==== VARIATION: optimized $match sort index ==== @@ -894,7 +894,7 @@ NestedLoopJoin [joinType: Inner, {rid_0}] | Seek [ridProjection: rid_0, {'<root>': scan_0}, collection] | RefBlock: | Variable [rid_0] -IndexScan [{'<rid>': rid_0}, scanDefName: collection, indexDefName: index1, interval: {=Const [2], =Const [2]}] +IndexScan [{'<rid>': rid_0}, scanDefName: collection, indexDefName: index1, interval: {=Const [2 | 2]}] ==== VARIATION: optimized index on one key ==== |