/** * Copyright (C) 2012 10gen 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. */ /** Unit tests for MatchMatchExpression operator implementations in match_operators.{h,cpp}. */ #include "mongo/unittest/unittest.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/matcher/expression_tree.h" #include "mongo/db/query/collation/collator_interface_mock.h" namespace mongo { using std::unique_ptr; TEST(ElemMatchObjectMatchExpression, MatchesElementSingle) { BSONObj baseOperand = BSON("b" << 5); BSONObj match = BSON("a" << BSON_ARRAY(BSON("b" << 5.0))); BSONObj notMatch = BSON("a" << BSON_ARRAY(BSON("b" << 6))); unique_ptr eq(new EqualityMatchExpression()); ASSERT(eq->init("b", baseOperand["b"]).isOK()); ElemMatchObjectMatchExpression op; ASSERT(op.init("a", eq.release()).isOK()); ASSERT(op.matchesSingleElement(match["a"])); ASSERT(!op.matchesSingleElement(notMatch["a"])); } TEST(ElemMatchObjectMatchExpression, MatchesElementArray) { BSONObj baseOperand = BSON("1" << 5); BSONObj match = BSON("a" << BSON_ARRAY(BSON_ARRAY('s' << 5.0))); BSONObj notMatch = BSON("a" << BSON_ARRAY(BSON_ARRAY(5 << 6))); unique_ptr eq(new EqualityMatchExpression()); ASSERT(eq->init("1", baseOperand["1"]).isOK()); ElemMatchObjectMatchExpression op; ASSERT(op.init("a", eq.release()).isOK()); ASSERT(op.matchesSingleElement(match["a"])); ASSERT(!op.matchesSingleElement(notMatch["a"])); } TEST(ElemMatchObjectMatchExpression, MatchesElementMultiple) { BSONObj baseOperand1 = BSON("b" << 5); BSONObj baseOperand2 = BSON("b" << 6); BSONObj baseOperand3 = BSON("c" << 7); BSONObj notMatch1 = BSON("a" << BSON_ARRAY(BSON("b" << 5 << "c" << 7))); BSONObj notMatch2 = BSON("a" << BSON_ARRAY(BSON("b" << 6 << "c" << 7))); BSONObj notMatch3 = BSON("a" << BSON_ARRAY(BSON("b" << BSON_ARRAY(5 << 6)))); BSONObj match = BSON("a" << BSON_ARRAY(BSON("b" << BSON_ARRAY(5 << 6) << "c" << 7))); unique_ptr eq1(new EqualityMatchExpression()); ASSERT(eq1->init("b", baseOperand1["b"]).isOK()); unique_ptr eq2(new EqualityMatchExpression()); ASSERT(eq2->init("b", baseOperand2["b"]).isOK()); unique_ptr eq3(new EqualityMatchExpression()); ASSERT(eq3->init("c", baseOperand3["c"]).isOK()); unique_ptr andOp(new AndMatchExpression()); andOp->add(eq1.release()); andOp->add(eq2.release()); andOp->add(eq3.release()); ElemMatchObjectMatchExpression op; ASSERT(op.init("a", andOp.release()).isOK()); ASSERT(!op.matchesSingleElement(notMatch1["a"])); ASSERT(!op.matchesSingleElement(notMatch2["a"])); ASSERT(!op.matchesSingleElement(notMatch3["a"])); ASSERT(op.matchesSingleElement(match["a"])); } TEST(ElemMatchObjectMatchExpression, MatchesNonArray) { BSONObj baseOperand = BSON("b" << 5); unique_ptr eq(new EqualityMatchExpression()); ASSERT(eq->init("b", baseOperand["b"]).isOK()); ElemMatchObjectMatchExpression op; ASSERT(op.init("a", eq.release()).isOK()); // Directly nested objects are not matched with $elemMatch. An intervening array is // required. ASSERT(!op.matchesBSON(BSON("a" << BSON("b" << 5)), NULL)); ASSERT(!op.matchesBSON(BSON("a" << BSON("0" << (BSON("b" << 5)))), NULL)); ASSERT(!op.matchesBSON(BSON("a" << 4), NULL)); } TEST(ElemMatchObjectMatchExpression, MatchesArrayObject) { BSONObj baseOperand = BSON("b" << 5); unique_ptr eq(new EqualityMatchExpression()); ASSERT(eq->init("b", baseOperand["b"]).isOK()); ElemMatchObjectMatchExpression op; ASSERT(op.init("a", eq.release()).isOK()); ASSERT(op.matchesBSON(BSON("a" << BSON_ARRAY(BSON("b" << 5))), NULL)); ASSERT(op.matchesBSON(BSON("a" << BSON_ARRAY(4 << BSON("b" << 5))), NULL)); ASSERT(op.matchesBSON(BSON("a" << BSON_ARRAY(BSONObj() << BSON("b" << 5))), NULL)); ASSERT(op.matchesBSON(BSON("a" << BSON_ARRAY(BSON("b" << 6) << BSON("b" << 5))), NULL)); } TEST(ElemMatchObjectMatchExpression, MatchesMultipleNamedValues) { BSONObj baseOperand = BSON("c" << 5); unique_ptr eq(new EqualityMatchExpression()); ASSERT(eq->init("c", baseOperand["c"]).isOK()); ElemMatchObjectMatchExpression op; ASSERT(op.init("a.b", eq.release()).isOK()); ASSERT(op.matchesBSON(BSON("a" << BSON_ARRAY(BSON("b" << BSON_ARRAY(BSON("c" << 5))))), NULL)); ASSERT(op.matchesBSON(BSON("a" << BSON_ARRAY(BSON("b" << BSON_ARRAY(BSON("c" << 1))) << BSON("b" << BSON_ARRAY(BSON("c" << 5))))), NULL)); } TEST(ElemMatchObjectMatchExpression, ElemMatchKey) { BSONObj baseOperand = BSON("c" << 6); unique_ptr eq(new EqualityMatchExpression()); ASSERT(eq->init("c", baseOperand["c"]).isOK()); ElemMatchObjectMatchExpression op; ASSERT(op.init("a.b", eq.release()).isOK()); MatchDetails details; details.requestElemMatchKey(); ASSERT(!op.matchesBSON(BSONObj(), &details)); ASSERT(!details.hasElemMatchKey()); ASSERT(!op.matchesBSON(BSON("a" << BSON("b" << BSON_ARRAY(BSON("c" << 7)))), &details)); ASSERT(!details.hasElemMatchKey()); ASSERT(op.matchesBSON(BSON("a" << BSON("b" << BSON_ARRAY(3 << BSON("c" << 6)))), &details)); ASSERT(details.hasElemMatchKey()); // The entry within the $elemMatch array is reported. ASSERT_EQUALS("1", details.elemMatchKey()); ASSERT(op.matchesBSON( BSON("a" << BSON_ARRAY(1 << 2 << BSON("b" << BSON_ARRAY(3 << 5 << BSON("c" << 6))))), &details)); ASSERT(details.hasElemMatchKey()); // The entry within a parent of the $elemMatch array is reported. ASSERT_EQUALS("2", details.elemMatchKey()); } TEST(ElemMatchObjectMatchExpression, Collation) { BSONObj baseOperand = BSON("b" << "string"); BSONObj match = BSON("a" << BSON_ARRAY(BSON("b" << "string"))); BSONObj notMatch = BSON("a" << BSON_ARRAY(BSON("b" << "string2"))); unique_ptr eq(new EqualityMatchExpression()); ASSERT(eq->init("b", baseOperand["b"]).isOK()); ElemMatchObjectMatchExpression op; ASSERT(op.init("a", eq.release()).isOK()); CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); op.setCollator(&collator); ASSERT(op.matchesSingleElement(match["a"])); ASSERT(op.matchesSingleElement(notMatch["a"])); } /** TEST( ElemMatchObjectMatchExpression, MatchesIndexKey ) { BSONObj baseOperand = BSON( "b" << 5 ); unique_ptr eq( new ComparisonMatchExpression() ); ASSERT( eq->init( "b", baseOperand[ "b" ] ).isOK() ); ElemMatchObjectMatchExpression op; ASSERT( op.init( "a", eq.release() ).isOK() ); IndexSpec indexSpec( BSON( "a.b" << 1 ) ); BSONObj indexKey = BSON( "" << "5" ); ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == op.matchesIndexKey( indexKey, indexSpec ) ); } */ TEST(ElemMatchValueMatchExpression, MatchesElementSingle) { BSONObj baseOperand = BSON("$gt" << 5); BSONObj match = BSON("a" << BSON_ARRAY(6)); BSONObj notMatch = BSON("a" << BSON_ARRAY(4)); unique_ptr gt(new GTMatchExpression()); ASSERT(gt->init("", baseOperand["$gt"]).isOK()); ElemMatchValueMatchExpression op; ASSERT(op.init("a", gt.release()).isOK()); ASSERT(op.matchesSingleElement(match["a"])); ASSERT(!op.matchesSingleElement(notMatch["a"])); } TEST(ElemMatchValueMatchExpression, MatchesElementMultiple) { BSONObj baseOperand1 = BSON("$gt" << 1); BSONObj baseOperand2 = BSON("$lt" << 10); BSONObj notMatch1 = BSON("a" << BSON_ARRAY(0 << 1)); BSONObj notMatch2 = BSON("a" << BSON_ARRAY(10 << 11)); BSONObj match = BSON("a" << BSON_ARRAY(0 << 5 << 11)); unique_ptr gt(new GTMatchExpression()); ASSERT(gt->init("", baseOperand1["$gt"]).isOK()); unique_ptr lt(new LTMatchExpression()); ASSERT(lt->init("", baseOperand2["$lt"]).isOK()); ElemMatchValueMatchExpression op; ASSERT(op.init("a").isOK()); op.add(gt.release()); op.add(lt.release()); ASSERT(!op.matchesSingleElement(notMatch1["a"])); ASSERT(!op.matchesSingleElement(notMatch2["a"])); ASSERT(op.matchesSingleElement(match["a"])); } TEST(ElemMatchValueMatchExpression, MatchesNonArray) { BSONObj baseOperand = BSON("$gt" << 5); unique_ptr gt(new GTMatchExpression()); ASSERT(gt->init("", baseOperand["$gt"]).isOK()); ElemMatchObjectMatchExpression op; ASSERT(op.init("a", gt.release()).isOK()); // Directly nested objects are not matched with $elemMatch. An intervening array is // required. ASSERT(!op.matchesBSON(BSON("a" << 6), NULL)); ASSERT(!op.matchesBSON(BSON("a" << BSON("0" << 6)), NULL)); } TEST(ElemMatchValueMatchExpression, MatchesArrayScalar) { BSONObj baseOperand = BSON("$gt" << 5); unique_ptr gt(new GTMatchExpression()); ASSERT(gt->init("", baseOperand["$gt"]).isOK()); ElemMatchValueMatchExpression op; ASSERT(op.init("a", gt.release()).isOK()); ASSERT(op.matchesBSON(BSON("a" << BSON_ARRAY(6)), NULL)); ASSERT(op.matchesBSON(BSON("a" << BSON_ARRAY(4 << 6)), NULL)); ASSERT(op.matchesBSON(BSON("a" << BSON_ARRAY(BSONObj() << 7)), NULL)); } TEST(ElemMatchValueMatchExpression, MatchesMultipleNamedValues) { BSONObj baseOperand = BSON("$gt" << 5); unique_ptr gt(new GTMatchExpression()); ASSERT(gt->init("", baseOperand["$gt"]).isOK()); ElemMatchValueMatchExpression op; ASSERT(op.init("a.b", gt.release()).isOK()); ASSERT(op.matchesBSON(BSON("a" << BSON_ARRAY(BSON("b" << BSON_ARRAY(6)))), NULL)); ASSERT(op.matchesBSON( BSON("a" << BSON_ARRAY(BSON("b" << BSON_ARRAY(4)) << BSON("b" << BSON_ARRAY(4 << 6)))), NULL)); } TEST(ElemMatchValueMatchExpression, ElemMatchKey) { BSONObj baseOperand = BSON("$gt" << 6); unique_ptr gt(new GTMatchExpression()); ASSERT(gt->init("", baseOperand["$gt"]).isOK()); ElemMatchValueMatchExpression op; ASSERT(op.init("a.b", gt.release()).isOK()); MatchDetails details; details.requestElemMatchKey(); ASSERT(!op.matchesBSON(BSONObj(), &details)); ASSERT(!details.hasElemMatchKey()); ASSERT(!op.matchesBSON(BSON("a" << BSON("b" << BSON_ARRAY(2))), &details)); ASSERT(!details.hasElemMatchKey()); ASSERT(op.matchesBSON(BSON("a" << BSON("b" << BSON_ARRAY(3 << 7))), &details)); ASSERT(details.hasElemMatchKey()); // The entry within the $elemMatch array is reported. ASSERT_EQUALS("1", details.elemMatchKey()); ASSERT(op.matchesBSON(BSON("a" << BSON_ARRAY(1 << 2 << BSON("b" << BSON_ARRAY(3 << 7)))), &details)); ASSERT(details.hasElemMatchKey()); // The entry within a parent of the $elemMatch array is reported. ASSERT_EQUALS("2", details.elemMatchKey()); } /** TEST( ElemMatchValueMatchExpression, MatchesIndexKey ) { BSONObj baseOperand = BSON( "$lt" << 5 ); unique_ptr lt( new ComparisonMatchExpression() ); ASSERT( lt->init( "a", baseOperand[ "$lt" ] ).isOK() ); ElemMatchValueMatchExpression op; ASSERT( op.init( "a", lt.release() ).isOK() ); IndexSpec indexSpec( BSON( "a" << 1 ) ); BSONObj indexKey = BSON( "" << "3" ); ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == op.matchesIndexKey( indexKey, indexSpec ) ); } */ TEST(AndOfElemMatch, MatchesElement) { BSONObj baseOperanda1 = BSON("a" << 1); unique_ptr eqa1(new EqualityMatchExpression()); ASSERT(eqa1->init("a", baseOperanda1["a"]).isOK()); BSONObj baseOperandb1 = BSON("b" << 1); unique_ptr eqb1(new EqualityMatchExpression()); ASSERT(eqb1->init("b", baseOperandb1["b"]).isOK()); unique_ptr and1(new AndMatchExpression()); and1->add(eqa1.release()); and1->add(eqb1.release()); // and1 = { a : 1, b : 1 } unique_ptr elemMatch1(new ElemMatchObjectMatchExpression()); elemMatch1->init("x", and1.release()); // elemMatch1 = { x : { $elemMatch : { a : 1, b : 1 } } } BSONObj baseOperanda2 = BSON("a" << 2); unique_ptr eqa2(new EqualityMatchExpression()); ASSERT(eqa2->init("a", baseOperanda2["a"]).isOK()); BSONObj baseOperandb2 = BSON("b" << 2); unique_ptr eqb2(new EqualityMatchExpression()); ASSERT(eqb2->init("b", baseOperandb2["b"]).isOK()); unique_ptr and2(new AndMatchExpression()); and2->add(eqa2.release()); and2->add(eqb2.release()); // and2 = { a : 2, b : 2 } unique_ptr elemMatch2(new ElemMatchObjectMatchExpression()); elemMatch2->init("x", and2.release()); // elemMatch2 = { x : { $elemMatch : { a : 2, b : 2 } } } unique_ptr andOfEM(new AndMatchExpression()); andOfEM->add(elemMatch1.release()); andOfEM->add(elemMatch2.release()); BSONObj nonArray = BSON("x" << 4); ASSERT(!andOfEM->matchesSingleElement(nonArray["x"])); BSONObj emptyArray = BSON("x" << BSONArray()); ASSERT(!andOfEM->matchesSingleElement(emptyArray["x"])); BSONObj nonObjArray = BSON("x" << BSON_ARRAY(4)); ASSERT(!andOfEM->matchesSingleElement(nonObjArray["x"])); BSONObj singleObjMatch = BSON("x" << BSON_ARRAY(BSON("a" << 1 << "b" << 1))); ASSERT(!andOfEM->matchesSingleElement(singleObjMatch["x"])); BSONObj otherObjMatch = BSON("x" << BSON_ARRAY(BSON("a" << 2 << "b" << 2))); ASSERT(!andOfEM->matchesSingleElement(otherObjMatch["x"])); BSONObj bothObjMatch = BSON("x" << BSON_ARRAY(BSON("a" << 1 << "b" << 1) << BSON("a" << 2 << "b" << 2))); ASSERT(andOfEM->matchesSingleElement(bothObjMatch["x"])); BSONObj noObjMatch = BSON("x" << BSON_ARRAY(BSON("a" << 1 << "b" << 2) << BSON("a" << 2 << "b" << 1))); ASSERT(!andOfEM->matchesSingleElement(noObjMatch["x"])); } TEST(AndOfElemMatch, Matches) { BSONObj baseOperandgt1 = BSON("$gt" << 1); unique_ptr gt1(new GTMatchExpression()); ASSERT(gt1->init("", baseOperandgt1["$gt"]).isOK()); BSONObj baseOperandlt1 = BSON("$lt" << 10); unique_ptr lt1(new LTMatchExpression()); ASSERT(lt1->init("", baseOperandlt1["$lt"]).isOK()); unique_ptr elemMatch1(new ElemMatchValueMatchExpression()); elemMatch1->init("x"); elemMatch1->add(gt1.release()); elemMatch1->add(lt1.release()); // elemMatch1 = { x : { $elemMatch : { $gt : 1 , $lt : 10 } } } BSONObj baseOperandgt2 = BSON("$gt" << 101); unique_ptr gt2(new GTMatchExpression()); ASSERT(gt2->init("", baseOperandgt2["$gt"]).isOK()); BSONObj baseOperandlt2 = BSON("$lt" << 110); unique_ptr lt2(new LTMatchExpression()); ASSERT(lt2->init("", baseOperandlt2["$lt"]).isOK()); unique_ptr elemMatch2(new ElemMatchValueMatchExpression()); elemMatch2->init("x"); elemMatch2->add(gt2.release()); elemMatch2->add(lt2.release()); // elemMatch2 = { x : { $elemMatch : { $gt : 101 , $lt : 110 } } } unique_ptr andOfEM(new AndMatchExpression()); andOfEM->add(elemMatch1.release()); andOfEM->add(elemMatch2.release()); BSONObj nonArray = BSON("x" << 4); ASSERT(!andOfEM->matchesBSON(nonArray, NULL)); BSONObj emptyArray = BSON("x" << BSONArray()); ASSERT(!andOfEM->matchesBSON(emptyArray, NULL)); BSONObj nonNumberArray = BSON("x" << BSON_ARRAY("q")); ASSERT(!andOfEM->matchesBSON(nonNumberArray, NULL)); BSONObj singleMatch = BSON("x" << BSON_ARRAY(5)); ASSERT(!andOfEM->matchesBSON(singleMatch, NULL)); BSONObj otherMatch = BSON("x" << BSON_ARRAY(105)); ASSERT(!andOfEM->matchesBSON(otherMatch, NULL)); BSONObj bothMatch = BSON("x" << BSON_ARRAY(5 << 105)); ASSERT(andOfEM->matchesBSON(bothMatch, NULL)); BSONObj neitherMatch = BSON("x" << BSON_ARRAY(0 << 200)); ASSERT(!andOfEM->matchesBSON(neitherMatch, NULL)); } TEST(SizeMatchExpression, MatchesElement) { BSONObj match = BSON("a" << BSON_ARRAY(5 << 6)); BSONObj notMatch = BSON("a" << BSON_ARRAY(5)); SizeMatchExpression size; ASSERT(size.init("", 2).isOK()); ASSERT(size.matchesSingleElement(match.firstElement())); ASSERT(!size.matchesSingleElement(notMatch.firstElement())); } TEST(SizeMatchExpression, MatchesNonArray) { // Non arrays do not match. BSONObj stringValue = BSON("a" << "z"); BSONObj numberValue = BSON("a" << 0); BSONObj arrayValue = BSON("a" << BSONArray()); SizeMatchExpression size; ASSERT(size.init("", 0).isOK()); ASSERT(!size.matchesSingleElement(stringValue.firstElement())); ASSERT(!size.matchesSingleElement(numberValue.firstElement())); ASSERT(size.matchesSingleElement(arrayValue.firstElement())); } TEST(SizeMatchExpression, MatchesArray) { SizeMatchExpression size; ASSERT(size.init("a", 2).isOK()); ASSERT(size.matchesBSON(BSON("a" << BSON_ARRAY(4 << 5.5)), NULL)); // Arrays are not unwound to look for matching subarrays. ASSERT(!size.matchesBSON(BSON("a" << BSON_ARRAY(4 << 5.5 << BSON_ARRAY(1 << 2))), NULL)); } TEST(SizeMatchExpression, MatchesNestedArray) { SizeMatchExpression size; ASSERT(size.init("a.2", 2).isOK()); // A numerically referenced nested array is matched. ASSERT(size.matchesBSON(BSON("a" << BSON_ARRAY(4 << 5.5 << BSON_ARRAY(1 << 2))), NULL)); } TEST(SizeMatchExpression, ElemMatchKey) { SizeMatchExpression size; ASSERT(size.init("a.b", 3).isOK()); MatchDetails details; details.requestElemMatchKey(); ASSERT(!size.matchesBSON(BSON("a" << 1), &details)); ASSERT(!details.hasElemMatchKey()); ASSERT(size.matchesBSON(BSON("a" << BSON("b" << BSON_ARRAY(1 << 2 << 3))), &details)); ASSERT(!details.hasElemMatchKey()); ASSERT(size.matchesBSON(BSON("a" << BSON_ARRAY(2 << BSON("b" << BSON_ARRAY(1 << 2 << 3)))), &details)); ASSERT(details.hasElemMatchKey()); ASSERT_EQUALS("1", details.elemMatchKey()); } TEST(SizeMatchExpression, Equivalent) { SizeMatchExpression e1; SizeMatchExpression e2; SizeMatchExpression e3; e1.init("a", 5); e2.init("a", 6); e3.init("v", 5); ASSERT(e1.equivalent(&e1)); ASSERT(!e1.equivalent(&e2)); ASSERT(!e1.equivalent(&e3)); } /** TEST( SizeMatchExpression, MatchesIndexKey ) { BSONObj operand = BSON( "$size" << 4 ); SizeMatchExpression size; ASSERT( size.init( "a", operand[ "$size" ] ).isOK() ); IndexSpec indexSpec( BSON( "a" << 1 ) ); BSONObj indexKey = BSON( "" << 1 ); ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == size.matchesIndexKey( indexKey, indexSpec ) ); } */ } // namespace mongo