summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--jstests/aggregation/bugs/server6239.js4
-rw-r--r--jstests/aggregation/bugs/server6570.js3
-rw-r--r--jstests/aggregation/expressions/in.js1
-rw-r--r--jstests/core/and3.js2
-rw-r--r--jstests/core/explain6.js2
-rw-r--r--jstests/core/index_bounds_pipe.js1
-rw-r--r--jstests/core/index_type_change.js2
-rw-r--r--jstests/core/mod_with_where.js1
-rw-r--r--jstests/core/or_inexact.js5
-rw-r--r--jstests/core/regex3.js2
-rw-r--r--jstests/core/regex4.js2
-rw-r--r--jstests/core/regex6.js2
-rw-r--r--jstests/core/regex8.js7
-rw-r--r--jstests/core/regexc.js4
-rw-r--r--jstests/core/remove9.js2
-rw-r--r--jstests/core/wildcard_index_type.js6
-rw-r--r--src/mongo/db/query/sbe_stage_builder.cpp32
-rw-r--r--src/mongo/db/query/sbe_stage_builder_filter.cpp192
-rw-r--r--src/mongo/db/query/sbe_stage_builder_filter.h20
-rw-r--r--src/mongo/db/query/sbe_stage_builder_helpers.h28
-rw-r--r--src/mongo/db/query/sbe_stage_builder_index_scan.cpp63
-rw-r--r--src/mongo/db/query/sbe_stage_builder_index_scan.h3
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,
&currentStage.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);
/**