// expression_parser_array_test.cpp /** * 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 * . * * 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 #include "mongo/unittest/unittest.h" #include "mongo/db/matcher/expression_parser.h" #include "mongo/db/jsobj.h" #include "mongo/db/json.h" #include "mongo/db/matcher/expression.h" #include "mongo/db/matcher/expression_array.h" #include "mongo/db/pipeline/expression_context_for_test.h" #include "mongo/db/query/collation/collator_interface_mock.h" namespace mongo { using std::string; TEST(MatchExpressionParserArrayTest, Size1) { BSONObj query = BSON("x" << BSON("$size" << 2)); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx); ASSERT_TRUE(result.isOK()); ASSERT(!result.getValue()->matchesBSON(BSON("x" << 1))); ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(1 << 2)))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(1)))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(1 << 2 << 3)))); } TEST(MatchExpressionParserArrayTest, SizeAsLong) { BSONObj query = BSON("x" << BSON("$size" << 2LL)); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx); ASSERT_TRUE(result.isOK()); ASSERT(!result.getValue()->matchesBSON(BSON("x" << 1))); ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(1 << 2)))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(1)))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(1 << 2 << 3)))); } TEST(MatchExpressionParserArrayTest, SizeAsNegativeLong) { BSONObj query = BSON("x" << BSON("$size" << -2LL)); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx); ASSERT_FALSE(result.isOK()); } TEST(MatchExpressionParserArrayTest, SizeTooLarge) { BSONObj query = BSON("x" << BSON("$size" << std::numeric_limits::max())); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx); ASSERT_FALSE(result.isOK()); } TEST(MatchExpressionParserArrayTest, SizeAsString) { BSONObj query = BSON("x" << BSON("$size" << "a")); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx); ASSERT_FALSE(result.isOK()); } TEST(MatchExpressionParserArrayTest, SizeWithIntegralDouble) { BSONObj query = BSON("x" << BSON("$size" << 2.0)); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx); ASSERT_TRUE(result.isOK()); ASSERT(!result.getValue()->matchesBSON(BSON("x" << 1))); ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(1 << 2)))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(1)))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(1 << 2 << 3)))); } TEST(MatchExpressionParserArrayTest, SizeWithNegativeIntegralDouble) { BSONObj query = BSON("x" << BSON("$size" << -2.0)); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx); ASSERT_FALSE(result.isOK()); } TEST(MatchExpressionParserArrayTest, SizeWithDouble) { BSONObj query = BSON("x" << BSON("$size" << 2.5)); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx); ASSERT_FALSE(result.isOK()); } TEST(MatchExpressionParserArrayTest, SizeWithNegative) { BSONObj query = BSON("x" << BSON("$size" << -2)); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx); ASSERT_FALSE(result.isOK()); } TEST(MatchExpressionParserArrayTest, SizeBad) { BSONObj query = BSON("x" << BSON("$size" << BSONNULL)); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx); ASSERT_FALSE(result.isOK()); } // --------- TEST(MatchExpressionParserArrayTest, ElemMatchArr1) { BSONObj query = BSON("x" << BSON("$elemMatch" << BSON("x" << 1 << "y" << 2))); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx); ASSERT_TRUE(result.isOK()); ASSERT(!result.getValue()->matchesBSON(BSON("x" << 1))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(1 << 2)))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSON("x" << 1))))); ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSON("x" << 1 << "y" << 2))))); } TEST(MatchExpressionParserArrayTest, ElemMatchAnd) { BSONObj query = BSON("x" << BSON("$elemMatch" << BSON("$and" << BSON_ARRAY(BSON("x" << 1 << "y" << 2))))); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx); ASSERT_TRUE(result.isOK()); ASSERT(!result.getValue()->matchesBSON(BSON("x" << 1))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(1 << 2)))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSON("x" << 1))))); ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSON("x" << 1 << "y" << 2))))); } TEST(MatchExpressionParserArrayTest, ElemMatchNor) { BSONObj query = BSON("x" << BSON("$elemMatch" << BSON("$nor" << BSON_ARRAY(BSON("x" << 1))))); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx); ASSERT_TRUE(result.isOK()); ASSERT(!result.getValue()->matchesBSON(BSON("x" << 1))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(1 << 2)))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSON("x" << 1))))); ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSON("x" << 2 << "y" << 2))))); } TEST(MatchExpressionParserArrayTest, ElemMatchOr) { BSONObj query = BSON("x" << BSON("$elemMatch" << BSON("$or" << BSON_ARRAY(BSON("x" << 1 << "y" << 2))))); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx); ASSERT_TRUE(result.isOK()); ASSERT(!result.getValue()->matchesBSON(BSON("x" << 1))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(1 << 2)))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSON("x" << 1))))); ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSON("x" << 1 << "y" << 2))))); } TEST(MatchExpressionParserArrayTest, ElemMatchVal1) { BSONObj query = BSON("x" << BSON("$elemMatch" << BSON("$gt" << 5))); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx); ASSERT_TRUE(result.isOK()); ASSERT(!result.getValue()->matchesBSON(BSON("x" << 1))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(4)))); ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(6)))); } // with explicit $eq TEST(MatchExpressionParserArrayTest, ElemMatchDBRef1) { OID oid = OID::gen(); BSONObj match = BSON("$ref" << "coll" << "$id" << oid << "$db" << "db"); OID oidx = OID::gen(); BSONObj notMatch = BSON("$ref" << "coll" << "$id" << oidx << "$db" << "db"); BSONObj query = BSON("x" << BSON("$elemMatch" << BSON("$eq" << match))); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx); ASSERT_TRUE(result.isOK()); ASSERT(!result.getValue()->matchesBSON(BSON("x" << match))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(notMatch)))); ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(match)))); } TEST(MatchExpressionParserArrayTest, ElemMatchDBRef2) { OID oid = OID::gen(); BSONObj match = BSON("$ref" << "coll" << "$id" << oid << "$db" << "db"); OID oidx = OID::gen(); BSONObj notMatch = BSON("$ref" << "coll" << "$id" << oidx << "$db" << "db"); BSONObj query = BSON("x" << BSON("$elemMatch" << match)); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx); ASSERT_TRUE(result.isOK()); ASSERT(!result.getValue()->matchesBSON(BSON("x" << match))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(notMatch)))); ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(match)))); } // Additional fields after $ref and $id. TEST(MatchExpressionParserArrayTest, ElemMatchDBRef3) { OID oid = OID::gen(); BSONObj match = BSON("$ref" << "coll" << "$id" << oid << "foo" << 12345); OID oidx = OID::gen(); BSONObj notMatch = BSON("$ref" << "coll" << "$id" << oidx << "foo" << 12345); BSONObj query = BSON("x" << BSON("$elemMatch" << match)); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx); ASSERT_TRUE(result.isOK()); ASSERT(!result.getValue()->matchesBSON(BSON("x" << match))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(notMatch)))); ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(match)))); // Document contains fields not referred to in $elemMatch query. ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSON("$ref" << "coll" << "$id" << oid << "foo" << 12345 << "bar" << 678))))); } // Query with DBRef fields out of order. TEST(MatchExpressionParserArrayTest, ElemMatchDBRef4) { OID oid = OID::gen(); BSONObj match = BSON("$ref" << "coll" << "$id" << oid << "$db" << "db"); BSONObj matchOutOfOrder = BSON("$db" << "db" << "$id" << oid << "$ref" << "coll"); OID oidx = OID::gen(); BSONObj notMatch = BSON("$ref" << "coll" << "$id" << oidx << "$db" << "db"); BSONObj query = BSON("x" << BSON("$elemMatch" << matchOutOfOrder)); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx); ASSERT_TRUE(result.isOK()); ASSERT(!result.getValue()->matchesBSON(BSON("x" << match))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(notMatch)))); ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(match)))); } // Query with DBRef fields out of order. // Additional fields besides $ref and $id. TEST(MatchExpressionParserArrayTest, ElemMatchDBRef5) { OID oid = OID::gen(); BSONObj match = BSON("$ref" << "coll" << "$id" << oid << "foo" << 12345); BSONObj matchOutOfOrder = BSON("foo" << 12345 << "$id" << oid << "$ref" << "coll"); OID oidx = OID::gen(); BSONObj notMatch = BSON("$ref" << "coll" << "$id" << oidx << "foo" << 12345); BSONObj query = BSON("x" << BSON("$elemMatch" << matchOutOfOrder)); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx); ASSERT_TRUE(result.isOK()); ASSERT(!result.getValue()->matchesBSON(BSON("x" << match))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(notMatch)))); ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(match)))); // Document contains fields not referred to in $elemMatch query. ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSON("$ref" << "coll" << "$id" << oid << "foo" << 12345 << "bar" << 678))))); } // Incomplete DBRef - $id missing. TEST(MatchExpressionParserArrayTest, ElemMatchDBRef6) { OID oid = OID::gen(); BSONObj match = BSON("$ref" << "coll" << "$id" << oid << "foo" << 12345); BSONObj matchMissingID = BSON("$ref" << "coll" << "foo" << 12345); BSONObj notMatch = BSON("$ref" << "collx" << "$id" << oid << "foo" << 12345); BSONObj query = BSON("x" << BSON("$elemMatch" << matchMissingID)); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx); ASSERT_TRUE(result.isOK()); ASSERT(!result.getValue()->matchesBSON(BSON("x" << match))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(notMatch)))); ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(match)))); // Document contains fields not referred to in $elemMatch query. ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSON("$ref" << "coll" << "$id" << oid << "foo" << 12345 << "bar" << 678))))); } // Incomplete DBRef - $ref missing. TEST(MatchExpressionParserArrayTest, ElemMatchDBRef7) { OID oid = OID::gen(); BSONObj match = BSON("$ref" << "coll" << "$id" << oid << "foo" << 12345); BSONObj matchMissingRef = BSON("$id" << oid << "foo" << 12345); OID oidx = OID::gen(); BSONObj notMatch = BSON("$ref" << "coll" << "$id" << oidx << "foo" << 12345); BSONObj query = BSON("x" << BSON("$elemMatch" << matchMissingRef)); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx); ASSERT_TRUE(result.isOK()); ASSERT(!result.getValue()->matchesBSON(BSON("x" << match))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(notMatch)))); ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(match)))); // Document contains fields not referred to in $elemMatch query. ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSON("$ref" << "coll" << "$id" << oid << "foo" << 12345 << "bar" << 678))))); } // Incomplete DBRef - $db only. TEST(MatchExpressionParserArrayTest, ElemMatchDBRef8) { OID oid = OID::gen(); BSONObj match = BSON("$ref" << "coll" << "$id" << oid << "$db" << "db" << "foo" << 12345); BSONObj matchDBOnly = BSON("$db" << "db" << "foo" << 12345); BSONObj notMatch = BSON("$ref" << "coll" << "$id" << oid << "$db" << "dbx" << "foo" << 12345); BSONObj query = BSON("x" << BSON("$elemMatch" << matchDBOnly)); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx); ASSERT_TRUE(result.isOK()); ASSERT(!result.getValue()->matchesBSON(BSON("x" << match))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(notMatch)))); ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(match)))); // Document contains fields not referred to in $elemMatch query. ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSON("$ref" << "coll" << "$id" << oid << "$db" << "db" << "foo" << 12345 << "bar" << 678))))); } TEST(MatchExpressionParserArrayTest, All1) { BSONObj query = BSON("x" << BSON("$all" << BSON_ARRAY(1 << 2))); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx); ASSERT_TRUE(result.isOK()); // Verify that the $all got parsed to AND. ASSERT_EQUALS(MatchExpression::AND, result.getValue()->matchType()); ASSERT(!result.getValue()->matchesBSON(BSON("x" << 1))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(1)))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(2)))); ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(1 << 2)))); ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(1 << 2 << 3)))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(2 << 3)))); } TEST(MatchExpressionParserArrayTest, AllNull) { BSONObj query = BSON("x" << BSON("$all" << BSON_ARRAY(BSONNULL))); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx); ASSERT_TRUE(result.isOK()); // Verify that the $all got parsed to AND. ASSERT_EQUALS(MatchExpression::AND, result.getValue()->matchType()); ASSERT(!result.getValue()->matchesBSON(BSON("x" << 1))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(1)))); ASSERT(result.getValue()->matchesBSON(BSON("x" << BSONNULL))); ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSONNULL)))); } TEST(MatchExpressionParserArrayTest, AllBadArg) { BSONObj query = BSON("x" << BSON("$all" << 1)); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx); ASSERT_FALSE(result.isOK()); } TEST(MatchExpressionParserArrayTest, AllBadRegexArg) { string tooLargePattern(50 * 1000, 'z'); BSONObjBuilder allArray; allArray.appendRegex("0", tooLargePattern, ""); BSONObjBuilder operand; operand.appendArray("$all", allArray.obj()); BSONObj query = BSON("x" << operand.obj()); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx); ASSERT_FALSE(result.isOK()); } TEST(MatchExpressionParserArrayTest, AllRegex1) { BSONObjBuilder allArray; allArray.appendRegex("0", "^a", ""); allArray.appendRegex("1", "B", "i"); BSONObjBuilder all; all.appendArray("$all", allArray.obj()); BSONObj query = BSON("a" << all.obj()); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx); ASSERT_TRUE(result.isOK()); // Verify that the $all got parsed to AND. ASSERT_EQUALS(MatchExpression::AND, result.getValue()->matchType()); BSONObj notMatchFirst = BSON("a" << "ax"); BSONObj notMatchSecond = BSON("a" << "qqb"); BSONObj matchesBoth = BSON("a" << "ab"); ASSERT(!result.getValue()->matchesSingleElement(notMatchFirst["a"])); ASSERT(!result.getValue()->matchesSingleElement(notMatchSecond["a"])); ASSERT(result.getValue()->matchesSingleElement(matchesBoth["a"])); } TEST(MatchExpressionParserArrayTest, AllRegex2) { BSONObjBuilder allArray; allArray.appendRegex("0", "^a", ""); allArray.append("1", "abc"); BSONObjBuilder all; all.appendArray("$all", allArray.obj()); BSONObj query = BSON("a" << all.obj()); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx); ASSERT_TRUE(result.isOK()); // Verify that the $all got parsed to AND. ASSERT_EQUALS(MatchExpression::AND, result.getValue()->matchType()); BSONObj notMatchFirst = BSON("a" << "ax"); BSONObj matchesBoth = BSON("a" << "abc"); ASSERT(!result.getValue()->matchesSingleElement(notMatchFirst["a"])); ASSERT(result.getValue()->matchesSingleElement(matchesBoth["a"])); } TEST(MatchExpressionParserArrayTest, AllNonArray) { BSONObj query = BSON("x" << BSON("$all" << BSON_ARRAY(5))); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx); ASSERT_TRUE(result.isOK()); // Verify that the $all got parsed to AND. ASSERT_EQUALS(MatchExpression::AND, result.getValue()->matchType()); ASSERT(result.getValue()->matchesBSON(BSON("x" << 5))); ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(5)))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << 4))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(4)))); } TEST(MatchExpressionParserArrayTest, AllElemMatch1) { BSONObj internal = BSON("x" << 1 << "y" << 2); BSONObj query = BSON("x" << BSON("$all" << BSON_ARRAY(BSON("$elemMatch" << internal)))); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx); ASSERT_TRUE(result.isOK()); // Verify that the $all got parsed to an AND with a single ELEM_MATCH_OBJECT child. ASSERT_EQUALS(MatchExpression::AND, result.getValue()->matchType()); ASSERT_EQUALS(1U, result.getValue()->numChildren()); MatchExpression* child = result.getValue()->getChild(0); ASSERT_EQUALS(MatchExpression::ELEM_MATCH_OBJECT, child->matchType()); ASSERT(!result.getValue()->matchesBSON(BSON("x" << 1))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(1 << 2)))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSON("x" << 1))))); ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSON("x" << 1 << "y" << 2))))); } // $all and $elemMatch on dotted field. // Top level field can be either document or array. TEST(MatchExpressionParserArrayTest, AllElemMatch2) { BSONObj internal = BSON("z" << 1); BSONObj query = BSON("x.y" << BSON("$all" << BSON_ARRAY(BSON("$elemMatch" << internal)))); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx); ASSERT_TRUE(result.isOK()); // Verify that the $all got parsed to an AND with a single ELEM_MATCH_OBJECT child. ASSERT_EQUALS(MatchExpression::AND, result.getValue()->matchType()); ASSERT_EQUALS(1U, result.getValue()->numChildren()); MatchExpression* child = result.getValue()->getChild(0); ASSERT_EQUALS(MatchExpression::ELEM_MATCH_OBJECT, child->matchType()); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON("y" << 1)))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON("y" << BSON_ARRAY(1 << 2))))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON("y" << BSON_ARRAY(BSON("x" << 1)))))); // x is a document. Internal document does not contain z. ASSERT(!result.getValue()->matchesBSON( BSON("x" << BSON("y" << BSON_ARRAY(BSON("x" << 1 << "y" << 1)))))); // x is an array. Internal document does not contain z. ASSERT(!result.getValue()->matchesBSON( BSON("x" << BSON_ARRAY(BSON("y" << BSON_ARRAY(BSON("x" << 1 << "y" << 1))))))); // x is a document but y is not an array. ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON("y" << BSON("x" << 1 << "z" << 1))))); // x is an array but y is not an array. ASSERT(!result.getValue()->matchesBSON( BSON("x" << BSON_ARRAY(BSON("y" << BSON("x" << 1 << "z" << 1)))))); // x is a document. ASSERT(result.getValue()->matchesBSON( BSON("x" << BSON("y" << BSON_ARRAY(BSON("x" << 1 << "z" << 1)))))); // x is an array. ASSERT(result.getValue()->matchesBSON( BSON("x" << BSON_ARRAY(BSON("y" << BSON_ARRAY(BSON("x" << 1 << "z" << 1))))))); } // Check the structure of the resulting MatchExpression, and make sure that the paths // are correct. TEST(MatchExpressionParserArrayTest, AllElemMatch3) { BSONObj query = fromjson("{x: {$all: [{$elemMatch: {y: 1, z: 1}}]}}"); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx); ASSERT_TRUE(result.isOK()); std::unique_ptr expr = std::move(result.getValue()); // Root node should be an AND with one child. ASSERT_EQUALS(MatchExpression::AND, expr->matchType()); ASSERT_EQUALS(1U, expr->numChildren()); // Child should be an ELEM_MATCH_OBJECT with one child and path "x". MatchExpression* emObject = expr->getChild(0); ASSERT_EQUALS(MatchExpression::ELEM_MATCH_OBJECT, emObject->matchType()); ASSERT_EQUALS(1U, emObject->numChildren()); ASSERT_EQUALS("x", emObject->path().toString()); // Child should be another AND with two children. MatchExpression* and2 = emObject->getChild(0); ASSERT_EQUALS(MatchExpression::AND, and2->matchType()); ASSERT_EQUALS(2U, and2->numChildren()); // Both children should be equalites, with paths "y" and "z". MatchExpression* leaf1 = and2->getChild(0); ASSERT_EQUALS(MatchExpression::EQ, leaf1->matchType()); ASSERT_EQUALS(0U, leaf1->numChildren()); ASSERT_EQUALS("y", leaf1->path().toString()); MatchExpression* leaf2 = and2->getChild(1); ASSERT_EQUALS(MatchExpression::EQ, leaf2->matchType()); ASSERT_EQUALS(0U, leaf2->numChildren()); ASSERT_EQUALS("z", leaf2->path().toString()); } TEST(MatchExpressionParserArrayTest, AllElemMatchBad) { BSONObj internal = BSON("x" << 1 << "y" << 2); BSONObj query = BSON("x" << BSON("$all" << BSON_ARRAY(BSON("$elemMatch" << internal) << 5))); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx); ASSERT_FALSE(result.isOK()); query = BSON("x" << BSON("$all" << BSON_ARRAY(5 << BSON("$elemMatch" << internal)))); result = MatchExpressionParser::parse(query, expCtx); ASSERT_FALSE(result.isOK()); } // You can't mix $elemMatch and regular equality inside $all. TEST(MatchExpressionParserArrayTest, AllElemMatchBadMixed) { // $elemMatch first, equality second. BSONObj bad1 = fromjson("{x: {$all: [{$elemMatch: {y: 1}}, 3]}}"); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result1 = MatchExpressionParser::parse(bad1, expCtx); ASSERT_FALSE(result1.isOK()); // equality first, $elemMatch second BSONObj bad2 = fromjson("{x: {$all: [3, {$elemMatch: {y: 1}}]}}"); StatusWithMatchExpression result2 = MatchExpressionParser::parse(bad2, expCtx); ASSERT_FALSE(result1.isOK()); // $elemMatch first, object second BSONObj bad3 = fromjson("{x: {$all: [{$elemMatch: {y: 1}}, {z: 1}]}}"); StatusWithMatchExpression result3 = MatchExpressionParser::parse(bad3, expCtx); ASSERT_FALSE(result3.isOK()); // object first, $elemMatch second BSONObj bad4 = fromjson("{x: {$all: [{z: 1}, {$elemMatch: {y: 1}}]}}"); StatusWithMatchExpression result4 = MatchExpressionParser::parse(bad4, expCtx); ASSERT_FALSE(result4.isOK()); } // $all with empty string. TEST(MatchExpressionParserArrayTest, AllEmptyString) { BSONObj query = BSON("x" << BSON("$all" << BSON_ARRAY(""))); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx); ASSERT_TRUE(result.isOK()); ASSERT(!result.getValue()->matchesBSON(BSON("x" << "a"))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSONNULL << "a")))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSONObj() << "a")))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSONArray()))); ASSERT(result.getValue()->matchesBSON(BSON("x" << ""))); ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSONNULL << "")))); ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSONObj() << "")))); } // $all with ISO date. TEST(MatchExpressionParserArrayTest, AllISODate) { StatusWith matchResult = dateFromISOString("2014-12-31T00:00:00.000Z"); ASSERT_TRUE(matchResult.isOK()); const Date_t& match = matchResult.getValue(); StatusWith notMatchResult = dateFromISOString("2014-12-30T00:00:00.000Z"); ASSERT_TRUE(notMatchResult.isOK()); const Date_t& notMatch = notMatchResult.getValue(); BSONObj query = BSON("x" << BSON("$all" << BSON_ARRAY(match))); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx); ASSERT_TRUE(result.isOK()); ASSERT(!result.getValue()->matchesBSON(BSON("x" << notMatch))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSONNULL << notMatch)))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSONObj() << notMatch)))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSONArray()))); ASSERT(result.getValue()->matchesBSON(BSON("x" << match))); ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSONNULL << match)))); ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSONObj() << match)))); } // $all on array element with empty string. TEST(MatchExpressionParserArrayTest, AllDottedEmptyString) { BSONObj query = BSON("x.1" << BSON("$all" << BSON_ARRAY(""))); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx); ASSERT_TRUE(result.isOK()); ASSERT(!result.getValue()->matchesBSON(BSON("x" << "a"))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSONNULL << "a")))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSONObj() << "a")))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY("" << BSONNULL)))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY("" << BSONObj())))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSONArray()))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << ""))); ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSONNULL << "")))); ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSONObj() << "")))); } // $all on array element with ISO date. TEST(MatchExpressionParserArrayTest, AllDottedISODate) { StatusWith matchResult = dateFromISOString("2014-12-31T00:00:00.000Z"); ASSERT_TRUE(matchResult.isOK()); const Date_t& match = matchResult.getValue(); StatusWith notMatchResult = dateFromISOString("2014-12-30T00:00:00.000Z"); ASSERT_TRUE(notMatchResult.isOK()); const Date_t& notMatch = notMatchResult.getValue(); BSONObj query = BSON("x.1" << BSON("$all" << BSON_ARRAY(match))); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx); ASSERT_TRUE(result.isOK()); ASSERT(!result.getValue()->matchesBSON(BSON("x" << notMatch))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSONNULL << notMatch)))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSONObj() << notMatch)))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(match << BSONNULL)))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(match << BSONObj())))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSONArray()))); ASSERT(!result.getValue()->matchesBSON(BSON("x" << match))); ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSONNULL << match)))); ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSONObj() << match)))); } TEST(MatchExpressionParserArrayTest, AllStringNullCollation) { BSONObj query = BSON("x" << BSON("$all" << BSON_ARRAY("string"))); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx); ASSERT_TRUE(result.isOK()); ASSERT_EQUALS(MatchExpression::AND, result.getValue()->matchType()); ASSERT_EQUALS(1U, result.getValue()->numChildren()); MatchExpression* child = result.getValue()->getChild(0); ASSERT_EQUALS(MatchExpression::EQ, child->matchType()); EqualityMatchExpression* eqMatch = static_cast(child); ASSERT_TRUE(eqMatch->getCollator() == nullptr); } TEST(MatchExpressionParserArrayTest, AllStringCollation) { BSONObj query = BSON("x" << BSON("$all" << BSON_ARRAY("string"))); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); expCtx->setCollator(&collator); StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx); ASSERT_TRUE(result.isOK()); ASSERT_EQUALS(MatchExpression::AND, result.getValue()->matchType()); ASSERT_EQUALS(1U, result.getValue()->numChildren()); MatchExpression* child = result.getValue()->getChild(0); ASSERT_EQUALS(MatchExpression::EQ, child->matchType()); EqualityMatchExpression* eqMatch = static_cast(child); ASSERT_TRUE(eqMatch->getCollator() == &collator); } }