diff options
Diffstat (limited to 'src/mongo/db/matcher/expression_parser_array_test.cpp')
-rw-r--r-- | src/mongo/db/matcher/expression_parser_array_test.cpp | 1292 |
1 files changed, 659 insertions, 633 deletions
diff --git a/src/mongo/db/matcher/expression_parser_array_test.cpp b/src/mongo/db/matcher/expression_parser_array_test.cpp index 26179f9b0c8..01bd6301d73 100644 --- a/src/mongo/db/matcher/expression_parser_array_test.cpp +++ b/src/mongo/db/matcher/expression_parser_array_test.cpp @@ -39,638 +39,664 @@ namespace mongo { - using std::string; - - TEST( MatchExpressionParserArrayTest, Size1 ) { - BSONObj query = BSON( "x" << BSON( "$size" << 2 ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - 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 ) ) ) ); - delete result.getValue(); - } - - TEST( MatchExpressionParserArrayTest, SizeAsString ) { - BSONObj query = BSON( "x" << BSON( "$size" << "a" ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - 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" << BSONArray() ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( 1 ) ) ) ); - delete result.getValue(); - } - - TEST( MatchExpressionParserArrayTest, SizeWithDouble ) { - BSONObj query = BSON( "x" << BSON( "$size" << 2.5 ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - 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" << BSONArray() ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( 1 << 2 << 3 ) ) ) ); - delete result.getValue(); - } - - TEST( MatchExpressionParserArrayTest, SizeBad ) { - BSONObj query = BSON( "x" << BSON( "$size" << BSONNULL ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_FALSE( result.isOK() ); - } - - // --------- - - TEST( MatchExpressionParserArrayTest, ElemMatchArr1 ) { - BSONObj query = BSON( "x" << BSON( "$elemMatch" << BSON( "x" << 1 << "y" << 2 ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - 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 ) ) ) ) ); - delete result.getValue(); - - } - - TEST( MatchExpressionParserArrayTest, ElemMatchAnd ) { - BSONObj query = BSON( "x" << - BSON( "$elemMatch" << - BSON( "$and" << BSON_ARRAY( BSON( "x" << 1 << "y" << 2 ) ) ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - 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 ) ) ) ) ); - delete result.getValue(); - - } - - TEST( MatchExpressionParserArrayTest, ElemMatchNor ) { - BSONObj query = BSON( "x" << - BSON( "$elemMatch" << - BSON( "$nor" << BSON_ARRAY( BSON( "x" << 1 ) ) ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - 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 ) ) ) ) ); - delete result.getValue(); - - } - - TEST( MatchExpressionParserArrayTest, ElemMatchOr ) { - BSONObj query = BSON( "x" << - BSON( "$elemMatch" << - BSON( "$or" << BSON_ARRAY( BSON( "x" << 1 << "y" << 2 ) ) ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - 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 ) ) ) ) ); - delete result.getValue(); - - } - - TEST( MatchExpressionParserArrayTest, ElemMatchVal1 ) { - BSONObj query = BSON( "x" << BSON( "$elemMatch" << BSON( "$gt" << 5 ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - 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 ) ) ) ); - delete result.getValue(); - } - - // 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 ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - 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 ) ) ) ); - delete result.getValue(); - } - - 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 ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - 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 ) ) ) ); - delete result.getValue(); - } - - // 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 ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - 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 ) ) ) ) ); - delete result.getValue(); - } - - // 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 ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - 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 ) ) ) ); - delete result.getValue(); - } - - // 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 ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - 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 ) ) ) ) ); - delete result.getValue(); - } - - // 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 ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - 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 ) ) ) ) ); - delete result.getValue(); - } - - // 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 ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - 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 ) ) ) ) ); - delete result.getValue(); - } - - // 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 ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - 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 ) ) ) ) ); - delete result.getValue(); - } - - TEST( MatchExpressionParserArrayTest, All1 ) { - BSONObj query = BSON( "x" << BSON( "$all" << BSON_ARRAY( 1 << 2 ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - 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 ) ) ) ); - delete result.getValue(); - } - - TEST( MatchExpressionParserArrayTest, AllNull ) { - BSONObj query = BSON( "x" << BSON( "$all" << BSON_ARRAY( BSONNULL ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - 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 ) ) ) ); - delete result.getValue(); - } - - TEST( MatchExpressionParserArrayTest, AllBadArg ) { - BSONObj query = BSON( "x" << BSON( "$all" << 1 ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - 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() ); - - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - 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() ); - - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - 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" ] ) ); - delete result.getValue(); - } - - 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() ); - - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - 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" ] ) ); - delete result.getValue(); - } - - TEST( MatchExpressionParserArrayTest, AllNonArray ) { - BSONObj query = BSON( "x" << BSON( "$all" << BSON_ARRAY( 5 ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - 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 ) ) ) ); - delete result.getValue(); - } - - - TEST( MatchExpressionParserArrayTest, AllElemMatch1 ) { - BSONObj internal = BSON( "x" << 1 << "y" << 2 ); - BSONObj query = BSON( "x" << BSON( "$all" << BSON_ARRAY( BSON( "$elemMatch" << internal ) ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - 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 ) ) ) ) ); - delete result.getValue(); - - } - - // $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 ) ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - 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 ) ) ) ) ) ) ); - delete result.getValue(); - } - - // 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}}]}}" ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - - std::unique_ptr<MatchExpression> expr( 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 ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_FALSE( result.isOK() ); - - query = BSON( "x" << BSON( "$all" << BSON_ARRAY( 5 << BSON( "$elemMatch" << internal ) ) ) ); - result = MatchExpressionParser::parse( query ); - 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]}}" ); - StatusWithMatchExpression result1 = MatchExpressionParser::parse( bad1 ); - ASSERT_FALSE( result1.isOK() ); - - // equality first, $elemMatch second - BSONObj bad2 = fromjson( "{x: {$all: [3, {$elemMatch: {y: 1}}]}}" ); - StatusWithMatchExpression result2 = MatchExpressionParser::parse( bad2 ); - ASSERT_FALSE( result1.isOK() ); - - // $elemMatch first, object second - BSONObj bad3 = fromjson( "{x: {$all: [{$elemMatch: {y: 1}}, {z: 1}]}}" ); - StatusWithMatchExpression result3 = MatchExpressionParser::parse( bad3 ); - ASSERT_FALSE( result3.isOK() ); - - // object first, $elemMatch second - BSONObj bad4 = fromjson( "{x: {$all: [{z: 1}, {$elemMatch: {y: 1}}]}}" ); - StatusWithMatchExpression result4 = MatchExpressionParser::parse( bad4 ); - ASSERT_FALSE( result4.isOK() ); - } - - // $all with empty string. - TEST( MatchExpressionParserArrayTest, AllEmptyString ) { - BSONObj query = BSON( "x" << BSON( "$all" << BSON_ARRAY( "" ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - 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() << "" ) ) ) ); - delete result.getValue(); - } - - // $all with ISO date. - TEST( MatchExpressionParserArrayTest, AllISODate ) { - StatusWith<Date_t> matchResult = dateFromISOString("2014-12-31T00:00:00.000Z"); - ASSERT_TRUE( matchResult.isOK() ); - const Date_t& match = matchResult.getValue(); - StatusWith<Date_t> 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 ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - 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 ) ) ) ); - delete result.getValue(); - } - - // $all on array element with empty string. - TEST( MatchExpressionParserArrayTest, AllDottedEmptyString ) { - BSONObj query = BSON( "x.1" << BSON( "$all" << BSON_ARRAY( "" ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - 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() << "" ) ) ) ); - delete result.getValue(); - } - - // $all on array element with ISO date. - TEST( MatchExpressionParserArrayTest, AllDottedISODate ) { - StatusWith<Date_t> matchResult = dateFromISOString("2014-12-31T00:00:00.000Z"); - ASSERT_TRUE( matchResult.isOK() ); - const Date_t& match = matchResult.getValue(); - StatusWith<Date_t> 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 ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - 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 ) ) ) ); - delete result.getValue(); - } +using std::string; + +TEST(MatchExpressionParserArrayTest, Size1) { + BSONObj query = BSON("x" << BSON("$size" << 2)); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + 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)))); + delete result.getValue(); +} + +TEST(MatchExpressionParserArrayTest, SizeAsString) { + BSONObj query = BSON("x" << BSON("$size" + << "a")); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + 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" << BSONArray()))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(1)))); + delete result.getValue(); +} + +TEST(MatchExpressionParserArrayTest, SizeWithDouble) { + BSONObj query = BSON("x" << BSON("$size" << 2.5)); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + 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" << BSONArray()))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(1 << 2 << 3)))); + delete result.getValue(); +} + +TEST(MatchExpressionParserArrayTest, SizeBad) { + BSONObj query = BSON("x" << BSON("$size" << BSONNULL)); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_FALSE(result.isOK()); +} + +// --------- + +TEST(MatchExpressionParserArrayTest, ElemMatchArr1) { + BSONObj query = BSON("x" << BSON("$elemMatch" << BSON("x" << 1 << "y" << 2))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + 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))))); + delete result.getValue(); +} + +TEST(MatchExpressionParserArrayTest, ElemMatchAnd) { + BSONObj query = + BSON("x" << BSON("$elemMatch" << BSON("$and" << BSON_ARRAY(BSON("x" << 1 << "y" << 2))))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + 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))))); + delete result.getValue(); +} + +TEST(MatchExpressionParserArrayTest, ElemMatchNor) { + BSONObj query = BSON("x" << BSON("$elemMatch" << BSON("$nor" << BSON_ARRAY(BSON("x" << 1))))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + 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))))); + delete result.getValue(); +} + +TEST(MatchExpressionParserArrayTest, ElemMatchOr) { + BSONObj query = + BSON("x" << BSON("$elemMatch" << BSON("$or" << BSON_ARRAY(BSON("x" << 1 << "y" << 2))))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + 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))))); + delete result.getValue(); +} + +TEST(MatchExpressionParserArrayTest, ElemMatchVal1) { + BSONObj query = BSON("x" << BSON("$elemMatch" << BSON("$gt" << 5))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + 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)))); + delete result.getValue(); +} + +// 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))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + 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)))); + delete result.getValue(); +} + +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)); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + 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)))); + delete result.getValue(); +} + +// 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)); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + 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))))); + delete result.getValue(); +} + +// 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)); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + 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)))); + delete result.getValue(); +} + +// 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)); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + 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))))); + delete result.getValue(); +} + +// 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)); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + 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))))); + delete result.getValue(); +} + +// 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)); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + 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))))); + delete result.getValue(); +} + +// 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)); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + 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))))); + delete result.getValue(); +} + +TEST(MatchExpressionParserArrayTest, All1) { + BSONObj query = BSON("x" << BSON("$all" << BSON_ARRAY(1 << 2))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + 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)))); + delete result.getValue(); +} + +TEST(MatchExpressionParserArrayTest, AllNull) { + BSONObj query = BSON("x" << BSON("$all" << BSON_ARRAY(BSONNULL))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + 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)))); + delete result.getValue(); +} + +TEST(MatchExpressionParserArrayTest, AllBadArg) { + BSONObj query = BSON("x" << BSON("$all" << 1)); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + 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()); + + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + 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()); + + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + 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"])); + delete result.getValue(); +} + +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()); + + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + 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"])); + delete result.getValue(); +} + +TEST(MatchExpressionParserArrayTest, AllNonArray) { + BSONObj query = BSON("x" << BSON("$all" << BSON_ARRAY(5))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + 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)))); + delete result.getValue(); +} + +TEST(MatchExpressionParserArrayTest, AllElemMatch1) { + BSONObj internal = BSON("x" << 1 << "y" << 2); + BSONObj query = BSON("x" << BSON("$all" << BSON_ARRAY(BSON("$elemMatch" << internal)))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + 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))))); + delete result.getValue(); +} + +// $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)))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + 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))))))); + delete result.getValue(); +} + +// 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}}]}}"); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + + std::unique_ptr<MatchExpression> expr(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))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_FALSE(result.isOK()); + + query = BSON("x" << BSON("$all" << BSON_ARRAY(5 << BSON("$elemMatch" << internal)))); + result = MatchExpressionParser::parse(query); + 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]}}"); + StatusWithMatchExpression result1 = MatchExpressionParser::parse(bad1); + ASSERT_FALSE(result1.isOK()); + + // equality first, $elemMatch second + BSONObj bad2 = fromjson("{x: {$all: [3, {$elemMatch: {y: 1}}]}}"); + StatusWithMatchExpression result2 = MatchExpressionParser::parse(bad2); + ASSERT_FALSE(result1.isOK()); + + // $elemMatch first, object second + BSONObj bad3 = fromjson("{x: {$all: [{$elemMatch: {y: 1}}, {z: 1}]}}"); + StatusWithMatchExpression result3 = MatchExpressionParser::parse(bad3); + ASSERT_FALSE(result3.isOK()); + + // object first, $elemMatch second + BSONObj bad4 = fromjson("{x: {$all: [{z: 1}, {$elemMatch: {y: 1}}]}}"); + StatusWithMatchExpression result4 = MatchExpressionParser::parse(bad4); + ASSERT_FALSE(result4.isOK()); +} + +// $all with empty string. +TEST(MatchExpressionParserArrayTest, AllEmptyString) { + BSONObj query = BSON("x" << BSON("$all" << BSON_ARRAY(""))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + 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() << "")))); + delete result.getValue(); +} + +// $all with ISO date. +TEST(MatchExpressionParserArrayTest, AllISODate) { + StatusWith<Date_t> matchResult = dateFromISOString("2014-12-31T00:00:00.000Z"); + ASSERT_TRUE(matchResult.isOK()); + const Date_t& match = matchResult.getValue(); + StatusWith<Date_t> 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))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + 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)))); + delete result.getValue(); +} + +// $all on array element with empty string. +TEST(MatchExpressionParserArrayTest, AllDottedEmptyString) { + BSONObj query = BSON("x.1" << BSON("$all" << BSON_ARRAY(""))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + 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() << "")))); + delete result.getValue(); +} + +// $all on array element with ISO date. +TEST(MatchExpressionParserArrayTest, AllDottedISODate) { + StatusWith<Date_t> matchResult = dateFromISOString("2014-12-31T00:00:00.000Z"); + ASSERT_TRUE(matchResult.isOK()); + const Date_t& match = matchResult.getValue(); + StatusWith<Date_t> 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))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + 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)))); + delete result.getValue(); +} } |