diff options
author | Alexander Ignatyev <alexander.ignatyev@mongodb.com> | 2022-04-08 12:22:12 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2022-04-08 13:31:12 +0000 |
commit | d84c2eff3e5bd195caadf8b202c736ae69e5735e (patch) | |
tree | baf8f8f4c775cb57a339d509aca35d1ba25ad2c9 /src | |
parent | 526193103b6064df5894b3d0c19c6cbdd15fbd3b (diff) | |
download | mongo-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.cpp | 14 | ||||
-rw-r--r-- | src/mongo/db/query/get_executor.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/query/planner_access.cpp | 54 | ||||
-rw-r--r-- | src/mongo/db/query/sbe_stage_builder.cpp | 90 | ||||
-rw-r--r-- | src/mongo/db/query/sbe_stage_builder.h | 19 | ||||
-rw-r--r-- | src/mongo/db/query/sbe_stage_builder_index_scan.cpp | 201 | ||||
-rw-r--r-- | src/mongo/db/query/sbe_stage_builder_index_scan.h | 24 |
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 |