summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSvilen Mihaylov <svilen.mihaylov@mongodb.com>2023-02-03 18:44:40 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2023-02-03 21:15:53 +0000
commitdd107e8f21b626df74a96db84d0a95cc0db05a75 (patch)
treec026b143c81eca0c84c2893526d9241fbc858dca
parent56616080c12298229fc6e3cc71ace4c85ac973f4 (diff)
downloadmongo-dd107e8f21b626df74a96db84d0a95cc0db05a75.tar.gz
SERVER-72784 [CQF] Update representation of compound index bounds
-rw-r--r--jstests/cqf/array_match.js12
-rw-r--r--jstests/cqf/find_sort.js2
-rw-r--r--jstests/libs/optimizer_utils.js57
-rw-r--r--src/mongo/db/exec/sbe/abt/abt_lower.cpp31
-rw-r--r--src/mongo/db/exec/sbe/abt/abt_lower.h9
-rw-r--r--src/mongo/db/exec/sbe/abt/abt_lower_bm.cpp15
-rw-r--r--src/mongo/db/exec/sbe/abt/abt_lower_test.cpp43
-rw-r--r--src/mongo/db/query/ce/bound_utils.cpp12
-rw-r--r--src/mongo/db/query/optimizer/explain.cpp128
-rw-r--r--src/mongo/db/query/optimizer/index_bounds.cpp92
-rw-r--r--src/mongo/db/query/optimizer/index_bounds.h170
-rw-r--r--src/mongo/db/query/optimizer/logical_rewriter_optimizer_test.cpp47
-rw-r--r--src/mongo/db/query/optimizer/physical_rewriter_optimizer_test.cpp124
-rw-r--r--src/mongo/db/query/optimizer/utils/abt_hash.cpp12
-rw-r--r--src/mongo/db/query/optimizer/utils/interval_utils.cpp27
-rw-r--r--src/mongo/db/query/optimizer/utils/interval_utils.h5
-rw-r--r--src/mongo/db/query/optimizer/utils/utils.cpp16
-rw-r--r--src/mongo/db/test_output/pipeline/abt/a_b_t_optimization_test/optimize_pipeline_tests.txt8
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 ====