// expression_algo_test.cpp /** * Copyright (C) 2015 MongoDB Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * 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 * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * * 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 GNU Affero General 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/unittest/unittest.h" #include #include "mongo/db/jsobj.h" #include "mongo/db/json.h" #include "mongo/db/matcher/expression.h" #include "mongo/db/matcher/expression_algo.h" #include "mongo/db/matcher/expression_parser.h" #include "mongo/db/matcher/extensions_callback_disallow_extensions.h" #include "mongo/db/matcher/extensions_callback_noop.h" #include "mongo/db/query/collation/collator_interface_mock.h" #include "mongo/platform/decimal128.h" namespace mongo { using std::unique_ptr; /** * A MatchExpression does not hold the memory for BSONElements, so use ParsedMatchExpression to * ensure that the BSONObj outlives the MatchExpression. */ class ParsedMatchExpression { public: ParsedMatchExpression(const std::string& str, const CollatorInterface* collator = nullptr) : _obj(fromjson(str)) { StatusWithMatchExpression result = MatchExpressionParser::parse(_obj, ExtensionsCallbackDisallowExtensions(), collator); ASSERT_OK(result.getStatus()); _expr = std::move(result.getValue()); } const MatchExpression* get() const { return _expr.get(); } private: const BSONObj _obj; std::unique_ptr _expr; }; TEST(ExpressionAlgoIsSubsetOf, NullAndOmittedField) { // Verify that ComparisonMatchExpression::init() prohibits creating a match expression with // an Undefined type. BSONObj undefined = fromjson("{a: undefined}"); const CollatorInterface* collator = nullptr; ASSERT_EQUALS( ErrorCodes::BadValue, MatchExpressionParser::parse(undefined, ExtensionsCallbackDisallowExtensions(), collator) .getStatus()); ParsedMatchExpression empty("{}"); ParsedMatchExpression null("{a: null}"); ASSERT_TRUE(expression::isSubsetOf(null.get(), empty.get())); ASSERT_FALSE(expression::isSubsetOf(empty.get(), null.get())); ParsedMatchExpression b1("{b: 1}"); ParsedMatchExpression aNullB1("{a: null, b: 1}"); ASSERT_TRUE(expression::isSubsetOf(aNullB1.get(), b1.get())); ASSERT_FALSE(expression::isSubsetOf(b1.get(), aNullB1.get())); ParsedMatchExpression a1C3("{a: 1, c: 3}"); ParsedMatchExpression a1BNullC3("{a: 1, b: null, c: 3}"); ASSERT_TRUE(expression::isSubsetOf(a1BNullC3.get(), a1C3.get())); ASSERT_FALSE(expression::isSubsetOf(a1C3.get(), a1BNullC3.get())); } TEST(ExpressionAlgoIsSubsetOf, NullAndIn) { ParsedMatchExpression eqNull("{x: null}"); ParsedMatchExpression inNull("{x: {$in: [null]}}"); ParsedMatchExpression inNullOr2("{x: {$in: [null, 2]}}"); ASSERT_TRUE(expression::isSubsetOf(inNull.get(), eqNull.get())); ASSERT_FALSE(expression::isSubsetOf(inNullOr2.get(), eqNull.get())); } TEST(ExpressionAlgoIsSubsetOf, NullAndExists) { ParsedMatchExpression null("{x: null}"); ParsedMatchExpression exists("{x: {$exists: true}}"); ASSERT_FALSE(expression::isSubsetOf(null.get(), exists.get())); ASSERT_FALSE(expression::isSubsetOf(exists.get(), null.get())); } TEST(ExpressionAlgoIsSubsetOf, Compare_NaN) { ParsedMatchExpression nan("{x: NaN}"); ParsedMatchExpression lt("{x: {$lt: 5}}"); ParsedMatchExpression lte("{x: {$lte: 5}}"); ParsedMatchExpression gte("{x: {$gte: 5}}"); ParsedMatchExpression gt("{x: {$gt: 5}}"); ParsedMatchExpression in("{x: {$in: [5]}}"); ASSERT_TRUE(expression::isSubsetOf(nan.get(), nan.get())); ASSERT_FALSE(expression::isSubsetOf(nan.get(), lt.get())); ASSERT_FALSE(expression::isSubsetOf(lt.get(), nan.get())); ASSERT_FALSE(expression::isSubsetOf(nan.get(), lte.get())); ASSERT_FALSE(expression::isSubsetOf(lte.get(), nan.get())); ASSERT_FALSE(expression::isSubsetOf(nan.get(), gte.get())); ASSERT_FALSE(expression::isSubsetOf(gte.get(), nan.get())); ASSERT_FALSE(expression::isSubsetOf(nan.get(), gt.get())); ASSERT_FALSE(expression::isSubsetOf(gt.get(), nan.get())); ASSERT_FALSE(expression::isSubsetOf(nan.get(), in.get())); ASSERT_FALSE(expression::isSubsetOf(in.get(), nan.get())); ParsedMatchExpression decNan("{x : NumberDecimal(\"NaN\") }"); ASSERT_TRUE(expression::isSubsetOf(decNan.get(), decNan.get())); ASSERT_TRUE(expression::isSubsetOf(nan.get(), decNan.get())); ASSERT_TRUE(expression::isSubsetOf(decNan.get(), nan.get())); ASSERT_FALSE(expression::isSubsetOf(decNan.get(), lt.get())); ASSERT_FALSE(expression::isSubsetOf(lt.get(), decNan.get())); ASSERT_FALSE(expression::isSubsetOf(decNan.get(), lte.get())); ASSERT_FALSE(expression::isSubsetOf(lte.get(), decNan.get())); ASSERT_FALSE(expression::isSubsetOf(decNan.get(), gte.get())); ASSERT_FALSE(expression::isSubsetOf(gte.get(), decNan.get())); ASSERT_FALSE(expression::isSubsetOf(decNan.get(), gt.get())); ASSERT_FALSE(expression::isSubsetOf(gt.get(), decNan.get())); } TEST(ExpressionAlgoIsSubsetOf, Compare_EQ) { ParsedMatchExpression a5("{a: 5}"); ParsedMatchExpression a6("{a: 6}"); ParsedMatchExpression b5("{b: 5}"); ASSERT_TRUE(expression::isSubsetOf(a5.get(), a5.get())); ASSERT_FALSE(expression::isSubsetOf(a5.get(), a6.get())); ASSERT_FALSE(expression::isSubsetOf(a5.get(), b5.get())); } TEST(ExpressionAlgoIsSubsetOf, CompareAnd_EQ) { ParsedMatchExpression a1B2("{a: 1, b: 2}"); ParsedMatchExpression a1B7("{a: 1, b: 7}"); ParsedMatchExpression a1("{a: 1}"); ParsedMatchExpression b2("{b: 2}"); ASSERT_TRUE(expression::isSubsetOf(a1B2.get(), a1B2.get())); ASSERT_FALSE(expression::isSubsetOf(a1B2.get(), a1B7.get())); ASSERT_TRUE(expression::isSubsetOf(a1B2.get(), a1.get())); ASSERT_TRUE(expression::isSubsetOf(a1B2.get(), b2.get())); ASSERT_FALSE(expression::isSubsetOf(a1B7.get(), b2.get())); } TEST(ExpressionAlgoIsSubsetOf, CompareAnd_GT) { ParsedMatchExpression filter("{a: {$gt: 5}, b: {$gt: 6}}"); ParsedMatchExpression query("{a: {$gt: 5}, b: {$gt: 6}, c: {$gt: 7}}"); ASSERT_TRUE(expression::isSubsetOf(query.get(), filter.get())); ASSERT_FALSE(expression::isSubsetOf(filter.get(), query.get())); } TEST(ExpressionAlgoIsSubsetOf, CompareOr_LT) { ParsedMatchExpression lt5("{a: {$lt: 5}}"); ParsedMatchExpression eq2OrEq3("{$or: [{a: 2}, {a: 3}]}"); ParsedMatchExpression eq4OrEq5("{$or: [{a: 4}, {a: 5}]}"); ParsedMatchExpression eq4OrEq6("{$or: [{a: 4}, {a: 6}]}"); ASSERT_TRUE(expression::isSubsetOf(eq2OrEq3.get(), lt5.get())); ASSERT_FALSE(expression::isSubsetOf(eq4OrEq5.get(), lt5.get())); ASSERT_FALSE(expression::isSubsetOf(eq4OrEq6.get(), lt5.get())); } TEST(ExpressionAlgoIsSubsetOf, CompareOr_GTE) { ParsedMatchExpression gte5("{a: {$gte: 5}}"); ParsedMatchExpression eq4OrEq6("{$or: [{a: 4}, {a: 6}]}"); ParsedMatchExpression eq5OrEq6("{$or: [{a: 5}, {a: 6}]}"); ParsedMatchExpression eq7OrEq8("{$or: [{a: 7}, {a: 8}]}"); ASSERT_FALSE(expression::isSubsetOf(eq4OrEq6.get(), gte5.get())); ASSERT_TRUE(expression::isSubsetOf(eq5OrEq6.get(), gte5.get())); ASSERT_TRUE(expression::isSubsetOf(eq7OrEq8.get(), gte5.get())); } TEST(ExpressionAlgoIsSubsetOf, DifferentCanonicalTypes) { ParsedMatchExpression number("{x: {$gt: 1}}"); ParsedMatchExpression string("{x: {$gt: 'a'}}"); ASSERT_FALSE(expression::isSubsetOf(number.get(), string.get())); ASSERT_FALSE(expression::isSubsetOf(string.get(), number.get())); } TEST(ExpressionAlgoIsSubsetOf, DifferentNumberTypes) { ParsedMatchExpression numberDouble("{x: 5.0}"); ParsedMatchExpression numberInt("{x: NumberInt(5)}"); ParsedMatchExpression numberLong("{x: NumberLong(5)}"); ASSERT_TRUE(expression::isSubsetOf(numberDouble.get(), numberInt.get())); ASSERT_TRUE(expression::isSubsetOf(numberDouble.get(), numberLong.get())); ASSERT_TRUE(expression::isSubsetOf(numberInt.get(), numberDouble.get())); ASSERT_TRUE(expression::isSubsetOf(numberInt.get(), numberLong.get())); ASSERT_TRUE(expression::isSubsetOf(numberLong.get(), numberDouble.get())); ASSERT_TRUE(expression::isSubsetOf(numberLong.get(), numberInt.get())); } TEST(ExpressionAlgoIsSubsetOf, PointInUnboundedRange) { ParsedMatchExpression a4("{a: 4}"); ParsedMatchExpression a5("{a: 5}"); ParsedMatchExpression a6("{a: 6}"); ParsedMatchExpression b5("{b: 5}"); ParsedMatchExpression lt5("{a: {$lt: 5}}"); ParsedMatchExpression lte5("{a: {$lte: 5}}"); ParsedMatchExpression gte5("{a: {$gte: 5}}"); ParsedMatchExpression gt5("{a: {$gt: 5}}"); ASSERT_TRUE(expression::isSubsetOf(a4.get(), lte5.get())); ASSERT_TRUE(expression::isSubsetOf(a5.get(), lte5.get())); ASSERT_FALSE(expression::isSubsetOf(a6.get(), lte5.get())); ASSERT_TRUE(expression::isSubsetOf(a4.get(), lt5.get())); ASSERT_FALSE(expression::isSubsetOf(a5.get(), lt5.get())); ASSERT_FALSE(expression::isSubsetOf(a6.get(), lt5.get())); ASSERT_FALSE(expression::isSubsetOf(a4.get(), gte5.get())); ASSERT_TRUE(expression::isSubsetOf(a5.get(), gte5.get())); ASSERT_TRUE(expression::isSubsetOf(a6.get(), gte5.get())); ASSERT_FALSE(expression::isSubsetOf(a4.get(), gt5.get())); ASSERT_FALSE(expression::isSubsetOf(a5.get(), gt5.get())); ASSERT_TRUE(expression::isSubsetOf(a6.get(), gt5.get())); // An unbounded range query does not match a subset of documents of a point query. ASSERT_FALSE(expression::isSubsetOf(lt5.get(), a5.get())); ASSERT_FALSE(expression::isSubsetOf(lte5.get(), a5.get())); ASSERT_FALSE(expression::isSubsetOf(gte5.get(), a5.get())); ASSERT_FALSE(expression::isSubsetOf(gt5.get(), a5.get())); // Cannot be a subset if comparing different field names. ASSERT_FALSE(expression::isSubsetOf(b5.get(), lt5.get())); ASSERT_FALSE(expression::isSubsetOf(b5.get(), lte5.get())); ASSERT_FALSE(expression::isSubsetOf(b5.get(), gte5.get())); ASSERT_FALSE(expression::isSubsetOf(b5.get(), gt5.get())); } TEST(ExpressionAlgoIsSubsetOf, PointInBoundedRange) { ParsedMatchExpression filter("{a: {$gt: 5, $lt: 10}}"); ParsedMatchExpression query("{a: 6}"); ASSERT_TRUE(expression::isSubsetOf(query.get(), filter.get())); ASSERT_FALSE(expression::isSubsetOf(filter.get(), query.get())); } TEST(ExpressionAlgoIsSubsetOf, PointInBoundedRange_FakeAnd) { ParsedMatchExpression filter("{a: {$gt: 5, $lt: 10}}"); ParsedMatchExpression query("{$and: [{a: 6}, {a: 6}]}"); ASSERT_TRUE(expression::isSubsetOf(query.get(), filter.get())); ASSERT_FALSE(expression::isSubsetOf(filter.get(), query.get())); } TEST(ExpressionAlgoIsSubsetOf, MultiplePointsInBoundedRange) { ParsedMatchExpression filter("{a: {$gt: 5, $lt: 10}}"); ParsedMatchExpression queryAllInside("{a: {$in: [6, 7, 8]}}"); ParsedMatchExpression queryStraddleLower("{a: {$in: [4.9, 5.1]}}"); ParsedMatchExpression queryStraddleUpper("{a: {$in: [9.9, 10.1]}}"); ASSERT_TRUE(expression::isSubsetOf(queryAllInside.get(), filter.get())); ASSERT_FALSE(expression::isSubsetOf(queryStraddleLower.get(), filter.get())); ASSERT_FALSE(expression::isSubsetOf(queryStraddleUpper.get(), filter.get())); } TEST(ExpressionAlgoIsSubsetOf, PointInCompoundRange) { ParsedMatchExpression filter("{a: {$gt: 5}, b: {$gt: 6}, c: {$gt: 7}}"); ParsedMatchExpression query("{a: 10, b: 10, c: 10}"); ASSERT_TRUE(expression::isSubsetOf(query.get(), filter.get())); ASSERT_FALSE(expression::isSubsetOf(filter.get(), query.get())); } TEST(ExpressionAlgoIsSubsetOf, Compare_LT_LTE) { ParsedMatchExpression lte4("{x: {$lte: 4}}"); ParsedMatchExpression lt5("{x: {$lt: 5}}"); ParsedMatchExpression lte5("{x: {$lte: 5}}"); ParsedMatchExpression lt6("{x: {$lt: 6}}"); ASSERT_TRUE(expression::isSubsetOf(lte4.get(), lte5.get())); ASSERT_TRUE(expression::isSubsetOf(lt5.get(), lte5.get())); ASSERT_TRUE(expression::isSubsetOf(lte5.get(), lte5.get())); ASSERT_FALSE(expression::isSubsetOf(lt6.get(), lte5.get())); ASSERT_TRUE(expression::isSubsetOf(lte4.get(), lt5.get())); ASSERT_TRUE(expression::isSubsetOf(lt5.get(), lt5.get())); ASSERT_FALSE(expression::isSubsetOf(lte5.get(), lt5.get())); ASSERT_FALSE(expression::isSubsetOf(lt6.get(), lt5.get())); } TEST(ExpressionAlgoIsSubsetOf, Compare_GT_GTE) { ParsedMatchExpression gte6("{x: {$gte: 6}}"); ParsedMatchExpression gt5("{x: {$gt: 5}}"); ParsedMatchExpression gte5("{x: {$gte: 5}}"); ParsedMatchExpression gt4("{x: {$gt: 4}}"); ASSERT_TRUE(expression::isSubsetOf(gte6.get(), gte5.get())); ASSERT_TRUE(expression::isSubsetOf(gt5.get(), gte5.get())); ASSERT_TRUE(expression::isSubsetOf(gte5.get(), gte5.get())); ASSERT_FALSE(expression::isSubsetOf(gt4.get(), gte5.get())); ASSERT_TRUE(expression::isSubsetOf(gte6.get(), gt5.get())); ASSERT_TRUE(expression::isSubsetOf(gt5.get(), gt5.get())); ASSERT_FALSE(expression::isSubsetOf(gte5.get(), gt5.get())); ASSERT_FALSE(expression::isSubsetOf(gt4.get(), gt5.get())); } TEST(ExpressionAlgoIsSubsetOf, BoundedRangeInUnboundedRange) { ParsedMatchExpression filter("{a: {$gt: 1}}"); ParsedMatchExpression query("{a: {$gt: 5, $lt: 10}}"); ASSERT_TRUE(expression::isSubsetOf(query.get(), filter.get())); ASSERT_FALSE(expression::isSubsetOf(filter.get(), query.get())); } TEST(ExpressionAlgoIsSubsetOf, MultipleRangesInUnboundedRange) { ParsedMatchExpression filter("{a: {$gt: 1}}"); ParsedMatchExpression negative("{$or: [{a: {$gt: 5, $lt: 10}}, {a: {$lt: 0}}]}"); ParsedMatchExpression unbounded("{$or: [{a: {$gt: 5, $lt: 10}}, {a: {$gt: 15}}]}"); ParsedMatchExpression bounded("{$or: [{a: {$gt: 5, $lt: 10}}, {a: {$gt: 20, $lt: 30}}]}"); ASSERT_FALSE(expression::isSubsetOf(negative.get(), filter.get())); ASSERT_TRUE(expression::isSubsetOf(unbounded.get(), filter.get())); ASSERT_TRUE(expression::isSubsetOf(bounded.get(), filter.get())); } TEST(ExpressionAlgoIsSubsetOf, MultipleFields) { ParsedMatchExpression filter("{a: {$gt: 5}, b: {$lt: 10}}"); ParsedMatchExpression onlyA("{$or: [{a: 6, b: {$lt: 4}}, {a: {$gt: 11}}]}"); ParsedMatchExpression onlyB("{$or: [{b: {$lt: 4}}, {a: {$gt: 11}, b: 9}]}"); ParsedMatchExpression both("{$or: [{a: 6, b: {$lt: 4}}, {a: {$gt: 11}, b: 9}]}"); ASSERT_FALSE(expression::isSubsetOf(onlyA.get(), filter.get())); ASSERT_FALSE(expression::isSubsetOf(onlyB.get(), filter.get())); ASSERT_TRUE(expression::isSubsetOf(both.get(), filter.get())); } TEST(ExpressionAlgoIsSubsetOf, Compare_LT_In) { ParsedMatchExpression lt("{a: {$lt: 5}}"); ParsedMatchExpression inLt("{a: {$in: [4.9]}}"); ParsedMatchExpression inEq("{a: {$in: [5]}}"); ParsedMatchExpression inGt("{a: {$in: [5.1]}}"); ParsedMatchExpression inNull("{a: {$in: [null]}}"); ParsedMatchExpression inAllEq("{a: {$in: [5, 5.0]}}"); ParsedMatchExpression inAllLte("{a: {$in: [4.9, 5]}}"); ParsedMatchExpression inAllLt("{a: {$in: [2, 3, 4]}}"); ParsedMatchExpression inStraddle("{a: {$in: [4, 6]}}"); ParsedMatchExpression inLtAndNull("{a: {$in: [1, null]}}"); ASSERT_TRUE(expression::isSubsetOf(inLt.get(), lt.get())); ASSERT_FALSE(expression::isSubsetOf(inEq.get(), lt.get())); ASSERT_FALSE(expression::isSubsetOf(inGt.get(), lt.get())); ASSERT_FALSE(expression::isSubsetOf(inAllEq.get(), lt.get())); ASSERT_FALSE(expression::isSubsetOf(inAllLte.get(), lt.get())); ASSERT_TRUE(expression::isSubsetOf(inAllLt.get(), lt.get())); ASSERT_FALSE(expression::isSubsetOf(inStraddle.get(), lt.get())); ASSERT_FALSE(expression::isSubsetOf(inLtAndNull.get(), lt.get())); } TEST(ExpressionAlgoIsSubsetOf, Compare_LTE_In) { ParsedMatchExpression lte("{a: {$lte: 5}}"); ParsedMatchExpression inLt("{a: {$in: [4.9]}}"); ParsedMatchExpression inEq("{a: {$in: [5]}}"); ParsedMatchExpression inGt("{a: {$in: [5.1]}}"); ParsedMatchExpression inNull("{a: {$in: [null]}}"); ParsedMatchExpression inAllEq("{a: {$in: [5, 5.0]}}"); ParsedMatchExpression inAllLte("{a: {$in: [4.9, 5]}}"); ParsedMatchExpression inAllLt("{a: {$in: [2, 3, 4]}}"); ParsedMatchExpression inStraddle("{a: {$in: [4, 6]}}"); ParsedMatchExpression inLtAndNull("{a: {$in: [1, null]}}"); ASSERT_TRUE(expression::isSubsetOf(inLt.get(), lte.get())); ASSERT_TRUE(expression::isSubsetOf(inEq.get(), lte.get())); ASSERT_FALSE(expression::isSubsetOf(inGt.get(), lte.get())); ASSERT_TRUE(expression::isSubsetOf(inAllEq.get(), lte.get())); ASSERT_TRUE(expression::isSubsetOf(inAllLte.get(), lte.get())); ASSERT_TRUE(expression::isSubsetOf(inAllLt.get(), lte.get())); ASSERT_FALSE(expression::isSubsetOf(inStraddle.get(), lte.get())); ASSERT_FALSE(expression::isSubsetOf(inLtAndNull.get(), lte.get())); } TEST(ExpressionAlgoIsSubsetOf, Compare_EQ_In) { ParsedMatchExpression eq("{a: 5}"); ParsedMatchExpression inLt("{a: {$in: [4.9]}}"); ParsedMatchExpression inEq("{a: {$in: [5]}}"); ParsedMatchExpression inGt("{a: {$in: [5.1]}}"); ParsedMatchExpression inNull("{a: {$in: [null]}}"); ParsedMatchExpression inAllEq("{a: {$in: [5, 5.0]}}"); ParsedMatchExpression inStraddle("{a: {$in: [4, 6]}}"); ParsedMatchExpression inEqAndNull("{a: {$in: [5, null]}}"); ASSERT_FALSE(expression::isSubsetOf(inLt.get(), eq.get())); ASSERT_TRUE(expression::isSubsetOf(inEq.get(), eq.get())); ASSERT_FALSE(expression::isSubsetOf(inGt.get(), eq.get())); ASSERT_TRUE(expression::isSubsetOf(inAllEq.get(), eq.get())); ASSERT_FALSE(expression::isSubsetOf(inStraddle.get(), eq.get())); ASSERT_FALSE(expression::isSubsetOf(inEqAndNull.get(), eq.get())); } TEST(ExpressionAlgoIsSubsetOf, Compare_GT_In) { ParsedMatchExpression gt("{a: {$gt: 5}}"); ParsedMatchExpression inLt("{a: {$in: [4.9]}}"); ParsedMatchExpression inEq("{a: {$in: [5]}}"); ParsedMatchExpression inGt("{a: {$in: [5.1]}}"); ParsedMatchExpression inNull("{a: {$in: [null]}}"); ParsedMatchExpression inAllEq("{a: {$in: [5, 5.0]}}"); ParsedMatchExpression inAllGte("{a: {$in: [5, 5.1]}}"); ParsedMatchExpression inAllGt("{a: {$in: [6, 7, 8]}}"); ParsedMatchExpression inStraddle("{a: {$in: [4, 6]}}"); ParsedMatchExpression inGtAndNull("{a: {$in: [9, null]}}"); ASSERT_FALSE(expression::isSubsetOf(inLt.get(), gt.get())); ASSERT_FALSE(expression::isSubsetOf(inEq.get(), gt.get())); ASSERT_TRUE(expression::isSubsetOf(inGt.get(), gt.get())); ASSERT_FALSE(expression::isSubsetOf(inAllEq.get(), gt.get())); ASSERT_FALSE(expression::isSubsetOf(inAllGte.get(), gt.get())); ASSERT_TRUE(expression::isSubsetOf(inAllGt.get(), gt.get())); ASSERT_FALSE(expression::isSubsetOf(inStraddle.get(), gt.get())); ASSERT_FALSE(expression::isSubsetOf(inGtAndNull.get(), gt.get())); } TEST(ExpressionAlgoIsSubsetOf, Compare_GTE_In) { ParsedMatchExpression gte("{a: {$gte: 5}}"); ParsedMatchExpression inLt("{a: {$in: [4.9]}}"); ParsedMatchExpression inEq("{a: {$in: [5]}}"); ParsedMatchExpression inGt("{a: {$in: [5.1]}}"); ParsedMatchExpression inNull("{a: {$in: [null]}}"); ParsedMatchExpression inAllEq("{a: {$in: [5, 5.0]}}"); ParsedMatchExpression inAllGte("{a: {$in: [5, 5.1]}}"); ParsedMatchExpression inAllGt("{a: {$in: [6, 7, 8]}}"); ParsedMatchExpression inStraddle("{a: {$in: [4, 6]}}"); ParsedMatchExpression inGtAndNull("{a: {$in: [9, null]}}"); ASSERT_FALSE(expression::isSubsetOf(inLt.get(), gte.get())); ASSERT_TRUE(expression::isSubsetOf(inEq.get(), gte.get())); ASSERT_TRUE(expression::isSubsetOf(inGt.get(), gte.get())); ASSERT_TRUE(expression::isSubsetOf(inAllEq.get(), gte.get())); ASSERT_TRUE(expression::isSubsetOf(inAllGte.get(), gte.get())); ASSERT_TRUE(expression::isSubsetOf(inAllGt.get(), gte.get())); ASSERT_FALSE(expression::isSubsetOf(inStraddle.get(), gte.get())); ASSERT_FALSE(expression::isSubsetOf(inGtAndNull.get(), gte.get())); } TEST(ExpressionAlgoIsSubsetOf, RegexAndIn) { ParsedMatchExpression eq1("{x: 1}"); ParsedMatchExpression eqA("{x: 'a'}"); ParsedMatchExpression inRegexA("{x: {$in: [/a/]}}"); ParsedMatchExpression inRegexAbc("{x: {$in: [/abc/]}}"); ParsedMatchExpression inRegexAOrEq1("{x: {$in: [/a/, 1]}}"); ParsedMatchExpression inRegexAOrNull("{x: {$in: [/a/, null]}}"); ASSERT_FALSE(expression::isSubsetOf(inRegexAOrEq1.get(), eq1.get())); ASSERT_FALSE(expression::isSubsetOf(inRegexA.get(), eqA.get())); ASSERT_FALSE(expression::isSubsetOf(inRegexAOrNull.get(), eqA.get())); } TEST(ExpressionAlgoIsSubsetOf, Exists) { ParsedMatchExpression aExists("{a: {$exists: true}}"); ParsedMatchExpression bExists("{b: {$exists: true}}"); ParsedMatchExpression aExistsBExists("{a: {$exists: true}, b: {$exists: true}}"); ParsedMatchExpression aExistsBExistsC5("{a: {$exists: true}, b: {$exists: true}, c: 5}"); ASSERT_TRUE(expression::isSubsetOf(aExists.get(), aExists.get())); ASSERT_FALSE(expression::isSubsetOf(aExists.get(), bExists.get())); ASSERT_TRUE(expression::isSubsetOf(aExistsBExists.get(), aExists.get())); ASSERT_TRUE(expression::isSubsetOf(aExistsBExists.get(), bExists.get())); ASSERT_FALSE(expression::isSubsetOf(aExistsBExists.get(), aExistsBExistsC5.get())); ASSERT_TRUE(expression::isSubsetOf(aExistsBExistsC5.get(), aExists.get())); ASSERT_TRUE(expression::isSubsetOf(aExistsBExistsC5.get(), bExists.get())); ASSERT_TRUE(expression::isSubsetOf(aExistsBExistsC5.get(), aExistsBExists.get())); } TEST(ExpressionAlgoIsSubsetOf, Compare_Exists) { ParsedMatchExpression exists("{a: {$exists: true}}"); ParsedMatchExpression eq("{a: 1}"); ParsedMatchExpression gt("{a: {$gt: 4}}"); ParsedMatchExpression lte("{a: {$lte: 7}}"); ASSERT_TRUE(expression::isSubsetOf(eq.get(), exists.get())); ASSERT_TRUE(expression::isSubsetOf(gt.get(), exists.get())); ASSERT_TRUE(expression::isSubsetOf(lte.get(), exists.get())); ASSERT_FALSE(expression::isSubsetOf(exists.get(), eq.get())); ASSERT_FALSE(expression::isSubsetOf(exists.get(), gt.get())); ASSERT_FALSE(expression::isSubsetOf(exists.get(), lte.get())); } TEST(ExpressionAlgoIsSubsetOf, Type) { ParsedMatchExpression aType1("{a: {$type: 1}}"); ParsedMatchExpression aType2("{a: {$type: 2}}"); ParsedMatchExpression bType2("{b: {$type: 2}}"); ASSERT_FALSE(expression::isSubsetOf(aType1.get(), aType2.get())); ASSERT_FALSE(expression::isSubsetOf(aType2.get(), aType1.get())); ASSERT_TRUE(expression::isSubsetOf(aType2.get(), aType2.get())); ASSERT_FALSE(expression::isSubsetOf(aType2.get(), bType2.get())); } TEST(ExpressionAlgoIsSubsetOf, TypeAndExists) { ParsedMatchExpression aExists("{a: {$exists: true}}"); ParsedMatchExpression aType2("{a: {$type: 2}}"); ParsedMatchExpression bType2("{b: {$type: 2}}"); ASSERT_TRUE(expression::isSubsetOf(aType2.get(), aExists.get())); ASSERT_FALSE(expression::isSubsetOf(aExists.get(), aType2.get())); ASSERT_FALSE(expression::isSubsetOf(bType2.get(), aExists.get())); } TEST(ExpressionAlgoIsSubsetOf, AllAndExists) { ParsedMatchExpression aExists("{a: {$exists: true}}"); ParsedMatchExpression aAll("{a: {$all: ['x', 'y', 'z']}}"); ParsedMatchExpression bAll("{b: {$all: ['x', 'y', 'z']}}"); ParsedMatchExpression aAllWithNull("{a: {$all: ['x', null, 'z']}}"); ASSERT_TRUE(expression::isSubsetOf(aAll.get(), aExists.get())); ASSERT_FALSE(expression::isSubsetOf(bAll.get(), aExists.get())); ASSERT_TRUE(expression::isSubsetOf(aAllWithNull.get(), aExists.get())); } TEST(ExpressionAlgoIsSubsetOf, ElemMatchAndExists_Value) { ParsedMatchExpression aExists("{a: {$exists: true}}"); ParsedMatchExpression aElemMatch("{a: {$elemMatch: {$gt: 5, $lte: 10}}}"); ParsedMatchExpression bElemMatch("{b: {$elemMatch: {$gt: 5, $lte: 10}}}"); ParsedMatchExpression aElemMatchNull("{a: {$elemMatch: {$eq: null}}}"); ASSERT_TRUE(expression::isSubsetOf(aElemMatch.get(), aExists.get())); ASSERT_FALSE(expression::isSubsetOf(aExists.get(), aElemMatch.get())); ASSERT_FALSE(expression::isSubsetOf(bElemMatch.get(), aExists.get())); ASSERT_TRUE(expression::isSubsetOf(aElemMatchNull.get(), aExists.get())); } TEST(ExpressionAlgoIsSubsetOf, ElemMatchAndExists_Object) { ParsedMatchExpression aExists("{a: {$exists: true}}"); ParsedMatchExpression aElemMatch("{a: {$elemMatch: {x: {$gt: 5}, y: {$lte: 10}}}}"); ParsedMatchExpression bElemMatch("{b: {$elemMatch: {x: {$gt: 5}, y: {$lte: 10}}}}"); ParsedMatchExpression aElemMatchNull("{a: {$elemMatch: {x: null, y: null}}}"); ASSERT_TRUE(expression::isSubsetOf(aElemMatch.get(), aExists.get())); ASSERT_FALSE(expression::isSubsetOf(aExists.get(), aElemMatch.get())); ASSERT_FALSE(expression::isSubsetOf(bElemMatch.get(), aExists.get())); ASSERT_TRUE(expression::isSubsetOf(aElemMatchNull.get(), aExists.get())); } TEST(ExpressionAlgoIsSubsetOf, SizeAndExists) { ParsedMatchExpression aExists("{a: {$exists: true}}"); ParsedMatchExpression aSize0("{a: {$size: 0}}"); ParsedMatchExpression aSize1("{a: {$size: 1}}"); ParsedMatchExpression aSize3("{a: {$size: 3}}"); ParsedMatchExpression bSize3("{b: {$size: 3}}"); ASSERT_TRUE(expression::isSubsetOf(aSize0.get(), aExists.get())); ASSERT_TRUE(expression::isSubsetOf(aSize1.get(), aExists.get())); ASSERT_TRUE(expression::isSubsetOf(aSize3.get(), aExists.get())); ASSERT_FALSE(expression::isSubsetOf(aExists.get(), aSize3.get())); ASSERT_FALSE(expression::isSubsetOf(bSize3.get(), aExists.get())); } TEST(ExpressionAlgoIsSubsetOf, ModAndExists) { ParsedMatchExpression aExists("{a: {$exists: true}}"); ParsedMatchExpression aMod5("{a: {$mod: [5, 0]}}"); ParsedMatchExpression bMod5("{b: {$mod: [5, 0]}}"); ASSERT_TRUE(expression::isSubsetOf(aMod5.get(), aExists.get())); ASSERT_FALSE(expression::isSubsetOf(bMod5.get(), aExists.get())); } TEST(ExpressionAlgoIsSubsetOf, RegexAndExists) { ParsedMatchExpression aExists("{a: {$exists: true}}"); ParsedMatchExpression aRegex("{a: {$regex: 'pattern'}}"); ParsedMatchExpression bRegex("{b: {$regex: 'pattern'}}"); ASSERT_TRUE(expression::isSubsetOf(aRegex.get(), aExists.get())); ASSERT_FALSE(expression::isSubsetOf(bRegex.get(), aExists.get())); } TEST(ExpressionAlgoIsSubsetOf, InAndExists) { ParsedMatchExpression aExists("{a: {$exists: true}}"); ParsedMatchExpression aIn("{a: {$in: [1, 2, 3]}}"); ParsedMatchExpression bIn("{b: {$in: [1, 2, 3]}}"); ParsedMatchExpression aInWithNull("{a: {$in: [1, null, 3]}}"); ASSERT_TRUE(expression::isSubsetOf(aIn.get(), aExists.get())); ASSERT_FALSE(expression::isSubsetOf(bIn.get(), aExists.get())); ASSERT_FALSE(expression::isSubsetOf(aInWithNull.get(), aExists.get())); } TEST(ExpressionAlgoIsSubsetOf, NinAndExists) { ParsedMatchExpression aExists("{a: {$exists: true}}"); ParsedMatchExpression aNin("{a: {$nin: [1, 2, 3]}}"); ParsedMatchExpression bNin("{b: {$nin: [1, 2, 3]}}"); ParsedMatchExpression aNinWithNull("{a: {$nin: [1, null, 3]}}"); ASSERT_FALSE(expression::isSubsetOf(aNin.get(), aExists.get())); ASSERT_FALSE(expression::isSubsetOf(bNin.get(), aExists.get())); ASSERT_TRUE(expression::isSubsetOf(aNinWithNull.get(), aExists.get())); } TEST(ExpressionAlgoIsSubsetOf, Compare_Exists_NE) { ParsedMatchExpression aExists("{a: {$exists: true}}"); ParsedMatchExpression aNotEqual1("{a: {$ne: 1}}"); ParsedMatchExpression bNotEqual1("{b: {$ne: 1}}"); ParsedMatchExpression aNotEqualNull("{a: {$ne: null}}"); ASSERT_FALSE(expression::isSubsetOf(aNotEqual1.get(), aExists.get())); ASSERT_FALSE(expression::isSubsetOf(bNotEqual1.get(), aExists.get())); ASSERT_TRUE(expression::isSubsetOf(aNotEqualNull.get(), aExists.get())); } TEST(ExpressionAlgoIsSubsetOf, CollationAwareStringComparison) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); ParsedMatchExpression lhs("{a: {$gt: 'abc'}}", &collator); ParsedMatchExpression rhs("{a: {$gt: 'cba'}}", &collator); ASSERT_TRUE(expression::isSubsetOf(lhs.get(), rhs.get())); ParsedMatchExpression lhsLT("{a: {$lt: 'abc'}}", &collator); ParsedMatchExpression rhsLT("{a: {$lt: 'cba'}}", &collator); ASSERT_FALSE(expression::isSubsetOf(lhsLT.get(), rhsLT.get())); } TEST(ExpressionAlgoIsSubsetOf, NonMatchingCollationsStringComparison) { CollatorInterfaceMock collatorAlwaysEqual(CollatorInterfaceMock::MockType::kAlwaysEqual); CollatorInterfaceMock collatorReverseString(CollatorInterfaceMock::MockType::kReverseString); ParsedMatchExpression lhs("{a: {$gt: 'abc'}}", &collatorAlwaysEqual); ParsedMatchExpression rhs("{a: {$gt: 'cba'}}", &collatorReverseString); ASSERT_FALSE(expression::isSubsetOf(lhs.get(), rhs.get())); ParsedMatchExpression lhsLT("{a: {$lt: 'abc'}}", &collatorAlwaysEqual); ParsedMatchExpression rhsLT("{a: {$lt: 'cba'}}", &collatorReverseString); ASSERT_FALSE(expression::isSubsetOf(lhsLT.get(), rhsLT.get())); } TEST(ExpressionAlgoIsSubsetOf, CollationAwareStringComparisonIn) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); ParsedMatchExpression lhsAllGTcba("{a: {$in: ['abc', 'cbc']}}", &collator); ParsedMatchExpression lhsSomeGTcba("{a: {$in: ['abc', 'aba']}}", &collator); ParsedMatchExpression rhs("{a: {$gt: 'cba'}}", &collator); ASSERT_TRUE(expression::isSubsetOf(lhsAllGTcba.get(), rhs.get())); ASSERT_FALSE(expression::isSubsetOf(lhsSomeGTcba.get(), rhs.get())); ParsedMatchExpression rhsLT("{a: {$lt: 'cba'}}", &collator); ASSERT_FALSE(expression::isSubsetOf(lhsAllGTcba.get(), rhsLT.get())); ASSERT_FALSE(expression::isSubsetOf(lhsSomeGTcba.get(), rhsLT.get())); } // TODO SERVER-24674: isSubsetOf should return true after exploring nested objects. TEST(ExpressionAlgoIsSubsetOf, NonMatchingCollationsNoStringComparisonLHS) { CollatorInterfaceMock collatorAlwaysEqual(CollatorInterfaceMock::MockType::kAlwaysEqual); CollatorInterfaceMock collatorReverseString(CollatorInterfaceMock::MockType::kReverseString); ParsedMatchExpression lhs("{a: {b: 1}}", &collatorAlwaysEqual); ParsedMatchExpression rhs("{a: {$lt: {b: 'abc'}}}", &collatorReverseString); ASSERT_FALSE(expression::isSubsetOf(lhs.get(), rhs.get())); } TEST(ExpressionAlgoIsSubsetOf, NonMatchingCollationsNoStringComparison) { CollatorInterfaceMock collatorAlwaysEqual(CollatorInterfaceMock::MockType::kAlwaysEqual); CollatorInterfaceMock collatorReverseString(CollatorInterfaceMock::MockType::kReverseString); ParsedMatchExpression lhs("{a: 1}", &collatorAlwaysEqual); ParsedMatchExpression rhs("{a: {$gt: 0}}", &collatorReverseString); ASSERT_TRUE(expression::isSubsetOf(lhs.get(), rhs.get())); } TEST(IsIndependent, AndIsIndependentOnlyIfChildrenAre) { BSONObj matchPredicate = fromjson("{$and: [{a: 1}, {b: 1}]}"); const CollatorInterface* collator = nullptr; StatusWithMatchExpression status = MatchExpressionParser::parse(matchPredicate, ExtensionsCallbackNoop(), collator); ASSERT_OK(status.getStatus()); unique_ptr expr = std::move(status.getValue()); ASSERT_FALSE(expression::isIndependentOf(*expr.get(), {"b"})); ASSERT_TRUE(expression::isIndependentOf(*expr.get(), {"c"})); } TEST(IsIndependent, ElemMatchIsNotIndependent) { BSONObj matchPredicate = fromjson("{x: {$elemMatch: {y: 1}}}"); const CollatorInterface* collator = nullptr; StatusWithMatchExpression status = MatchExpressionParser::parse(matchPredicate, ExtensionsCallbackNoop(), collator); ASSERT_OK(status.getStatus()); unique_ptr expr = std::move(status.getValue()); ASSERT_FALSE(expression::isIndependentOf(*expr.get(), {"x"})); ASSERT_FALSE(expression::isIndependentOf(*expr.get(), {"x.y"})); ASSERT_FALSE(expression::isIndependentOf(*expr.get(), {"y"})); } TEST(IsIndependent, NorIsIndependentOnlyIfChildrenAre) { BSONObj matchPredicate = fromjson("{$nor: [{a: 1}, {b: 1}]}"); const CollatorInterface* collator = nullptr; StatusWithMatchExpression status = MatchExpressionParser::parse(matchPredicate, ExtensionsCallbackNoop(), collator); ASSERT_OK(status.getStatus()); unique_ptr expr = std::move(status.getValue()); ASSERT_FALSE(expression::isIndependentOf(*expr.get(), {"b"})); ASSERT_TRUE(expression::isIndependentOf(*expr.get(), {"c"})); } TEST(IsIndependent, NotIsIndependentOnlyIfChildrenAre) { BSONObj matchPredicate = fromjson("{a: {$not: {$eq: 1}}}"); const CollatorInterface* collator = nullptr; StatusWithMatchExpression status = MatchExpressionParser::parse(matchPredicate, ExtensionsCallbackNoop(), collator); ASSERT_OK(status.getStatus()); unique_ptr expr = std::move(status.getValue()); ASSERT_TRUE(expression::isIndependentOf(*expr.get(), {"b"})); ASSERT_FALSE(expression::isIndependentOf(*expr.get(), {"a"})); } TEST(IsIndependent, OrIsIndependentOnlyIfChildrenAre) { BSONObj matchPredicate = fromjson("{$or: [{a: 1}, {b: 1}]}"); const CollatorInterface* collator = nullptr; StatusWithMatchExpression status = MatchExpressionParser::parse(matchPredicate, ExtensionsCallbackNoop(), collator); ASSERT_OK(status.getStatus()); unique_ptr expr = std::move(status.getValue()); ASSERT_FALSE(expression::isIndependentOf(*expr.get(), {"a"})); ASSERT_TRUE(expression::isIndependentOf(*expr.get(), {"c"})); } TEST(IsIndependent, AndWithDottedFieldPathsIsNotIndependent) { BSONObj matchPredicate = fromjson("{$and: [{'a': 1}, {'a.b': 1}]}"); const CollatorInterface* collator = nullptr; StatusWithMatchExpression status = MatchExpressionParser::parse(matchPredicate, ExtensionsCallbackNoop(), collator); ASSERT_OK(status.getStatus()); unique_ptr expr = std::move(status.getValue()); ASSERT_FALSE(expression::isIndependentOf(*expr.get(), {"a.b.c"})); ASSERT_FALSE(expression::isIndependentOf(*expr.get(), {"a.b"})); } TEST(IsIndependent, BallIsIndependentOfBalloon) { BSONObj matchPredicate = fromjson("{'a.ball': 4}"); const CollatorInterface* collator = nullptr; StatusWithMatchExpression status = MatchExpressionParser::parse(matchPredicate, ExtensionsCallbackNoop(), collator); ASSERT_OK(status.getStatus()); unique_ptr expr = std::move(status.getValue()); ASSERT_TRUE(expression::isIndependentOf(*expr.get(), {"a.balloon"})); ASSERT_TRUE(expression::isIndependentOf(*expr.get(), {"a.b"})); ASSERT_FALSE(expression::isIndependentOf(*expr.get(), {"a.ball.c"})); } TEST(SplitMatchExpression, AndWithSplittableChildrenIsSplittable) { BSONObj matchPredicate = fromjson("{$and: [{a: 1}, {b: 1}]}"); const CollatorInterface* collator = nullptr; StatusWithMatchExpression status = MatchExpressionParser::parse(matchPredicate, ExtensionsCallbackNoop(), collator); ASSERT_OK(status.getStatus()); std::pair, unique_ptr> splitExpr = expression::splitMatchExpressionBy(std::move(status.getValue()), {"b"}, {}); ASSERT_TRUE(splitExpr.first.get()); BSONObjBuilder firstBob; splitExpr.first->serialize(&firstBob); ASSERT_TRUE(splitExpr.second.get()); BSONObjBuilder secondBob; splitExpr.second->serialize(&secondBob); ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{a: {$eq: 1}}")); ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{b: {$eq: 1}}")); } TEST(SplitMatchExpression, NorWithIndependentChildrenIsSplittable) { BSONObj matchPredicate = fromjson("{$nor: [{a: 1}, {b: 1}]}"); const CollatorInterface* collator = nullptr; StatusWithMatchExpression status = MatchExpressionParser::parse(matchPredicate, ExtensionsCallbackNoop(), collator); ASSERT_OK(status.getStatus()); std::pair, unique_ptr> splitExpr = expression::splitMatchExpressionBy(std::move(status.getValue()), {"b"}, {}); ASSERT_TRUE(splitExpr.first.get()); BSONObjBuilder firstBob; splitExpr.first->serialize(&firstBob); ASSERT_TRUE(splitExpr.second.get()); BSONObjBuilder secondBob; splitExpr.second->serialize(&secondBob); ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{$nor: [{a: {$eq: 1}}]}")); ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{$nor: [{b: {$eq: 1}}]}")); } TEST(SplitMatchExpression, NotWithIndependentChildIsSplittable) { BSONObj matchPredicate = fromjson("{x: {$not: {$gt: 4}}}"); const CollatorInterface* collator = nullptr; StatusWithMatchExpression status = MatchExpressionParser::parse(matchPredicate, ExtensionsCallbackNoop(), collator); ASSERT_OK(status.getStatus()); std::pair, unique_ptr> splitExpr = expression::splitMatchExpressionBy(std::move(status.getValue()), {"y"}, {}); ASSERT_TRUE(splitExpr.first.get()); BSONObjBuilder firstBob; splitExpr.first->serialize(&firstBob); ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{$nor: [{$and: [{x: {$gt: 4}}]}]}")); ASSERT_FALSE(splitExpr.second); } TEST(SplitMatchExpression, OrWithOnlyIndependentChildrenIsNotSplittable) { BSONObj matchPredicate = fromjson("{$or: [{a: 1}, {b: 1}]}"); const CollatorInterface* collator = nullptr; StatusWithMatchExpression status = MatchExpressionParser::parse(matchPredicate, ExtensionsCallbackNoop(), collator); ASSERT_OK(status.getStatus()); std::pair, unique_ptr> splitExpr = expression::splitMatchExpressionBy(std::move(status.getValue()), {"b"}, {}); ASSERT_TRUE(splitExpr.second.get()); BSONObjBuilder bob; splitExpr.second->serialize(&bob); ASSERT_FALSE(splitExpr.first); ASSERT_BSONOBJ_EQ(bob.obj(), fromjson("{$or: [{a: {$eq: 1}}, {b: {$eq: 1}}]}")); } TEST(SplitMatchExpression, ComplexMatchExpressionSplitsCorrectly) { BSONObj matchPredicate = fromjson( "{$and: [{x: {$not: {$size: 2}}}," "{$or: [{'a.b' : 3}, {'a.b.c': 4}]}," "{$nor: [{x: {$gt: 4}}, {$and: [{x: {$not: {$eq: 1}}}, {y: 3}]}]}]}"); const CollatorInterface* collator = nullptr; StatusWithMatchExpression status = MatchExpressionParser::parse(matchPredicate, ExtensionsCallbackNoop(), collator); ASSERT_OK(status.getStatus()); std::pair, unique_ptr> splitExpr = expression::splitMatchExpressionBy(std::move(status.getValue()), {"x"}, {}); ASSERT_TRUE(splitExpr.first.get()); BSONObjBuilder firstBob; splitExpr.first->serialize(&firstBob); ASSERT_TRUE(splitExpr.second.get()); BSONObjBuilder secondBob; splitExpr.second->serialize(&secondBob); ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{$or: [{'a.b': {$eq: 3}}, {'a.b.c': {$eq: 4}}]}")); ASSERT_BSONOBJ_EQ( secondBob.obj(), fromjson("{$and: [{$nor: [{$and: [{x: {$size: 2}}]}]}, {$nor: [{x: {$gt: 4}}, {$and: " "[{$nor: [{$and: [{x: " "{$eq: 1}}]}]}, {y: {$eq: 3}}]}]}]}")); } TEST(SplitMatchExpression, ShouldNotExtractPrefixOfDottedPathAsIndependent) { BSONObj matchPredicate = fromjson("{$and: [{a: 1}, {'a.b': 1}, {'a.c': 1}]}"); const CollatorInterface* collator = nullptr; StatusWithMatchExpression status = MatchExpressionParser::parse(matchPredicate, ExtensionsCallbackNoop(), collator); ASSERT_OK(status.getStatus()); std::pair, unique_ptr> splitExpr = expression::splitMatchExpressionBy(std::move(status.getValue()), {"a.b"}, {}); ASSERT_TRUE(splitExpr.first.get()); BSONObjBuilder firstBob; splitExpr.first->serialize(&firstBob); ASSERT_TRUE(splitExpr.second.get()); BSONObjBuilder secondBob; splitExpr.second->serialize(&secondBob); ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{'a.c': {$eq: 1}}")); ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{$and: [{a: {$eq: 1}}, {'a.b': {$eq: 1}}]}")); } TEST(SplitMatchExpression, ShouldMoveIndependentLeafPredicateAcrossRename) { BSONObj matchPredicate = fromjson("{a: 1}"); const CollatorInterface* collator = nullptr; auto matcher = MatchExpressionParser::parse(matchPredicate, ExtensionsCallbackNoop(), collator); ASSERT_OK(matcher.getStatus()); StringMap renames{{"a", "b"}}; std::pair, unique_ptr> splitExpr = expression::splitMatchExpressionBy(std::move(matcher.getValue()), {}, renames); ASSERT_TRUE(splitExpr.first.get()); BSONObjBuilder firstBob; splitExpr.first->serialize(&firstBob); ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{b: {$eq: 1}}")); ASSERT_FALSE(splitExpr.second.get()); } TEST(SplitMatchExpression, ShouldMoveIndependentAndPredicateAcrossRename) { BSONObj matchPredicate = fromjson("{$and: [{a: 1}, {b: 2}]}"); const CollatorInterface* collator = nullptr; auto matcher = MatchExpressionParser::parse(matchPredicate, ExtensionsCallbackNoop(), collator); ASSERT_OK(matcher.getStatus()); StringMap renames{{"a", "c"}}; std::pair, unique_ptr> splitExpr = expression::splitMatchExpressionBy(std::move(matcher.getValue()), {}, renames); ASSERT_TRUE(splitExpr.first.get()); BSONObjBuilder firstBob; splitExpr.first->serialize(&firstBob); ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{$and: [{c: {$eq: 1}}, {b: {$eq: 2}}]}")); ASSERT_FALSE(splitExpr.second.get()); } TEST(SplitMatchExpression, ShouldSplitPartiallyDependentAndPredicateAcrossRename) { BSONObj matchPredicate = fromjson("{$and: [{a: 1}, {b: 2}]}"); const CollatorInterface* collator = nullptr; auto matcher = MatchExpressionParser::parse(matchPredicate, ExtensionsCallbackNoop(), collator); ASSERT_OK(matcher.getStatus()); StringMap renames{{"a", "c"}}; std::pair, unique_ptr> splitExpr = expression::splitMatchExpressionBy(std::move(matcher.getValue()), {"b"}, renames); ASSERT_TRUE(splitExpr.first.get()); BSONObjBuilder firstBob; splitExpr.first->serialize(&firstBob); ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{c: {$eq: 1}}")); ASSERT_TRUE(splitExpr.second.get()); BSONObjBuilder secondBob; splitExpr.second->serialize(&secondBob); ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{b: {$eq: 2}}")); } TEST(SplitMatchExpression, ShouldSplitPartiallyDependentComplexPredicateMultipleRenames) { BSONObj matchPredicate = fromjson("{$and: [{a: 1}, {$or: [{b: 2}, {c: 3}]}]}"); const CollatorInterface* collator = nullptr; auto matcher = MatchExpressionParser::parse(matchPredicate, ExtensionsCallbackNoop(), collator); ASSERT_OK(matcher.getStatus()); StringMap renames{{"b", "d"}, {"c", "e"}}; std::pair, unique_ptr> splitExpr = expression::splitMatchExpressionBy(std::move(matcher.getValue()), {"a"}, renames); ASSERT_TRUE(splitExpr.first.get()); BSONObjBuilder firstBob; splitExpr.first->serialize(&firstBob); ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{$or: [{d: {$eq: 2}}, {e: {$eq: 3}}]}")); ASSERT_TRUE(splitExpr.second.get()); BSONObjBuilder secondBob; splitExpr.second->serialize(&secondBob); ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{a: {$eq: 1}}")); } TEST(SplitMatchExpression, ShouldSplitPartiallyDependentComplexPredicateMultipleRenamesDottedPaths) { BSONObj matchPredicate = fromjson("{$and: [{a: 1}, {$or: [{'d.e.f': 2}, {'e.f.g': 3}]}]}"); const CollatorInterface* collator = nullptr; auto matcher = MatchExpressionParser::parse(matchPredicate, ExtensionsCallbackNoop(), collator); ASSERT_OK(matcher.getStatus()); StringMap renames{{"d.e.f", "x"}, {"e.f.g", "y"}}; std::pair, unique_ptr> splitExpr = expression::splitMatchExpressionBy(std::move(matcher.getValue()), {"a"}, renames); ASSERT_TRUE(splitExpr.first.get()); BSONObjBuilder firstBob; splitExpr.first->serialize(&firstBob); ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{$or: [{x: {$eq: 2}}, {y: {$eq: 3}}]}")); ASSERT_TRUE(splitExpr.second.get()); BSONObjBuilder secondBob; splitExpr.second->serialize(&secondBob); ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{a: {$eq: 1}}")); } TEST(SplitMatchExpression, ShouldNotMoveElemMatchObjectAcrossRename) { BSONObj matchPredicate = fromjson("{a: {$elemMatch: {b: 3}}}"); const CollatorInterface* collator = nullptr; auto matcher = MatchExpressionParser::parse(matchPredicate, ExtensionsCallbackNoop(), collator); ASSERT_OK(matcher.getStatus()); StringMap renames{{"a", "c"}}; std::pair, unique_ptr> splitExpr = expression::splitMatchExpressionBy(std::move(matcher.getValue()), {}, renames); ASSERT_FALSE(splitExpr.first.get()); ASSERT_TRUE(splitExpr.second.get()); BSONObjBuilder secondBob; splitExpr.second->serialize(&secondBob); ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{a: {$elemMatch: {b: {$eq: 3}}}}")); } TEST(SplitMatchExpression, ShouldNotMoveElemMatchValueAcrossRename) { BSONObj matchPredicate = fromjson("{a: {$elemMatch: {$eq: 3}}}"); const CollatorInterface* collator = nullptr; auto matcher = MatchExpressionParser::parse(matchPredicate, ExtensionsCallbackNoop(), collator); ASSERT_OK(matcher.getStatus()); StringMap renames{{"a", "c"}}; std::pair, unique_ptr> splitExpr = expression::splitMatchExpressionBy(std::move(matcher.getValue()), {}, renames); ASSERT_FALSE(splitExpr.first.get()); ASSERT_TRUE(splitExpr.second.get()); BSONObjBuilder secondBob; splitExpr.second->serialize(&secondBob); ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{a: {$elemMatch: {$eq: 3}}}")); } TEST(SplitMatchExpression, ShouldMoveTypeAcrossRename) { BSONObj matchPredicate = fromjson("{a: {$type: 16}}"); const CollatorInterface* collator = nullptr; auto matcher = MatchExpressionParser::parse(matchPredicate, ExtensionsCallbackNoop(), collator); ASSERT_OK(matcher.getStatus()); StringMap renames{{"a", "c"}}; std::pair, unique_ptr> splitExpr = expression::splitMatchExpressionBy(std::move(matcher.getValue()), {}, renames); ASSERT_TRUE(splitExpr.first.get()); BSONObjBuilder firstBob; splitExpr.first->serialize(&firstBob); ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{c: {$type: 16}}")); ASSERT_FALSE(splitExpr.second.get()); } TEST(SplitMatchExpression, ShouldNotMoveSizeAcrossRename) { BSONObj matchPredicate = fromjson("{a: {$size: 3}}"); const CollatorInterface* collator = nullptr; auto matcher = MatchExpressionParser::parse(matchPredicate, ExtensionsCallbackNoop(), collator); ASSERT_OK(matcher.getStatus()); StringMap renames{{"a", "c"}}; std::pair, unique_ptr> splitExpr = expression::splitMatchExpressionBy(std::move(matcher.getValue()), {}, renames); ASSERT_FALSE(splitExpr.first.get()); ASSERT_TRUE(splitExpr.second.get()); BSONObjBuilder secondBob; splitExpr.second->serialize(&secondBob); ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{a: {$size: 3}}")); } TEST(SplitMatchExpression, ShouldNotMoveMinItemsAcrossRename) { BSONObj matchPredicate = fromjson("{a: {$_internalSchemaMinItems: 3}}"); const CollatorInterface* collator = nullptr; auto matcher = MatchExpressionParser::parse(matchPredicate, ExtensionsCallbackNoop(), collator); ASSERT_OK(matcher.getStatus()); StringMap renames{{"a", "c"}}; std::pair, unique_ptr> splitExpr = expression::splitMatchExpressionBy(std::move(matcher.getValue()), {}, renames); ASSERT_FALSE(splitExpr.first.get()); ASSERT_TRUE(splitExpr.second.get()); BSONObjBuilder secondBob; splitExpr.second->serialize(&secondBob); ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{a: {$_internalSchemaMinItems: 3}}")); } TEST(SplitMatchExpression, ShouldNotMoveMaxItemsAcrossRename) { BSONObj matchPredicate = fromjson("{a: {$_internalSchemaMaxItems: 3}}"); const CollatorInterface* collator = nullptr; auto matcher = MatchExpressionParser::parse(matchPredicate, ExtensionsCallbackNoop(), collator); ASSERT_OK(matcher.getStatus()); StringMap renames{{"a", "c"}}; std::pair, unique_ptr> splitExpr = expression::splitMatchExpressionBy(std::move(matcher.getValue()), {}, renames); ASSERT_FALSE(splitExpr.first.get()); ASSERT_TRUE(splitExpr.second.get()); BSONObjBuilder secondBob; splitExpr.second->serialize(&secondBob); ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{a: {$_internalSchemaMaxItems: 3}}")); } TEST(SplitMatchExpression, ShouldMoveMinLengthAcrossRename) { BSONObj matchPredicate = fromjson("{a: {$_internalSchemaMinLength: 3}}"); const CollatorInterface* collator = nullptr; auto matcher = MatchExpressionParser::parse(matchPredicate, ExtensionsCallbackNoop(), collator); ASSERT_OK(matcher.getStatus()); StringMap renames{{"a", "c"}}; std::pair, unique_ptr> splitExpr = expression::splitMatchExpressionBy(std::move(matcher.getValue()), {}, renames); ASSERT_TRUE(splitExpr.first.get()); BSONObjBuilder firstBob; splitExpr.first->serialize(&firstBob); ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{c: {$_internalSchemaMinLength: 3}}")); ASSERT_FALSE(splitExpr.second.get()); } TEST(SplitMatchExpression, ShouldMoveMaxLengthAcrossRename) { BSONObj matchPredicate = fromjson("{a: {$_internalSchemaMaxLength: 3}}"); const CollatorInterface* collator = nullptr; auto matcher = MatchExpressionParser::parse(matchPredicate, ExtensionsCallbackNoop(), collator); ASSERT_OK(matcher.getStatus()); StringMap renames{{"a", "c"}}; std::pair, unique_ptr> splitExpr = expression::splitMatchExpressionBy(std::move(matcher.getValue()), {}, renames); ASSERT_TRUE(splitExpr.first.get()); BSONObjBuilder firstBob; splitExpr.first->serialize(&firstBob); ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{c: {$_internalSchemaMaxLength: 3}}")); ASSERT_FALSE(splitExpr.second.get()); } TEST(MapOverMatchExpression, DoesMapOverLogicalNodes) { BSONObj matchPredicate = fromjson("{a: {$not: {$eq: 1}}}"); const CollatorInterface* collator = nullptr; auto swMatchExpression = MatchExpressionParser::parse(matchPredicate, ExtensionsCallbackNoop(), collator); ASSERT_OK(swMatchExpression.getStatus()); bool hasLogicalNode = false; expression::mapOver(swMatchExpression.getValue().get(), [&hasLogicalNode](MatchExpression* expression, std::string path) -> void { if (expression->getCategory() == MatchExpression::MatchCategory::kLogical) { hasLogicalNode = true; } }); ASSERT_TRUE(hasLogicalNode); } TEST(MapOverMatchExpression, DoesMapOverLeafNodes) { BSONObj matchPredicate = fromjson("{a: {$not: {$eq: 1}}}"); const CollatorInterface* collator = nullptr; auto swMatchExpression = MatchExpressionParser::parse(matchPredicate, ExtensionsCallbackNoop(), collator); ASSERT_OK(swMatchExpression.getStatus()); bool hasLeafNode = false; expression::mapOver(swMatchExpression.getValue().get(), [&hasLeafNode](MatchExpression* expression, std::string path) -> void { if (expression->getCategory() != MatchExpression::MatchCategory::kLogical) { hasLeafNode = true; } }); ASSERT_TRUE(hasLeafNode); } TEST(MapOverMatchExpression, DoesPassPath) { BSONObj matchPredicate = fromjson("{a: {$elemMatch: {b: 1}}}"); const CollatorInterface* collator = nullptr; auto swMatchExpression = MatchExpressionParser::parse(matchPredicate, ExtensionsCallbackNoop(), collator); ASSERT_OK(swMatchExpression.getStatus()); std::vector paths; expression::mapOver(swMatchExpression.getValue().get(), [&paths](MatchExpression* expression, std::string path) -> void { if (!expression->numChildren()) { paths.push_back(path); } }); ASSERT_EQ(paths.size(), 1U); ASSERT_EQ(paths[0], "a.b"); } TEST(MapOverMatchExpression, DoesMapOverNodesWithMultipleChildren) { BSONObj matchPredicate = fromjson("{$and: [{a: {$gt: 1}}, {b: {$lte: 2}}]}"); const CollatorInterface* collator = nullptr; auto swMatchExpression = MatchExpressionParser::parse(matchPredicate, ExtensionsCallbackNoop(), collator); ASSERT_OK(swMatchExpression.getStatus()); size_t nodeCount = 0; expression::mapOver( swMatchExpression.getValue().get(), [&nodeCount](MatchExpression* expression, std::string path) -> void { ++nodeCount; }); ASSERT_EQ(nodeCount, 3U); } TEST(IsPathPrefixOf, ComputesPrefixesCorrectly) { ASSERT_TRUE(expression::isPathPrefixOf("a.b", "a.b.c")); ASSERT_TRUE(expression::isPathPrefixOf("a", "a.b")); ASSERT_FALSE(expression::isPathPrefixOf("a.b", "a.balloon")); ASSERT_FALSE(expression::isPathPrefixOf("a", "a")); ASSERT_FALSE(expression::isPathPrefixOf("a.b", "a")); } } // namespace mongo