// expression_parser_test.cpp
/**
* Copyright (C) 2013 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/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_always_boolean.h"
#include "mongo/db/matcher/expression_leaf.h"
#include "mongo/db/matcher/extensions_callback_noop.h"
#include "mongo/db/pipeline/expression_context_for_test.h"
namespace mongo {
TEST(MatchExpressionParserTest, SimpleEQ1) {
BSONObj query = BSON("x" << 2);
boost::intrusive_ptr expCtx(new ExpressionContextForTest());
StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx);
ASSERT_TRUE(result.isOK());
ASSERT(result.getValue()->matchesBSON(BSON("x" << 2)));
ASSERT(!result.getValue()->matchesBSON(BSON("x" << 3)));
}
TEST(MatchExpressionParserTest, Multiple1) {
BSONObj query = BSON("x" << 5 << "y" << BSON("$gt" << 5 << "$lt" << 8));
boost::intrusive_ptr expCtx(new ExpressionContextForTest());
StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx);
ASSERT_TRUE(result.isOK());
ASSERT(result.getValue()->matchesBSON(BSON("x" << 5 << "y" << 7)));
ASSERT(result.getValue()->matchesBSON(BSON("x" << 5 << "y" << 6)));
ASSERT(!result.getValue()->matchesBSON(BSON("x" << 6 << "y" << 7)));
ASSERT(!result.getValue()->matchesBSON(BSON("x" << 5 << "y" << 9)));
ASSERT(!result.getValue()->matchesBSON(BSON("x" << 5 << "y" << 4)));
}
TEST(MatchExpressionParserTest, MinDistanceWithoutNearFailsToParse) {
BSONObj query = fromjson("{loc: {$minDistance: 10}}");
boost::intrusive_ptr expCtx(new ExpressionContextForTest());
StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx);
ASSERT_FALSE(result.isOK());
}
TEST(MatchExpressionParserTest, ParseIntegerElementToNonNegativeLongRejectsNegative) {
BSONObj query = BSON("" << -2LL);
ASSERT_NOT_OK(
MatchExpressionParser::parseIntegerElementToNonNegativeLong(query.firstElement()));
}
TEST(MatchExpressionParserTest, ParseIntegerElementToLongAcceptsNegative) {
BSONObj query = BSON("" << -2LL);
auto result = MatchExpressionParser::parseIntegerElementToLong(query.firstElement());
ASSERT_OK(result.getStatus());
ASSERT_EQ(-2LL, result.getValue());
}
TEST(MatchExpressionParserTest, ParseIntegerElementToNonNegativeLongRejectsTooLargeDouble) {
BSONObj query = BSON("" << MatchExpressionParser::kLongLongMaxPlusOneAsDouble);
ASSERT_NOT_OK(
MatchExpressionParser::parseIntegerElementToNonNegativeLong(query.firstElement()));
}
TEST(MatchExpressionParserTest, ParseIntegerElementToLongRejectsTooLargeDouble) {
BSONObj query = BSON("" << MatchExpressionParser::kLongLongMaxPlusOneAsDouble);
ASSERT_NOT_OK(MatchExpressionParser::parseIntegerElementToLong(query.firstElement()));
}
TEST(MatchExpressionParserTest, ParseIntegerElementToLongRejectsTooLargeNegativeDouble) {
BSONObj query = BSON("" << std::numeric_limits::min());
ASSERT_NOT_OK(MatchExpressionParser::parseIntegerElementToLong(query.firstElement()));
}
TEST(MatchExpressionParserTest, ParseIntegerElementToNonNegativeLongRejectsString) {
BSONObj query = BSON(""
<< "1");
ASSERT_NOT_OK(
MatchExpressionParser::parseIntegerElementToNonNegativeLong(query.firstElement()));
}
TEST(MatchExpressionParserTest, ParseIntegerElementToLongRejectsString) {
BSONObj query = BSON(""
<< "1");
ASSERT_NOT_OK(MatchExpressionParser::parseIntegerElementToLong(query.firstElement()));
}
TEST(MatchExpressionParserTest, ParseIntegerElementToNonNegativeLongRejectsNonIntegralDouble) {
BSONObj query = BSON("" << 2.5);
ASSERT_NOT_OK(
MatchExpressionParser::parseIntegerElementToNonNegativeLong(query.firstElement()));
}
TEST(MatchExpressionParserTest, ParseIntegerElementToLongRejectsNonIntegralDouble) {
BSONObj query = BSON("" << 2.5);
ASSERT_NOT_OK(MatchExpressionParser::parseIntegerElementToLong(query.firstElement()));
}
TEST(MatchExpressionParserTest, ParseIntegerElementToNonNegativeLongRejectsNonIntegralDecimal) {
BSONObj query = BSON("" << Decimal128("2.5"));
ASSERT_NOT_OK(
MatchExpressionParser::parseIntegerElementToNonNegativeLong(query.firstElement()));
}
TEST(MatchExpressionParserTest, ParseIntegerElementToLongRejectsNonIntegralDecimal) {
BSONObj query = BSON("" << Decimal128("2.5"));
ASSERT_NOT_OK(MatchExpressionParser::parseIntegerElementToLong(query.firstElement()));
}
TEST(MatchExpressionParserTest, ParseIntegerElementToNonNegativeLongRejectsLargestDecimal) {
BSONObj query = BSON("" << Decimal128(Decimal128::kLargestPositive));
ASSERT_NOT_OK(
MatchExpressionParser::parseIntegerElementToNonNegativeLong(query.firstElement()));
}
TEST(MatchExpressionParserTest, ParseIntegerElementToLongRejectsLargestDecimal) {
BSONObj query = BSON("" << Decimal128(Decimal128::kLargestPositive));
ASSERT_NOT_OK(MatchExpressionParser::parseIntegerElementToLong(query.firstElement()));
}
TEST(MatchExpressionParserTest, ParseIntegerElementToNonNegativeLongAcceptsZero) {
BSONObj query = BSON("" << 0);
auto result = MatchExpressionParser::parseIntegerElementToNonNegativeLong(query.firstElement());
ASSERT_OK(result.getStatus());
ASSERT_EQ(result.getValue(), 0LL);
}
TEST(MatchExpressionParserTest, ParseIntegerElementToLongAcceptsZero) {
BSONObj query = BSON("" << 0);
auto result = MatchExpressionParser::parseIntegerElementToLong(query.firstElement());
ASSERT_OK(result.getStatus());
ASSERT_EQ(result.getValue(), 0LL);
}
TEST(MatchExpressionParserTest, ParseIntegerElementToNonNegativeLongAcceptsThree) {
BSONObj query = BSON("" << 3.0);
auto result = MatchExpressionParser::parseIntegerElementToNonNegativeLong(query.firstElement());
ASSERT_OK(result.getStatus());
ASSERT_EQ(result.getValue(), 3LL);
}
TEST(MatchExpressionParserTest, ParseIntegerElementToLongAcceptsThree) {
BSONObj query = BSON("" << 3.0);
auto result = MatchExpressionParser::parseIntegerElementToLong(query.firstElement());
ASSERT_OK(result.getStatus());
ASSERT_EQ(result.getValue(), 3LL);
}
StatusWith fib(int n) {
if (n < 0)
return StatusWith(ErrorCodes::BadValue, "paramter to fib has to be >= 0");
if (n <= 1)
return StatusWith(1);
StatusWith a = fib(n - 1);
StatusWith b = fib(n - 2);
if (!a.isOK())
return a;
if (!b.isOK())
return b;
return StatusWith(a.getValue() + b.getValue());
}
TEST(StatusWithTest, Fib1) {
StatusWith x = fib(-2);
ASSERT(!x.isOK());
x = fib(0);
ASSERT(x.isOK());
ASSERT(1 == x.getValue());
x = fib(1);
ASSERT(x.isOK());
ASSERT(1 == x.getValue());
x = fib(2);
ASSERT(x.isOK());
ASSERT(2 == x.getValue());
x = fib(3);
ASSERT(x.isOK());
ASSERT(3 == x.getValue());
}
TEST(MatchExpressionParserTest, AlwaysFalseFailsToParseNonOneArguments) {
auto queryIntArgument = BSON(AlwaysFalseMatchExpression::kName << 0);
boost::intrusive_ptr expCtx(new ExpressionContextForTest());
auto expr = MatchExpressionParser::parse(queryIntArgument, expCtx);
ASSERT_EQ(expr.getStatus(), ErrorCodes::FailedToParse);
auto queryStringArgument = BSON(AlwaysFalseMatchExpression::kName << "");
expr = MatchExpressionParser::parse(queryStringArgument, expCtx);
ASSERT_EQ(expr.getStatus(), ErrorCodes::FailedToParse);
auto queryDoubleArgument = BSON(AlwaysFalseMatchExpression::kName << 1.1);
expr = MatchExpressionParser::parse(queryDoubleArgument, expCtx);
ASSERT_EQ(expr.getStatus(), ErrorCodes::FailedToParse);
auto queryFalseArgument = BSON(AlwaysFalseMatchExpression::kName << true);
expr = MatchExpressionParser::parse(queryFalseArgument, expCtx);
ASSERT_EQ(expr.getStatus(), ErrorCodes::FailedToParse);
}
TEST(MatchExpressionParserTest, AlwaysFalseParsesIntegerArgument) {
auto query = BSON(AlwaysFalseMatchExpression::kName << 1);
boost::intrusive_ptr expCtx(new ExpressionContextForTest());
auto expr = MatchExpressionParser::parse(query, expCtx);
ASSERT_OK(expr.getStatus());
ASSERT_FALSE(expr.getValue()->matchesBSON(fromjson("{}")));
ASSERT_FALSE(expr.getValue()->matchesBSON(fromjson("{x: 1}")));
ASSERT_FALSE(expr.getValue()->matchesBSON(fromjson("{x: 'blah'}")));
}
TEST(MatchExpressionParserTest, AlwaysTrueFailsToParseNonOneArguments) {
auto queryIntArgument = BSON(AlwaysTrueMatchExpression::kName << 0);
boost::intrusive_ptr expCtx(new ExpressionContextForTest());
auto expr = MatchExpressionParser::parse(queryIntArgument, expCtx);
ASSERT_EQ(expr.getStatus(), ErrorCodes::FailedToParse);
auto queryStringArgument = BSON(AlwaysTrueMatchExpression::kName << "");
expr = MatchExpressionParser::parse(queryStringArgument, expCtx);
ASSERT_EQ(expr.getStatus(), ErrorCodes::FailedToParse);
auto queryDoubleArgument = BSON(AlwaysTrueMatchExpression::kName << 1.1);
expr = MatchExpressionParser::parse(queryDoubleArgument, expCtx);
ASSERT_EQ(expr.getStatus(), ErrorCodes::FailedToParse);
auto queryFalseArgument = BSON(AlwaysTrueMatchExpression::kName << true);
expr = MatchExpressionParser::parse(queryFalseArgument, expCtx);
ASSERT_EQ(expr.getStatus(), ErrorCodes::FailedToParse);
}
TEST(MatchExpressionParserTest, AlwaysTrueParsesIntegerArgument) {
auto query = BSON(AlwaysTrueMatchExpression::kName << 1);
boost::intrusive_ptr expCtx(new ExpressionContextForTest());
auto expr = MatchExpressionParser::parse(query, expCtx);
ASSERT_OK(expr.getStatus());
ASSERT_TRUE(expr.getValue()->matchesBSON(fromjson("{}")));
ASSERT_TRUE(expr.getValue()->matchesBSON(fromjson("{x: 1}")));
ASSERT_TRUE(expr.getValue()->matchesBSON(fromjson("{x: 'blah'}")));
}
TEST(MatchExpressionParserTest, TextFailsToParseWhenDisallowed) {
auto query = fromjson("{$text: {$search: 'str'}}");
boost::intrusive_ptr expCtx(new ExpressionContextForTest());
ASSERT_NOT_OK(MatchExpressionParser::parse(query, expCtx).getStatus());
}
TEST(MatchExpressionParserTest, TextParsesSuccessfullyWhenAllowed) {
auto query = fromjson("{$text: {$search: 'str'}}");
boost::intrusive_ptr expCtx(new ExpressionContextForTest());
ASSERT_OK(
MatchExpressionParser::parse(
query, expCtx, ExtensionsCallbackNoop(), MatchExpressionParser::AllowedFeatures::kText)
.getStatus());
}
TEST(MatchExpressionParserTest, TextFailsToParseIfNotTopLevel) {
auto query = fromjson("{a: {$text: {$search: 'str'}}}");
boost::intrusive_ptr expCtx(new ExpressionContextForTest());
ASSERT_NOT_OK(
MatchExpressionParser::parse(
query, expCtx, ExtensionsCallbackNoop(), MatchExpressionParser::AllowedFeatures::kText)
.getStatus());
}
TEST(MatchExpressionParserTest, TextWithinElemMatchFailsToParse) {
auto query = fromjson("{a: {$elemMatch: {$text: {$search: 'str'}}}}");
boost::intrusive_ptr expCtx(new ExpressionContextForTest());
ASSERT_NOT_OK(
MatchExpressionParser::parse(
query, expCtx, ExtensionsCallbackNoop(), MatchExpressionParser::AllowedFeatures::kText)
.getStatus());
query = fromjson("{a: {$elemMatch: {$elemMatch: {$text: {$search: 'str'}}}}}");
ASSERT_NOT_OK(
MatchExpressionParser::parse(
query, expCtx, ExtensionsCallbackNoop(), MatchExpressionParser::AllowedFeatures::kText)
.getStatus());
}
TEST(MatchExpressionParserTest, WhereFailsToParseWhenDisallowed) {
auto query = fromjson("{$where: 'this.a == this.b'}");
boost::intrusive_ptr expCtx(new ExpressionContextForTest());
ASSERT_NOT_OK(MatchExpressionParser::parse(query, expCtx).getStatus());
}
TEST(MatchExpressionParserTest, WhereParsesSuccessfullyWhenAllowed) {
auto query = fromjson("{$where: 'this.a == this.b'}");
boost::intrusive_ptr expCtx(new ExpressionContextForTest());
ASSERT_OK(MatchExpressionParser::parse(query,
expCtx,
ExtensionsCallbackNoop(),
MatchExpressionParser::AllowedFeatures::kJavascript)
.getStatus());
}
TEST(MatchExpressionParserTest, NearSphereFailsToParseWhenDisallowed) {
auto query = fromjson("{a: {$nearSphere: {$geometry: {type: 'Point', coordinates: [0, 0]}}}}");
boost::intrusive_ptr expCtx(new ExpressionContextForTest());
ASSERT_NOT_OK(MatchExpressionParser::parse(query, expCtx).getStatus());
}
TEST(MatchExpressionParserTest, NearSphereParsesSuccessfullyWhenAllowed) {
auto query = fromjson("{a: {$nearSphere: {$geometry: {type: 'Point', coordinates: [0, 0]}}}}");
boost::intrusive_ptr expCtx(new ExpressionContextForTest());
ASSERT_OK(MatchExpressionParser::parse(query,
expCtx,
ExtensionsCallbackNoop(),
MatchExpressionParser::AllowedFeatures::kGeoNear)
.getStatus());
}
TEST(MatchExpressionParserTest, GeoNearFailsToParseWhenDisallowed) {
auto query = fromjson("{a: {$geoNear: {$geometry: {type: 'Point', coordinates: [0, 0]}}}}");
boost::intrusive_ptr expCtx(new ExpressionContextForTest());
ASSERT_NOT_OK(MatchExpressionParser::parse(query, expCtx).getStatus());
}
TEST(MatchExpressionParserTest, GeoNearParsesSuccessfullyWhenAllowed) {
auto query = fromjson("{a: {$geoNear: {$geometry: {type: 'Point', coordinates: [0, 0]}}}}");
boost::intrusive_ptr expCtx(new ExpressionContextForTest());
ASSERT_OK(MatchExpressionParser::parse(query,
expCtx,
ExtensionsCallbackNoop(),
MatchExpressionParser::AllowedFeatures::kGeoNear)
.getStatus());
}
TEST(MatchExpressionParserTest, NearFailsToParseWhenDisallowed) {
auto query = fromjson("{a: {$near: {$geometry: {type: 'Point', coordinates: [0, 0]}}}}");
boost::intrusive_ptr expCtx(new ExpressionContextForTest());
ASSERT_NOT_OK(MatchExpressionParser::parse(query, expCtx).getStatus());
}
TEST(MatchExpressionParserTest, NearParsesSuccessfullyWhenAllowed) {
auto query = fromjson("{a: {$near: {$geometry: {type: 'Point', coordinates: [0, 0]}}}}");
boost::intrusive_ptr expCtx(new ExpressionContextForTest());
ASSERT_OK(MatchExpressionParser::parse(query,
expCtx,
ExtensionsCallbackNoop(),
MatchExpressionParser::AllowedFeatures::kGeoNear)
.getStatus());
}
TEST(MatchExpressionParserTest, ExprFailsToParseWhenDisallowed) {
auto query = fromjson("{$expr: {$eq: ['$a', 5]}}");
boost::intrusive_ptr expCtx(new ExpressionContextForTest());
ASSERT_NOT_OK(
MatchExpressionParser::parse(
query, expCtx, ExtensionsCallbackNoop(), MatchExpressionParser::kBanAllSpecialFeatures)
.getStatus());
}
TEST(MatchExpressionParserTest, ExprParsesSuccessfullyWhenAllowed) {
auto query = fromjson("{$expr: {$eq: ['$a', 5]}}");
boost::intrusive_ptr expCtx(new ExpressionContextForTest());
ASSERT_OK(MatchExpressionParser::parse(query, expCtx).getStatus());
}
TEST(MatchExpressionParserTest, ExprParsesSuccessfullyWithAdditionalTopLevelPredicates) {
auto query = fromjson("{x: 1, $expr: {$eq: ['$a', 5]}, y: 1}");
boost::intrusive_ptr expCtx(new ExpressionContextForTest());
ASSERT_OK(MatchExpressionParser::parse(query, expCtx).getStatus());
}
TEST(MatchExpressionParserTest, ExprParsesSuccessfullyWithinTopLevelOr) {
auto query = fromjson("{$or: [{x: 1}, {$expr: {$eq: ['$a', 5]}}]}");
boost::intrusive_ptr expCtx(new ExpressionContextForTest());
ASSERT_OK(
MatchExpressionParser::parse(
query, expCtx, ExtensionsCallbackNoop(), MatchExpressionParser::AllowedFeatures::kExpr)
.getStatus());
}
TEST(MatchExpressionParserTest, ExprParsesSuccessfullyWithinTopLevelAnd) {
auto query = fromjson("{$and: [{x: 1}, {$expr: {$eq: ['$a', 5]}}]}");
boost::intrusive_ptr expCtx(new ExpressionContextForTest());
ASSERT_OK(
MatchExpressionParser::parse(
query, expCtx, ExtensionsCallbackNoop(), MatchExpressionParser::AllowedFeatures::kExpr)
.getStatus());
}
TEST(MatchExpressionParserTest, ExprFailsToParseWithinElemMatch) {
auto query = fromjson("{a: {$elemMatch: {$expr: {$eq: ['$foo', '$bar']}}}}");
boost::intrusive_ptr expCtx(new ExpressionContextForTest());
ASSERT_NOT_OK(
MatchExpressionParser::parse(
query, expCtx, ExtensionsCallbackNoop(), MatchExpressionParser::AllowedFeatures::kExpr)
.getStatus());
}
TEST(MatchExpressionParserTest, ExprNestedFailsToParseWithinElemMatch) {
auto query =
fromjson("{a: {$elemMatch: {b: 1, $or: [{$expr: {$eq: ['$foo', '$bar']}}, {c: 1}]}}}");
boost::intrusive_ptr expCtx(new ExpressionContextForTest());
ASSERT_NOT_OK(
MatchExpressionParser::parse(
query, expCtx, ExtensionsCallbackNoop(), MatchExpressionParser::AllowedFeatures::kExpr)
.getStatus());
}
TEST(MatchExpressionParserTest, ExprFailsToParseWithinInternalSchemaObjectMatch) {
auto query = fromjson("{a: {$_internalSchemaObjectMatch: {$expr: {$eq: ['$foo', '$bar']}}}}");
boost::intrusive_ptr expCtx(new ExpressionContextForTest());
ASSERT_NOT_OK(
MatchExpressionParser::parse(
query, expCtx, ExtensionsCallbackNoop(), MatchExpressionParser::AllowedFeatures::kExpr)
.getStatus());
}
TEST(MatchExpressionParserTest, InternalExprEqParsesCorrectly) {
boost::intrusive_ptr expCtx(new ExpressionContextForTest());
auto query = fromjson("{a: {$_internalExprEq: 'foo'}}");
auto statusWith = MatchExpressionParser::parse(query, expCtx);
ASSERT_OK(statusWith.getStatus());
ASSERT_TRUE(statusWith.getValue()->matchesBSON(fromjson("{a: 'foo'}")));
ASSERT_TRUE(statusWith.getValue()->matchesBSON(fromjson("{a: ['foo']}")));
ASSERT_TRUE(statusWith.getValue()->matchesBSON(fromjson("{a: ['bar']}")));
ASSERT_FALSE(statusWith.getValue()->matchesBSON(fromjson("{a: 'bar'}")));
query = fromjson("{'a.b': {$_internalExprEq: 5}}");
statusWith = MatchExpressionParser::parse(query, expCtx);
ASSERT_OK(statusWith.getStatus());
ASSERT_TRUE(statusWith.getValue()->matchesBSON(fromjson("{a: {b: 5}}")));
ASSERT_TRUE(statusWith.getValue()->matchesBSON(fromjson("{a: {b: [5]}}")));
ASSERT_TRUE(statusWith.getValue()->matchesBSON(fromjson("{a: {b: [6]}}")));
ASSERT_FALSE(statusWith.getValue()->matchesBSON(fromjson("{a: {b: 6}}")));
}
TEST(MatchesExpressionParserTest, InternalExprEqComparisonToArrayDoesNotParse) {
boost::intrusive_ptr expCtx(new ExpressionContextForTest());
auto query = fromjson("{'a.b': {$_internalExprEq: [5]}}");
ASSERT_EQ(MatchExpressionParser::parse(query, expCtx).getStatus(), ErrorCodes::BadValue);
}
TEST(MatchesExpressionParserTest, InternalExprEqComparisonToUndefinedDoesNotParse) {
boost::intrusive_ptr expCtx(new ExpressionContextForTest());
auto query = fromjson("{'a.b': {$_internalExprEq: undefined}}");
ASSERT_EQ(MatchExpressionParser::parse(query, expCtx).getStatus(), ErrorCodes::BadValue);
}
} // namespace mongo