summaryrefslogtreecommitdiff
path: root/src/mongo/db/query/optimizer
diff options
context:
space:
mode:
authorauto-revert-processor <dev-prod-dag@mongodb.com>2023-04-26 02:43:09 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2023-04-26 03:55:20 +0000
commit5c1f588bfa4ed2edbeb3abbb26e952e08641da14 (patch)
tree06518ba42a4cb8aa5d3f30439405f949d86fd142 /src/mongo/db/query/optimizer
parenta199d5f7b81b303f0eb155469593889db5d8c4ef (diff)
downloadmongo-5c1f588bfa4ed2edbeb3abbb26e952e08641da14.tar.gz
Revert "SERVER-69026 [CQF] Rewrite Sargable disjunction to RIDUnion"
This reverts commit f55584c091541a27c0ccf3c1eceb5d58a4a9c896.
Diffstat (limited to 'src/mongo/db/query/optimizer')
-rw-r--r--src/mongo/db/query/optimizer/bool_expression.h23
-rw-r--r--src/mongo/db/query/optimizer/cascades/implementers.cpp7
-rw-r--r--src/mongo/db/query/optimizer/cascades/logical_props_derivation.cpp2
-rw-r--r--src/mongo/db/query/optimizer/cascades/logical_rewriter.cpp419
-rw-r--r--src/mongo/db/query/optimizer/index_bounds.cpp65
-rw-r--r--src/mongo/db/query/optimizer/index_bounds.h28
-rw-r--r--src/mongo/db/query/optimizer/index_union_optimizer_test.cpp106
-rw-r--r--src/mongo/db/query/optimizer/partial_schema_requirements.cpp91
-rw-r--r--src/mongo/db/query/optimizer/partial_schema_requirements.h22
-rw-r--r--src/mongo/db/query/optimizer/physical_rewriter_optimizer_test.cpp17
-rw-r--r--src/mongo/db/query/optimizer/reference_tracker.cpp2
-rw-r--r--src/mongo/db/query/optimizer/utils/abt_compare.cpp11
-rw-r--r--src/mongo/db/query/optimizer/utils/utils.cpp9
-rw-r--r--src/mongo/db/query/optimizer/utils/utils.h2
14 files changed, 198 insertions, 606 deletions
diff --git a/src/mongo/db/query/optimizer/bool_expression.h b/src/mongo/db/query/optimizer/bool_expression.h
index c83d014e744..fb5a851781c 100644
--- a/src/mongo/db/query/optimizer/bool_expression.h
+++ b/src/mongo/db/query/optimizer/bool_expression.h
@@ -154,7 +154,6 @@ struct BoolExpr {
using ChildVisitorConst = std::function<void(const Node& child, const size_t childIndex)>;
using AtomVisitor = std::function<void(T& expr)>;
using AtomVisitorConst = std::function<void(const T& expr)>;
- using AtomPredConst = std::function<bool(const T& expr)>;
static size_t visitConjuncts(const Node& node, const ChildVisitorConst& visitor) {
size_t index = 0;
@@ -225,18 +224,6 @@ struct BoolExpr {
algebra::transport<false>(node, impl);
}
- static bool any(const Node& node, const AtomPredConst& atomPred) {
- bool result = false;
- visitAnyShape(node, [&](const T& atom) { result = result || atomPred(atom); });
- return result;
- }
-
- static bool all(const Node& node, const AtomPredConst& atomPred) {
- bool result = true;
- visitAnyShape(node, [&](const T& atom) { result = result && atomPred(atom); });
- return result;
- }
-
static void visitCNF(Node& node, const AtomVisitor& visitor) {
visitConjuncts(node, [&](Node& child, const size_t) {
visitDisjuncts(child,
@@ -264,6 +251,7 @@ struct BoolExpr {
algebra::transport<false>(node, impl);
}
+
static bool isCNF(const Node& n) {
if (n.template is<Conjunction>()) {
bool disjunctions = true;
@@ -343,15 +331,6 @@ struct BoolExpr {
return *this;
}
- Builder& subtree(BoolExpr::Node expr) {
- tassert(6902603,
- "BoolExpr::Builder::subtree does not support negation",
- !isCurrentlyNegated());
- _result = std::move(expr);
- maybeAddToParent();
- return *this;
- }
-
Builder& push(const bool isConjunction) {
const bool negated = isCurrentlyNegated();
_stack.push_back({(negated == isConjunction) ? NodeType::Disj : NodeType::Conj,
diff --git a/src/mongo/db/query/optimizer/cascades/implementers.cpp b/src/mongo/db/query/optimizer/cascades/implementers.cpp
index a98ef1e1d39..834066e6040 100644
--- a/src/mongo/db/query/optimizer/cascades/implementers.cpp
+++ b/src/mongo/db/query/optimizer/cascades/implementers.cpp
@@ -416,7 +416,7 @@ public:
if (_hints._disableScan) {
return;
}
- if (_hints._forceIndexScanForPredicates && hasProperIntervals(reqMap.getRoot())) {
+ if (_hints._forceIndexScanForPredicates && hasProperIntervals(reqMap)) {
return;
}
break;
@@ -473,7 +473,7 @@ public:
requiresRootProjection = projectionsLeftToSatisfy.erase(scanProjectionName);
}
- for (const auto& [key, boundProjName] : getBoundProjections(reqMap)) {
+ for (const auto& [key, boundProjName] : getBoundProjections(reqMap.getRoot())) {
projectionsLeftToSatisfy.erase(boundProjName);
}
if (!projectionsLeftToSatisfy.getVector().empty()) {
@@ -973,7 +973,8 @@ public:
}
void operator()(const ABT& /*n*/, const RIDUnionNode& node) {
- // TODO SERVER-75587 should implement this.
+ // TODO SERVER-69026 should implement this.
+ tasserted(7016300, "RIDUnionNode not implemented yet.");
return;
}
diff --git a/src/mongo/db/query/optimizer/cascades/logical_props_derivation.cpp b/src/mongo/db/query/optimizer/cascades/logical_props_derivation.cpp
index 83215119727..7afbaa04bb5 100644
--- a/src/mongo/db/query/optimizer/cascades/logical_props_derivation.cpp
+++ b/src/mongo/db/query/optimizer/cascades/logical_props_derivation.cpp
@@ -249,7 +249,7 @@ public:
}
}
- indexingAvailability.setHasProperInterval(hasProperIntervals(node.getReqMap().getRoot()));
+ indexingAvailability.setHasProperInterval(hasProperIntervals(node.getReqMap()));
return maybeUpdateNodePropsMap(node, std::move(result));
}
diff --git a/src/mongo/db/query/optimizer/cascades/logical_rewriter.cpp b/src/mongo/db/query/optimizer/cascades/logical_rewriter.cpp
index e68098c1eb1..d9ede8e5f82 100644
--- a/src/mongo/db/query/optimizer/cascades/logical_rewriter.cpp
+++ b/src/mongo/db/query/optimizer/cascades/logical_rewriter.cpp
@@ -34,6 +34,7 @@
#include "mongo/db/query/optimizer/utils/path_utils.h"
#include "mongo/db/query/optimizer/utils/reftracker_utils.h"
+
namespace mongo::optimizer::cascades {
LogicalRewriter::RewriteSet LogicalRewriter::_explorationSet = {
@@ -1161,6 +1162,7 @@ struct ExploreConvert {
* Used to pre-compute properties of a PSR.
*/
struct RequirementProps {
+ bool _isFullyOpen;
bool _mayReturnNull;
};
@@ -1176,216 +1178,99 @@ struct SplitRequirementsResult {
};
/**
- * Helper transport for 'splitRequirementsFetch': adds a PSRExpr::Node to a builder. The caller can
- * specify whether to keep only predicates, only projections, or both. Implicitly it handles
- * perfOnly predicates: either dropping them (on the fetch side) or converting them to non-perfOnly
- * (on the index side).
+ * Used to split requirements into left and right side. If "isIndex" is false, this is a separation
+ * between "index" and "fetch" predicates, otherwise it is a separation between the two sides of
+ * index intersection. The separation handles cases where we may have intervals which include Null
+ * and return the value, in which case instead of moving the requirement on the left, we insert a
+ * copy on the right side which will fetch the value from the collection. We convert perf-only
+ * requirements to non-perf when inserting on the left under "isIndex", otherwise we drop them. The
+ * mask parameter represents a bitmask indicating which requirements go on the left (bit is 1) and
+ * which go on the right.
*/
-struct SplitRequirementsFetchTransport {
- enum class Keep {
- kBoth,
- kPredicateOnly,
- kProjectionOnly,
- };
- static void addReq(const bool left,
- const PSRExpr::Node& expr,
- const Keep keep,
- const boost::optional<FieldNameSet>& indexFieldPrefixMap,
- PSRExprBuilder& leftReqs,
- PSRExprBuilder& rightReqs,
- bool& hasFieldCoverage) {
- auto& builder = left ? leftReqs : rightReqs;
-
- SplitRequirementsFetchTransport impl{
- left,
- keep,
- indexFieldPrefixMap,
- builder,
- hasFieldCoverage,
- };
- algebra::transport<false>(expr, impl);
- }
-
- void prepare(const PSRExpr::Conjunction&) {
- builder.pushConj();
- }
- void transport(const PSRExpr::Conjunction&, const PSRExpr::NodeVector&) {
- builder.pop();
- }
- void prepare(const PSRExpr::Disjunction&) {
- builder.pushDisj();
- }
- void transport(const PSRExpr::Disjunction&, const PSRExpr::NodeVector&) {
- builder.pop();
- }
- void transport(const PSRExpr::Atom& node) {
- const bool keepPred = keep != Keep::kProjectionOnly;
- const bool keepProj = keep != Keep::kPredicateOnly;
-
- const auto& [key, req] = node.getExpr();
- const bool perfOnly = req.getIsPerfOnly();
- const auto outputBinding = keepProj ? req.getBoundProjectionName() : boost::none;
- // perfOnly predicates on the fetch side become trivially true.
- const auto intervals = ((perfOnly && !left) || !keepPred)
- ? IntervalReqExpr::makeSingularDNF()
- : req.getIntervals();
-
- if (outputBinding || !isIntervalReqFullyOpenDNF(intervals)) {
- builder.atom(key,
- PartialSchemaRequirement{
- std::move(outputBinding), std::move(intervals), false /*isPerfOnly*/});
-
- if (left && indexFieldPrefixMap) {
- if (auto pathPtr = key._path.cast<PathGet>();
- pathPtr != nullptr && indexFieldPrefixMap->count(pathPtr->name()) == 0) {
- // We have found a left requirement which cannot be covered with an
- // index.
- hasFieldCoverage = false;
- }
- }
- } else {
- // The whole predicate/projection is trivial and its indexability doesn't
- // matter.
- }
- }
-
- const bool left;
- const Keep keep;
- const boost::optional<FieldNameSet>& indexFieldPrefixMap;
-
- PSRExprBuilder& builder;
- bool& hasFieldCoverage;
-};
-
-/**
- * Takes a vector of PSRExpr, 'conjuncts', and splits them into an index side (on the left) and a
- * fetch side (on the right).
- *
- * The bitfield 'mask' says how to split: each corresponding bit is 1 for left or 0 for right.
- *
- * 'perfOnly' predicates are preserved and converted to non-perfOnly when they go on the index side.
- * On the fetch side they are dropped, by converting them to trivially-true.
- *
- * If yielding-tolerant plans are requested (by 'hints._disableYieldingTolerantPlans == false') then
- * any predicate that should go on the left, we actually put on both sides.
- *
- * Some special cases apply when we attempt to put a predicate on the index side:
- * - If yielding-tolerant plans are requested (by 'hints._disableYieldingTolerantPlans == false')
- * then we put the predicate on both sides.
- * - If correct null handling is requested (by 'hints._fastIndexNullHandling == false') and the
- * predicate may contain null, we satisfy its output projection (if any) on the fetch side
- * instead.
- */
-static SplitRequirementsResult splitRequirementsFetch(
+static SplitRequirementsResult splitRequirements(
const size_t mask,
+ const bool isIndex,
const QueryHints& hints,
const std::vector<RequirementProps>& reqProps,
- const boost::optional<FieldNameSet>& indexFieldPrefixMap,
- const PSRExpr::NodeVector& conjuncts) {
-
- bool hasFieldCoverage = true;
- PSRExprBuilder leftReqs;
- PSRExprBuilder rightReqs;
- leftReqs.pushConj();
- rightReqs.pushConj();
-
- // Adds a PSRExpr 'expr' to the left or right, as specified by 'left'.
- // When adding to the right, replaces any 'perfOnly' atoms with trivially-true.
- // When adding to the left, keeps 'perfOnly' atoms and marks them non-perfOnly.
- //
- // 'keep' specifies whether to keep only the predicate, only the projection, or both.
- // It defaults to both.
- //
- // If we end up adding an unindexed path (one we know does not appear in any index),
- // set 'hasFieldCoverage' to false as a signal to bail out.
- using Keep = SplitRequirementsFetchTransport::Keep;
- const auto addReq =
- [&](const bool left, const PSRExpr::Node& expr, const Keep keep = Keep::kBoth) {
- SplitRequirementsFetchTransport::addReq(
- left, expr, keep, indexFieldPrefixMap, leftReqs, rightReqs, hasFieldCoverage);
- };
+ const boost::optional<FieldNameSet>& indexFieldPrefixMapForScanDef,
+ const PartialSchemaRequirements& reqMap) {
+ SplitRequirementsResult result;
+
+ result._leftReqsBuilder.pushDisj().pushConj();
+ result._rightReqsBuilder.pushDisj().pushConj();
+
+ const auto addRequirement = [&](const bool left,
+ PartialSchemaKey key,
+ boost::optional<ProjectionName> boundProjectionName,
+ IntervalReqExpr::Node intervals) {
+ // We always strip out the perf-only flag.
+ auto& builder = left ? result._leftReqsBuilder : result._rightReqsBuilder;
+ builder.atom(std::move(key),
+ PartialSchemaRequirement{std::move(boundProjectionName),
+ std::move(intervals),
+ false /*isPerfOnly*/});
+ };
size_t index = 0;
-
- for (const auto& conjunct : conjuncts) {
+ for (const auto& [key, req] : reqMap.conjuncts()) {
+ const auto& intervals = req.getIntervals();
+ const auto& outputBinding = req.getBoundProjectionName();
+ const bool perfOnly = req.getIsPerfOnly();
const auto& reqProp = reqProps.at(index);
- const bool left = ((1ull << index) & mask);
- if (!left) {
+ if (((1ull << index) & mask) == 0) {
// Predicate should go on the right side.
- addReq(false /*left*/, conjunct);
+ if (isIndex || !perfOnly) {
+ addRequirement(false /*left*/, key, outputBinding, intervals);
+ }
index++;
continue;
}
- // Predicate should go on the left side. However:
- // - Correct null handling requires moving the projection to the fetch side.
- // - Yield-safe plans require duplicating the predicate to both sides.
- // - Except that 'perfOnly' predicates become true on the fetch side.
-
- if (hints._fastIndexNullHandling || !reqProp._mayReturnNull) {
+ // Predicate should go on the left side.
+ bool addedToLeft = false;
+ if (isIndex || hints._fastIndexNullHandling || !reqProp._mayReturnNull) {
// We can never return Null values from the requirement.
- if (hints._disableYieldingTolerantPlans) {
+ if (isIndex || hints._disableYieldingTolerantPlans || perfOnly) {
// Insert into left side unchanged.
- addReq(true /*left*/, conjunct);
-
+ addRequirement(true /*left*/, key, outputBinding, intervals);
} else {
// Insert a requirement on the right side too, left side is non-binding.
- addReq(true /*left*/, conjunct, Keep::kPredicateOnly);
- addReq(false /*left*/, conjunct);
+ addRequirement(true /*left*/, key, boost::none /*boundProjectionName*/, intervals);
+ addRequirement(false /*left*/, key, outputBinding, intervals);
}
+ addedToLeft = true;
} else {
// At this point we should not be seeing perf-only predicates.
+ invariant(!perfOnly);
- // We cannot return index values, since the interval can possibly contain Null. Instead,
+ // We cannot return index values if our interval can possibly contain Null. Instead,
// we remove the output binding for the left side, and return the value from the
// right (seek) side.
- addReq(true /*left*/, conjunct, Keep::kPredicateOnly);
- addReq(false /*left*/,
- conjunct,
- // Yield-safe plans keep both the predicate and projection on the fetch side.
- // Yield-unsafe plans only need the projection.
- hints._disableYieldingTolerantPlans ? Keep::kProjectionOnly : Keep::kBoth);
- }
-
- if (!hasFieldCoverage) {
- break;
+ if (!reqProp._isFullyOpen) {
+ addRequirement(true /*left*/, key, boost::none /*boundProjectionName*/, intervals);
+ addedToLeft = true;
+ }
+ addRequirement(false /*left*/,
+ key,
+ outputBinding,
+ hints._disableYieldingTolerantPlans ? IntervalReqExpr::makeSingularDNF()
+ : intervals);
}
- index++;
- }
-
- return {
- std::move(leftReqs),
- std::move(rightReqs),
- hasFieldCoverage,
- };
-}
-
-static SplitRequirementsResult splitRequirementsIndex(const size_t mask,
- const PSRExpr::NodeVector& reqs,
- const bool disjunctive) {
- PSRExprBuilder leftReqs;
- PSRExprBuilder rightReqs;
- if (disjunctive) {
- leftReqs.pushDisj();
- rightReqs.pushDisj();
- } else {
- leftReqs.pushConj();
- rightReqs.pushConj();
- }
- size_t index = 0;
- for (const auto& req : reqs) {
- if ((1ull << index) & mask) {
- leftReqs.subtree(req);
- } else {
- rightReqs.subtree(req);
+ if (addedToLeft && indexFieldPrefixMapForScanDef) {
+ if (auto pathPtr = key._path.cast<PathGet>();
+ pathPtr != nullptr && indexFieldPrefixMapForScanDef->count(pathPtr->name()) == 0) {
+ // We have found a left requirement which cannot be covered with an
+ // index.
+ result._hasFieldCoverage = false;
+ break;
+ }
}
-
index++;
}
- return {std::move(leftReqs), std::move(rightReqs)};
+ return result;
}
template <>
@@ -1409,6 +1294,11 @@ struct ExploreConvert<SargableNode> {
return;
}
+ // SERVER-69026: Support "splitting" a top-level disjunction for index ORing.
+ if (!PSRExpr::isSingletonDisjunction(sargableNode.getReqMap().getRoot())) {
+ return;
+ }
+
const std::string& scanDefName = indexingAvailability.getScanDefName();
const ScanDefinition& scanDef = ctx.getMetadata()._scanDefs.at(scanDefName);
if (scanDef.getIndexDefs().empty()) {
@@ -1432,42 +1322,6 @@ struct ExploreConvert<SargableNode> {
const bool isIndex = target == IndexReqTarget::Index;
- // Decide whether to do a conjunctive or disjunctive split.
- // Rearrange the predicates so that the top-level node is the one we want to split:
- // - DNF if we want a disjunctive split.
- // - CNF if we want a conjunctive split.
- boost::optional<PSRExpr::Node> splittable;
- {
- const auto& reqMap = sargableNode.getReqMap().getRoot();
- if (isIndex) {
- // When targeting an index, do a disjunctive split if possible.
- if (PSRExpr::isSingletonDisjunction(reqMap)) {
- // Trivial disjunction means we can only do a conjunctive split.
- splittable = PSRExpr::convertToCNF(reqMap, SargableNode::kMaxPartialSchemaReqs);
- tassert(6902602,
- "converting DNF with only trivial disjunction to CNF should never fail",
- splittable);
- } else {
- splittable = reqMap;
- }
- } else {
- // When targeting 'Complete', the only split we allow is index/fetch,
- // because we want to do all union/intersection of record IDs within the index side,
- // to avoid redundant fetching.
-
- // Index/fetch is a conjunctive split.
- splittable = PSRExpr::convertToCNF(reqMap);
- }
- }
- if (!splittable) {
- // Conversion between DNF/CNF can fail if the result would be too big.
- return;
- }
- const bool disjunctive = splittable->is<PSRExpr::Disjunction>();
- const PSRExpr::NodeVector& reqs = disjunctive
- ? splittable->cast<PSRExpr::Disjunction>()->nodes()
- : splittable->cast<PSRExpr::Conjunction>()->nodes();
-
const auto& indexFieldPrefixMap = ctx.getIndexFieldPrefixMap();
boost::optional<FieldNameSet> indexFieldPrefixMapForScanDef;
if (auto it = indexFieldPrefixMap.find(scanDefName);
@@ -1475,27 +1329,21 @@ struct ExploreConvert<SargableNode> {
indexFieldPrefixMapForScanDef = it->second;
}
+ const auto& reqMap = sargableNode.getReqMap();
const auto& hints = ctx.getHints();
// Pre-computed properties of the requirements.
- // We only need these for the index/fetch split.
std::vector<RequirementProps> reqProps;
- if (!isIndex) {
- reqProps.reserve(reqs.size());
- for (const auto& conjunct : reqs) {
- // Pre-compute if a requirement's interval is fully open.
-
- // Pre-compute if a requirement's interval may contain nulls, and also has an output
- // binding. Do use constant folding if we do not have to.
- const bool mayReturnNull = !hints._fastIndexNullHandling && !isIndex &&
- PSRExpr::any(conjunct, [&](const PartialSchemaEntry& entry) {
- return entry.second.mayReturnNull(ctx.getConstFold());
- });
-
- reqProps.push_back({
- mayReturnNull,
- });
- }
+ for (const auto& [key, req] : reqMap.conjuncts()) {
+ // Pre-compute if a requirement's interval is fully open.
+ const bool fullyOpen = isIntervalReqFullyOpenDNF(req.getIntervals());
+
+ // Pre-compute if a requirement's interval may contain nulls, and also has an output
+ // binding. Do use constant folding if we do not have to.
+ const bool mayReturnNull =
+ !hints._fastIndexNullHandling && !isIndex && req.mayReturnNull(ctx.getConstFold());
+
+ reqProps.push_back({fullyOpen, mayReturnNull});
}
// We iterate over the possible ways to split N predicates into 2^N subsets, one goes to the
@@ -1503,16 +1351,12 @@ struct ExploreConvert<SargableNode> {
// try having at least one predicate on the left (mask = 1), and we try all possible
// subsets. For index intersection however (isIndex = true), we try symmetric partitioning
// (thus the high bound is 2^(N-1)).
- const size_t highMask = isIndex ? (1ull << (reqs.size() - 1)) : (1ull << reqs.size());
+ const size_t reqSize = PSRExpr::numLeaves(reqMap.getRoot()); // assumes singular DNF
+ const size_t highMask = isIndex ? (1ull << (reqSize - 1)) : (1ull << reqSize);
for (size_t mask = 1; mask < highMask; mask++) {
- auto splitResult = isIndex
- ? splitRequirementsIndex(mask, reqs, disjunctive)
- : splitRequirementsFetch(
- mask, hints, reqProps, indexFieldPrefixMapForScanDef, reqs);
- if (!splitResult._hasFieldCoverage) {
- // Reject rewrite. No suitable indexes.
- continue;
- }
+ SplitRequirementsResult splitResult = splitRequirements(
+ mask, isIndex, hints, reqProps, indexFieldPrefixMapForScanDef, reqMap);
+
auto leftReqs = splitResult._leftReqsBuilder.finish();
auto rightReqs = splitResult._rightReqsBuilder.finish();
@@ -1521,37 +1365,6 @@ struct ExploreConvert<SargableNode> {
invariant(!hints._fastIndexNullHandling && !isIndex);
continue;
}
- // Convert everything back to DNF.
- if (!PSRExpr::isDNF(*leftReqs)) {
- leftReqs = PSRExpr::convertToDNF(std::move(*leftReqs));
- if (!leftReqs) {
- continue;
- }
- }
- if (rightReqs && !PSRExpr::isDNF(*rightReqs)) {
- rightReqs = PSRExpr::convertToDNF(std::move(*rightReqs));
- if (!rightReqs) {
- continue;
- }
- }
- // DNF / CNF conversions can create redundant predicates; try to simplify.
- // If the reqs are too big, even after simplification, creating a SargableNode will
- // fail, so bail out.
- const auto isTooBig = [&](const PSRExpr::Node& reqs) -> bool {
- return PSRExpr::numLeaves(reqs) > SargableNode::kMaxPartialSchemaReqs;
- };
- if (leftReqs) {
- PartialSchemaRequirements::simplifyRedundantDNF(*leftReqs);
- if (isTooBig(*leftReqs)) {
- continue;
- }
- }
- if (rightReqs) {
- PartialSchemaRequirements::simplifyRedundantDNF(*rightReqs);
- if (isTooBig(*rightReqs)) {
- continue;
- }
- }
const bool hasLeftintervals = hasProperIntervals(*leftReqs);
const bool hasRightIntervals = rightReqs && hasProperIntervals(*rightReqs);
@@ -1565,40 +1378,39 @@ struct ExploreConvert<SargableNode> {
continue;
}
- auto leftCandidateIndexes =
- computeCandidateIndexes(ctx.getPrefixId(),
- scanProjectionName,
- PartialSchemaRequirements{*leftReqs},
- scanDef,
- hints,
- ctx.getConstFold());
- if (isIndex && leftCandidateIndexes.empty() &&
- PSRExpr::isSingletonDisjunction(*leftReqs)) {
- // Reject rewrite, because further splitting can only be conjunctive,
- // which does not increase the set of candidate indexes.
+ if (!splitResult._hasFieldCoverage) {
+ // Reject rewrite. No suitable indexes.
+ continue;
+ }
+
+ auto leftCandidateIndexes = computeCandidateIndexes(ctx.getPrefixId(),
+ scanProjectionName,
+ *leftReqs,
+ scanDef,
+ hints,
+ ctx.getConstFold());
+ if (isIndex && leftCandidateIndexes.empty()) {
+ // Reject rewrite.
continue;
}
CandidateIndexes rightCandidateIndexes;
if (rightReqs) {
- rightCandidateIndexes =
- computeCandidateIndexes(ctx.getPrefixId(),
- scanProjectionName,
- PartialSchemaRequirements{*rightReqs},
- scanDef,
- hints,
- ctx.getConstFold());
+ rightCandidateIndexes = computeCandidateIndexes(ctx.getPrefixId(),
+ scanProjectionName,
+ *rightReqs,
+ scanDef,
+ hints,
+ ctx.getConstFold());
}
- if (isIndex && rightCandidateIndexes.empty() &&
- PSRExpr::isSingletonDisjunction(*rightReqs)) {
- // Reject rewrite, because further splitting can only be conjunctive,
- // which does not increase the set of candidate indexes.
+ if (isIndex && rightCandidateIndexes.empty()) {
+ // With empty candidate map, reject only if we cannot implement as Seek.
continue;
}
ABT scanDelegator = make<MemoLogicalDelegatorNode>(scanGroupId);
- ABT leftChild = make<SargableNode>(PartialSchemaRequirements{std::move(*leftReqs)},
+ ABT leftChild = make<SargableNode>(std::move(*leftReqs),
std::move(leftCandidateIndexes),
boost::none,
IndexReqTarget::Index,
@@ -1606,23 +1418,20 @@ struct ExploreConvert<SargableNode> {
boost::optional<ScanParams> rightScanParams;
if (rightReqs) {
- rightScanParams = computeScanParams(
- ctx.getPrefixId(), PartialSchemaRequirements{*rightReqs}, scanProjectionName);
+ rightScanParams =
+ computeScanParams(ctx.getPrefixId(), *rightReqs, scanProjectionName);
}
ABT rightChild = rightReqs
- ? make<SargableNode>(PartialSchemaRequirements{std::move(*rightReqs)},
+ ? make<SargableNode>(std::move(*rightReqs),
std::move(rightCandidateIndexes),
std::move(rightScanParams),
isIndex ? IndexReqTarget::Index : IndexReqTarget::Seek,
scanDelegator)
: scanDelegator;
- ABT newRoot = disjunctive
- ? make<RIDUnionNode>(
- scanProjectionName, std::move(leftChild), std::move(rightChild))
- : make<RIDIntersectNode>(
- scanProjectionName, std::move(leftChild), std::move(rightChild));
+ ABT newRoot = make<RIDIntersectNode>(
+ scanProjectionName, std::move(leftChild), std::move(rightChild));
const auto& result = ctx.addNode(newRoot, false /*substitute*/);
for (const MemoLogicalNodeId nodeId : result.second) {
diff --git a/src/mongo/db/query/optimizer/index_bounds.cpp b/src/mongo/db/query/optimizer/index_bounds.cpp
index 295ca6cc6a4..72de5613a18 100644
--- a/src/mongo/db/query/optimizer/index_bounds.cpp
+++ b/src/mongo/db/query/optimizer/index_bounds.cpp
@@ -180,80 +180,27 @@ bool PartialSchemaRequirement::mayReturnNull(const ConstFoldFn& constFold) const
return _boundProjectionName && checkMaybeHasNull(getIntervals(), constFold);
};
-bool IndexPathLessComparator::operator()(const ABT& path1, const ABT& path2) const {
+bool IndexPath3WComparator::operator()(const ABT& path1, const ABT& path2) const {
return compareExprAndPaths(path1, path2) < 0;
}
-int PartialSchemaKeyComparator::Cmp3W::operator()(const PartialSchemaKey& k1,
- const PartialSchemaKey& k2) const {
+bool PartialSchemaKeyLessComparator::operator()(const PartialSchemaKey& k1,
+ const PartialSchemaKey& k2) const {
if (const auto& p1 = k1._projectionName) {
if (const auto& p2 = k2._projectionName) {
const int projCmp = p1->compare(*p2);
if (projCmp != 0) {
- return projCmp;
+ return projCmp < 0;
}
// Fallthrough to comparison below.
} else {
- // p1 > p2 because nonempty > empty.
- return 1;
+ return false;
}
} else if (k2._projectionName) {
- // p1 < p2 because empty < nonempty
- return -1;
- }
- // p1 == p2 so compare paths.
- return compareExprAndPaths(k1._path, k2._path);
-}
-bool PartialSchemaKeyComparator::Less::operator()(const PartialSchemaKey& k1,
- const PartialSchemaKey& k2) const {
- return PartialSchemaKeyComparator::Cmp3W{}(k1, k2) < 0;
-}
-
-int PartialSchemaRequirementComparator::Cmp3W::operator()(
- const PartialSchemaRequirement& req1, const PartialSchemaRequirement& req2) const {
-
- int intervalCmp = compareIntervalExpr(req1.getIntervals(), req2.getIntervals());
- if (intervalCmp != 0) {
- return intervalCmp;
- }
-
- // Intervals are equal: compare the output bindings.
- auto b1 = req1.getBoundProjectionName();
- auto b2 = req2.getBoundProjectionName();
- if (b1 && b2) {
- return b1->compare(*b2);
- } else if (b1) {
- // b1 > b2 because nonempty > empty.
- return 1;
- } else if (b2) {
- // b1 < b2 because empty < nonempty.
- return -1;
- } else {
- // empty == empty.
- return 0;
- }
-}
-
-bool PartialSchemaRequirementComparator::Less::operator()(
- const PartialSchemaRequirement& req1, const PartialSchemaRequirement& req2) const {
-
- int intervalCmp = compareIntervalExpr(req1.getIntervals(), req2.getIntervals());
- if (intervalCmp < 0) {
- return true;
- } else if (intervalCmp > 0) {
return false;
}
- // Intervals are equal: compare the output bindings.
- auto b1 = req1.getBoundProjectionName();
- auto b2 = req2.getBoundProjectionName();
- if (b1 && b2) {
- return b1->compare(*b2) < 0;
- } else {
- // If either or both optionals are empty, then the only way for
- // 'b1 < b2' is '{} < {anything}'.
- return !b1 && b2;
- }
+ return compareExprAndPaths(k1._path, k2._path) < 0;
}
ResidualRequirement::ResidualRequirement(PartialSchemaKey key,
diff --git a/src/mongo/db/query/optimizer/index_bounds.h b/src/mongo/db/query/optimizer/index_bounds.h
index 84b24f5e327..b23ae574a48 100644
--- a/src/mongo/db/query/optimizer/index_bounds.h
+++ b/src/mongo/db/query/optimizer/index_bounds.h
@@ -237,41 +237,23 @@ private:
/**
* This comparator is used to compare paths with Get, Traverse, and Id.
*/
-struct IndexPathLessComparator {
+struct IndexPath3WComparator {
bool operator()(const ABT& path1, const ABT& path2) const;
};
-using IndexPathSet = std::set<ABT, IndexPathLessComparator>;
+using IndexPathSet = std::set<ABT, IndexPath3WComparator>;
-struct PartialSchemaKeyComparator {
- struct Less {
- bool operator()(const PartialSchemaKey& k1, const PartialSchemaKey& k2) const;
- };
-
- struct Cmp3W {
- int operator()(const PartialSchemaKey& k1, const PartialSchemaKey& k2) const;
- };
-};
-struct PartialSchemaRequirementComparator {
- struct Less {
- bool operator()(const PartialSchemaRequirement& req1,
- const PartialSchemaRequirement& req2) const;
- };
-
- struct Cmp3W {
- int operator()(const PartialSchemaRequirement& req1,
- const PartialSchemaRequirement& req2) const;
- };
+struct PartialSchemaKeyLessComparator {
+ bool operator()(const PartialSchemaKey& k1, const PartialSchemaKey& k2) const;
};
-
/**
* Used to track cardinality estimates per predicate inside a PartialSchemaRequirement. The order of
* estimates is the same as the leaf order in the primary PartialSchemaRequirements.
*/
using PartialSchemaKeyCE = std::vector<std::pair<PartialSchemaKey, CEType>>;
-using PartialSchemaKeySet = std::set<PartialSchemaKey, PartialSchemaKeyComparator::Less>;
+using PartialSchemaKeySet = std::set<PartialSchemaKey, PartialSchemaKeyLessComparator>;
// Requirements which are not satisfied directly by an IndexScan, PhysicalScan or Seek (e.g. using
// an index field, or scan field). The index refers to the underlying entry in the
diff --git a/src/mongo/db/query/optimizer/index_union_optimizer_test.cpp b/src/mongo/db/query/optimizer/index_union_optimizer_test.cpp
index ebba5611135..06592638760 100644
--- a/src/mongo/db/query/optimizer/index_union_optimizer_test.cpp
+++ b/src/mongo/db/query/optimizer/index_union_optimizer_test.cpp
@@ -335,6 +335,7 @@ TEST(LogicalRewriter, DisjunctionConversionDedup) {
// We should see everything get reordered and deduped,
// so each of the leaf predicates appears once.
+ // TODO SERVER-73827 We should get 2 leaf predicates instead of 3 here.
ASSERT_EXPLAIN_V2_AUTO(
"Root [{scan_0}]\n"
"Sargable [Complete]\n"
@@ -342,6 +343,8 @@ TEST(LogicalRewriter, DisjunctionConversionDedup) {
"| | {\n"
"| | {{scan_0, 'PathGet [a] PathIdentity []', {{{=Const [0]}}}}}\n"
"| | U \n"
+ "| | {{scan_0, 'PathGet [a] PathIdentity []', {{{=Const [0]}}}}}\n"
+ "| | U \n"
"| | {{scan_0, 'PathGet [b] PathIdentity []', {{{=Const [1]}}}}}\n"
"| | }\n"
"| scanParams: \n"
@@ -350,7 +353,9 @@ TEST(LogicalRewriter, DisjunctionConversionDedup) {
"| {\n"
"| {{evalTemp_0, 'PathIdentity []', {{{=Const [0]}}}, entryIndex: 0}}\n"
"| U \n"
- "| {{evalTemp_1, 'PathIdentity []', {{{=Const [1]}}}, entryIndex: 1}}\n"
+ "| {{evalTemp_0, 'PathIdentity []', {{{=Const [0]}}}, entryIndex: 1}}\n"
+ "| U \n"
+ "| {{evalTemp_1, 'PathIdentity []', {{{=Const [1]}}}, entryIndex: 2}}\n"
"| }\n"
"Scan [coll, {scan_0}]\n",
optimized);
@@ -473,11 +478,11 @@ TEST(PhysRewriter, OptimizeSargableNodeWithTopLevelDisjunction) {
ABT scanNode = make<ScanNode>("ptest", "test");
ABT sargableNode1 = make<SargableNode>(
- reqs1, CandidateIndexes(), scanParams, IndexReqTarget::Complete, std::move(scanNode));
+ reqs1, CandidateIndexes(), scanParams, IndexReqTarget::Index, std::move(scanNode));
ABT sargableNode2 = make<SargableNode>(
- reqs2, CandidateIndexes(), boost::none, IndexReqTarget::Complete, std::move(sargableNode1));
+ reqs2, CandidateIndexes(), boost::none, IndexReqTarget::Index, std::move(sargableNode1));
ABT sargableNode3 = make<SargableNode>(
- reqs3, CandidateIndexes(), boost::none, IndexReqTarget::Complete, std::move(sargableNode2));
+ reqs3, CandidateIndexes(), boost::none, IndexReqTarget::Index, std::move(sargableNode2));
ABT rootNode = make<RootNode>(properties::ProjectionRequirement{ProjectionNameVector{"ptest"}},
std::move(sargableNode3));
@@ -485,24 +490,25 @@ TEST(PhysRewriter, OptimizeSargableNodeWithTopLevelDisjunction) {
// SargableNodes are correctly lowered to FilterNodes.
auto prefixId = PrefixId::createForTests();
auto phaseManager = makePhaseManager(
- {
- OptPhase::MemoSubstitutionPhase, OptPhase::MemoExplorationPhase,
- // OptPhase::MemoImplementationPhase,
- },
+ {OptPhase::MemoSubstitutionPhase,
+ OptPhase::MemoExplorationPhase,
+ OptPhase::MemoImplementationPhase},
prefixId,
{{{"test",
createScanDef(
{},
{
+ // For now, verify that we do not get an indexed plan even when there
+ // are indexes available on the queried fields.
{"ab",
- IndexDefinition{{{makeNonMultikeyIndexPath("a"), CollationOp::Ascending},
- {makeNonMultikeyIndexPath("b"), CollationOp::Ascending}},
+ IndexDefinition{{{makeIndexPath("a"), CollationOp::Ascending},
+ {makeIndexPath("b"), CollationOp::Ascending}},
false /*isMultiKey*/,
{DistributionType::Centralized},
{}}},
{"cd",
- IndexDefinition{{{makeNonMultikeyIndexPath("c"), CollationOp::Ascending},
- {makeNonMultikeyIndexPath("d"), CollationOp::Ascending}},
+ IndexDefinition{{{makeIndexPath("c"), CollationOp::Ascending},
+ {makeIndexPath("d"), CollationOp::Ascending}},
false /*isMultiKey*/,
{DistributionType::Centralized},
{}}},
@@ -511,17 +517,9 @@ TEST(PhysRewriter, OptimizeSargableNodeWithTopLevelDisjunction) {
{"g", makeIndexDefinition("g", CollationOp::Ascending, false /*isMultiKey*/)},
})}}},
boost::none /*costModel*/,
- DebugInfo::kDefaultForTests,
- QueryHints{
- ._disableScan = true,
- });
+ DebugInfo::kDefaultForTests);
phaseManager.optimize(rootNode);
- // We should get an index union between 'ab' and 'cd'.
- // TODO SERVER-75587 In the logical plan, we get an alternative where 'ab' and 'cd' are each
- // satisfied by an intersection. Instead we should get a single scan of the compound indexes
- // 'ab' and 'cd'. Hopefully that will work out once we enable the physical phase, which makes it
- // possible to cost the alternatives.
ASSERT_EXPLAIN_V2Compact_AUTO(
"Root [{ptest}]\n"
"Filter []\n"
@@ -536,55 +534,23 @@ TEST(PhysRewriter, OptimizeSargableNodeWithTopLevelDisjunction) {
"| EvalFilter []\n"
"| | Variable [ptest]\n"
"| PathGet [e] PathCompare [Eq] Const [1]\n"
- "RIDIntersect [ptest]\n"
- "| Scan [test, {ptest}]\n"
- "RIDUnion [ptest]\n"
- "| RIDIntersect [ptest]\n"
- "| | Sargable [Index]\n"
- "| | | | | requirements: \n"
- "| | | | | {{{ptest, 'PathGet [d] PathIdentity []', {{{=Const [1]}}}}}}\n"
- "| | | | candidateIndexes: \n"
- "| | | | candidateId: 1, cd, {'<indexKey> 1': evalTemp_49}, {Unbound, "
- "Unbound}, {{{<fully open>}}}\n"
- "| | | | residualReqs: \n"
- "| | | | {{{evalTemp_49, 'PathIdentity []', {{{=Const [1]}}}, "
- "entryIndex: 0}}}\n"
- "| | | scanParams: \n"
- "| | | {'d': evalTemp_50}\n"
- "| | | residualReqs: \n"
- "| | | {{{evalTemp_50, 'PathIdentity []', {{{=Const [1]}}}, entryIndex: "
- "0}}}\n"
- "| | Scan [test, {ptest}]\n"
- "| Sargable [Index]\n"
- "| | | | requirements: \n"
- "| | | | {{{ptest, 'PathGet [c] PathIdentity []', {{{=Const [1]}}}}}}\n"
- "| | | candidateIndexes: \n"
- "| | | candidateId: 1, cd, {}, {SimpleEquality, Unbound}, {{{[Const [1 | "
- "minKey], Const [1 | maxKey]]}}}\n"
- "| | scanParams: \n"
- "| | {'c': evalTemp_44}\n"
- "| | residualReqs: \n"
- "| | {{{evalTemp_44, 'PathIdentity []', {{{=Const [1]}}}, entryIndex: "
- "0}}}\n"
- "| Scan [test, {ptest}]\n"
- "RIDIntersect [ptest]\n"
- "| Sargable [Index]\n"
- "| | | requirements: \n"
- "| | | {{{ptest, 'PathGet [b] PathIdentity []', {{{=Const [1]}}}}}}\n"
- "| | candidateIndexes: \n"
- "| | candidateId: 1, ab, {'<indexKey> 1': evalTemp_45}, {Unbound, Unbound}, "
- "{{{<fully open>}}}\n"
- "| | residualReqs: \n"
- "| | {{{evalTemp_45, 'PathIdentity []', {{{=Const [1]}}}, entryIndex: "
- "0}}}\n"
- "| Scan [test, {ptest}]\n"
- "Sargable [Index]\n"
- "| | requirements: \n"
- "| | {{{ptest, 'PathGet [a] PathIdentity []', {{{=Const [1]}}}}}}\n"
- "| candidateIndexes: \n"
- "| candidateId: 1, ab, {}, {SimpleEquality, Unbound}, {{{[Const [1 | minKey], Const "
- "[1 | maxKey]]}}}\n"
- "Scan [test, {ptest}]\n",
+ "Filter []\n"
+ "| BinaryOp [Or]\n"
+ "| | BinaryOp [And]\n"
+ "| | | EvalFilter []\n"
+ "| | | | Variable [ptest]\n"
+ "| | | PathGet [d] PathCompare [Eq] Const [1]\n"
+ "| | EvalFilter []\n"
+ "| | | Variable [ptest]\n"
+ "| | PathGet [c] PathCompare [Eq] Const [1]\n"
+ "| BinaryOp [And]\n"
+ "| | EvalFilter []\n"
+ "| | | Variable [ptest]\n"
+ "| | PathGet [b] PathCompare [Eq] Const [1]\n"
+ "| EvalFilter []\n"
+ "| | Variable [ptest]\n"
+ "| PathGet [a] PathCompare [Eq] Const [1]\n"
+ "PhysicalScan [{'<root>': ptest}, test]\n",
rootNode);
}
diff --git a/src/mongo/db/query/optimizer/partial_schema_requirements.cpp b/src/mongo/db/query/optimizer/partial_schema_requirements.cpp
index 60d8145bdb0..40b54d4d61d 100644
--- a/src/mongo/db/query/optimizer/partial_schema_requirements.cpp
+++ b/src/mongo/db/query/optimizer/partial_schema_requirements.cpp
@@ -42,11 +42,11 @@ public:
}
void transport(PSRExpr::Conjunction& node, std::vector<PSRExpr::Node>& children) {
- sortAndDedupChildren(children);
+ sortChildren(children);
}
void transport(PSRExpr::Disjunction& node, std::vector<PSRExpr::Node>& children) {
- sortAndDedupChildren(children);
+ sortChildren(children);
}
void normalize(PSRExpr::Node& node) {
@@ -54,16 +54,13 @@ public:
}
private:
- void sortAndDedupChildren(std::vector<PSRExpr::Node>& children) {
+ void sortChildren(std::vector<PSRExpr::Node>& children) {
struct Comparator {
bool operator()(const PSRExpr::Node& i1, const PSRExpr::Node& i2) const {
return comparePartialSchemaRequirementsExpr(i1, i2) < 0;
}
};
std::sort(children.begin(), children.end(), Comparator{});
-
- auto end = std::unique(children.begin(), children.end());
- children.erase(end, children.end());
}
};
@@ -77,11 +74,8 @@ PartialSchemaEntry makeNoopPartialSchemaEntry() {
}
} // namespace
-void PartialSchemaRequirements::normalize(PSRExpr::Node& expr) {
- PSRNormalizeTransporter{}.normalize(expr);
-}
void PartialSchemaRequirements::normalize() {
- normalize(_expr);
+ PSRNormalizeTransporter{}.normalize(_expr);
}
PartialSchemaRequirements::PartialSchemaRequirements(PSRExpr::Node requirements)
@@ -158,6 +152,7 @@ PartialSchemaRequirements::findFirstConjunct(const PartialSchemaKey& key) const
void PartialSchemaRequirements::add(PartialSchemaKey key, PartialSchemaRequirement req) {
tassert(7016406, "Expected a PartialSchemaRequirements in DNF form", PSRExpr::isDNF(_expr));
+ // TODO SERVER-69026 Consider applying the distributive law.
tassert(7453912, "Expected a singleton disjunction", PSRExpr::isSingletonDisjunction(_expr));
// Add an entry to the first conjunction
@@ -233,82 +228,12 @@ static bool simplifyExpr(
bool PartialSchemaRequirements::simplify(
std::function<bool(const PartialSchemaKey&, PartialSchemaRequirement&)> func) {
- return simplify(_expr, func);
-}
-bool PartialSchemaRequirements::simplify(
- PSRExpr::Node& expr,
- std::function<bool(const PartialSchemaKey&, PartialSchemaRequirement&)> func) {
- if (PSRExpr::isCNF(expr)) {
- return simplifyExpr<true /*isCNF*/>(expr, func);
- }
- return simplifyExpr<false /*isCNF*/>(expr, func);
-}
-
-void PartialSchemaRequirements::simplifyRedundantDNF(PSRExpr::Node& expr) {
- tassert(6902601, "simplifyRedundantDNF expects DNF", PSRExpr::isDNF(expr));
-
- // Normalizing ensures:
- // - Each term has no duplicate atoms.
- // - The overall expression has no duplicate terms.
- // - The terms are sorted in increasing length.
- PSRNormalizeTransporter{}.normalize(expr);
-
- // Now remove terms that are subsumed by some other term. This means try to remove terms whose
- // atoms are a superset of some other term: (a^b) subsumes (a^b^c), so remove (a^b^c). Since
- // there are no duplicate atoms, we're looking to remove terms whose 'nodes().size()' is large.
- PSRExpr::NodeVector& terms = expr.cast<PSRExpr::Disjunction>()->nodes();
-
- // First give each unique atom a label.
- // Store each atom by value because 'remove_if' can move-from a 'PSRExpr::Node', which deletes
- // the heap-allocated 'Atom'.
- std::vector<PSRExpr::Atom> atoms;
- const auto atomLabel = [&](const PSRExpr::Atom& atom) -> size_t {
- size_t i = 0;
- for (const auto& seen : atoms) {
- if (atom == seen) {
- return i;
- }
- ++i;
- }
- atoms.emplace_back(atom);
- return i;
- };
- using Mask = size_t;
- static constexpr size_t maxAtoms = sizeof(Mask) * CHAR_BIT;
- for (const PSRExpr::Node& termNode : terms) {
- for (const PSRExpr::Node& atomNode : termNode.cast<PSRExpr::Conjunction>()->nodes()) {
- const PSRExpr::Atom& atom = *atomNode.cast<PSRExpr::Atom>();
- atomLabel(atom);
- if (atoms.size() > maxAtoms) {
- return;
- }
- }
+ if (PSRExpr::isCNF(_expr)) {
+ return simplifyExpr<true /*isCNF*/>(_expr, func);
}
-
- std::vector<Mask> seen;
- seen.reserve(terms.size());
- auto last = std::remove_if(terms.begin(), terms.end(), [&](const PSRExpr::Node& term) -> bool {
- Mask mask = 0;
- for (const PSRExpr::Node& atomNode : term.cast<PSRExpr::Conjunction>()->nodes()) {
- const PSRExpr::Atom& atom = *atomNode.cast<PSRExpr::Atom>();
- mask |= Mask{1} << atomLabel(atom);
- }
-
- // Does any previously-seen mask subsume this one?
- for (Mask prev : seen) {
- const bool isSuperset = (prev & mask) == prev;
- if (isSuperset) {
- return true;
- }
- }
-
- seen.push_back(mask);
- return false;
- });
- terms.erase(last, terms.end());
+ return simplifyExpr<false /*isCNF*/>(_expr, func);
}
-
/**
* Returns a vector of ((input binding, path), output binding). The output binding names
* are unique and you can think of the vector as a product: every row has all the projections
diff --git a/src/mongo/db/query/optimizer/partial_schema_requirements.h b/src/mongo/db/query/optimizer/partial_schema_requirements.h
index 55f03d149c3..47feba141e8 100644
--- a/src/mongo/db/query/optimizer/partial_schema_requirements.h
+++ b/src/mongo/db/query/optimizer/partial_schema_requirements.h
@@ -52,7 +52,7 @@ class PartialSchemaRequirements {
public:
using Entry = std::pair<PartialSchemaKey, PartialSchemaRequirement>;
- // TODO SERVER-73828: Remove these iterator constructs.
+ // TODO SERVER-69026: Remove these iterator constructs.
using ConstNodeVecIter = std::vector<PSRExpr::Node>::const_iterator;
using NodeVecIter = std::vector<PSRExpr::Node>::iterator;
@@ -121,7 +121,7 @@ public:
// fully-open PartialSchemaRequirement which does not bind.
PartialSchemaRequirements();
- explicit PartialSchemaRequirements(PSRExpr::Node requirements);
+ PartialSchemaRequirements(PSRExpr::Node requirements);
bool operator==(const PartialSchemaRequirements& other) const;
@@ -145,7 +145,7 @@ public:
boost::optional<std::pair<size_t, PartialSchemaRequirement>> findFirstConjunct(
const PartialSchemaKey&) const;
- // TODO SERVER-73828: Remove these methods in favor of visitDis/Conjuncts().
+ // TODO SERVER-69026: Remove these methods in favor of visitDis/Conjuncts().
Range<true> conjuncts() const {
tassert(7453905,
"Expected PartialSchemaRequirement to be a singleton disjunction",
@@ -176,6 +176,9 @@ public:
*
* For now, we assert that we have only one disjunct. This means we avoid applying
* the distributive law, which would duplicate the new requirement into each disjunct.
+ *
+ * TODO SERVER-69026 Consider applying the distributive law to allow contained-OR in
+ * SargableNode.
*/
void add(PartialSchemaKey, PartialSchemaRequirement);
@@ -193,19 +196,6 @@ public:
* TODO SERVER-73827: Consider applying this simplification during BoolExpr building.
*/
bool simplify(std::function<bool(const PartialSchemaKey&, PartialSchemaRequirement&)>);
- static bool simplify(PSRExpr::Node& expr,
- std::function<bool(const PartialSchemaKey&, PartialSchemaRequirement&)>);
- static void normalize(PSRExpr::Node& expr);
-
- /**
- * Given a DNF, try to detect and remove redundant terms.
- *
- * For example, in ((a ^ b) U (z) U (a ^ b ^ c)) the (a ^ b) is redundant because
- * (a ^ b ^ c) implies (a ^ b).
- *
- * TODO SERVER-73827 Consider doing this simplification as part of BoolExpr::Builder.
- */
- static void simplifyRedundantDNF(PSRExpr::Node& expr);
const auto& getRoot() const {
return _expr;
diff --git a/src/mongo/db/query/optimizer/physical_rewriter_optimizer_test.cpp b/src/mongo/db/query/optimizer/physical_rewriter_optimizer_test.cpp
index d503c4b69cb..0c8ecd80db3 100644
--- a/src/mongo/db/query/optimizer/physical_rewriter_optimizer_test.cpp
+++ b/src/mongo/db/query/optimizer/physical_rewriter_optimizer_test.cpp
@@ -1806,14 +1806,14 @@ TEST(PhysRewriter, SargableProjectionRenames) {
// projections.
ASSERT_EXPLAIN_V2_AUTO(
"Root [{root}]\n"
- "Evaluation [{pa} = Variable [pa1]]\n"
+ "Evaluation [{pa1} = Variable [pa]]\n"
"Sargable [Complete]\n"
"| | requirements: \n"
- "| | {{{root, 'PathGet [a] PathIdentity []', pa1, {{{=Const [1]}}}}}}\n"
+ "| | {{{root, 'PathGet [a] PathIdentity []', pa, {{{=Const [1]}}}}}}\n"
"| scanParams: \n"
- "| {'a': pa1}\n"
+ "| {'a': pa}\n"
"| residualReqs: \n"
- "| {{{pa1, 'PathIdentity []', {{{=Const [1]}}}, entryIndex: 0}}}\n"
+ "| {{{pa, 'PathIdentity []', {{{=Const [1]}}}, entryIndex: 0}}}\n"
"Scan [c1, {root}]\n",
optimized);
}
@@ -1981,10 +1981,7 @@ TEST(PhysRewriter, CoveredScan) {
ABT optimized = std::move(rootNode);
phaseManager.optimize(optimized);
- ASSERT_BETWEEN_AUTO( //
- 3,
- 6,
- phaseManager.getMemo().getStats()._physPlanExplorationCount);
+ ASSERT_EQ(5, phaseManager.getMemo().getStats()._physPlanExplorationCount);
// Since we do not optimize with fast null handling, we need to split the predicate between the
// index scan and fetch in order to handle null.
@@ -1994,8 +1991,8 @@ TEST(PhysRewriter, CoveredScan) {
"| | Const [true]\n"
"| LimitSkip [limit: 1, skip: 0]\n"
"| Seek [ridProjection: rid_0, {'a': pa}, c1]\n"
- "IndexScan [{'<rid>': rid_0}, scanDefName: c1, indexDefName: index1, interval: {<Const "
- "[1]}]\n",
+ "IndexScan [{'<rid>': rid_0}, scanDefName: c1, indexDefName: index1, interval: {<Const [1"
+ "]}]\n",
optimized);
}
diff --git a/src/mongo/db/query/optimizer/reference_tracker.cpp b/src/mongo/db/query/optimizer/reference_tracker.cpp
index 6d8bdfc5582..79c2d7bda35 100644
--- a/src/mongo/db/query/optimizer/reference_tracker.cpp
+++ b/src/mongo/db/query/optimizer/reference_tracker.cpp
@@ -510,7 +510,7 @@ struct Collector {
const RIDUnionNode& node,
CollectedInfo leftChildResult,
CollectedInfo rightChildResult) {
- // TODO SERVER-75587 should determine how the reference tracker for RIDUnionNode will work.
+ // TODO SERVER-69026 should determine how the reference tracker for RIDUnionNode will work.
return handleRIDNodeReferences(
node, std::move(leftChildResult), std::move(rightChildResult));
}
diff --git a/src/mongo/db/query/optimizer/utils/abt_compare.cpp b/src/mongo/db/query/optimizer/utils/abt_compare.cpp
index 12e4fcf1fe4..c321add3efa 100644
--- a/src/mongo/db/query/optimizer/utils/abt_compare.cpp
+++ b/src/mongo/db/query/optimizer/utils/abt_compare.cpp
@@ -411,14 +411,9 @@ public:
private:
int compare(const PSRExpr::Atom& node, const PSRExpr::Atom& other) {
- const auto& [key1, req1] = node.getExpr();
- const auto& [key2, req2] = other.getExpr();
-
- int keyCmp = PartialSchemaKeyComparator::Cmp3W{}(key1, key2);
- if (keyCmp != 0) {
- return keyCmp;
- }
- return PartialSchemaRequirementComparator::Cmp3W{}(req1, req2);
+ auto isSorted =
+ PartialSchemaKeyLessComparator{}(node.getExpr().first, other.getExpr().first);
+ return isSorted ? -1 : 1;
}
int compare(const PSRExpr::Conjunction& node, const PSRExpr::Conjunction& other) {
diff --git a/src/mongo/db/query/optimizer/utils/utils.cpp b/src/mongo/db/query/optimizer/utils/utils.cpp
index 68b21a1b422..881cad5c5f4 100644
--- a/src/mongo/db/query/optimizer/utils/utils.cpp
+++ b/src/mongo/db/query/optimizer/utils/utils.cpp
@@ -377,7 +377,7 @@ public:
}
}
- leftReqMap = PartialSchemaRequirements{std::move(*resultReqs.finish())};
+ leftReqMap = std::move(*resultReqs.finish());
return leftResult;
}
// Left and right don't all use the same key.
@@ -979,6 +979,7 @@ bool isSubsetOfPartialSchemaReq(const PartialSchemaRequirements& lhs,
bool intersectPartialSchemaReq(PartialSchemaRequirements& target,
const PartialSchemaRequirements& source) {
+ // TODO SERVER-69026 Consider implementing intersect for top-level disjunctions.
if (!PSRExpr::isSingletonDisjunction(target.getRoot()) ||
!PSRExpr::isSingletonDisjunction(source.getRoot())) {
return false;
@@ -1006,7 +1007,7 @@ PartialSchemaRequirements unionPartialSchemaReq(PartialSchemaRequirements&& left
resultNodes.insert(resultNodes.end(),
std::make_move_iterator(rightDisj.nodes().begin()),
std::make_move_iterator(rightDisj.nodes().end()));
- return PartialSchemaRequirements{PSRExpr::make<PSRExpr::Disjunction>(std::move(resultNodes))};
+ return PSRExpr::make<PSRExpr::Disjunction>(std::move(resultNodes));
}
std::string encodeIndexKeyName(const size_t indexField) {
@@ -2658,10 +2659,10 @@ PhysPlanBuilder lowerEqPrefixes(PrefixId& prefixId,
return lowerTransport.lower(eqPrefixes.at(eqPrefixIndex)._interval);
}
-bool hasProperIntervals(const PSRExpr::Node& reqs) {
+bool hasProperIntervals(const PartialSchemaRequirements& reqMap) {
// Compute if this node has any proper (not fully open) intervals.
bool hasProperIntervals = false;
- PSRExpr::visitAnyShape(reqs, [&](const PartialSchemaEntry& e) {
+ PSRExpr::visitDNF(reqMap.getRoot(), [&](const PartialSchemaEntry& e) {
hasProperIntervals |= !isIntervalReqFullyOpenDNF(e.second.getIntervals());
});
return hasProperIntervals;
diff --git a/src/mongo/db/query/optimizer/utils/utils.h b/src/mongo/db/query/optimizer/utils/utils.h
index 6653a0f308a..8fb3602e31d 100644
--- a/src/mongo/db/query/optimizer/utils/utils.h
+++ b/src/mongo/db/query/optimizer/utils/utils.h
@@ -458,5 +458,5 @@ PhysPlanBuilder lowerEqPrefixes(PrefixId& prefixId,
CEType scanGroupCE,
bool useSortedMerge);
-bool hasProperIntervals(const PSRExpr::Node& reqs);
+bool hasProperIntervals(const PartialSchemaRequirements& reqMap);
} // namespace mongo::optimizer