diff options
-rw-r--r-- | jstests/aggregation/bugs/server6239.js | 4 | ||||
-rw-r--r-- | jstests/aggregation/bugs/server6570.js | 3 | ||||
-rw-r--r-- | jstests/aggregation/expressions/in.js | 1 | ||||
-rw-r--r-- | jstests/core/and3.js | 2 | ||||
-rw-r--r-- | jstests/core/explain6.js | 2 | ||||
-rw-r--r-- | jstests/core/index_bounds_pipe.js | 1 | ||||
-rw-r--r-- | jstests/core/index_type_change.js | 2 | ||||
-rw-r--r-- | jstests/core/mod_with_where.js | 1 | ||||
-rw-r--r-- | jstests/core/or_inexact.js | 5 | ||||
-rw-r--r-- | jstests/core/regex3.js | 2 | ||||
-rw-r--r-- | jstests/core/regex4.js | 2 | ||||
-rw-r--r-- | jstests/core/regex6.js | 2 | ||||
-rw-r--r-- | jstests/core/regex8.js | 7 | ||||
-rw-r--r-- | jstests/core/regexc.js | 4 | ||||
-rw-r--r-- | jstests/core/remove9.js | 2 | ||||
-rw-r--r-- | jstests/core/wildcard_index_type.js | 6 | ||||
-rw-r--r-- | src/mongo/db/query/sbe_stage_builder.cpp | 32 | ||||
-rw-r--r-- | src/mongo/db/query/sbe_stage_builder_filter.cpp | 192 | ||||
-rw-r--r-- | src/mongo/db/query/sbe_stage_builder_filter.h | 20 | ||||
-rw-r--r-- | src/mongo/db/query/sbe_stage_builder_helpers.h | 28 | ||||
-rw-r--r-- | src/mongo/db/query/sbe_stage_builder_index_scan.cpp | 63 | ||||
-rw-r--r-- | src/mongo/db/query/sbe_stage_builder_index_scan.h | 3 |
22 files changed, 267 insertions, 117 deletions
diff --git a/jstests/aggregation/bugs/server6239.js b/jstests/aggregation/bugs/server6239.js index 4b9cc350ee5..a88bf7527d6 100644 --- a/jstests/aggregation/bugs/server6239.js +++ b/jstests/aggregation/bugs/server6239.js @@ -1,9 +1,5 @@ // SERVER-6239 reenable $add and $subtract with dates with better semantics // Note: error conditions tested also in server6240.js -// @tags: [ -// sbe_incompatible, -// ] - load('jstests/aggregation/extras/utils.js'); load("jstests/libs/sbe_assert_error_override.js"); // Override error-code-checking APIs. diff --git a/jstests/aggregation/bugs/server6570.js b/jstests/aggregation/bugs/server6570.js index 7e92dc05875..112feb49406 100644 --- a/jstests/aggregation/bugs/server6570.js +++ b/jstests/aggregation/bugs/server6570.js @@ -1,7 +1,4 @@ // ensure $add asserts on string -// @tags: [ -// sbe_incompatible, -// ] load('jstests/aggregation/extras/utils.js'); load("jstests/libs/sbe_assert_error_override.js"); // Override error-code-checking APIs. diff --git a/jstests/aggregation/expressions/in.js b/jstests/aggregation/expressions/in.js index 6dfa72c973a..5e4c9f70bc7 100644 --- a/jstests/aggregation/expressions/in.js +++ b/jstests/aggregation/expressions/in.js @@ -2,7 +2,6 @@ // and error cases of the expression. // @tags: [ // assumes_no_implicit_collection_creation_after_drop, -// sbe_incompatible, // ] load("jstests/aggregation/extras/utils.js"); // For assertErrorCode. diff --git a/jstests/core/and3.js b/jstests/core/and3.js index 77f769c805a..a64cafd8a08 100644 --- a/jstests/core/and3.js +++ b/jstests/core/and3.js @@ -1,10 +1,8 @@ // Check key match with sub matchers - part of SERVER-3192 -// TODO SERVER-52734: remove sbe_incompatible tag // @tags: [ // assumes_balancer_off, // # Uses $where operator // requires_scripting, -// sbe_incompatible, // ] t = db.jstests_and3; diff --git a/jstests/core/explain6.js b/jstests/core/explain6.js index 4f5e9fab082..fbb6ecf6d76 100644 --- a/jstests/core/explain6.js +++ b/jstests/core/explain6.js @@ -1,8 +1,6 @@ -// TODO SERVER-52734: remove sbe_incompatible tag // @tags: [ // assumes_balancer_off, // requires_non_retryable_writes, -// sbe_incompatible, // ] // Basic test which checks the number of documents returned, keys examined, and documents diff --git a/jstests/core/index_bounds_pipe.js b/jstests/core/index_bounds_pipe.js index fffb203f793..88c9f9a9a7f 100644 --- a/jstests/core/index_bounds_pipe.js +++ b/jstests/core/index_bounds_pipe.js @@ -1,7 +1,6 @@ /** * Tests the tightness of index bounds when attempting to match a regex that contains escaped and * non-escaped pipe '|' characters. - * TODO SERVER-52734: remove sbe_incompatible tag * @tags: [ * sbe_incompatible, * ] diff --git a/jstests/core/index_type_change.js b/jstests/core/index_type_change.js index 0fd2476c747..5931b072e8d 100644 --- a/jstests/core/index_type_change.js +++ b/jstests/core/index_type_change.js @@ -1,10 +1,8 @@ // Cannot implicitly shard accessed collections because of following errmsg: A single // update/delete on a sharded collection must contain an exact match on _id or contain the shard // key. -// TODO SERVER-52734: remove sbe_incompatible tag // @tags: [ // assumes_unsharded_collection, -// sbe_incompatible, // ] /** diff --git a/jstests/core/mod_with_where.js b/jstests/core/mod_with_where.js index 7cf1cd20bee..81e47066bde 100644 --- a/jstests/core/mod_with_where.js +++ b/jstests/core/mod_with_where.js @@ -3,7 +3,6 @@ // assumes_balancer_off, // # Uses $where operator // requires_scripting, -// sbe_incompatible, // ] (function() { diff --git a/jstests/core/or_inexact.js b/jstests/core/or_inexact.js index 82a845554b8..82f6c078d4e 100644 --- a/jstests/core/or_inexact.js +++ b/jstests/core/or_inexact.js @@ -1,10 +1,5 @@ // Test $or with predicates that generate inexact bounds. The access planner // has special logic for such queries. -// TODO SERVER-52734: remove sbe_incompatible tag -// @tags: [ -// sbe_incompatible, -// ] - var t = db.jstests_or_inexact; var cursor; diff --git a/jstests/core/regex3.js b/jstests/core/regex3.js index 986176679e5..a8c550213d5 100644 --- a/jstests/core/regex3.js +++ b/jstests/core/regex3.js @@ -1,7 +1,5 @@ -// TODO SERVER-52734: remove sbe_incompatible tag // @tags: [ // assumes_balancer_off, -// sbe_incompatible, // ] t = db.regex3; diff --git a/jstests/core/regex4.js b/jstests/core/regex4.js index 178f859e666..393698f3c63 100644 --- a/jstests/core/regex4.js +++ b/jstests/core/regex4.js @@ -1,7 +1,5 @@ -// TODO SERVER-52734: remove sbe_incompatible tag // @tags: [ // assumes_balancer_off, -// sbe_incompatible, // ] t = db.regex4; diff --git a/jstests/core/regex6.js b/jstests/core/regex6.js index c35261762e0..cc7b507f610 100644 --- a/jstests/core/regex6.js +++ b/jstests/core/regex6.js @@ -1,10 +1,8 @@ // contributed by Andrew Kempe // This test makes assertions about how many keys are examined during query execution, which can // change depending on whether/how many documents are filtered out by the SHARDING_FILTER stage. -// TODO SERVER-52734: remove sbe_incompatible tag // @tags: [ // assumes_unsharded_collection, -// sbe_incompatible, // ] t = db.regex6; t.drop(); diff --git a/jstests/core/regex8.js b/jstests/core/regex8.js index af16a7f4ea0..20164acf464 100644 --- a/jstests/core/regex8.js +++ b/jstests/core/regex8.js @@ -1,10 +1,3 @@ - -/** - * TODO SERVER-52734: remove sbe_incompatible tag - * @tags: [ - * sbe_incompatible, - * ] - */ t = db.regex8; t.drop(); diff --git a/jstests/core/regexc.js b/jstests/core/regexc.js index 7bd832a1254..235d509a3c2 100644 --- a/jstests/core/regexc.js +++ b/jstests/core/regexc.js @@ -1,8 +1,4 @@ // Multiple regular expressions using the same index -// TODO SERVER-52734: remove sbe_incompatible tag -// @tags: [ -// sbe_incompatible, -// ] var t = db.jstests_regexc; diff --git a/jstests/core/remove9.js b/jstests/core/remove9.js index 0cded02cc2e..ce8e714eccf 100644 --- a/jstests/core/remove9.js +++ b/jstests/core/remove9.js @@ -1,8 +1,6 @@ -// TODO SERVER-52734: remove sbe_incompatible tag // @tags: [ // requires_getmore, // requires_non_retryable_writes, -// sbe_incompatible, // uses_multiple_connections, // uses_parallel_shell, // ] diff --git a/jstests/core/wildcard_index_type.js b/jstests/core/wildcard_index_type.js index d9623a47ba9..e47815e026a 100644 --- a/jstests/core/wildcard_index_type.js +++ b/jstests/core/wildcard_index_type.js @@ -1,9 +1,5 @@ /** * Test $** support for the $type operator. - * TODO SERVER-52734: remove sbe_incompatible tag - * @tags: [ - * sbe_incompatible, - * ] */ (function() { "use strict"; @@ -37,7 +33,7 @@ function assertExpectedDocAnswersWildcardIndexQuery(doc, query, match, expectedB assert.eq(1, explain.executionStats.nReturned, explain); // Winning plan uses a wildcard index scan. - const winningPlan = explain.queryPlanner.winningPlan; + const winningPlan = getWinningPlan(explain.queryPlanner); const ixScans = getPlanStages(winningPlan, "IXSCAN"); assert.gt(ixScans.length, 0, explain); ixScans.forEach((ixScan) => assert(ixScan.keyPattern.$_path)); diff --git a/src/mongo/db/query/sbe_stage_builder.cpp b/src/mongo/db/query/sbe_stage_builder.cpp index c7da41d5daa..3a0d9821add 100644 --- a/src/mongo/db/query/sbe_stage_builder.cpp +++ b/src/mongo/db/query/sbe_stage_builder.cpp @@ -137,36 +137,6 @@ sbe::LockAcquisitionCallback makeLockAcquisitionCallback(bool checkNodeCanServeR opCtx, coll.getNss(), true)); }; } - -/** - * Given an index key pattern, and a subset of the fields of the index key pattern that are depended - * on to compute the query, returns the corresponding 'IndexKeysInclusionSet' bit vector and field - * name vector. - * - * For example, suppose that we have an index key pattern {d: 1, c: 1, b: 1, a: 1}, and the caller - * depends on the set of 'requiredFields' {"b", "d"}. In this case, the pair of return values would - * be: - * - 'IndexKeysInclusionSet' bit vector of 1010 - * - Field name vector of <"d", "b"> - */ -template <typename T> -std::pair<sbe::IndexKeysInclusionSet, std::vector<std::string>> makeIndexKeyInclusionSet( - const BSONObj& indexKeyPattern, const T& requiredFields) { - sbe::IndexKeysInclusionSet indexKeyBitset; - std::vector<std::string> keyFieldNames; - size_t i = 0; - for (auto&& elt : indexKeyPattern) { - if (requiredFields.count(elt.fieldNameStringData())) { - indexKeyBitset.set(i); - keyFieldNames.push_back(elt.fieldName()); - } - - ++i; - } - - return {std::move(indexKeyBitset), std::move(keyFieldNames)}; -} - } // namespace SlotBasedStageBuilder::SlotBasedStageBuilder(OperationContext* opCtx, @@ -345,8 +315,10 @@ std::pair<std::unique_ptr<sbe::PlanStage>, PlanStageSlots> SlotBasedStageBuilder ixn, reqs, &_slotIdGenerator, + &_frameIdGenerator, &_spoolIdGenerator, _yieldPolicy, + _data.env, _lockAcquisitionCallback); } diff --git a/src/mongo/db/query/sbe_stage_builder_filter.cpp b/src/mongo/db/query/sbe_stage_builder_filter.cpp index 5a3b9e21862..849f9348192 100644 --- a/src/mongo/db/query/sbe_stage_builder_filter.cpp +++ b/src/mongo/db/query/sbe_stage_builder_filter.cpp @@ -113,6 +113,8 @@ using MakePredicateFn = * A struct for storing context across calls to visit() methods in MatchExpressionVisitor's. */ struct MatchExpressionVisitorContext { + // Construct a visitor context to generate a filter expression from a single input slot + // holding a document against which to perform the match. MatchExpressionVisitorContext(OperationContext* opCtx, sbe::value::SlotIdGenerator* slotIdGenerator, sbe::value::FrameIdGenerator* frameIdGenerator, @@ -140,6 +142,49 @@ struct MatchExpressionVisitorContext { } } + // Construct a visitor context to generate a filter expression that is attached to an index + // scan and can evaluate an expression from the index keys without fetching an entire document. + // Instead of a single input slot holding the root document, it takes a vector of 'keySlots' and + // 'keyFields' which represent a subset of the fields of the index key pattern that are depended + // on to evaluate the predicate, and corresponding slots for each of the key fields. + MatchExpressionVisitorContext(OperationContext* opCtx, + sbe::value::SlotIdGenerator* slotIdGenerator, + sbe::value::FrameIdGenerator* frameIdGenerator, + EvalStage inputStage, + sbe::value::SlotVector keySlots, + std::vector<std::string> keyFields, + const MatchExpression* root, + sbe::RuntimeEnvironment* env, + PlanNodeId planNodeId, + const FilterStateHelper& stateHelper) + : opCtx{opCtx}, + slotIdGenerator{slotIdGenerator}, + frameIdGenerator{frameIdGenerator}, + topLevelAnd{nullptr}, + env{env}, + planNodeId{planNodeId}, + stateHelper{stateHelper} { + // Set up the top-level EvalFrame. + evalStack.emplaceFrame(std::move(inputStage), boost::none); + + tassert(5273400, "Index key slots vector is empty", keySlots.size() > 0); + tassert(5273401, + "Mismatch between index key slots and fields", + keySlots.size() == keyFields.size()); + + for (size_t idx = 0; idx < keySlots.size(); ++idx) { + auto&& field = keyFields[idx]; + tassert(5273410, "Index key field is empty", !field.empty()); + indexKeySlots[field] = keySlots[idx]; + } + + // If the root node is an $and, store it in 'topLevelAnd'. + // TODO: SERVER-50673: Revisit how we implement the top-level $and optimization. + if (root->matchType() == MatchExpression::AND) { + topLevelAnd = root; + } + } + std::pair<boost::optional<sbe::value::SlotId>, EvalStage> done() { invariant(evalStack.framesCount() == 1); auto& frame = evalStack.topFrame(); @@ -166,14 +211,26 @@ struct MatchExpressionVisitorContext { } struct FrameData { - sbe::value::SlotId inputSlot; - - FrameData(sbe::value::SlotId inputSlot) : inputSlot{inputSlot} {} + // For an index filter we don't build a traversal sub-tree, and do not use complex + // expressions, such as $elemMatch or nested logical $and/$or/$nor. As such, we don't need + // to create nested EvalFrames, and we don't need an 'inputSlot' for the frame, because + // values are read from the 'indexKeySlots' map stored in the context. Yet, we still need a + // top-level EvalFrame, as the the entire filter generator logic is based on the assumption + // that we've got at least one EvalFrame. Hence, the 'inputSlot' is declared optional. + boost::optional<sbe::value::SlotId> inputSlot; + + FrameData(boost::optional<sbe::value::SlotId> inputSlot) : inputSlot{inputSlot} {} }; OperationContext* opCtx; EvalStack<FrameData> evalStack; - sbe::value::SlotId inputSlot; + // The current context must be initialized either with an 'inputSlot' over which an entire match + // expression needs to be evaluated, or a pair of 'keySlots' and 'keyFields' vectors + // representing a subset of the fields of the index key pattern that are depended on to evaluate + // the predicate, and corresponding slots for each of the fields, which are stored in + // 'indexKeySlots' map. + boost::optional<sbe::value::SlotId> inputSlot; + StringMap<sbe::value::SlotId> indexKeySlots; sbe::value::SlotIdGenerator* slotIdGenerator; sbe::value::FrameIdGenerator* frameIdGenerator; const MatchExpression* topLevelAnd; @@ -401,24 +458,47 @@ void generatePredicate(MatchExpressionVisitorContext* context, LeafTraversalMode mode = LeafTraversalMode::kArrayAndItsElements, bool useCombinator = true) { auto& frame = context->evalStack.topFrame(); + auto&& [expr, stage] = [&]() { - if (!path.empty()) { - return generatePathTraversal(frame.extractStage(), - frame.data().inputSlot, - FieldRef{path}, - 0, - context->planNodeId, - context->slotIdGenerator, - context->frameIdGenerator, - makePredicate, - mode, - context->stateHelper); + if (frame.data().inputSlot) { + if (!path.empty()) { + return generatePathTraversal(frame.extractStage(), + *frame.data().inputSlot, + FieldRef{path}, + 0, + context->planNodeId, + context->slotIdGenerator, + context->frameIdGenerator, + makePredicate, + mode, + context->stateHelper); + } else { + // If matchExpr's parent is a ElemMatchValueMatchExpression, then + // matchExpr()->path() will be empty. In this case, 'inputSlot' will be a + // "correlated slot" that holds the value of the ElemMatchValueMatchExpression's + // field path, and we should apply the predicate directly on 'inputSlot' without + // array traversal. + auto result = makePredicate(*frame.data().inputSlot, frame.extractStage()); + if (useCombinator) { + return context->stateHelper.makePredicateCombinator(std::move(result)); + } + return result; + } } else { - // If matchExpr's parent is a ElemMatchValueMatchExpression, then matchExpr()->path() - // will be empty. In this case, 'inputSlot' will be a "correlated slot" that holds the - // value of the ElemMatchValueMatchExpression's field path, and we should apply the - // predicate directly on 'inputSlot' without array traversal. - auto result = makePredicate(frame.data().inputSlot, frame.extractStage()); + // If an input slot for the current frame is not defined, then we must generating a + // filter predicate for an index scan. In this case we don't need to perform any complex + // path traversal but rather evaluate the predicate directly on the input slot for the + // current field path - the index scan will extract the value for this field path and + // will store it in a corresponding slot in the 'indexKeySlots' map. + + tassert(5273402, "Field path cannot be empty for an index filter", !path.empty()); + + auto it = context->indexKeySlots.find(path.toString()); + tassert(5273403, + str::stream() << "Unknown field path in index filter: " << path, + it != context->indexKeySlots.end()); + + auto result = makePredicate(it->second, frame.extractStage()); if (useCombinator) { return context->stateHelper.makePredicateCombinator(std::move(result)); } @@ -1028,7 +1108,10 @@ public: // Extract the input slot, the output, and the stage from of the child's EvalFrame, and // remove the child's EvalFrame from the stack. - auto childInputSlot = _context->evalStack.topFrame().data().inputSlot; + tassert(5273405, + "Eval frame's input slot is not defined", + static_cast<bool>(_context->evalStack.topFrame().data().inputSlot)); + auto childInputSlot = *_context->evalStack.topFrame().data().inputSlot; auto [filterSlot, filterStage] = [&]() { auto [expr, stage] = _context->evalStack.popFrame(); auto [predicateSlot, predicateStage] = projectEvalExpr( @@ -1068,7 +1151,10 @@ public: auto numChildren = matchExpr->numChildren(); invariant(numChildren >= 1); - auto childInputSlot = _context->evalStack.topFrame().data().inputSlot; + tassert(5273406, + "Eval frame's input slot is not defined", + static_cast<bool>(_context->evalStack.topFrame().data().inputSlot)); + auto childInputSlot = *_context->evalStack.topFrame().data().inputSlot; // Move the children's outputs off of the evalStack into a vector in preparation for // calling generateShortCircuitingLogicalOp(). @@ -1132,8 +1218,16 @@ public: // The $expr expression must by applied to the current $$ROOT document, so make sure that // an input slot associated with the current frame is the same slot as the input slot for - // the entire match expression we're translating. - invariant(frame.data().inputSlot == _context->inputSlot); + // the entire match expression we're translating + tassert(5273407, + "Match expression's input slot is not defined", + static_cast<bool>(_context->inputSlot)); + tassert(5273408, + "Eval frame's input slot is not defined", + static_cast<bool>(frame.data().inputSlot)); + tassert(5273409, + "Eval frame for $expr is not computed over expression's input slot", + *frame.data().inputSlot == *_context->inputSlot); auto currentStage = stageOrLimitCoScan(frame.extractStage(), _context->planNodeId); auto&& [_, expr, stage] = generateExpression(_context->opCtx, @@ -1141,7 +1235,7 @@ public: std::move(currentStage.stage), _context->slotIdGenerator, _context->frameIdGenerator, - frame.data().inputSlot, + *frame.data().inputSlot, _context->env, _context->planNodeId, ¤tStage.outSlots); @@ -1627,4 +1721,52 @@ std::pair<boost::optional<sbe::value::SlotId>, std::unique_ptr<sbe::PlanStage>> auto [resultSlot, resultStage] = context.done(); return {resultSlot, std::move(resultStage.stage)}; } + +std::unique_ptr<sbe::PlanStage> generateIndexFilter(OperationContext* opCtx, + const MatchExpression* root, + std::unique_ptr<sbe::PlanStage> stage, + sbe::value::SlotIdGenerator* slotIdGenerator, + sbe::value::FrameIdGenerator* frameIdGenerator, + sbe::value::SlotVector keySlots, + std::vector<std::string> keyFields, + sbe::RuntimeEnvironment* env, + sbe::value::SlotVector relevantSlots, + PlanNodeId planNodeId) { + // The planner adds an $and expression without the operands if the query was empty. We can bail + // out early without generating the filter plan stage if this is the case. + if (root->matchType() == MatchExpression::AND && root->numChildren() == 0) { + return stage; + } + + // If 'keySlots' are not present within 'relevantSlots', add them now. + for (auto keySlot : keySlots) { + if (!std::count(relevantSlots.begin(), relevantSlots.end(), keySlot)) { + relevantSlots.push_back(keySlot); + } + } + + // Index filters never need to track the index of a matching element in the array as they cannot + // be used with a positional projection. + const bool trackIndex = false; + auto stateHelper = makeFilterStateHelper(trackIndex); + MatchExpressionVisitorContext context{opCtx, + slotIdGenerator, + frameIdGenerator, + EvalStage{std::move(stage), std::move(relevantSlots)}, + std::move(keySlots), + std::move(keyFields), + root, + env, + planNodeId, + *stateHelper}; + MatchExpressionPreVisitor preVisitor{&context}; + MatchExpressionInVisitor inVisitor{&context}; + MatchExpressionPostVisitor postVisitor{&context}; + MatchExpressionWalker walker{&preVisitor, &inVisitor, &postVisitor}; + tree_walker::walk<true, MatchExpression>(root, &walker); + + auto [resultSlot, resultStage] = context.done(); + tassert(5273409, "Index filter must not track a matching element index", !resultSlot); + return std::move(resultStage.stage); +} } // namespace mongo::stage_builder diff --git a/src/mongo/db/query/sbe_stage_builder_filter.h b/src/mongo/db/query/sbe_stage_builder_filter.h index 8e4dc230422..192dd356d9d 100644 --- a/src/mongo/db/query/sbe_stage_builder_filter.h +++ b/src/mongo/db/query/sbe_stage_builder_filter.h @@ -61,4 +61,24 @@ std::pair<boost::optional<sbe::value::SlotId>, std::unique_ptr<sbe::PlanStage>> PlanNodeId planNodeId, bool trackIndex = false); +/** + * Similar to 'generateFilter' but used to generate a PlanStage sub-tree implementing a filter + * attached to an 'IndexScan' QSN. It differs from 'generateFilter' in the following way: + * - Instead of a single input slot it takes 'keyFields' and 'keySlots' vectors representing a + * subset of the fields of the index key pattern that are depended on to evaluate the predicate, + * and corresponding slots for each of the fields. + * - It cannot track and returned an index of a matching element within an array, because index + * keys cannot contain an array. As such, this function doesn't take a 'trackIndex' parameter + * and doesn't return an optional SLotId holding the index of a matching array element. + */ +std::unique_ptr<sbe::PlanStage> generateIndexFilter(OperationContext* opCtx, + const MatchExpression* root, + std::unique_ptr<sbe::PlanStage> stage, + sbe::value::SlotIdGenerator* slotIdGenerator, + sbe::value::FrameIdGenerator* frameIdGenerator, + sbe::value::SlotVector keySlots, + std::vector<std::string> keyFields, + sbe::RuntimeEnvironment* env, + sbe::value::SlotVector relevantSlots, + PlanNodeId planNodeId); } // namespace mongo::stage_builder diff --git a/src/mongo/db/query/sbe_stage_builder_helpers.h b/src/mongo/db/query/sbe_stage_builder_helpers.h index d51a56f1902..d5956b0ca8d 100644 --- a/src/mongo/db/query/sbe_stage_builder_helpers.h +++ b/src/mongo/db/query/sbe_stage_builder_helpers.h @@ -718,4 +718,32 @@ sbe::value::SlotVector makeIndexKeyOutputSlotsMatchingParentReqs( sbe::IndexKeysInclusionSet childIndexKeyReqs, sbe::value::SlotVector childOutputSlots); +/** + * Given an index key pattern, and a subset of the fields of the index key pattern that are depended + * on to compute the query, returns the corresponding 'IndexKeysInclusionSet' bit vector and field + * name vector. + * + * For example, suppose that we have an index key pattern {d: 1, c: 1, b: 1, a: 1}, and the caller + * depends on the set of 'requiredFields' {"b", "d"}. In this case, the pair of return values would + * be: + * - 'IndexKeysInclusionSet' bit vector of 1010 + * - Field name vector of <"d", "b"> + */ +template <typename T> +std::pair<sbe::IndexKeysInclusionSet, std::vector<std::string>> makeIndexKeyInclusionSet( + const BSONObj& indexKeyPattern, const T& requiredFields) { + sbe::IndexKeysInclusionSet indexKeyBitset; + std::vector<std::string> keyFieldNames; + size_t i = 0; + for (auto&& elt : indexKeyPattern) { + if (requiredFields.count(elt.fieldName())) { + indexKeyBitset.set(i); + keyFieldNames.push_back(elt.fieldName()); + } + + ++i; + } + + return {std::move(indexKeyBitset), std::move(keyFieldNames)}; +} } // namespace mongo::stage_builder 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 6b53eaf64c3..9e7a2cff9b7 100644 --- a/src/mongo/db/query/sbe_stage_builder_index_scan.cpp +++ b/src/mongo/db/query/sbe_stage_builder_index_scan.cpp @@ -51,6 +51,7 @@ #include "mongo/db/query/index_bounds_builder.h" #include "mongo/db/query/query_knobs_gen.h" #include "mongo/db/query/sbe_stage_builder.h" +#include "mongo/db/query/sbe_stage_builder_filter.h" #include "mongo/db/query/sbe_stage_builder_helpers.h" #include "mongo/db/query/util/make_data_structure.h" #include "mongo/logv2/log.h" @@ -680,10 +681,11 @@ std::pair<std::unique_ptr<sbe::PlanStage>, PlanStageSlots> generateIndexScan( const IndexScanNode* ixn, PlanStageReqs reqs, sbe::value::SlotIdGenerator* slotIdGenerator, + sbe::value::SlotIdGenerator* frameIdGenerator, sbe::value::SpoolIdGenerator* spoolIdGenerator, PlanYieldPolicy* yieldPolicy, + sbe::RuntimeEnvironment* env, sbe::LockAcquisitionCallback lockAcquisitionCallback) { - uassert(4822864, "Index scans with a filter are not supported in SBE", !ixn->filter); auto descriptor = collection->getIndexCatalog()->findIndexByName(opCtx, ixn->index.identifier.catalogName); @@ -701,6 +703,25 @@ std::pair<std::unique_ptr<sbe::PlanStage>, PlanStageSlots> generateIndexScan( PlanStageSlots outputs; + // Save the bit vector describing the fields from the index that our caller requires. If an + // index filter is defined, we may require additional index fields which are not needed by the + // parent stage. We will need the parent's reqs later on so that we can hand the correct slot + // vector for these fields back to our parent. + auto parentIndexKeyBitset = reqs.getIndexKeyBitset(); + + // Determine the set of fields from the index required to apply the filter and union those with + // the set of fields from the index required by the parent stage. + auto [indexFilterKeyBitset, indexFilterKeyFields] = [&]() { + if (ixn->filter) { + DepsTracker tracker; + ixn->filter->addDependencies(&tracker); + return makeIndexKeyInclusionSet(ixn->index.keyPattern, tracker.fields); + } + return std::make_pair(sbe::IndexKeysInclusionSet{}, std::vector<std::string>{}); + }(); + reqs.getIndexKeyBitset() = + parentIndexKeyBitset.value_or(sbe::IndexKeysInclusionSet{}) | indexFilterKeyBitset; + if (reqs.has(PlanStageSlots::kResult) || reqs.has(PlanStageSlots::kReturnKey)) { // If either 'reqs.result' or 'reqs.returnKey' is true, we need to get all parts of the // index key (regardless of what was requested by 'reqs.indexKeyBitset') so that we can @@ -788,6 +809,23 @@ std::pair<std::unique_ptr<sbe::PlanStage>, PlanStageSlots> generateIndexScan( std::move(stage), sbe::makeSV(outputs.get(PlanStageSlots::kRecordId)), ixn->nodeId()); } + if (ixn->filter) { + // We only need to pass those index key slots to the filter generator which correspond to + // the fields of the index key pattern that are depended on to compute the predicate. + auto indexFilterKeySlots = makeIndexKeyOutputSlotsMatchingParentReqs( + ixn->index.keyPattern, indexFilterKeyBitset, indexKeyBitset, indexKeySlots); + stage = generateIndexFilter(opCtx, + ixn->filter.get(), + std::move(stage), + slotIdGenerator, + frameIdGenerator, + std::move(indexFilterKeySlots), + std::move(indexFilterKeyFields), + env, + sbe::makeSV(outputs.get(PlanStageSlots::kRecordId)), + ixn->nodeId()); + } + if (reqs.has(PlanStageSlots::kResult) || reqs.has(PlanStageSlots::kReturnKey)) { if (reqs.has(PlanStageSlots::kResult)) { outputs.set(PlanStageSlots::kResult, slotIdGenerator->generate()); @@ -811,24 +849,15 @@ std::pair<std::unique_ptr<sbe::PlanStage>, PlanStageSlots> generateIndexScan( outputs.get(PlanStageSlots::kReturnKey), std::move(keyExpr)); } - - // If either 'reqs.result' or 'reqs.returnKey' is true, then at this point 'indexKeySlots' - // contain slots for _all_ parts of the index key. However, we only want to return the slots - // that were explicitly requested as given by 'reqs.indexKeyBitset'. - if (reqs.getIndexKeyBitset()) { - sbe::value::SlotVector outputIndexKeySlots; - for (size_t keyIndex = 0; keyIndex < indexKeySlots.size(); ++keyIndex) { - if ((*reqs.getIndexKeyBitset())[keyIndex]) { - outputIndexKeySlots.push_back(indexKeySlots[keyIndex]); - } - } - - outputs.setIndexKeySlots(std::move(outputIndexKeySlots)); - } - } else if (reqs.getIndexKeyBitset()) { - outputs.setIndexKeySlots(std::move(indexKeySlots)); } + // We only need to return the slots which were explicitly requested by our parent stage. + outputs.setIndexKeySlots( + !parentIndexKeyBitset + ? boost::none + : boost::optional<sbe::value::SlotVector>{makeIndexKeyOutputSlotsMatchingParentReqs( + ixn->index.keyPattern, *parentIndexKeyBitset, indexKeyBitset, indexKeySlots)}); + 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 bdfa8661291..b6bd6818c18 100644 --- a/src/mongo/db/query/sbe_stage_builder_index_scan.h +++ b/src/mongo/db/query/sbe_stage_builder_index_scan.h @@ -29,6 +29,7 @@ #pragma once +#include "mongo/db/exec/sbe/expressions/expression.h" #include "mongo/db/exec/sbe/stages/lock_acquisition_callback.h" #include "mongo/db/exec/sbe/stages/stages.h" #include "mongo/db/exec/sbe/values/value.h" @@ -55,8 +56,10 @@ std::pair<std::unique_ptr<sbe::PlanStage>, PlanStageSlots> generateIndexScan( const IndexScanNode* ixn, PlanStageReqs reqs, sbe::value::SlotIdGenerator* slotIdGenerator, + sbe::value::SlotIdGenerator* frameIdGenerator, sbe::value::SpoolIdGenerator* spoolIdGenerator, PlanYieldPolicy* yieldPolicy, + sbe::RuntimeEnvironment* env, sbe::LockAcquisitionCallback lockAcquisitionCallback); /** |