summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAlexander Ignatyev <alexander.ignatyev@mongodb.com>2022-04-08 12:22:12 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2022-04-08 13:31:12 +0000
commitd84c2eff3e5bd195caadf8b202c736ae69e5735e (patch)
treebaf8f8f4c775cb57a339d509aca35d1ba25ad2c9 /src
parent526193103b6064df5894b3d0c19c6cbdd15fbd3b (diff)
downloadmongo-d84c2eff3e5bd195caadf8b202c736ae69e5735e.tar.gz
SERVER-63352 Evaluate IETs and bind them into SBE plan
Diffstat (limited to 'src')
-rw-r--r--src/mongo/db/query/bind_input_params.cpp14
-rw-r--r--src/mongo/db/query/get_executor.cpp2
-rw-r--r--src/mongo/db/query/planner_access.cpp54
-rw-r--r--src/mongo/db/query/sbe_stage_builder.cpp90
-rw-r--r--src/mongo/db/query/sbe_stage_builder.h19
-rw-r--r--src/mongo/db/query/sbe_stage_builder_index_scan.cpp201
-rw-r--r--src/mongo/db/query/sbe_stage_builder_index_scan.h24
7 files changed, 271 insertions, 133 deletions
diff --git a/src/mongo/db/query/bind_input_params.cpp b/src/mongo/db/query/bind_input_params.cpp
index 5e3f279002d..955d34afddb 100644
--- a/src/mongo/db/query/bind_input_params.cpp
+++ b/src/mongo/db/query/bind_input_params.cpp
@@ -278,13 +278,13 @@ private:
// The encoding of the plan cache key should ensure that if we recover a cached plan from
// the cached, the auto-parameterization of the query is consistent with the way that the
// cached plan is parameterized.
- tassert(6279500,
- str::stream() << "expected value in 'inputParamsToSlotsMap' for param id: "
- << paramId,
- it != _inputParamToSlotMap.end());
- auto accessor = _runtimeEnvironment->getAccessor(it->second);
-
- accessor->reset(owned, typeTag, value);
+ if (it != _inputParamToSlotMap.end()) {
+ auto accessor = _runtimeEnvironment->getAccessor(it->second);
+ accessor->reset(owned, typeTag, value);
+ } else {
+ // TODO SERVER-65361: add tassert here if the slotId is missing from
+ // _inputParamToSlotMap but not from IETs.
+ }
}
const stage_builder::InputParamToSlotMap& _inputParamToSlotMap;
diff --git a/src/mongo/db/query/get_executor.cpp b/src/mongo/db/query/get_executor.cpp
index b952ca69ea9..55c9d78deed 100644
--- a/src/mongo/db/query/get_executor.cpp
+++ b/src/mongo/db/query/get_executor.cpp
@@ -282,7 +282,7 @@ void fillOutIndexEntries(OperationContext* opCtx,
const CanonicalQuery* canonicalQuery,
const CollectionPtr& collection,
std::vector<IndexEntry>& entries) {
- // TODO SERVER-63352: Eliminate this check once we support auto-parameterized index scan plans.
+ // TODO SERVER-65129: Eliminate this check once we support auto-parameterized index scan plans.
if (feature_flags::gFeatureFlagAutoParameterization.isEnabledAndIgnoreFCV()) {
// Indexed plans are not yet supported when auto-parameterization is enabled, so make it
// look to the planner like there are no indexes.
diff --git a/src/mongo/db/query/planner_access.cpp b/src/mongo/db/query/planner_access.cpp
index fec58be6796..58d85e5f0cf 100644
--- a/src/mongo/db/query/planner_access.cpp
+++ b/src/mongo/db/query/planner_access.cpp
@@ -981,6 +981,32 @@ void QueryPlannerAccess::finishLeafNode(
}
}
+ // Process a case when some fields are not filled out with bounds.
+ if (firstEmptyField != bounds->fields.size()) {
+ // Skip ahead to the firstEmptyField-th element, where we begin filling in bounds.
+ BSONObjIterator it(nodeIndex->keyPattern);
+ for (size_t i = 0; i < firstEmptyField; ++i) {
+ verify(it.more());
+ it.next();
+ }
+
+ // For each field in the key...
+ while (it.more()) {
+ BSONElement kpElt = it.next();
+ // There may be filled-in fields to the right of the firstEmptyField; for instance, the
+ // index {loc:"2dsphere", x:1} with a predicate over x and a near search over loc.
+ if (bounds->fields[firstEmptyField].name.empty()) {
+ verify(bounds->fields[firstEmptyField].intervals.empty());
+ IndexBoundsBuilder::allValuesForField(kpElt, &bounds->fields[firstEmptyField]);
+ }
+ ++firstEmptyField;
+ }
+
+ // Make sure that the length of the key is the length of the bounds we started.
+ verify(firstEmptyField == bounds->fields.size());
+ }
+
+ // Build Interval Evaluation Trees used to restore index bounds from cached SBE Plans.
if (node->getType() == STAGE_IXSCAN && !ietBuilders.empty()) {
auto ixScan = static_cast<IndexScanNode*>(node);
ixScan->iets.reserve(ietBuilders.size());
@@ -997,34 +1023,6 @@ void QueryPlannerAccess::finishLeafNode(
LOGV2_DEBUG(6334900, 5, "Build IETs", "iets"_attr = ietsToString(index, ixScan->iets));
}
- // All fields are filled out with bounds, nothing to do.
- if (firstEmptyField == bounds->fields.size()) {
- return IndexBoundsBuilder::alignBounds(
- bounds, nodeIndex->keyPattern, nodeIndex->collator != nullptr);
- }
-
- // Skip ahead to the firstEmptyField-th element, where we begin filling in bounds.
- BSONObjIterator it(nodeIndex->keyPattern);
- for (size_t i = 0; i < firstEmptyField; ++i) {
- verify(it.more());
- it.next();
- }
-
- // For each field in the key...
- while (it.more()) {
- BSONElement kpElt = it.next();
- // There may be filled-in fields to the right of the firstEmptyField; for instance, the
- // index {loc:"2dsphere", x:1} with a predicate over x and a near search over loc.
- if (bounds->fields[firstEmptyField].name.empty()) {
- verify(bounds->fields[firstEmptyField].intervals.empty());
- IndexBoundsBuilder::allValuesForField(kpElt, &bounds->fields[firstEmptyField]);
- }
- ++firstEmptyField;
- }
-
- // Make sure that the length of the key is the length of the bounds we started.
- verify(firstEmptyField == bounds->fields.size());
-
// We create bounds assuming a forward direction but can easily reverse bounds to align
// according to our desired direction.
IndexBoundsBuilder::alignBounds(bounds, nodeIndex->keyPattern, nodeIndex->collator != nullptr);
diff --git a/src/mongo/db/query/sbe_stage_builder.cpp b/src/mongo/db/query/sbe_stage_builder.cpp
index 6e3b054f851..334c42e98a2 100644
--- a/src/mongo/db/query/sbe_stage_builder.cpp
+++ b/src/mongo/db/query/sbe_stage_builder.cpp
@@ -64,6 +64,7 @@
#include "mongo/db/pipeline/expression_visitor.h"
#include "mongo/db/query/bind_input_params.h"
#include "mongo/db/query/expression_walker.h"
+#include "mongo/db/query/index_bounds_builder.h"
#include "mongo/db/query/optimizer/rewrites/const_eval.h"
#include "mongo/db/query/optimizer/rewrites/path_lower.h"
#include "mongo/db/query/sbe_stage_builder_accumulator.h"
@@ -353,6 +354,91 @@ std::pair<std::unique_ptr<sbe::PlanStage>, PlanStageSlots> generateEofPlan(
return {std::move(stage), std::move(outputs)};
}
+
+/**
+ * Evaluates IndexBounds from the given IntervalEvaluationTrees for the given query.
+ * 'indexBoundsInfo' contains the interval evaluation trees.
+ *
+ * Returns the built index bounds.
+ */
+std::unique_ptr<IndexBounds> makeIndexBounds(const IndexBoundsEvaluationInfo& indexBoundsInfo,
+ const CanonicalQuery& cq) {
+ auto bounds = std::make_unique<IndexBounds>();
+ bounds->fields.reserve(indexBoundsInfo.iets.size());
+
+ tassert(6335200,
+ "IET list size must be equal to the number of fields in the key pattern",
+ static_cast<size_t>(indexBoundsInfo.index.keyPattern.nFields()) ==
+ indexBoundsInfo.iets.size());
+
+ BSONObjIterator it{indexBoundsInfo.index.keyPattern};
+ BSONElement keyElt = it.next();
+ for (auto&& iet : indexBoundsInfo.iets) {
+ auto oil = interval_evaluation_tree::evaluateIntervals(
+ iet, cq.getInputParamIdToMatchExpressionMap(), keyElt, indexBoundsInfo.index);
+ bounds->fields.emplace_back(std::move(oil));
+ keyElt = it.next();
+ }
+
+ IndexBoundsBuilder::alignBounds(bounds.get(),
+ indexBoundsInfo.index.keyPattern,
+ indexBoundsInfo.index.collator != nullptr,
+ indexBoundsInfo.direction);
+ return bounds;
+}
+
+/**
+ * Binds index bounds evaluated from IETs to index bounds slots for the given query.
+ *
+ * - 'cq' is the query
+ * - 'indexBoundsInfo' contains the IETs and the slots
+ * - runtimeEnvironment SBE runtime environment
+ */
+void bindIndexBoundsParams(const CanonicalQuery& cq,
+ const IndexBoundsEvaluationInfo& indexBoundsInfo,
+ sbe::RuntimeEnvironment* runtimeEnvironment) {
+ auto bounds = makeIndexBounds(indexBoundsInfo, cq);
+ auto intervals = makeIntervalsFromIndexBounds(*bounds,
+ indexBoundsInfo.direction == 1,
+ indexBoundsInfo.keyStringVersion,
+ indexBoundsInfo.ordering);
+ const bool isGenericScan = intervals.empty();
+ runtimeEnvironment->resetSlot(indexBoundsInfo.slots.isGenericScan,
+ sbe::value::TypeTags::Boolean,
+ sbe::value::bitcastFrom<bool>(isGenericScan),
+ /*owned*/ true);
+ if (isGenericScan) {
+ IndexBoundsChecker checker{
+ bounds.get(), indexBoundsInfo.index.keyPattern, indexBoundsInfo.direction};
+ IndexSeekPoint seekPoint;
+ if (checker.getStartSeekPoint(&seekPoint)) {
+ auto startKey = std::make_unique<KeyString::Value>(
+ IndexEntryComparison::makeKeyStringFromSeekPointForSeek(
+ seekPoint,
+ indexBoundsInfo.keyStringVersion,
+ indexBoundsInfo.ordering,
+ indexBoundsInfo.direction == 1));
+ runtimeEnvironment->resetSlot(
+ indexBoundsInfo.slots.initialStartKey,
+ sbe::value::TypeTags::ksValue,
+ sbe::value::bitcastFrom<KeyString::Value*>(startKey.release()),
+ /*owned*/ true);
+ runtimeEnvironment->resetSlot(indexBoundsInfo.slots.indexBounds,
+ sbe::value::TypeTags::indexBounds,
+ sbe::value::bitcastFrom<IndexBounds*>(bounds.release()),
+ /*owned*/ true);
+ } else {
+ runtimeEnvironment->resetSlot(indexBoundsInfo.slots.initialStartKey,
+ sbe::value::TypeTags::Nothing,
+ 0,
+ /*owned*/ true);
+ }
+ } else {
+ auto [boundsTag, boundsVal] = packIndexIntervalsInSbeArray(std::move(intervals));
+ runtimeEnvironment->resetSlot(
+ indexBoundsInfo.slots.lowHighKeyIntervals, boundsTag, boundsVal, /*owned*/ true);
+ }
+}
} // namespace
std::unique_ptr<sbe::RuntimeEnvironment> makeRuntimeEnvironment(
@@ -457,6 +543,10 @@ void prepareSlotBasedExecutableTree(OperationContext* opCtx,
// If the cached plan is parameterized, bind new values for the parameters into the runtime
// environment.
input_params::bind(cq, data->inputParamToSlotMap, env);
+
+ for (auto&& indexBoundsInfo : data->indexBoundsEvaluationInfos) {
+ bindIndexBoundsParams(cq, indexBoundsInfo, env);
+ }
}
PlanStageSlots::PlanStageSlots(const PlanStageReqs& reqs,
diff --git a/src/mongo/db/query/sbe_stage_builder.h b/src/mongo/db/query/sbe_stage_builder.h
index f3413a72dd2..e7a33a7f411 100644
--- a/src/mongo/db/query/sbe_stage_builder.h
+++ b/src/mongo/db/query/sbe_stage_builder.h
@@ -34,6 +34,7 @@
#include "mongo/db/exec/sbe/values/slot.h"
#include "mongo/db/exec/sbe/values/value.h"
#include "mongo/db/exec/trial_period_utils.h"
+#include "mongo/db/query/interval_evaluation_tree.h"
#include "mongo/db/query/multiple_collection_accessor.h"
#include "mongo/db/query/plan_yield_policy_sbe.h"
#include "mongo/db/query/sbe_stage_builder_helpers.h"
@@ -267,6 +268,20 @@ using InputParamToSlotMap = stdx::unordered_map<MatchExpression::InputParamId, s
using VariableIdToSlotMap = stdx::unordered_map<Variables::Id, sbe::value::SlotId>;
/**
+ * IndexBoundsEvaluationInfo struct contains Interval Evaluation Trees (IETs) and additional data
+ * structures required to restore index bounds from IETs and bind them to generic index scan
+ * algorithm.
+ */
+struct IndexBoundsEvaluationInfo {
+ IndexEntry index;
+ KeyString::Version keyStringVersion;
+ Ordering ordering;
+ int direction;
+ std::vector<interval_evaluation_tree::IET> iets;
+ ParameterizedIndexScanSlots slots;
+};
+
+/**
* Some auxiliary data returned by a 'SlotBasedStageBuilder' along with a PlanStage tree root, which
* is needed to execute the PlanStage tree.
*/
@@ -337,6 +352,10 @@ struct PlanStageData {
// SBE Slots. The slots are registered in the 'RuntimeEnvironment'.
VariableIdToSlotMap variableIdToSlotMap;
+ // Stores auxiliary data to restore index bounds for a cached auto-parameterized SBE plan for
+ // every index used by the plan.
+ std::vector<IndexBoundsEvaluationInfo> indexBoundsEvaluationInfos;
+
private:
// This copy function copies data from 'other' but will not create a copy of its
// RuntimeEnvironment and CompileCtx.
diff --git a/src/mongo/db/query/sbe_stage_builder_index_scan.cpp b/src/mongo/db/query/sbe_stage_builder_index_scan.cpp
index cb62613d394..7d63bc91e6c 100644
--- a/src/mongo/db/query/sbe_stage_builder_index_scan.cpp
+++ b/src/mongo/db/query/sbe_stage_builder_index_scan.cpp
@@ -196,62 +196,6 @@ std::vector<std::pair<BSONObj, BSONObj>> decomposeIntoSingleIntervals(
}
/**
- * Constructs low/high key values from the given index 'bounds' if they can be represented either as
- * a single interval between the low and high keys, or multiple single intervals. If index bounds
- * for some interval cannot be expressed as valid low/high keys, then an empty vector is returned.
- */
-std::vector<std::pair<std::unique_ptr<KeyString::Value>, std::unique_ptr<KeyString::Value>>>
-makeIntervalsFromIndexBounds(const IndexBounds& bounds,
- bool forward,
- KeyString::Version version,
- Ordering ordering) {
- auto lowKeyInclusive{IndexBounds::isStartIncludedInBound(bounds.boundInclusion)};
- auto highKeyInclusive{IndexBounds::isEndIncludedInBound(bounds.boundInclusion)};
- auto intervals = [&]() -> std::vector<std::pair<BSONObj, BSONObj>> {
- auto lowKey = bounds.startKey;
- auto highKey = bounds.endKey;
- if (bounds.isSimpleRange ||
- IndexBoundsBuilder::isSingleInterval(
- bounds, &lowKey, &lowKeyInclusive, &highKey, &highKeyInclusive)) {
- return {{lowKey, highKey}};
- } else if (canBeDecomposedIntoSingleIntervals(
- bounds.fields, &lowKeyInclusive, &highKeyInclusive)) {
- return decomposeIntoSingleIntervals(bounds.fields, lowKeyInclusive, highKeyInclusive);
- } else {
- // Index bounds cannot be represented as valid low/high keys.
- return {};
- }
- }();
-
- LOGV2_DEBUG(
- 4742905, 5, "Number of generated interval(s) for ixscan", "num"_attr = intervals.size());
- std::vector<std::pair<std::unique_ptr<KeyString::Value>, std::unique_ptr<KeyString::Value>>>
- result;
- for (auto&& [lowKey, highKey] : intervals) {
- LOGV2_DEBUG(4742906,
- 5,
- "Generated interval [lowKey, highKey]",
- "lowKey"_attr = lowKey,
- "highKey"_attr = highKey);
- // Note that 'makeKeyFromBSONKeyForSeek()' is intended to compute the "start" key for an
- // index scan. The logic for computing a "discriminator" for an "end" key is reversed, which
- // is why we use 'makeKeyStringFromBSONKey()' to manually specify the discriminator for the
- // end key.
- result.push_back(
- {std::make_unique<KeyString::Value>(
- IndexEntryComparison::makeKeyStringFromBSONKeyForSeek(
- lowKey, version, ordering, forward, lowKeyInclusive)),
- std::make_unique<KeyString::Value>(IndexEntryComparison::makeKeyStringFromBSONKey(
- highKey,
- version,
- ordering,
- forward != highKeyInclusive ? KeyString::Discriminator::kExclusiveBefore
- : KeyString::Discriminator::kExclusiveAfter))});
- }
- return result;
-}
-
-/**
* Constructs an optimized version of an index scan for multi-interval index bounds for the case
* when the bounds can be decomposed in a number of single-interval bounds. In this case, instead
* of building a generic index scan to navigate through the index using the 'IndexBoundsChecker',
@@ -281,22 +225,20 @@ makeIntervalsFromIndexBounds(const IndexBounds& bounds,
* environment and returned as a third element of the tuple.
*/
std::tuple<sbe::value::SlotId, std::unique_ptr<sbe::PlanStage>, boost::optional<sbe::value::SlotId>>
-generateOptimizedMultiIntervalIndexScan(
- StageBuilderState& state,
- const CollectionPtr& collection,
- const std::string& indexName,
- const BSONObj& keyPattern,
- bool forward,
- boost::optional<std::vector<
- std::pair<std::unique_ptr<KeyString::Value>, std::unique_ptr<KeyString::Value>>>> intervals,
- sbe::IndexKeysInclusionSet indexKeysToInclude,
- sbe::value::SlotVector indexKeySlots,
- boost::optional<sbe::value::SlotId> snapshotIdSlot,
- boost::optional<sbe::value::SlotId> indexIdSlot,
- boost::optional<sbe::value::SlotId> recordSlot,
- boost::optional<sbe::value::SlotId> indexKeyPatternSlot,
- PlanYieldPolicy* yieldPolicy,
- PlanNodeId planNodeId) {
+generateOptimizedMultiIntervalIndexScan(StageBuilderState& state,
+ const CollectionPtr& collection,
+ const std::string& indexName,
+ const BSONObj& keyPattern,
+ bool forward,
+ boost::optional<IndexIntervals> intervals,
+ sbe::IndexKeysInclusionSet indexKeysToInclude,
+ sbe::value::SlotVector indexKeySlots,
+ boost::optional<sbe::value::SlotId> snapshotIdSlot,
+ boost::optional<sbe::value::SlotId> indexIdSlot,
+ boost::optional<sbe::value::SlotId> recordSlot,
+ boost::optional<sbe::value::SlotId> indexKeyPatternSlot,
+ PlanYieldPolicy* yieldPolicy,
+ PlanNodeId planNodeId) {
using namespace std::literals;
auto slotIdGenerator = state.slotIdGenerator;
@@ -309,25 +251,7 @@ generateOptimizedMultiIntervalIndexScan(
auto&& [boundsSlot, boundsStage] =
[&]() -> std::pair<sbe::value::SlotId, std::unique_ptr<sbe::PlanStage>> {
if (intervals) {
- // Construct an array containing objects with the low and high keys
- // for each interval. E.g.,
- // [ {l: KS(...), h: KS(...)},
- // {l: KS(...), h: KS(...)}, ... ]
- auto [boundsTag, boundsVal] = sbe::value::makeNewArray();
- auto arr = sbe::value::getArrayView(boundsVal);
- arr->reserve(intervals->size());
- for (auto&& [lowKey, highKey] : *intervals) {
- auto [tag, val] = sbe::value::makeNewObject();
- auto obj = sbe::value::getObjectView(val);
- obj->reserve(2);
- obj->push_back("l"_sd,
- sbe::value::TypeTags::ksValue,
- sbe::value::bitcastFrom<KeyString::Value*>(lowKey.release()));
- obj->push_back("h"_sd,
- sbe::value::TypeTags::ksValue,
- sbe::value::bitcastFrom<KeyString::Value*>(highKey.release()));
- arr->push_back(tag, val);
- }
+ auto [boundsTag, boundsVal] = packIndexIntervalsInSbeArray(std::move(*intervals));
const auto boundsSlot = slotIdGenerator->generate();
return {boundsSlot,
sbe::makeProjectStage(std::move(limitStage),
@@ -683,7 +607,7 @@ generateGenericMultiIntervalIndexScan(StageBuilderState& state,
// Get the start seek key for our recursive scan. If there are no possible index entries that
// match the bounds and we cannot generate a start seek key, inject an EOF sub-tree an exit
// straight away - this index scan won't emit any results.
- if (!didGetStartSeekPoint) {
+ if (!hasDynamicIndexBounds && !didGetStartSeekPoint) {
sbe::value::SlotMap<std::unique_ptr<sbe::EExpression>> projects;
projects.emplace(resultSlot, makeConstant(sbe::value::TypeTags::Nothing, 0));
@@ -1118,6 +1042,79 @@ std::pair<std::unique_ptr<sbe::PlanStage>, PlanStageSlots> generateIndexScan(
return {std::move(stage), std::move(outputs)};
}
+IndexIntervals makeIntervalsFromIndexBounds(const IndexBounds& bounds,
+ bool forward,
+ KeyString::Version version,
+ Ordering ordering) {
+ auto lowKeyInclusive{IndexBounds::isStartIncludedInBound(bounds.boundInclusion)};
+ auto highKeyInclusive{IndexBounds::isEndIncludedInBound(bounds.boundInclusion)};
+ auto intervals = [&]() -> std::vector<std::pair<BSONObj, BSONObj>> {
+ auto lowKey = bounds.startKey;
+ auto highKey = bounds.endKey;
+ if (bounds.isSimpleRange ||
+ IndexBoundsBuilder::isSingleInterval(
+ bounds, &lowKey, &lowKeyInclusive, &highKey, &highKeyInclusive)) {
+ return {{lowKey, highKey}};
+ } else if (canBeDecomposedIntoSingleIntervals(
+ bounds.fields, &lowKeyInclusive, &highKeyInclusive)) {
+ return decomposeIntoSingleIntervals(bounds.fields, lowKeyInclusive, highKeyInclusive);
+ } else {
+ // Index bounds cannot be represented as valid low/high keys.
+ return {};
+ }
+ }();
+
+ LOGV2_DEBUG(
+ 4742905, 5, "Number of generated interval(s) for ixscan", "num"_attr = intervals.size());
+ IndexIntervals result;
+ for (auto&& [lowKey, highKey] : intervals) {
+ LOGV2_DEBUG(4742906,
+ 5,
+ "Generated interval [lowKey, highKey]",
+ "lowKey"_attr = lowKey,
+ "highKey"_attr = highKey);
+ // Note that 'makeKeyFromBSONKeyForSeek()' is intended to compute the "start" key for an
+ // index scan. The logic for computing a "discriminator" for an "end" key is reversed, which
+ // is why we use 'makeKeyStringFromBSONKey()' to manually specify the discriminator for the
+ // end key.
+ result.push_back(
+ {std::make_unique<KeyString::Value>(
+ IndexEntryComparison::makeKeyStringFromBSONKeyForSeek(
+ lowKey, version, ordering, forward, lowKeyInclusive)),
+ std::make_unique<KeyString::Value>(IndexEntryComparison::makeKeyStringFromBSONKey(
+ highKey,
+ version,
+ ordering,
+ forward != highKeyInclusive ? KeyString::Discriminator::kExclusiveBefore
+ : KeyString::Discriminator::kExclusiveAfter))});
+ }
+ return result;
+}
+
+std::pair<sbe::value::TypeTags, sbe::value::Value> packIndexIntervalsInSbeArray(
+ IndexIntervals intervals) {
+ auto [boundsTag, boundsVal] = sbe::value::makeNewArray();
+ auto arr = sbe::value::getArrayView(boundsVal);
+ sbe::value::ValueGuard boundsGuard{boundsTag, boundsVal};
+ arr->reserve(intervals.size());
+ for (auto&& [lowKey, highKey] : intervals) {
+ auto [tag, val] = sbe::value::makeNewObject();
+ auto obj = sbe::value::getObjectView(val);
+ sbe::value::ValueGuard guard{tag, val};
+ obj->reserve(2);
+ obj->push_back("l"_sd,
+ sbe::value::TypeTags::ksValue,
+ sbe::value::bitcastFrom<KeyString::Value*>(lowKey.release()));
+ obj->push_back("h"_sd,
+ sbe::value::TypeTags::ksValue,
+ sbe::value::bitcastFrom<KeyString::Value*>(highKey.release()));
+ guard.reset();
+ arr->push_back(tag, val);
+ }
+ boundsGuard.reset();
+ return {boundsTag, boundsVal};
+}
+
std::pair<std::unique_ptr<sbe::PlanStage>, PlanStageSlots> generateIndexScanWithDynamicBounds(
StageBuilderState& state,
const CollectionPtr& collection,
@@ -1205,6 +1202,7 @@ std::pair<std::unique_ptr<sbe::PlanStage>, PlanStageSlots> generateIndexScanWith
state.slotIdGenerator,
state.spoolIdGenerator,
yieldPolicy);
+ tassert(6335203, "bounds slots for index scan are undefined", genericIndexScanBoundsSlots);
genericIndexScanSlots.push_back(genericIndexScanRecordIdSlot);
// If we were able to decompose multi-interval index bounds into a number of single-interval
@@ -1226,6 +1224,7 @@ std::pair<std::unique_ptr<sbe::PlanStage>, PlanStageSlots> generateIndexScanWith
optimizedIndexKeyPatternSlot,
yieldPolicy,
ixn->nodeId());
+ tassert(6335204, "bounds slot for index scan is undefined", optimizedIndexScanBoundsSlot);
optimizedIndexScanSlots.push_back(optimizedIndexScanRecordIdSlot);
// Generate a branch stage that will either execute an optimized or a generic index scan
@@ -1268,11 +1267,19 @@ std::pair<std::unique_ptr<sbe::PlanStage>, PlanStageSlots> generateIndexScanWith
outputs.setIndexKeySlots(makeIndexKeyOutputSlotsMatchingParentReqs(
ixn->index.keyPattern, originalIndexKeyBitset, indexKeyBitset, outputIndexKeySlots));
- [[maybe_unused]] ParameterizedIndexScanSlots indexScanStageSlots{
- isGenericScanSlot,
- genericIndexScanBoundsSlots->first,
- genericIndexScanBoundsSlots->second,
- *optimizedIndexScanBoundsSlot};
+ ParameterizedIndexScanSlots indexScanStageSlots{isGenericScanSlot,
+ genericIndexScanBoundsSlots->first,
+ genericIndexScanBoundsSlots->second,
+ *optimizedIndexScanBoundsSlot};
+
+ state.data->indexBoundsEvaluationInfos.emplace_back(
+ IndexBoundsEvaluationInfo{ixn->index,
+ accessMethod->getSortedDataInterface()->getKeyStringVersion(),
+ accessMethod->getSortedDataInterface()->getOrdering(),
+ ixn->direction,
+ std::move(ixn->iets),
+ std::move(indexScanStageSlots)});
+
return {std::move(stage), std::move(outputs)};
}
} // namespace mongo::stage_builder
diff --git a/src/mongo/db/query/sbe_stage_builder_index_scan.h b/src/mongo/db/query/sbe_stage_builder_index_scan.h
index a67112f7f79..340a03051eb 100644
--- a/src/mongo/db/query/sbe_stage_builder_index_scan.h
+++ b/src/mongo/db/query/sbe_stage_builder_index_scan.h
@@ -42,6 +42,12 @@ class PlanStageReqs;
class PlanStageSlots;
/**
+ * A list of low and high key values representing ranges over a particular index.
+ */
+using IndexIntervals =
+ std::vector<std::pair<std::unique_ptr<KeyString::Value>, std::unique_ptr<KeyString::Value>>>;
+
+/**
* This method generates an SBE plan stage tree implementing an index scan. It returns a tuple
* containing: (1) a slot produced by the index scan that holds the record ID ('recordIdSlot');
* (2) a slot vector produced by the index scan which hold parts of the index key ('indexKeySlots');
@@ -96,6 +102,24 @@ std::pair<sbe::value::SlotId, std::unique_ptr<sbe::PlanStage>> generateSingleInt
PlanYieldPolicy* yieldPolicy,
PlanNodeId nodeId);
+/**
+ * Constructs low/high key values from the given index 'bounds' if they can be represented either as
+ * a single interval between the low and high keys, or multiple single intervals. If index bounds
+ * for some interval cannot be expressed as valid low/high keys, then an empty vector is returned.
+ */
+IndexIntervals makeIntervalsFromIndexBounds(const IndexBounds& bounds,
+ bool forward,
+ KeyString::Version version,
+ Ordering ordering);
+
+/**
+ * Construct an array containing objects with the low and high keys for each interval.
+ *
+ * E.g., [ {l: KS(...), h: KS(...)},
+ * {l: KS(...), h: KS(...)}, ... ]
+ */
+std::pair<sbe::value::TypeTags, sbe::value::Value> packIndexIntervalsInSbeArray(
+ IndexIntervals intervals);
/**
* Constructs a generic multi-interval index scan. Depending on the intervals will either execute