diff options
Diffstat (limited to 'src/mongo/db/matcher')
-rw-r--r-- | src/mongo/db/matcher/SConscript | 2 | ||||
-rw-r--r-- | src/mongo/db/matcher/doc_validation_error.cpp | 12 | ||||
-rw-r--r-- | src/mongo/db/matcher/expression.h | 9 | ||||
-rw-r--r-- | src/mongo/db/matcher/expression_internal_expr_comparison.h | 205 | ||||
-rw-r--r-- | src/mongo/db/matcher/expression_internal_expr_comparison_test.cpp | 279 | ||||
-rw-r--r-- | src/mongo/db/matcher/expression_internal_expr_eq.cpp | 69 | ||||
-rw-r--r-- | src/mongo/db/matcher/expression_internal_expr_eq.h | 82 | ||||
-rw-r--r-- | src/mongo/db/matcher/expression_internal_expr_eq_test.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/matcher/expression_leaf.h | 13 | ||||
-rw-r--r-- | src/mongo/db/matcher/expression_parser.cpp | 55 | ||||
-rw-r--r-- | src/mongo/db/matcher/expression_parser.h | 4 | ||||
-rw-r--r-- | src/mongo/db/matcher/expression_visitor.h | 10 | ||||
-rw-r--r-- | src/mongo/db/matcher/rewrite_expr.cpp | 80 |
13 files changed, 657 insertions, 165 deletions
diff --git a/src/mongo/db/matcher/SConscript b/src/mongo/db/matcher/SConscript index 1e9e2a7d199..eef4a2f9be7 100644 --- a/src/mongo/db/matcher/SConscript +++ b/src/mongo/db/matcher/SConscript @@ -27,7 +27,6 @@ env.Library( 'expression_array.cpp', 'expression_expr.cpp', 'expression_geo.cpp', - 'expression_internal_expr_eq.cpp', 'expression_leaf.cpp', 'expression_parser.cpp', 'expression_text_base.cpp', @@ -111,6 +110,7 @@ env.CppUnitTest( 'expression_array_test.cpp', 'expression_expr_test.cpp', 'expression_geo_test.cpp', + 'expression_internal_expr_comparison_test.cpp', 'expression_internal_expr_eq_test.cpp', 'expression_leaf_test.cpp', 'expression_optimize_test.cpp', diff --git a/src/mongo/db/matcher/doc_validation_error.cpp b/src/mongo/db/matcher/doc_validation_error.cpp index 8d0c73ab824..164bb3d16c2 100644 --- a/src/mongo/db/matcher/doc_validation_error.cpp +++ b/src/mongo/db/matcher/doc_validation_error.cpp @@ -806,6 +806,10 @@ public: generatePathError(*expr, kNormalReason, kInvertedReason); } 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 { switch (toItemsKeywordType(*expr)) { case ItemsKeywordType::kItems: { @@ -1794,6 +1798,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 {} void visit(const InternalSchemaAllowedPropertiesMatchExpression* expr) final { if (_context->shouldGenerateError(*expr)) { @@ -2002,6 +2010,10 @@ public: _context->finishCurrentError(expr); } 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 { switch (toItemsKeywordType(*expr)) { case ItemsKeywordType::kItems: diff --git a/src/mongo/db/matcher/expression.h b/src/mongo/db/matcher/expression.h index 71c781fed09..3c3077ea5df 100644 --- a/src/mongo/db/matcher/expression.h +++ b/src/mongo/db/matcher/expression.h @@ -108,9 +108,14 @@ public: // Expressions that are only created internally INTERNAL_2D_POINT_IN_ANNULUS, - // Used to represent an expression language equality in a match expression tree, since $eq - // in the expression language has different semantics than the equality match expression. + // Used to represent expression language comparisons in a match expression tree, since $eq, + // $gt, $gte, $lt and $lte in the expression language has different semantics than their + // respective match expressions. INTERNAL_EXPR_EQ, + INTERNAL_EXPR_GT, + INTERNAL_EXPR_GTE, + INTERNAL_EXPR_LT, + INTERNAL_EXPR_LTE, // JSON Schema expressions. INTERNAL_SCHEMA_ALLOWED_PROPERTIES, diff --git a/src/mongo/db/matcher/expression_internal_expr_comparison.h b/src/mongo/db/matcher/expression_internal_expr_comparison.h new file mode 100644 index 00000000000..123813d1b80 --- /dev/null +++ b/src/mongo/db/matcher/expression_internal_expr_comparison.h @@ -0,0 +1,205 @@ +/** + * Copyright (C) 2021-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#pragma once + +#include "mongo/db/matcher/expression_leaf.h" + +namespace mongo { + +/** + * InternalExprComparisonMatchExpression consists of comparison expressions with similar semantics + * to the correlating $eq, $gt, $gte, $lt, and $lte aggregation expression. They differ from the + * regular comparison match expressions in the following ways: + * + * - There will be no type bracketing. For instance, null is considered less than 2, and objects + * are considered greater than 2. + * + * - The document will match if there is an array anywhere along the path. By always returning true + * in such cases, we match a superset of documents that the related aggregation expression would + * match. This sidesteps us having to implement field path expression evaluation as part of this + * match expression. + * + * - Comparison to NaN will consider NaN as the less than all numbers. + * + * - Comparison to an array is illegal. It is invalid usage to construct a + * InternalExprComparisonMatchExpression node which compares to an array. + */ +template <typename T> +class InternalExprComparisonMatchExpression : public ComparisonMatchExpressionBase { +public: + InternalExprComparisonMatchExpression(MatchType type, StringData path, BSONElement value) + : ComparisonMatchExpressionBase(type, + path, + Value(value), + ElementPath::LeafArrayBehavior::kNoTraversal, + ElementPath::NonLeafArrayBehavior::kMatchSubpath) { + invariant(_rhs.type() != BSONType::Undefined); + invariant(_rhs.type() != BSONType::Array); + } + + virtual ~InternalExprComparisonMatchExpression() = default; + + bool matchesSingleElement(const BSONElement& elem, MatchDetails* details) const final { + // We use NonLeafArrayBehavior::kMatchSubpath traversal in + // InternalExprComparisonMatchExpression. This means matchesSinglElement() will be called + // when an array is found anywhere along the patch we are matching against. When this + // occurs, we return 'true' and depend on the corresponding ExprMatchExpression node to + // filter properly. + if (elem.type() == BSONType::Array) { + return true; + } + + auto comp = elem.woCompare(_rhs, BSONElement::ComparisonRulesSet(0), _collator); + + switch (matchType()) { + case INTERNAL_EXPR_GT: + return comp > 0; + case INTERNAL_EXPR_GTE: + return comp >= 0; + case INTERNAL_EXPR_LT: + return comp < 0; + case INTERNAL_EXPR_LTE: + return comp <= 0; + case INTERNAL_EXPR_EQ: + return comp == 0; + default: + // This is a comparison match expression, so it must be either a $eq, $lt, $lte, $gt + // or $gte expression. + MONGO_UNREACHABLE_TASSERT(3994308); + } + }; + + std::unique_ptr<MatchExpression> shallowClone() const final { + auto clone = std::make_unique<T>(path(), _rhs); + clone->setCollator(_collator); + if (getTag()) { + clone->setTag(getTag()->clone()); + } + return clone; + } + + StringData name() const final { + return T::kName; + }; +}; + + +class InternalExprEqMatchExpression final + : public InternalExprComparisonMatchExpression<InternalExprEqMatchExpression> { +public: + static constexpr StringData kName = "$_internalExprEq"_sd; + + InternalExprEqMatchExpression(StringData path, BSONElement value) + : InternalExprComparisonMatchExpression<InternalExprEqMatchExpression>( + MatchType::INTERNAL_EXPR_EQ, path, value) {} + + void acceptVisitor(MatchExpressionMutableVisitor* visitor) final { + visitor->visit(this); + } + + void acceptVisitor(MatchExpressionConstVisitor* visitor) const final { + visitor->visit(this); + } +}; + +class InternalExprGTMatchExpression final + : public InternalExprComparisonMatchExpression<InternalExprGTMatchExpression> { +public: + static constexpr StringData kName = "$_internalExprGt"_sd; + + InternalExprGTMatchExpression(StringData path, BSONElement value) + : InternalExprComparisonMatchExpression<InternalExprGTMatchExpression>( + MatchType::INTERNAL_EXPR_GT, path, value) {} + + + void acceptVisitor(MatchExpressionMutableVisitor* visitor) final { + visitor->visit(this); + } + + void acceptVisitor(MatchExpressionConstVisitor* visitor) const final { + visitor->visit(this); + } +}; + +class InternalExprGTEMatchExpression final + : public InternalExprComparisonMatchExpression<InternalExprGTEMatchExpression> { +public: + static constexpr StringData kName = "$_internalExprGte"_sd; + + InternalExprGTEMatchExpression(StringData path, BSONElement value) + : InternalExprComparisonMatchExpression<InternalExprGTEMatchExpression>( + MatchType::INTERNAL_EXPR_GTE, path, value) {} + + void acceptVisitor(MatchExpressionMutableVisitor* visitor) final { + visitor->visit(this); + } + + void acceptVisitor(MatchExpressionConstVisitor* visitor) const final { + visitor->visit(this); + } +}; + +class InternalExprLTMatchExpression final + : public InternalExprComparisonMatchExpression<InternalExprLTMatchExpression> { +public: + static constexpr StringData kName = "$_internalExprLt"_sd; + + InternalExprLTMatchExpression(StringData path, BSONElement value) + : InternalExprComparisonMatchExpression<InternalExprLTMatchExpression>( + MatchType::INTERNAL_EXPR_LT, path, value) {} + + void acceptVisitor(MatchExpressionMutableVisitor* visitor) final { + visitor->visit(this); + } + + void acceptVisitor(MatchExpressionConstVisitor* visitor) const final { + visitor->visit(this); + } +}; + +class InternalExprLTEMatchExpression final + : public InternalExprComparisonMatchExpression<InternalExprLTEMatchExpression> { +public: + static constexpr StringData kName = "$_internalExprLte"_sd; + + InternalExprLTEMatchExpression(StringData path, BSONElement value) + : InternalExprComparisonMatchExpression<InternalExprLTEMatchExpression>( + MatchType::INTERNAL_EXPR_LTE, path, value) {} + + void acceptVisitor(MatchExpressionMutableVisitor* visitor) final { + visitor->visit(this); + } + + void acceptVisitor(MatchExpressionConstVisitor* visitor) const final { + visitor->visit(this); + } +}; + +} // namespace mongo diff --git a/src/mongo/db/matcher/expression_internal_expr_comparison_test.cpp b/src/mongo/db/matcher/expression_internal_expr_comparison_test.cpp new file mode 100644 index 00000000000..9085008ad75 --- /dev/null +++ b/src/mongo/db/matcher/expression_internal_expr_comparison_test.cpp @@ -0,0 +1,279 @@ +/** + * Copyright (C) 2021-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/db/matcher/expression_internal_expr_comparison.h" +#include "mongo/db/matcher/matcher.h" +#include "mongo/db/pipeline/expression_context_for_test.h" +#include "mongo/db/query/collation/collator_interface_mock.h" +#include "mongo/db/query/index_tag.h" +#include "mongo/unittest/death_test.h" +#include "mongo/unittest/unittest.h" + +namespace mongo { +namespace { + +const double kNaN = std::numeric_limits<double>::quiet_NaN(); + +TEST(InternalExprComparisonMatchExpression, DoesNotPerformTypeBracketing) { + BSONObj operand = BSON("x" << 2); + { + InternalExprGTMatchExpression gt(operand.firstElement().fieldNameStringData(), + operand.firstElement()); + ASSERT_FALSE(gt.matchesBSON(BSON("x" << MINKEY))); + ASSERT_FALSE(gt.matchesBSON(BSON("y" << 0))); + ASSERT_FALSE(gt.matchesBSON(BSON("x" << BSONNULL))); + ASSERT_FALSE(gt.matchesBSON(BSON("x" << BSONUndefined))); + ASSERT_FALSE(gt.matchesBSON(BSON("x" << 1))); + ASSERT_FALSE(gt.matchesBSON(BSON("x" << 2))); + ASSERT_TRUE(gt.matchesBSON(BSON("x" << 3.5))); + ASSERT_TRUE(gt.matchesBSON(BSON("x" + << "string"))); + ASSERT_TRUE(gt.matchesBSON(BSON("x" << BSON("a" << 1)))); + ASSERT_TRUE(gt.matchesBSON(BSON("x" << BSON_ARRAY(1 << 2 << 3)))); + ASSERT_TRUE(gt.matchesBSON(BSON("x" << OID()))); + ASSERT_TRUE(gt.matchesBSON(BSON("x" << DATENOW))); + ASSERT_TRUE(gt.matchesBSON(BSON("x" << Timestamp(0, 3)))); + ASSERT_TRUE(gt.matchesBSON(BSON("x" + << "/^m/"))); + ASSERT_TRUE(gt.matchesBSON(BSON("x" << MAXKEY))); + } + { + InternalExprGTEMatchExpression gte(operand.firstElement().fieldNameStringData(), + operand.firstElement()); + ASSERT_FALSE(gte.matchesBSON(BSON("x" << MINKEY))); + ASSERT_FALSE(gte.matchesBSON(BSON("y" << 0))); + ASSERT_FALSE(gte.matchesBSON(BSON("x" << BSONNULL))); + ASSERT_FALSE(gte.matchesBSON(BSON("x" << BSONUndefined))); + ASSERT_FALSE(gte.matchesBSON(BSON("x" << 1))); + ASSERT_TRUE(gte.matchesBSON(BSON("x" << 2))); + ASSERT_TRUE(gte.matchesBSON(BSON("x" << 3.5))); + ASSERT_TRUE(gte.matchesBSON(BSON("x" + << "string"))); + ASSERT_TRUE(gte.matchesBSON(BSON("x" << BSON("a" << 1)))); + ASSERT_TRUE(gte.matchesBSON(BSON("x" << BSON_ARRAY(1 << 2 << 3)))); + ASSERT_TRUE(gte.matchesBSON(BSON("x" << OID()))); + ASSERT_TRUE(gte.matchesBSON(BSON("x" << DATENOW))); + ASSERT_TRUE(gte.matchesBSON(BSON("x" << Timestamp(0, 3)))); + ASSERT_TRUE(gte.matchesBSON(BSON("x" + << "/^m/"))); + ASSERT_TRUE(gte.matchesBSON(BSON("x" << MAXKEY))); + } + { + InternalExprLTMatchExpression lt(operand.firstElement().fieldNameStringData(), + operand.firstElement()); + ASSERT_TRUE(lt.matchesBSON(BSON("x" << MINKEY))); + ASSERT_TRUE(lt.matchesBSON(BSON("y" << 0))); + ASSERT_TRUE(lt.matchesBSON(BSON("x" << BSONNULL))); + ASSERT_TRUE(lt.matchesBSON(BSON("x" << BSONUndefined))); + ASSERT_TRUE(lt.matchesBSON(BSON("x" << 1))); + ASSERT_FALSE(lt.matchesBSON(BSON("x" << 2))); + ASSERT_FALSE(lt.matchesBSON(BSON("x" << 3.5))); + ASSERT_FALSE(lt.matchesBSON(BSON("x" + << "string"))); + ASSERT_FALSE(lt.matchesBSON(BSON("x" << BSON("a" << 1)))); + ASSERT_TRUE(lt.matchesBSON(BSON( + "x" << BSON_ARRAY(1 << 2 << 3)))); // Always returns true if path contains an array. + ASSERT_FALSE(lt.matchesBSON(BSON("x" << OID()))); + ASSERT_FALSE(lt.matchesBSON(BSON("x" << DATENOW))); + ASSERT_FALSE(lt.matchesBSON(BSON("x" << Timestamp(0, 3)))); + ASSERT_FALSE(lt.matchesBSON(BSON("x" + << "/^m/"))); + ASSERT_FALSE(lt.matchesBSON(BSON("x" << MAXKEY))); + } + { + InternalExprLTEMatchExpression lte(operand.firstElement().fieldNameStringData(), + operand.firstElement()); + ASSERT_TRUE(lte.matchesBSON(BSON("x" << MINKEY))); + ASSERT_TRUE(lte.matchesBSON(BSON("y" << 0))); + ASSERT_TRUE(lte.matchesBSON(BSON("x" << BSONNULL))); + ASSERT_TRUE(lte.matchesBSON(BSON("x" << BSONUndefined))); + ASSERT_TRUE(lte.matchesBSON(BSON("x" << 1))); + ASSERT_TRUE(lte.matchesBSON(BSON("x" << 2))); + ASSERT_FALSE(lte.matchesBSON(BSON("x" << 3.5))); + ASSERT_FALSE(lte.matchesBSON(BSON("x" + << "string"))); + ASSERT_FALSE(lte.matchesBSON(BSON("x" << BSON("a" << 1)))); + ASSERT_TRUE(lte.matchesBSON(BSON( + "x" << BSON_ARRAY(1 << 2 << 3)))); // Always returns true if path contains an array. + ASSERT_FALSE(lte.matchesBSON(BSON("x" << OID()))); + ASSERT_FALSE(lte.matchesBSON(BSON("x" << DATENOW))); + ASSERT_FALSE(lte.matchesBSON(BSON("x" << Timestamp(0, 3)))); + ASSERT_FALSE(lte.matchesBSON(BSON("x" + << "/^m/"))); + ASSERT_FALSE(lte.matchesBSON(BSON("x" << MAXKEY))); + } +} + +TEST(InternalExprComparisonMatchExpression, CorrectlyComparesNaN) { + BSONObj operand = BSON("x" << kNaN); + // This behavior differs from how regular comparison MatchExpressions treat NaN, and places NaN + // within the total order of values as less than all numbers. + { + InternalExprGTMatchExpression gt(operand.firstElement().fieldNameStringData(), + operand.firstElement()); + ASSERT_FALSE(gt.matchesBSON(BSON("x" << MINKEY))); + ASSERT_FALSE(gt.matchesBSON(BSON("x" << BSONNULL))); + ASSERT_FALSE(gt.matchesBSON(BSON("x" << kNaN))); + ASSERT_TRUE(gt.matchesBSON(BSON("x" << Decimal128::kNegativeInfinity))); + ASSERT_TRUE(gt.matchesBSON(BSON("x" << 2))); + ASSERT_TRUE(gt.matchesBSON(BSON("x" << 3.5))); + ASSERT_TRUE(gt.matchesBSON(BSON("x" << MAXKEY))); + } + { + InternalExprGTEMatchExpression gte(operand.firstElement().fieldNameStringData(), + operand.firstElement()); + ASSERT_FALSE(gte.matchesBSON(BSON("x" << MINKEY))); + ASSERT_FALSE(gte.matchesBSON(BSON("x" << BSONNULL))); + ASSERT_TRUE(gte.matchesBSON(BSON("x" << kNaN))); + ASSERT_TRUE(gte.matchesBSON(BSON("x" << Decimal128::kNegativeInfinity))); + ASSERT_TRUE(gte.matchesBSON(BSON("x" << 2))); + ASSERT_TRUE(gte.matchesBSON(BSON("x" << 3.5))); + ASSERT_TRUE(gte.matchesBSON(BSON("x" << MAXKEY))); + } + { + InternalExprLTMatchExpression lt(operand.firstElement().fieldNameStringData(), + operand.firstElement()); + ASSERT_TRUE(lt.matchesBSON(BSON("x" << MINKEY))); + ASSERT_TRUE(lt.matchesBSON(BSON("x" << BSONNULL))); + ASSERT_FALSE(lt.matchesBSON(BSON("x" << kNaN))); + ASSERT_FALSE(lt.matchesBSON(BSON("x" << Decimal128::kNegativeInfinity))); + ASSERT_FALSE(lt.matchesBSON(BSON("x" << 2))); + ASSERT_FALSE(lt.matchesBSON(BSON("x" << 3.5))); + ASSERT_FALSE(lt.matchesBSON(BSON("x" << MAXKEY))); + } + { + InternalExprLTEMatchExpression lte(operand.firstElement().fieldNameStringData(), + operand.firstElement()); + ASSERT_TRUE(lte.matchesBSON(BSON("x" << MINKEY))); + ASSERT_TRUE(lte.matchesBSON(BSON("x" << BSONNULL))); + ASSERT_TRUE(lte.matchesBSON(BSON("x" << kNaN))); + ASSERT_FALSE(lte.matchesBSON(BSON("x" << Decimal128::kNegativeInfinity))); + ASSERT_FALSE(lte.matchesBSON(BSON("x" << 2))); + ASSERT_FALSE(lte.matchesBSON(BSON("x" << 3.5))); + ASSERT_FALSE(lte.matchesBSON(BSON("x" << MAXKEY))); + } +} + +TEST(InternalExprComparisonMatchExpression, AlwaysReturnsTrueWithLeafArrays) { + BSONObj operand = BSON("x" << 2); + { + InternalExprGTMatchExpression gt(operand.firstElement().fieldNameStringData(), + operand.firstElement()); + ASSERT_TRUE(gt.matchesBSON(BSON("x" << BSON_ARRAY(BSONNULL)))); + ASSERT_TRUE(gt.matchesBSON(BSON("x" << BSON_ARRAY(0)))); + ASSERT_TRUE(gt.matchesBSON(BSON("x" << BSON_ARRAY(1 << 2 << 3)))); + ASSERT_FALSE(gt.matchesBSON(BSON("x" << 1))); + } + { + InternalExprGTEMatchExpression gte(operand.firstElement().fieldNameStringData(), + operand.firstElement()); + ASSERT_TRUE(gte.matchesBSON(BSON("x" << BSON_ARRAY(BSONNULL)))); + ASSERT_TRUE(gte.matchesBSON(BSON("x" << BSON_ARRAY(0)))); + ASSERT_TRUE(gte.matchesBSON(BSON("x" << BSON_ARRAY(1 << 2 << 3)))); + ASSERT_FALSE(gte.matchesBSON(BSON("x" << 1))); + } + { + InternalExprLTMatchExpression lt(operand.firstElement().fieldNameStringData(), + operand.firstElement()); + ASSERT_TRUE(lt.matchesBSON(BSON("x" << BSON_ARRAY(BSONNULL)))); + ASSERT_TRUE(lt.matchesBSON(BSON("x" << BSON_ARRAY(0)))); + ASSERT_TRUE(lt.matchesBSON(BSON("x" << BSON_ARRAY(1 << 2 << 3)))); + ASSERT_FALSE(lt.matchesBSON(BSON("x" << 3))); + } + { + InternalExprLTEMatchExpression lte(operand.firstElement().fieldNameStringData(), + operand.firstElement()); + ASSERT_TRUE(lte.matchesBSON(BSON("x" << BSON_ARRAY(BSONNULL)))); + ASSERT_TRUE(lte.matchesBSON(BSON("x" << BSON_ARRAY(0)))); + ASSERT_TRUE(lte.matchesBSON(BSON("x" << BSON_ARRAY(1 << 2 << 3)))); + ASSERT_FALSE(lte.matchesBSON(BSON("x" << 3))); + } +} + +TEST(InternalExprComparisonMatchExpression, AlwaysReturnsTrueWithNonLeafArrays) { + BSONObj operand = BSON("x.y" << 2); + { + InternalExprGTMatchExpression gt(operand.firstElement().fieldNameStringData(), + operand.firstElement()); + ASSERT_TRUE(gt.matchesBSON(BSON("x" << BSON_ARRAY(BSONNULL)))); + ASSERT_TRUE(gt.matchesBSON(BSON("x" << BSON_ARRAY(1 << 2 << 3)))); + ASSERT_TRUE(gt.matchesBSON(BSON("x" << BSON_ARRAY(BSON("y" << 1))))); + ASSERT_TRUE(gt.matchesBSON(BSON("x" << BSON_ARRAY(BSON("y" << 2) << BSON("y" << 3))))); + ASSERT_FALSE(gt.matchesBSON(BSON("x" << BSON("y" << 1)))); + } + { + InternalExprGTEMatchExpression gte(operand.firstElement().fieldNameStringData(), + operand.firstElement()); + ASSERT_TRUE(gte.matchesBSON(BSON("x" << BSON_ARRAY(BSONNULL)))); + ASSERT_TRUE(gte.matchesBSON(BSON("x" << BSON_ARRAY(1 << 2 << 3)))); + ASSERT_TRUE(gte.matchesBSON(BSON("x" << BSON_ARRAY(BSON("y" << 1))))); + ASSERT_TRUE(gte.matchesBSON(BSON("x" << BSON_ARRAY(BSON("y" << 2) << BSON("y" << 3))))); + ASSERT_FALSE(gte.matchesBSON(BSON("x" << BSON("y" << 1)))); + } + { + InternalExprLTMatchExpression lt(operand.firstElement().fieldNameStringData(), + operand.firstElement()); + ASSERT_TRUE(lt.matchesBSON(BSON("x" << BSON_ARRAY(BSONNULL)))); + ASSERT_TRUE(lt.matchesBSON(BSON("x" << BSON_ARRAY(1 << 2 << 3)))); + ASSERT_TRUE(lt.matchesBSON(BSON("x" << BSON_ARRAY(BSON("y" << 3))))); + ASSERT_TRUE(lt.matchesBSON(BSON("x" << BSON_ARRAY(BSON("y" << 1) << BSON("y" << 2))))); + ASSERT_FALSE(lt.matchesBSON(BSON("x" << BSON("y" << 3)))); + } + { + InternalExprLTEMatchExpression lte(operand.firstElement().fieldNameStringData(), + operand.firstElement()); + ASSERT_TRUE(lte.matchesBSON(BSON("x" << BSON_ARRAY(BSONNULL)))); + ASSERT_TRUE(lte.matchesBSON(BSON("x" << BSON_ARRAY(1 << 2 << 3)))); + ASSERT_TRUE(lte.matchesBSON(BSON("x" << BSON_ARRAY(BSON("y" << 3))))); + ASSERT_TRUE(lte.matchesBSON(BSON("x" << BSON_ARRAY(BSON("y" << 1) << BSON("y" << 2))))); + ASSERT_FALSE(lte.matchesBSON(BSON("x" << BSON("y" << 3)))); + } +} + +DEATH_TEST_REGEX(InternalExprComparisonMatchExpression, + CannotCompareToArray, + R"#(Invariant failure.*_rhs.type\(\) != BSONType::Array)#") { + BSONObj operand = BSON("x" << BSON_ARRAY(1 << 2)); + InternalExprGTMatchExpression gt(operand.firstElement().fieldNameStringData(), + operand.firstElement()); +} + +DEATH_TEST_REGEX(InternalExprComparisonMatchExpression, + CannotCompareToUndefined, + R"#(Invariant failure.*_rhs.type\(\) != BSONType::Undefined)#") { + BSONObj operand = BSON("x" << BSONUndefined); + InternalExprGTMatchExpression gt(operand.firstElement().fieldNameStringData(), + operand.firstElement()); +} + + +} // namespace +} // namespace mongo diff --git a/src/mongo/db/matcher/expression_internal_expr_eq.cpp b/src/mongo/db/matcher/expression_internal_expr_eq.cpp deleted file mode 100644 index 83d84254c32..00000000000 --- a/src/mongo/db/matcher/expression_internal_expr_eq.cpp +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Copyright (C) 2018-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side Public License - * along with this program. If not, see - * <http://www.mongodb.com/licensing/server-side-public-license>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the Server Side Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#include "mongo/platform/basic.h" - -#include "mongo/db/matcher/expression_internal_expr_eq.h" - -#include "mongo/bson/bsonobj.h" -#include "mongo/bson/bsonobjbuilder.h" - -namespace mongo { - -constexpr StringData InternalExprEqMatchExpression::kName; - -bool InternalExprEqMatchExpression::matchesSingleElement(const BSONElement& elem, - MatchDetails* details) const { - // We use NonLeafArrayBehavior::kMatchSubpath traversal in InternalExprEqMatchExpression. This - // means matchesSinglElement() will be called when an array is found anywhere along the patch we - // are matching against. When this occurs, we return 'true' and depend on the corresponding - // ExprMatchExpression node to filter properly. - if (elem.type() == BSONType::Array) { - return true; - } - - if (elem.canonicalType() != _rhs.canonicalType()) { - return false; - } - - auto comp = BSONElement::compareElements( - elem, _rhs, BSONElement::ComparisonRules::kConsiderFieldName, _collator); - return comp == 0; -} - -std::unique_ptr<MatchExpression> InternalExprEqMatchExpression::shallowClone() const { - auto clone = std::make_unique<InternalExprEqMatchExpression>(path(), _rhs); - clone->setCollator(_collator); - if (getTag()) { - clone->setTag(getTag()->clone()); - } - return clone; -} - -} // namespace mongo diff --git a/src/mongo/db/matcher/expression_internal_expr_eq.h b/src/mongo/db/matcher/expression_internal_expr_eq.h deleted file mode 100644 index 2c52eb4e67d..00000000000 --- a/src/mongo/db/matcher/expression_internal_expr_eq.h +++ /dev/null @@ -1,82 +0,0 @@ -/** - * Copyright (C) 2018-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side Public License - * along with this program. If not, see - * <http://www.mongodb.com/licensing/server-side-public-license>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the Server Side Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#pragma once - -#include "mongo/db/matcher/expression_leaf.h" - -namespace mongo { - -/** - * An InternalExprEqMatchExpression is an equality expression with similar semantics to the $eq - * expression. It differs from the regular equality match expression in the following ways: - * - * - The document will match if there is an array anywhere along the path. By always returning true - * in such cases, we match a superset of documents that the related aggregation expression would - * match. This sidesteps us having to implement field path expression evaluation as part of this - * match expression. - * - * - Equality to null matches literal nulls, but not documents in which the field path is missing or - * undefined. - * - * - Equality to an array is illegal. It is invalid usage to construct a - * InternalExprEqMatchExpression node which compares to an array. - */ -class InternalExprEqMatchExpression final : public ComparisonMatchExpressionBase { -public: - static constexpr StringData kName = "$_internalExprEq"_sd; - - InternalExprEqMatchExpression(StringData path, BSONElement value) - : ComparisonMatchExpressionBase(MatchType::INTERNAL_EXPR_EQ, - path, - Value(value), - ElementPath::LeafArrayBehavior::kNoTraversal, - ElementPath::NonLeafArrayBehavior::kMatchSubpath) { - invariant(_rhs.type() != BSONType::Undefined); - invariant(_rhs.type() != BSONType::Array); - } - - StringData name() const final { - return kName; - } - - bool matchesSingleElement(const BSONElement&, MatchDetails*) const final; - - std::unique_ptr<MatchExpression> shallowClone() const final; - - void acceptVisitor(MatchExpressionMutableVisitor* visitor) final { - visitor->visit(this); - } - - void acceptVisitor(MatchExpressionConstVisitor* visitor) const final { - visitor->visit(this); - } -}; - -} // namespace mongo diff --git a/src/mongo/db/matcher/expression_internal_expr_eq_test.cpp b/src/mongo/db/matcher/expression_internal_expr_eq_test.cpp index bd5ad2d878a..0de1fbec00b 100644 --- a/src/mongo/db/matcher/expression_internal_expr_eq_test.cpp +++ b/src/mongo/db/matcher/expression_internal_expr_eq_test.cpp @@ -30,7 +30,7 @@ #include "mongo/platform/basic.h" #include "mongo/bson/json.h" -#include "mongo/db/matcher/expression_internal_expr_eq.h" +#include "mongo/db/matcher/expression_internal_expr_comparison.h" #include "mongo/db/matcher/matcher.h" #include "mongo/db/pipeline/expression_context_for_test.h" #include "mongo/db/query/collation/collator_interface_mock.h" diff --git a/src/mongo/db/matcher/expression_leaf.h b/src/mongo/db/matcher/expression_leaf.h index d9a4d6192dc..e042e6d99fb 100644 --- a/src/mongo/db/matcher/expression_leaf.h +++ b/src/mongo/db/matcher/expression_leaf.h @@ -104,6 +104,19 @@ public: } } + static bool isInternalExprComparison(MatchType matchType) { + switch (matchType) { + case MatchExpression::INTERNAL_EXPR_EQ: + case MatchExpression::INTERNAL_EXPR_GT: + case MatchExpression::INTERNAL_EXPR_GTE: + case MatchExpression::INTERNAL_EXPR_LT: + case MatchExpression::INTERNAL_EXPR_LTE: + return true; + default: + return false; + } + } + ComparisonMatchExpressionBase(MatchType type, StringData path, Value rhs, diff --git a/src/mongo/db/matcher/expression_parser.cpp b/src/mongo/db/matcher/expression_parser.cpp index 197cc481e3c..7ef37bc2e02 100644 --- a/src/mongo/db/matcher/expression_parser.cpp +++ b/src/mongo/db/matcher/expression_parser.cpp @@ -44,7 +44,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_tree.h" #include "mongo/db/matcher/expression_type.h" @@ -1692,6 +1692,55 @@ StatusWithMatchExpression parseSubField(const BSONObj& context, return {std::move(exprEqExpr)}; } + case PathAcceptingKeyword::INTERNAL_EXPR_GT: { + if (e.type() == BSONType::Undefined || e.type() == BSONType::Array) { + return {Status(ErrorCodes::BadValue, + str::stream() << InternalExprGTMatchExpression::kName + << " cannot be used to compare to type: " + << typeName(e.type()))}; + } + + auto exprGtExpr = std::make_unique<InternalExprGTMatchExpression>(name, e); + exprGtExpr->setCollator(expCtx->getCollator()); + return {std::move(exprGtExpr)}; + } + case PathAcceptingKeyword::INTERNAL_EXPR_GTE: { + if (e.type() == BSONType::Undefined || e.type() == BSONType::Array) { + return {Status(ErrorCodes::BadValue, + str::stream() << InternalExprGTEMatchExpression::kName + << " cannot be used to compare to type: " + << typeName(e.type()))}; + } + + auto exprGteExpr = std::make_unique<InternalExprGTEMatchExpression>(name, e); + exprGteExpr->setCollator(expCtx->getCollator()); + return {std::move(exprGteExpr)}; + } + case PathAcceptingKeyword::INTERNAL_EXPR_LT: { + if (e.type() == BSONType::Undefined || e.type() == BSONType::Array) { + return {Status(ErrorCodes::BadValue, + str::stream() << InternalExprLTMatchExpression::kName + << " cannot be used to compare to type: " + << typeName(e.type()))}; + } + + auto exprLtExpr = std::make_unique<InternalExprLTMatchExpression>(name, e); + exprLtExpr->setCollator(expCtx->getCollator()); + return {std::move(exprLtExpr)}; + } + case PathAcceptingKeyword::INTERNAL_EXPR_LTE: { + if (e.type() == BSONType::Undefined || e.type() == BSONType::Array) { + return {Status(ErrorCodes::BadValue, + str::stream() << InternalExprLTEMatchExpression::kName + << " cannot be used to compare to type: " + << typeName(e.type()))}; + } + + auto exprLteExpr = std::make_unique<InternalExprLTEMatchExpression>(name, e); + exprLteExpr->setCollator(expCtx->getCollator()); + return {std::move(exprLteExpr)}; + } + // Handles bitwise query operators. case PathAcceptingKeyword::BITS_ALL_SET: { return parseBitTest<BitsAllSetMatchExpression>(name, e, expCtx); @@ -1990,6 +2039,10 @@ MONGO_INITIALIZER(MatchExpressionParser)(InitializerContext* context) { queryOperatorMap = std::make_unique<StringMap<PathAcceptingKeyword>>(StringMap<PathAcceptingKeyword>{ {"_internalExprEq", PathAcceptingKeyword::INTERNAL_EXPR_EQ}, + {"_internalExprGt", PathAcceptingKeyword::INTERNAL_EXPR_GT}, + {"_internalExprGte", PathAcceptingKeyword::INTERNAL_EXPR_GTE}, + {"_internalExprLt", PathAcceptingKeyword::INTERNAL_EXPR_LT}, + {"_internalExprLte", PathAcceptingKeyword::INTERNAL_EXPR_LTE}, {"_internalSchemaAllElemMatchFromIndex", PathAcceptingKeyword::INTERNAL_SCHEMA_ALL_ELEM_MATCH_FROM_INDEX}, {"_internalSchemaBinDataEncryptedType", diff --git a/src/mongo/db/matcher/expression_parser.h b/src/mongo/db/matcher/expression_parser.h index 98ed368d95e..101a7e10332 100644 --- a/src/mongo/db/matcher/expression_parser.h +++ b/src/mongo/db/matcher/expression_parser.h @@ -62,6 +62,10 @@ enum class PathAcceptingKeyword { GREATER_THAN, GREATER_THAN_OR_EQUAL, INTERNAL_EXPR_EQ, + INTERNAL_EXPR_GT, + INTERNAL_EXPR_GTE, + INTERNAL_EXPR_LT, + INTERNAL_EXPR_LTE, INTERNAL_SCHEMA_ALL_ELEM_MATCH_FROM_INDEX, INTERNAL_SCHEMA_BIN_DATA_ENCRYPTED_TYPE, INTERNAL_SCHEMA_BIN_DATA_SUBTYPE, diff --git a/src/mongo/db/matcher/expression_visitor.h b/src/mongo/db/matcher/expression_visitor.h index 237263cd4ba..59ec095ea9a 100644 --- a/src/mongo/db/matcher/expression_visitor.h +++ b/src/mongo/db/matcher/expression_visitor.h @@ -50,6 +50,10 @@ class GeoMatchExpression; class GeoNearMatchExpression; class InMatchExpression; class InternalExprEqMatchExpression; +class InternalExprGTMatchExpression; +class InternalExprGTEMatchExpression; +class InternalExprLTMatchExpression; +class InternalExprLTEMatchExpression; class InternalSchemaAllElemMatchFromIndexMatchExpression; class InternalSchemaAllowedPropertiesMatchExpression; class InternalSchemaBinDataEncryptedTypeExpression; @@ -115,6 +119,12 @@ public: virtual void visit(tree_walker::MaybeConstPtr<IsConst, GeoNearMatchExpression> expr) = 0; virtual void visit(tree_walker::MaybeConstPtr<IsConst, InMatchExpression> expr) = 0; virtual void visit(tree_walker::MaybeConstPtr<IsConst, InternalExprEqMatchExpression> expr) = 0; + virtual void visit(tree_walker::MaybeConstPtr<IsConst, InternalExprGTMatchExpression> expr) = 0; + virtual void visit( + tree_walker::MaybeConstPtr<IsConst, InternalExprGTEMatchExpression> expr) = 0; + virtual void visit(tree_walker::MaybeConstPtr<IsConst, InternalExprLTMatchExpression> expr) = 0; + virtual void visit( + tree_walker::MaybeConstPtr<IsConst, InternalExprLTEMatchExpression> expr) = 0; virtual void visit( tree_walker::MaybeConstPtr<IsConst, InternalSchemaAllElemMatchFromIndexMatchExpression> expr) = 0; diff --git a/src/mongo/db/matcher/rewrite_expr.cpp b/src/mongo/db/matcher/rewrite_expr.cpp index b5b12278d17..27e19d4c5fe 100644 --- a/src/mongo/db/matcher/rewrite_expr.cpp +++ b/src/mongo/db/matcher/rewrite_expr.cpp @@ -33,7 +33,7 @@ #include "mongo/db/matcher/rewrite_expr.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_tree.h" #include "mongo/logv2/log.h" @@ -141,6 +141,32 @@ std::unique_ptr<MatchExpression> RewriteExpr::_rewriteComparisonExpression( lhs = dynamic_cast<ExpressionFieldPath*>(operandList[1].get()); rhs = dynamic_cast<ExpressionConstant*>(operandList[0].get()); invariant(lhs && rhs); + + // The MatchExpression is normalized so that the field path expression is on the left. For + // cases like {$gt: [1, "$x"]} where the order of the child expressions matter, we also + // change the comparison operator. + switch (cmpOperator) { + case ExpressionCompare::GT: { + cmpOperator = ExpressionCompare::LT; + break; + } + case ExpressionCompare::GTE: { + cmpOperator = ExpressionCompare::LTE; + break; + } + case ExpressionCompare::LT: { + cmpOperator = ExpressionCompare::GT; + break; + } + case ExpressionCompare::LTE: { + cmpOperator = ExpressionCompare::GTE; + break; + } + case ExpressionCompare::EQ: + break; + default: + MONGO_UNREACHABLE_TASSERT(3994306); + } } // Build argument for ComparisonMatchExpression. @@ -155,20 +181,56 @@ std::unique_ptr<MatchExpression> RewriteExpr::_rewriteComparisonExpression( std::unique_ptr<MatchExpression> RewriteExpr::_buildComparisonMatchExpression( ExpressionCompare::CmpOp comparisonOp, BSONElement fieldAndValue) { - invariant(comparisonOp == ExpressionCompare::EQ); - - auto eqMatchExpr = - std::make_unique<InternalExprEqMatchExpression>(fieldAndValue.fieldName(), fieldAndValue); - eqMatchExpr->setCollator(_collator); + tassert(3994301, + "comparisonOp must be one of the following: $eq, $gt, $gte, $lt, $lte", + comparisonOp == ExpressionCompare::EQ || comparisonOp == ExpressionCompare::GT || + comparisonOp == ExpressionCompare::GTE || comparisonOp == ExpressionCompare::LT || + comparisonOp == ExpressionCompare::LTE); + + std::unique_ptr<MatchExpression> matchExpr; + + switch (comparisonOp) { + case ExpressionCompare::EQ: { + matchExpr = std::make_unique<InternalExprEqMatchExpression>(fieldAndValue.fieldName(), + fieldAndValue); + break; + } + case ExpressionCompare::GT: { + matchExpr = std::make_unique<InternalExprGTMatchExpression>(fieldAndValue.fieldName(), + fieldAndValue); + break; + } + case ExpressionCompare::GTE: { + matchExpr = std::make_unique<InternalExprGTEMatchExpression>(fieldAndValue.fieldName(), + fieldAndValue); + break; + } + case ExpressionCompare::LT: { + matchExpr = std::make_unique<InternalExprLTMatchExpression>(fieldAndValue.fieldName(), + fieldAndValue); + break; + } + case ExpressionCompare::LTE: { + matchExpr = std::make_unique<InternalExprLTEMatchExpression>(fieldAndValue.fieldName(), + fieldAndValue); + break; + } + default: + MONGO_UNREACHABLE_TASSERT(3994307); + } + matchExpr->setCollator(_collator); - return eqMatchExpr; + return matchExpr; } bool RewriteExpr::_canRewriteComparison( const boost::intrusive_ptr<ExpressionCompare>& expression) const { - // Currently we only rewrite $eq expressions. - if (expression->getOp() != ExpressionCompare::EQ) { + // Currently we only rewrite $eq, $gt, $gte, $lt and $lte expressions. + auto op = expression->getOp(); + if (op != ExpressionCompare::EQ && op != ExpressionCompare::GT && + op != ExpressionCompare::GTE && op != ExpressionCompare::LT && + op != ExpressionCompare::LTE) { return false; } |