/**
* 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.
*/
#include "mongo/platform/basic.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_leaf.h"
#include "mongo/db/matcher/expression_tree.h"
#include "mongo/db/query/collation/collator_interface_mock.h"
#include "mongo/unittest/unittest.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("b", baseOperand["b"]));
ElemMatchObjectMatchExpression op("a", eq.release());
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("1", baseOperand["1"]));
ElemMatchObjectMatchExpression op("a", eq.release());
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("b", baseOperand1["b"]));
unique_ptr eq2(new EqualityMatchExpression("b", baseOperand2["b"]));
unique_ptr eq3(new EqualityMatchExpression("c", baseOperand3["c"]));
unique_ptr andOp(new AndMatchExpression());
andOp->add(eq1.release());
andOp->add(eq2.release());
andOp->add(eq3.release());
ElemMatchObjectMatchExpression op("a", andOp.release());
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("b", baseOperand["b"]));
ElemMatchObjectMatchExpression op("a", eq.release());
// 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("b", baseOperand["b"]));
ElemMatchObjectMatchExpression op("a", eq.release());
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("c", baseOperand["c"]));
ElemMatchObjectMatchExpression op("a.b", eq.release());
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("c", baseOperand["c"]));
ElemMatchObjectMatchExpression op("a.b", eq.release());
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("b", baseOperand["b"]));
ElemMatchObjectMatchExpression op("a", eq.release());
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("", baseOperand["$gt"]));
ElemMatchValueMatchExpression op("a", gt.release());
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("", baseOperand1["$gt"]));
unique_ptr lt(new LTMatchExpression("", baseOperand2["$lt"]));
ElemMatchValueMatchExpression op("a");
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("", baseOperand["$gt"]));
ElemMatchObjectMatchExpression op("a", gt.release());
// 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("", baseOperand["$gt"]));
ElemMatchValueMatchExpression op("a", gt.release());
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("", baseOperand["$gt"]));
ElemMatchValueMatchExpression op("a.b", gt.release());
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("", baseOperand["$gt"]));
ElemMatchValueMatchExpression op("a.b", gt.release());
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("a", baseOperanda1["a"]));
BSONObj baseOperandb1 = BSON("b" << 1);
unique_ptr eqb1(
new EqualityMatchExpression("b", baseOperandb1["b"]));
unique_ptr and1(new AndMatchExpression());
and1->add(eqa1.release());
and1->add(eqb1.release());
// and1 = { a : 1, b : 1 }
unique_ptr elemMatch1(
new ElemMatchObjectMatchExpression("x", and1.release()));
// elemMatch1 = { x : { $elemMatch : { a : 1, b : 1 } } }
BSONObj baseOperanda2 = BSON("a" << 2);
unique_ptr eqa2(
new EqualityMatchExpression("a", baseOperanda2["a"]));
BSONObj baseOperandb2 = BSON("b" << 2);
unique_ptr eqb2(
new EqualityMatchExpression("b", baseOperandb2["b"]));
unique_ptr and2(new AndMatchExpression());
and2->add(eqa2.release());
and2->add(eqb2.release());
// and2 = { a : 2, b : 2 }
unique_ptr elemMatch2(
new ElemMatchObjectMatchExpression("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("", baseOperandgt1["$gt"]));
BSONObj baseOperandlt1 = BSON("$lt" << 10);
unique_ptr lt1(new LTMatchExpression("", baseOperandlt1["$lt"]));
unique_ptr elemMatch1(new ElemMatchValueMatchExpression("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("", baseOperandgt2["$gt"]));
BSONObj baseOperandlt2 = BSON("$lt" << 110);
unique_ptr lt2(new LTMatchExpression("", baseOperandlt2["$lt"]));
unique_ptr elemMatch2(new ElemMatchValueMatchExpression("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("", 2);
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("", 0);
ASSERT(!size.matchesSingleElement(stringValue.firstElement()));
ASSERT(!size.matchesSingleElement(numberValue.firstElement()));
ASSERT(size.matchesSingleElement(arrayValue.firstElement()));
}
TEST(SizeMatchExpression, MatchesArray) {
SizeMatchExpression size("a", 2);
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("a.2", 2);
// 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("a.b", 3);
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("a", 5);
SizeMatchExpression e2("a", 6);
SizeMatchExpression e3("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