summaryrefslogtreecommitdiff
path: root/src/mongo/db/query
diff options
context:
space:
mode:
authorKatherine Wu <katherine.wu@mongodb.com>2021-02-01 09:59:12 -0500
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2021-02-01 22:02:40 +0000
commit120901099ba270b557054bc8ef06ac87418c0834 (patch)
treefb2474b3c7621cd8b68325f71eac307d0e1b0518 /src/mongo/db/query
parent060d4ef8514110ff4d03867b8f704cf9de307905 (diff)
downloadmongo-120901099ba270b557054bc8ef06ac87418c0834.tar.gz
SERVER-39943 Create match expressions for aggregation $gt/$gte/$lt/$lte and add to $expr rewrite
Diffstat (limited to 'src/mongo/db/query')
-rw-r--r--src/mongo/db/query/canonical_query_encoder.cpp14
-rw-r--r--src/mongo/db/query/index_bounds_builder.cpp176
-rw-r--r--src/mongo/db/query/index_bounds_builder_test.cpp384
-rw-r--r--src/mongo/db/query/index_bounds_builder_test.h19
-rw-r--r--src/mongo/db/query/indexability.h6
-rw-r--r--src/mongo/db/query/plan_cache_indexability.cpp2
-rw-r--r--src/mongo/db/query/planner_ixselect.cpp7
-rw-r--r--src/mongo/db/query/sbe_stage_builder_filter.cpp33
8 files changed, 598 insertions, 43 deletions
diff --git a/src/mongo/db/query/canonical_query_encoder.cpp b/src/mongo/db/query/canonical_query_encoder.cpp
index ce2f70ec0fb..fb73aa3e5c2 100644
--- a/src/mongo/db/query/canonical_query_encoder.cpp
+++ b/src/mongo/db/query/canonical_query_encoder.cpp
@@ -190,7 +190,19 @@ const char* encodeMatchType(MatchExpression::MatchType mt) {
return "xp";
case MatchExpression::INTERNAL_EXPR_EQ:
- return "ee";
+ return "eeq";
+
+ case MatchExpression::INTERNAL_EXPR_GT:
+ return "egt";
+
+ case MatchExpression::INTERNAL_EXPR_GTE:
+ return "ege";
+
+ case MatchExpression::INTERNAL_EXPR_LT:
+ return "elt";
+
+ case MatchExpression::INTERNAL_EXPR_LTE:
+ return "ele";
case MatchExpression::INTERNAL_SCHEMA_ALL_ELEM_MATCH_FROM_INDEX:
return "internalSchemaAllElemMatchFromIndex";
diff --git a/src/mongo/db/query/index_bounds_builder.cpp b/src/mongo/db/query/index_bounds_builder.cpp
index e423f60844c..aa05e986aed 100644
--- a/src/mongo/db/query/index_bounds_builder.cpp
+++ b/src/mongo/db/query/index_bounds_builder.cpp
@@ -41,7 +41,7 @@
#include "mongo/db/index/expression_params.h"
#include "mongo/db/index/s2_common.h"
#include "mongo/db/matcher/expression_geo.h"
-#include "mongo/db/matcher/expression_internal_expr_eq.h"
+#include "mongo/db/matcher/expression_internal_expr_comparison.h"
#include "mongo/db/query/collation/collation_index_key.h"
#include "mongo/db/query/collation/collator_interface.h"
#include "mongo/db/query/expression_index.h"
@@ -626,6 +626,75 @@ void IndexBoundsBuilder::_translatePredicate(const MatchExpression* expr,
// There is no need to sort intervals or merge overlapping intervals here since the output
// is from one element.
translateEquality(node->getData(), index, isHashed, oilOut, tightnessOut);
+ } else if (MatchExpression::LT == expr->matchType()) {
+ const LTMatchExpression* node = static_cast<const LTMatchExpression*>(expr);
+ BSONElement dataElt = node->getData();
+
+ // Everything is < MaxKey, except for MaxKey. However the bounds need to be inclusive to
+ // find the array [MaxKey] which is smaller for a comparison but equal in a multikey index.
+ if (MaxKey == dataElt.type()) {
+ oilOut->intervals.push_back(allValuesRespectingInclusion(
+ IndexBounds::makeBoundInclusionFromBoundBools(true, index.multikey)));
+ *tightnessOut = index.collator || index.multikey ? IndexBoundsBuilder::INEXACT_FETCH
+ : IndexBoundsBuilder::EXACT;
+ return;
+ }
+
+ // Nothing is < NaN.
+ if (std::isnan(dataElt.numberDouble())) {
+ *tightnessOut = IndexBoundsBuilder::EXACT;
+ return;
+ }
+
+ BSONObjBuilder bob;
+ buildBoundsForQueryElementForLT(dataElt, index.collator, &bob);
+ BSONObj dataObj = bob.done().getOwned();
+ verify(dataObj.isOwned());
+ bool inclusiveBounds = dataElt.type() == BSONType::Array;
+ Interval interval =
+ makeRangeInterval(dataObj,
+ IndexBounds::makeBoundInclusionFromBoundBools(
+ typeMatch(dataObj) || inclusiveBounds, inclusiveBounds));
+
+ // If the operand to LT is equal to the lower bound X, the interval [X, X) is invalid
+ // and should not be added to the bounds.
+ if (!interval.isNull()) {
+ oilOut->intervals.push_back(interval);
+ }
+
+ *tightnessOut = getInequalityPredicateTightness(interval, dataElt, index);
+ } else if (MatchExpression::INTERNAL_EXPR_LT == expr->matchType()) {
+ const auto* node = static_cast<const InternalExprLTMatchExpression*>(expr);
+ BSONElement dataElt = node->getData();
+
+ // Unlike the regular $lt match expression, $_internalExprLt does not make special checks
+ // for when dataElt is MaxKey or NaN because it doesn't do type bracketing for any operand.
+ // Another difference is that $internalExprLt predicates on multikey paths will not use an
+ // index.
+ tassert(3994304,
+ "$expr comparison predicates on multikey paths cannot use an index",
+ !index.pathHasMultikeyComponent(elt.fieldNameStringData()));
+
+ BSONObjBuilder bob;
+ bob.appendMinKey("");
+ CollationIndexKey::collationAwareIndexKeyAppend(dataElt, index.collator, &bob);
+ BSONObj dataObj = bob.obj();
+
+ // Generally all intervals for $_internalExprLt will exclude the end key, however because
+ // null and missing are conflated in the index but treated as distinct values for
+ // expressions (with missing ordered as less than null), when dataElt is null we must build
+ // index bounds [MinKey, null] to include missing values and filter out the literal null
+ // values with an INEXACT_FETCH.
+ Interval interval = makeRangeInterval(
+ dataObj, IndexBounds::makeBoundInclusionFromBoundBools(true, dataElt.isNull()));
+
+ // If the operand to $_internalExprLt is equal to the lower bound X, the interval [X, X) is
+ // invalid and should not be added to the bounds. Because $_internalExprLt doesn't perform
+ // type bracketing, here we need to avoid adding the interval [MinKey, MinKey).
+ if (!interval.isNull()) {
+ oilOut->intervals.push_back(interval);
+ }
+ *tightnessOut = getInequalityPredicateTightness(interval, dataElt, index);
} else if (MatchExpression::LTE == expr->matchType()) {
const LTEMatchExpression* node = static_cast<const LTEMatchExpression*>(expr);
BSONElement dataElt = node->getData();
@@ -664,40 +733,32 @@ void IndexBoundsBuilder::_translatePredicate(const MatchExpression* expr,
oilOut->intervals.push_back(interval);
*tightnessOut = getInequalityPredicateTightness(interval, dataElt, index);
- } else if (MatchExpression::LT == expr->matchType()) {
- const LTMatchExpression* node = static_cast<const LTMatchExpression*>(expr);
+ } else if (MatchExpression::INTERNAL_EXPR_LTE == expr->matchType()) {
+ const auto* node = static_cast<const InternalExprLTEMatchExpression*>(expr);
BSONElement dataElt = node->getData();
- // Everything is < MaxKey, except for MaxKey. However the bounds need to be inclusive to
- // find the array [MaxKey] which is smaller for a comparison but equal in a multikey index.
- if (MaxKey == dataElt.type()) {
- oilOut->intervals.push_back(allValuesRespectingInclusion(
- IndexBounds::makeBoundInclusionFromBoundBools(true, index.multikey)));
- *tightnessOut = index.collator || index.multikey ? IndexBoundsBuilder::INEXACT_FETCH
- : IndexBoundsBuilder::EXACT;
- return;
- }
-
- // Nothing is < NaN.
- if (std::isnan(dataElt.numberDouble())) {
- *tightnessOut = IndexBoundsBuilder::EXACT;
- return;
- }
+ // Unlike the regular $lte match expression, $_internalExprLte does not make special checks
+ // for when dataElt is MaxKey or NaN because it doesn't do type bracketing for any operand.
+ // Another difference is that $internalExprLte predicates on multikey paths will not use an
+ // index.
+ tassert(3994305,
+ "$expr comparison predicates on multikey paths cannot use an index",
+ !index.pathHasMultikeyComponent(elt.fieldNameStringData()));
BSONObjBuilder bob;
- buildBoundsForQueryElementForLT(dataElt, index.collator, &bob);
- BSONObj dataObj = bob.done().getOwned();
- verify(dataObj.isOwned());
- bool inclusiveBounds = dataElt.type() == BSONType::Array;
- Interval interval =
- makeRangeInterval(dataObj,
- IndexBounds::makeBoundInclusionFromBoundBools(
- typeMatch(dataObj) || inclusiveBounds, inclusiveBounds));
+ bob.appendMinKey("");
+ CollationIndexKey::collationAwareIndexKeyAppend(dataElt, index.collator, &bob);
+ BSONObj dataObj = bob.obj();
- // If the operand to LT is equal to the lower bound X, the interval [X, X) is invalid
- // and should not be added to the bounds.
- if (!interval.isNull()) {
- oilOut->intervals.push_back(interval);
+ Interval interval = makeRangeInterval(dataObj, BoundInclusion::kIncludeBothStartAndEndKeys);
+ oilOut->intervals.push_back(interval);
+
+ // Expressions treat null and missing as distinct values, with missing ordered as less than
+ // null. Thus for $_internalExprLte when dataElt is null we can treat the bounds as EXACT,
+ // since both null and missing values should be included.
+ if (dataElt.isNull()) {
+ *tightnessOut = IndexBoundsBuilder::EXACT;
+ return;
}
*tightnessOut = getInequalityPredicateTightness(interval, dataElt, index);
@@ -737,6 +798,41 @@ void IndexBoundsBuilder::_translatePredicate(const MatchExpression* expr,
oilOut->intervals.push_back(interval);
}
*tightnessOut = getInequalityPredicateTightness(interval, dataElt, index);
+ } else if (MatchExpression::INTERNAL_EXPR_GT == expr->matchType()) {
+ const auto* node = static_cast<const InternalExprGTMatchExpression*>(expr);
+ BSONElement dataElt = node->getData();
+
+ // Unlike the regular $gt match expression, $_internalExprGt does not make special checks
+ // for when dataElt is MinKey or NaN because it doesn't do type bracketing for any operand.
+ // Another difference is that $internalExprGt predicates on multikey paths will not use an
+ // index.
+ tassert(3994302,
+ "$expr comparison predicates on multikey paths cannot use an index",
+ !index.pathHasMultikeyComponent(elt.fieldNameStringData()));
+
+ BSONObjBuilder bob;
+ CollationIndexKey::collationAwareIndexKeyAppend(dataElt, index.collator, &bob);
+ bob.appendMaxKey("");
+ BSONObj dataObj = bob.obj();
+
+ Interval interval = makeRangeInterval(dataObj, BoundInclusion::kIncludeEndKeyOnly);
+
+ // If the operand to $_internalExprGt is equal to the upper bound X, the interval (X, X] is
+ // invalid and should not be added to the bounds. Because $_internalExprGt doesn't perform
+ // type bracketing, here we need to avoid adding the interval (MaxKey, MaxKey].
+ if (!interval.isNull()) {
+ oilOut->intervals.push_back(interval);
+ }
+
+ // Expressions treat null and missing as distinct values, with missing ordered as less than
+ // null. Thus for $_internalExprGt when dataElt is null we can treat the bounds as EXACT,
+ // since both null and missing values should be excluded.
+ if (dataElt.isNull()) {
+ *tightnessOut = IndexBoundsBuilder::EXACT;
+ return;
+ }
+
+ *tightnessOut = getInequalityPredicateTightness(interval, dataElt, index);
} else if (MatchExpression::GTE == expr->matchType()) {
const GTEMatchExpression* node = static_cast<const GTEMatchExpression*>(expr);
BSONElement dataElt = node->getData();
@@ -773,6 +869,26 @@ void IndexBoundsBuilder::_translatePredicate(const MatchExpression* expr,
oilOut->intervals.push_back(interval);
*tightnessOut = getInequalityPredicateTightness(interval, dataElt, index);
+ } else if (MatchExpression::INTERNAL_EXPR_GTE == expr->matchType()) {
+ const auto* node = static_cast<const InternalExprGTEMatchExpression*>(expr);
+ BSONElement dataElt = node->getData();
+
+ // Unlike the regular $gte match expression, $_internalExprGte does not make special checks
+ // for when dataElt is MinKey or NaN because it doesn't do type bracketing for any operand.
+ // Another difference is that $internalExprGte predicates on multikey paths will not use an
+ // index.
+ tassert(3994303,
+ "$expr comparison predicates on multikey paths cannot use an index",
+ !index.pathHasMultikeyComponent(elt.fieldNameStringData()));
+
+ BSONObjBuilder bob;
+ CollationIndexKey::collationAwareIndexKeyAppend(dataElt, index.collator, &bob);
+ bob.appendMaxKey("");
+ BSONObj dataObj = bob.obj();
+
+ Interval interval = makeRangeInterval(dataObj, BoundInclusion::kIncludeBothStartAndEndKeys);
+ oilOut->intervals.push_back(interval);
+ *tightnessOut = getInequalityPredicateTightness(interval, dataElt, index);
} else if (MatchExpression::REGEX == expr->matchType()) {
const RegexMatchExpression* rme = static_cast<const RegexMatchExpression*>(expr);
translateRegex(rme, index, oilOut, tightnessOut);
diff --git a/src/mongo/db/query/index_bounds_builder_test.cpp b/src/mongo/db/query/index_bounds_builder_test.cpp
index 4c141b52ee2..8413c471214 100644
--- a/src/mongo/db/query/index_bounds_builder_test.cpp
+++ b/src/mongo/db/query/index_bounds_builder_test.cpp
@@ -38,6 +38,7 @@
#include "mongo/db/matcher/expression_parser.h"
#include "mongo/db/query/collation/collator_interface_mock.h"
#include "mongo/db/query/expression_index.h"
+#include "mongo/unittest/death_test.h"
#include "mongo/unittest/unittest.h"
namespace {
@@ -1358,4 +1359,387 @@ TEST_F(IndexBoundsBuilderTest, IntersectizeBasic) {
ASSERT_TRUE(oil2 == expectedIntersection);
}
+TEST_F(IndexBoundsBuilderTest, TranslateInternalExprGT) {
+ BSONObj keyPattern = BSON("a" << 1);
+ BSONElement elt = keyPattern.firstElement();
+ auto testIndex = buildSimpleIndexEntry(keyPattern);
+ BSONObj obj = BSON("a" << BSON("$_internalExprGt" << 4));
+ auto expr = parseMatchExpression(obj);
+ OrderedIntervalList oil;
+ IndexBoundsBuilder::BoundsTightness tightness;
+ IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness);
+ ASSERT_EQUALS(oil.name, "a");
+ ASSERT_EQUALS(oil.intervals.size(), 1U);
+ ASSERT_EQUALS(Interval::INTERVAL_EQUALS,
+ oil.intervals[0].compare(Interval(maxKeyIntObj(4), false, true)));
+ ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT);
+}
+
+TEST_F(IndexBoundsBuilderTest, TranslateInternalExprGTNull) {
+ BSONObj keyPattern = BSON("a" << 1);
+ BSONElement elt = keyPattern.firstElement();
+ auto testIndex = buildSimpleIndexEntry(keyPattern);
+ BSONObj obj = BSON("a" << BSON("$_internalExprGt" << BSONNULL));
+ auto expr = parseMatchExpression(obj);
+ OrderedIntervalList oil;
+ IndexBoundsBuilder::BoundsTightness tightness;
+ IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness);
+ ASSERT_EQUALS(oil.name, "a");
+ ASSERT_EQUALS(oil.intervals.size(), 1U);
+ ASSERT_EQUALS(oil.intervals[0].toString(), "(null, MaxKey]");
+ ASSERT_FALSE(oil.intervals[0].startInclusive);
+ ASSERT_TRUE(oil.intervals[0].endInclusive);
+ ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT);
+}
+
+TEST_F(IndexBoundsBuilderTest, TranslateInternalExprGTMaxKeyDoesNotGenerateBounds) {
+ BSONObj keyPattern = BSON("a" << 1);
+ BSONElement elt = keyPattern.firstElement();
+ auto testIndex = buildSimpleIndexEntry(keyPattern);
+ BSONObj obj = BSON("a" << BSON("$_internalExprGt" << MAXKEY));
+ auto expr = parseMatchExpression(obj);
+ OrderedIntervalList oil;
+ IndexBoundsBuilder::BoundsTightness tightness;
+ IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness);
+ ASSERT_EQUALS(oil.name, "a");
+ ASSERT_EQUALS(oil.intervals.size(), 0U);
+ ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT);
+}
+
+DEATH_TEST_F(IndexBoundsBuilderTest,
+ TranslateInternalExprGTMultikeyPathFails,
+ "$expr comparison predicates on multikey paths cannot use an index") {
+ BSONObj keyPattern = BSON("a" << 1 << "b" << 1);
+ BSONElement elt = keyPattern.firstElement();
+ auto testIndex = buildMultikeyIndexEntry(keyPattern, {{0U}, {}});
+ BSONObj obj = BSON("a" << BSON("$_internalExprGt" << 4));
+ auto expr = parseMatchExpression(obj);
+ OrderedIntervalList oil;
+ IndexBoundsBuilder::BoundsTightness tightness;
+ IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness);
+}
+
+TEST_F(IndexBoundsBuilderTest, TranslateInternalExprGTNonMultikeyPathOnMultikeyIndexSucceeds) {
+ BSONObj keyPattern = BSON("a" << 1 << "b" << 1);
+ BSONElement elt = keyPattern.firstElement();
+ auto testIndex = buildMultikeyIndexEntry(keyPattern, {{}, {0U}});
+ BSONObj obj = BSON("a" << BSON("$_internalExprGt" << 4));
+ auto expr = parseMatchExpression(obj);
+ OrderedIntervalList oil;
+ IndexBoundsBuilder::BoundsTightness tightness;
+ IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness);
+ ASSERT_EQUALS(oil.name, "a");
+ ASSERT_EQUALS(oil.intervals.size(), 1U);
+ ASSERT_EQUALS(Interval::INTERVAL_EQUALS,
+ oil.intervals[0].compare(Interval(maxKeyIntObj(4), false, true)));
+ ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT);
+}
+
+TEST_F(IndexBoundsBuilderTest, TranslateInternalExprGTSubObjectContainingBadValuesSucceeds) {
+ BSONObj keyPattern = BSON("_id" << 1);
+ BSONElement elt = keyPattern.firstElement();
+ auto testIndex = buildSimpleIndexEntry(keyPattern);
+ BSONObj subObj = BSON("subObj" << BSON("a" << BSONUndefined << "b" << BSON_ARRAY("array")));
+ BSONObj obj = BSON("_id" << BSON("$_internalExprGt" << subObj));
+ auto expr = parseMatchExpression(obj);
+ OrderedIntervalList oil;
+ IndexBoundsBuilder::BoundsTightness tightness;
+ IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness);
+ ASSERT_EQUALS(oil.name, "_id");
+ ASSERT_EQUALS(oil.intervals.size(), 1U);
+ ASSERT_EQUALS(
+ Interval::INTERVAL_EQUALS,
+ oil.intervals[0].compare(Interval(BSON("" << subObj << "" << MAXKEY), false, true)));
+ ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT);
+}
+
+TEST_F(IndexBoundsBuilderTest, TranslateInternalExprGTE) {
+ BSONObj keyPattern = BSON("a" << 1);
+ BSONElement elt = keyPattern.firstElement();
+ auto testIndex = buildSimpleIndexEntry(keyPattern);
+ BSONObj obj = BSON("a" << BSON("$_internalExprGte" << 4));
+ auto expr = parseMatchExpression(obj);
+ OrderedIntervalList oil;
+ IndexBoundsBuilder::BoundsTightness tightness;
+ IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness);
+ ASSERT_EQUALS(oil.name, "a");
+ ASSERT_EQUALS(oil.intervals.size(), 1U);
+ ASSERT_EQUALS(Interval::INTERVAL_EQUALS,
+ oil.intervals[0].compare(Interval(maxKeyIntObj(4), true, true)));
+ ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT);
+}
+
+TEST_F(IndexBoundsBuilderTest, TranslateInternalExprGTENull) {
+ BSONObj keyPattern = BSON("a" << 1);
+ BSONElement elt = keyPattern.firstElement();
+ auto testIndex = buildSimpleIndexEntry(keyPattern);
+ BSONObj obj = BSON("a" << BSON("$_internalExprGte" << BSONNULL));
+ auto expr = parseMatchExpression(obj);
+ OrderedIntervalList oil;
+ IndexBoundsBuilder::BoundsTightness tightness;
+ IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness);
+ ASSERT_EQUALS(oil.name, "a");
+ ASSERT_EQUALS(oil.intervals.size(), 1U);
+ ASSERT_EQUALS(oil.intervals[0].toString(), "[null, MaxKey]");
+ ASSERT_TRUE(oil.intervals[0].startInclusive);
+ ASSERT_TRUE(oil.intervals[0].endInclusive);
+ ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH);
+}
+
+TEST_F(IndexBoundsBuilderTest, TranslateInternalExprGTEMaxKeyGeneratesBounds) {
+ BSONObj keyPattern = BSON("a" << 1);
+ BSONElement elt = keyPattern.firstElement();
+ auto testIndex = buildSimpleIndexEntry(keyPattern);
+ BSONObj obj = BSON("a" << BSON("$_internalExprGte" << MAXKEY));
+ auto expr = parseMatchExpression(obj);
+ OrderedIntervalList oil;
+ IndexBoundsBuilder::BoundsTightness tightness;
+ IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness);
+ ASSERT_EQUALS(oil.name, "a");
+ ASSERT_EQUALS(oil.intervals.size(), 1U);
+ ASSERT_EQUALS(oil.intervals[0].toString(), "[MaxKey, MaxKey]");
+ ASSERT_TRUE(oil.intervals[0].startInclusive);
+ ASSERT_TRUE(oil.intervals[0].endInclusive);
+ ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT);
+}
+
+DEATH_TEST_F(IndexBoundsBuilderTest,
+ TranslateInternalExprGTEMultikeyPathFails,
+ "$expr comparison predicates on multikey paths cannot use an index") {
+ BSONObj keyPattern = BSON("a" << 1 << "b" << 1);
+ BSONElement elt = keyPattern.firstElement();
+ auto testIndex = buildMultikeyIndexEntry(keyPattern, {{0U}, {}});
+ BSONObj obj = BSON("a" << BSON("$_internalExprGte" << 4));
+ auto expr = parseMatchExpression(obj);
+ OrderedIntervalList oil;
+ IndexBoundsBuilder::BoundsTightness tightness;
+ IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness);
+}
+
+TEST_F(IndexBoundsBuilderTest, TranslateInternalExprGTENonMultikeyPathOnMultikeyIndexSucceeds) {
+ BSONObj keyPattern = BSON("a" << 1 << "b" << 1);
+ BSONElement elt = keyPattern.firstElement();
+ auto testIndex = buildMultikeyIndexEntry(keyPattern, {{}, {0U}});
+ BSONObj obj = BSON("a" << BSON("$_internalExprGte" << 4));
+ auto expr = parseMatchExpression(obj);
+ OrderedIntervalList oil;
+ IndexBoundsBuilder::BoundsTightness tightness;
+ IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness);
+ ASSERT_EQUALS(oil.name, "a");
+ ASSERT_EQUALS(oil.intervals.size(), 1U);
+ ASSERT_EQUALS(Interval::INTERVAL_EQUALS,
+ oil.intervals[0].compare(Interval(maxKeyIntObj(4), true, true)));
+ ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT);
+}
+
+TEST_F(IndexBoundsBuilderTest, TranslateInternalExprGTESubObjectContainingBadValuesSucceeds) {
+ BSONObj keyPattern = BSON("_id" << 1);
+ BSONElement elt = keyPattern.firstElement();
+ auto testIndex = buildSimpleIndexEntry(keyPattern);
+ BSONObj subObj = BSON("subObj" << BSON("a" << BSONUndefined << "b" << BSON_ARRAY("array")));
+ BSONObj obj = BSON("_id" << BSON("$_internalExprGte" << subObj));
+ auto expr = parseMatchExpression(obj);
+ OrderedIntervalList oil;
+ IndexBoundsBuilder::BoundsTightness tightness;
+ IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness);
+ ASSERT_EQUALS(oil.name, "_id");
+ ASSERT_EQUALS(oil.intervals.size(), 1U);
+ ASSERT_EQUALS(
+ Interval::INTERVAL_EQUALS,
+ oil.intervals[0].compare(Interval(BSON("" << subObj << "" << MAXKEY), true, true)));
+ ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT);
+}
+
+TEST_F(IndexBoundsBuilderTest, TranslateInternalExprLT) {
+ BSONObj keyPattern = BSON("a" << 1);
+ BSONElement elt = keyPattern.firstElement();
+ auto testIndex = buildSimpleIndexEntry(keyPattern);
+ BSONObj obj = BSON("a" << BSON("$_internalExprLt" << 4));
+ auto expr = parseMatchExpression(obj);
+ OrderedIntervalList oil;
+ IndexBoundsBuilder::BoundsTightness tightness;
+ IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness);
+ ASSERT_EQUALS(oil.name, "a");
+ ASSERT_EQUALS(oil.intervals.size(), 1U);
+ ASSERT_EQUALS(Interval::INTERVAL_EQUALS,
+ oil.intervals[0].compare(Interval(minKeyIntObj(4), true, false)));
+ ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT);
+}
+
+TEST_F(IndexBoundsBuilderTest, TranslateInternalExprLTNull) {
+ BSONObj keyPattern = BSON("a" << 1);
+ BSONElement elt = keyPattern.firstElement();
+ auto testIndex = buildSimpleIndexEntry(keyPattern);
+ BSONObj obj = BSON("a" << BSON("$_internalExprLt" << BSONNULL));
+ auto expr = parseMatchExpression(obj);
+ OrderedIntervalList oil;
+ IndexBoundsBuilder::BoundsTightness tightness;
+ IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness);
+ ASSERT_EQUALS(oil.name, "a");
+ ASSERT_EQUALS(oil.intervals.size(), 1U);
+ ASSERT_EQUALS(oil.intervals[0].toString(), "[MinKey, null]");
+ ASSERT_TRUE(oil.intervals[0].startInclusive);
+ ASSERT_TRUE(oil.intervals[0].endInclusive);
+ ASSERT_EQUALS(tightness, IndexBoundsBuilder::INEXACT_FETCH);
+}
+
+TEST_F(IndexBoundsBuilderTest, TranslateInternalExprLTMinKeyDoesNotGenerateBounds) {
+ BSONObj keyPattern = BSON("a" << 1);
+ BSONElement elt = keyPattern.firstElement();
+ auto testIndex = buildSimpleIndexEntry(keyPattern);
+ BSONObj obj = BSON("a" << BSON("$_internalExprLt" << MINKEY));
+ auto expr = parseMatchExpression(obj);
+ OrderedIntervalList oil;
+ IndexBoundsBuilder::BoundsTightness tightness;
+ IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness);
+ ASSERT_EQUALS(oil.name, "a");
+ ASSERT_EQUALS(oil.intervals.size(), 0U);
+ ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT);
+}
+
+DEATH_TEST_F(IndexBoundsBuilderTest,
+ TranslateInternalExprLTMultikeyPathFails,
+ "$expr comparison predicates on multikey paths cannot use an index") {
+ BSONObj keyPattern = BSON("a" << 1 << "b" << 1);
+ BSONElement elt = keyPattern.firstElement();
+ auto testIndex = buildMultikeyIndexEntry(keyPattern, {{0U}, {}});
+ BSONObj obj = BSON("a" << BSON("$_internalExprLt" << 4));
+ auto expr = parseMatchExpression(obj);
+ OrderedIntervalList oil;
+ IndexBoundsBuilder::BoundsTightness tightness;
+ IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness);
+}
+
+TEST_F(IndexBoundsBuilderTest, TranslateInternalExprLTNonMultikeyPathOnMultikeyIndexSucceeds) {
+ BSONObj keyPattern = BSON("a" << 1 << "b" << 1);
+ BSONElement elt = keyPattern.firstElement();
+ auto testIndex = buildMultikeyIndexEntry(keyPattern, {{}, {0U}});
+ BSONObj obj = BSON("a" << BSON("$_internalExprLt" << 4));
+ auto expr = parseMatchExpression(obj);
+ OrderedIntervalList oil;
+ IndexBoundsBuilder::BoundsTightness tightness;
+ IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness);
+ ASSERT_EQUALS(oil.name, "a");
+ ASSERT_EQUALS(oil.intervals.size(), 1U);
+ ASSERT_EQUALS(Interval::INTERVAL_EQUALS,
+ oil.intervals[0].compare(Interval(minKeyIntObj(4), true, false)));
+ ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT);
+}
+
+TEST_F(IndexBoundsBuilderTest, TranslateInternalExprLTSubObjectContainingBadValuesSucceeds) {
+ BSONObj keyPattern = BSON("_id" << 1);
+ BSONElement elt = keyPattern.firstElement();
+ auto testIndex = buildSimpleIndexEntry(keyPattern);
+ BSONObj subObj = BSON("subObj" << BSON("a" << BSONUndefined << "b" << BSON_ARRAY("array")));
+ BSONObj obj = BSON("_id" << BSON("$_internalExprLt" << subObj));
+ auto expr = parseMatchExpression(obj);
+ OrderedIntervalList oil;
+ IndexBoundsBuilder::BoundsTightness tightness;
+ IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness);
+ ASSERT_EQUALS(oil.name, "_id");
+ ASSERT_EQUALS(oil.intervals.size(), 1U);
+ ASSERT_EQUALS(
+ Interval::INTERVAL_EQUALS,
+ oil.intervals[0].compare(Interval(BSON("" << MINKEY << "" << subObj), true, false)));
+ ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT);
+}
+
+TEST_F(IndexBoundsBuilderTest, TranslateInternalExprLTE) {
+ BSONObj keyPattern = BSON("a" << 1);
+ BSONElement elt = keyPattern.firstElement();
+ auto testIndex = buildSimpleIndexEntry(keyPattern);
+ BSONObj obj = BSON("a" << BSON("$_internalExprLte" << 4));
+ auto expr = parseMatchExpression(obj);
+ OrderedIntervalList oil;
+ IndexBoundsBuilder::BoundsTightness tightness;
+ IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness);
+ ASSERT_EQUALS(oil.name, "a");
+ ASSERT_EQUALS(oil.intervals.size(), 1U);
+ ASSERT_EQUALS(Interval::INTERVAL_EQUALS,
+ oil.intervals[0].compare(Interval(minKeyIntObj(4), true, true)));
+ ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT);
+}
+
+TEST_F(IndexBoundsBuilderTest, TranslateInternalExprLTENull) {
+ BSONObj keyPattern = BSON("a" << 1);
+ BSONElement elt = keyPattern.firstElement();
+ auto testIndex = buildSimpleIndexEntry(keyPattern);
+ BSONObj obj = BSON("a" << BSON("$_internalExprLte" << BSONNULL));
+ auto expr = parseMatchExpression(obj);
+ OrderedIntervalList oil;
+ IndexBoundsBuilder::BoundsTightness tightness;
+ IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness);
+ ASSERT_EQUALS(oil.name, "a");
+ ASSERT_EQUALS(oil.intervals.size(), 1U);
+ ASSERT_EQUALS(oil.intervals[0].toString(), "[MinKey, null]");
+ ASSERT_TRUE(oil.intervals[0].startInclusive);
+ ASSERT_TRUE(oil.intervals[0].endInclusive);
+ ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT);
+}
+
+TEST_F(IndexBoundsBuilderTest, TranslateInternalExprLTEMinKeyGeneratesBounds) {
+ BSONObj keyPattern = BSON("a" << 1);
+ BSONElement elt = keyPattern.firstElement();
+ auto testIndex = buildSimpleIndexEntry(keyPattern);
+ BSONObj obj = BSON("a" << BSON("$_internalExprLte" << MINKEY));
+ auto expr = parseMatchExpression(obj);
+ OrderedIntervalList oil;
+ IndexBoundsBuilder::BoundsTightness tightness;
+ IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness);
+ ASSERT_EQUALS(oil.name, "a");
+ ASSERT_EQUALS(oil.intervals.size(), 1U);
+ ASSERT_EQUALS(oil.intervals[0].toString(), "[MinKey, MinKey]");
+ ASSERT_TRUE(oil.intervals[0].startInclusive);
+ ASSERT_TRUE(oil.intervals[0].endInclusive);
+ ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT);
+}
+
+DEATH_TEST_F(IndexBoundsBuilderTest,
+ TranslateInternalExprLTEMultikeyPathFails,
+ "$expr comparison predicates on multikey paths cannot use an index") {
+ BSONObj keyPattern = BSON("a" << 1 << "b" << 1);
+ BSONElement elt = keyPattern.firstElement();
+ auto testIndex = buildMultikeyIndexEntry(keyPattern, {{0U}, {}});
+
+ BSONObj obj = BSON("a" << BSON("$_internalExprLte" << 4));
+ auto expr = parseMatchExpression(obj);
+ OrderedIntervalList oil;
+ IndexBoundsBuilder::BoundsTightness tightness;
+ IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness);
+}
+
+TEST_F(IndexBoundsBuilderTest, TranslateInternalExprLTENonMultikeyPathOnMultikeyIndexSucceeds) {
+ BSONObj keyPattern = BSON("a" << 1 << "b" << 1);
+ BSONElement elt = keyPattern.firstElement();
+ auto testIndex = buildMultikeyIndexEntry(keyPattern, {{}, {0U}});
+ BSONObj obj = BSON("a" << BSON("$_internalExprLte" << 4));
+ auto expr = parseMatchExpression(obj);
+ OrderedIntervalList oil;
+ IndexBoundsBuilder::BoundsTightness tightness;
+ IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness);
+ ASSERT_EQUALS(oil.name, "a");
+ ASSERT_EQUALS(oil.intervals.size(), 1U);
+ ASSERT_EQUALS(Interval::INTERVAL_EQUALS,
+ oil.intervals[0].compare(Interval(minKeyIntObj(4), true, true)));
+ ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT);
+}
+
+TEST_F(IndexBoundsBuilderTest, TranslateInternalExprLTESubObjectContainingBadValuesSucceeds) {
+ BSONObj keyPattern = BSON("_id" << 1);
+ BSONElement elt = keyPattern.firstElement();
+ auto testIndex = buildSimpleIndexEntry(keyPattern);
+ BSONObj subObj = BSON("subObj" << BSON("a" << BSONUndefined << "b" << BSON_ARRAY("array")));
+ BSONObj obj = BSON("_id" << BSON("$_internalExprLte" << subObj));
+ auto expr = parseMatchExpression(obj);
+ OrderedIntervalList oil;
+ IndexBoundsBuilder::BoundsTightness tightness;
+ IndexBoundsBuilder::translate(expr.get(), elt, testIndex, &oil, &tightness);
+ ASSERT_EQUALS(oil.name, "_id");
+ ASSERT_EQUALS(oil.intervals.size(), 1U);
+ ASSERT_EQUALS(
+ Interval::INTERVAL_EQUALS,
+ oil.intervals[0].compare(Interval(BSON("" << MINKEY << "" << subObj), true, true)));
+ ASSERT_EQUALS(tightness, IndexBoundsBuilder::EXACT);
+}
+
} // namespace
diff --git a/src/mongo/db/query/index_bounds_builder_test.h b/src/mongo/db/query/index_bounds_builder_test.h
index f3abf8c212c..421794fecf4 100644
--- a/src/mongo/db/query/index_bounds_builder_test.h
+++ b/src/mongo/db/query/index_bounds_builder_test.h
@@ -69,6 +69,25 @@ public:
}
/**
+ * Make a multikey IndexEntry with the provided key pattern and multikey paths.
+ */
+ IndexEntry buildMultikeyIndexEntry(const BSONObj& kp, const MultikeyPaths& mkp) {
+ return {kp,
+ IndexNames::nameToType(IndexNames::findPluginName(kp)),
+ IndexDescriptor::kLatestIndexVersion,
+ true, // multikey
+ mkp, // multikey paths
+ {},
+ false,
+ false,
+ IndexEntry::Identifier{"test_multikey"},
+ nullptr,
+ {},
+ nullptr,
+ nullptr};
+ }
+
+ /**
* Given a list of queries in 'toUnion', translate into index bounds and return
* the union of these bounds in the out-parameter 'oilOut'.
*/
diff --git a/src/mongo/db/query/indexability.h b/src/mongo/db/query/indexability.h
index 7442e251c3b..96930df7650 100644
--- a/src/mongo/db/query/indexability.h
+++ b/src/mongo/db/query/indexability.h
@@ -176,7 +176,11 @@ private:
me->matchType() == MatchExpression::GEO_NEAR ||
me->matchType() == MatchExpression::EXISTS ||
me->matchType() == MatchExpression::TEXT ||
- me->matchType() == MatchExpression::INTERNAL_EXPR_EQ;
+ me->matchType() == MatchExpression::INTERNAL_EXPR_EQ ||
+ me->matchType() == MatchExpression::INTERNAL_EXPR_GT ||
+ me->matchType() == MatchExpression::INTERNAL_EXPR_GTE ||
+ me->matchType() == MatchExpression::INTERNAL_EXPR_LT ||
+ me->matchType() == MatchExpression::INTERNAL_EXPR_LTE;
}
};
diff --git a/src/mongo/db/query/plan_cache_indexability.cpp b/src/mongo/db/query/plan_cache_indexability.cpp
index 216b714dbed..dbf0250966c 100644
--- a/src/mongo/db/query/plan_cache_indexability.cpp
+++ b/src/mongo/db/query/plan_cache_indexability.cpp
@@ -39,7 +39,7 @@
#include "mongo/db/index/wildcard_key_generator.h"
#include "mongo/db/matcher/expression.h"
#include "mongo/db/matcher/expression_algo.h"
-#include "mongo/db/matcher/expression_internal_expr_eq.h"
+#include "mongo/db/matcher/expression_internal_expr_comparison.h"
#include "mongo/db/matcher/expression_leaf.h"
#include "mongo/db/query/collation/collation_index_key.h"
#include "mongo/db/query/collation/collator_interface.h"
diff --git a/src/mongo/db/query/planner_ixselect.cpp b/src/mongo/db/query/planner_ixselect.cpp
index 9e7fc493d77..d9cc7855d19 100644
--- a/src/mongo/db/query/planner_ixselect.cpp
+++ b/src/mongo/db/query/planner_ixselect.cpp
@@ -40,7 +40,7 @@
#include "mongo/db/index_names.h"
#include "mongo/db/matcher/expression_algo.h"
#include "mongo/db/matcher/expression_geo.h"
-#include "mongo/db/matcher/expression_internal_expr_eq.h"
+#include "mongo/db/matcher/expression_internal_expr_comparison.h"
#include "mongo/db/matcher/expression_text.h"
#include "mongo/db/query/canonical_query_encoder.h"
#include "mongo/db/query/collation/collator_interface.h"
@@ -383,9 +383,10 @@ bool QueryPlannerIXSelect::_compatible(const BSONElement& keyPatternElt,
// We know keyPatternElt.fieldname() == node->path().
MatchExpression::MatchType exprtype = node->matchType();
- if (exprtype == MatchExpression::INTERNAL_EXPR_EQ &&
+ if (ComparisonMatchExpressionBase::isInternalExprComparison(exprtype) &&
index.pathHasMultikeyComponent(keyPatternElt.fieldNameStringData())) {
- // Expression language equality cannot be indexed if the field path has multikey components.
+ // Expression language comparisons cannot be indexed if the field path has multikey
+ // components.
return false;
}
diff --git a/src/mongo/db/query/sbe_stage_builder_filter.cpp b/src/mongo/db/query/sbe_stage_builder_filter.cpp
index 5b3e6672a15..b33b48efb6b 100644
--- a/src/mongo/db/query/sbe_stage_builder_filter.cpp
+++ b/src/mongo/db/query/sbe_stage_builder_filter.cpp
@@ -43,7 +43,7 @@
#include "mongo/db/matcher/expression_array.h"
#include "mongo/db/matcher/expression_expr.h"
#include "mongo/db/matcher/expression_geo.h"
-#include "mongo/db/matcher/expression_internal_expr_eq.h"
+#include "mongo/db/matcher/expression_internal_expr_comparison.h"
#include "mongo/db/matcher/expression_leaf.h"
#include "mongo/db/matcher/expression_text.h"
#include "mongo/db/matcher/expression_text_noop.h"
@@ -700,6 +700,10 @@ public:
}
void visit(const InMatchExpression* expr) final {}
void visit(const InternalExprEqMatchExpression* expr) final {}
+ void visit(const InternalExprGTMatchExpression* expr) final {}
+ void visit(const InternalExprGTEMatchExpression* expr) final {}
+ void visit(const InternalExprLTMatchExpression* expr) final {}
+ void visit(const InternalExprLTEMatchExpression* expr) final {}
void visit(const InternalSchemaAllElemMatchFromIndexMatchExpression* expr) final {
unsupportedExpression(expr);
}
@@ -1185,13 +1189,24 @@ public:
LeafTraversalMode::kArrayAndItsElements);
}
}
-
+ // The following are no-ops. The internal expr comparison match expression are produced
+ // internally by rewriting an $expr expression to an AND($expr, $_internalExpr[OP]), which can
+ // later be eliminated by via a conversion into EXACT index bounds, or remains present. In the
+ // latter case we can simply ignore it, as the result of AND($expr, $_internalExpr[OP]) is equal
+ // to just $expr.
void visit(const InternalExprEqMatchExpression* expr) final {
- // This is a no-op. The $_internalExprEq match expression is produced internally by
- // rewriting an $expr expression to an AND($expr, $_internalExprEq), which can later be
- // eliminated by via a conversion into EXACT index bounds, or remains present. In the latter
- // case we can simply ignore it, as the result of AND($expr, $_internalExprEq) is equal to
- // just $expr.
+ generateAlwaysBoolean(_context, true);
+ }
+ void visit(const InternalExprGTMatchExpression* expr) final {
+ generateAlwaysBoolean(_context, true);
+ }
+ void visit(const InternalExprGTEMatchExpression* expr) final {
+ generateAlwaysBoolean(_context, true);
+ }
+ void visit(const InternalExprLTMatchExpression* expr) final {
+ generateAlwaysBoolean(_context, true);
+ }
+ void visit(const InternalExprLTEMatchExpression* expr) final {
generateAlwaysBoolean(_context, true);
}
@@ -1388,6 +1403,10 @@ public:
void visit(const GeoNearMatchExpression* expr) final {}
void visit(const InMatchExpression* expr) final {}
void visit(const InternalExprEqMatchExpression* expr) final {}
+ void visit(const InternalExprGTMatchExpression* expr) final {}
+ void visit(const InternalExprGTEMatchExpression* expr) final {}
+ void visit(const InternalExprLTMatchExpression* expr) final {}
+ void visit(const InternalExprLTEMatchExpression* expr) final {}
void visit(const InternalSchemaAllElemMatchFromIndexMatchExpression* expr) final {}
void visit(const InternalSchemaAllowedPropertiesMatchExpression* expr) final {}
void visit(const InternalSchemaBinDataEncryptedTypeExpression* expr) final {}