/** * Copyright (C) 2017 MongoDB 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/json.h" #include "mongo/db/matcher/expression_parser.h" #include "mongo/db/matcher/matcher.h" #include "mongo/db/matcher/schema/expression_internal_schema_object_match.h" #include "mongo/db/pipeline/expression_context_for_test.h" #include "mongo/db/query/collation/collator_interface_mock.h" #include "mongo/unittest/death_test.h" #include "mongo/unittest/unittest.h" namespace mongo { namespace { TEST(InternalSchemaObjectMatchExpression, RejectsNonObjectElements) { boost::intrusive_ptr expCtx(new ExpressionContextForTest()); auto subExpr = MatchExpressionParser::parse(BSON("b" << 1), expCtx); ASSERT_OK(subExpr.getStatus()); InternalSchemaObjectMatchExpression objMatch("a"_sd, std::move(subExpr.getValue())); ASSERT_FALSE(objMatch.matchesBSON(BSON("a" << 1))); ASSERT_FALSE(objMatch.matchesBSON(BSON("a" << "string"))); ASSERT_FALSE(objMatch.matchesBSON(BSON("a" << BSON_ARRAY(BSONNULL)))); } TEST(InternalSchemaObjectMatchExpression, RejectsObjectsThatDontMatch) { boost::intrusive_ptr expCtx(new ExpressionContextForTest()); auto subExpr = MatchExpressionParser::parse(BSON("b" << BSON("$type" << "string")), expCtx); ASSERT_OK(subExpr.getStatus()); InternalSchemaObjectMatchExpression objMatch("a"_sd, std::move(subExpr.getValue())); ASSERT_FALSE(objMatch.matchesBSON(BSON("a" << BSON("b" << 1)))); ASSERT_FALSE(objMatch.matchesBSON(BSON("a" << BSON("b" << BSONObj())))); } TEST(InternalSchemaObjectMatchExpression, AcceptsObjectsThatMatch) { boost::intrusive_ptr expCtx(new ExpressionContextForTest()); auto subExpr = MatchExpressionParser::parse(BSON("b" << BSON("$type" << "string")), expCtx); ASSERT_OK(subExpr.getStatus()); InternalSchemaObjectMatchExpression objMatch("a"_sd, std::move(subExpr.getValue())); ASSERT_TRUE(objMatch.matchesBSON(BSON("a" << BSON("b" << "string")))); ASSERT_TRUE(objMatch.matchesBSON(BSON("a" << BSON("b" << "string" << "c" << 1)))); ASSERT_FALSE( objMatch.matchesBSON(BSON("a" << BSON_ARRAY(BSON("b" << 1) << BSON("b" << "string"))))); ASSERT_TRUE(objMatch.matchesBSON(BSON("a" << BSON("b" << BSON_ARRAY("string"))))); } TEST(InternalSchemaObjectMatchExpression, DottedPathAcceptsObjectsThatMatch) { boost::intrusive_ptr expCtx(new ExpressionContextForTest()); auto subExpr = MatchExpressionParser::parse(BSON("b.c.d" << BSON("$type" << "string")), expCtx); ASSERT_OK(subExpr.getStatus()); InternalSchemaObjectMatchExpression objMatch("a"_sd, std::move(subExpr.getValue())); ASSERT_FALSE(objMatch.matchesBSON(BSON("a" << BSON("d" << "string")))); ASSERT_FALSE(objMatch.matchesBSON(BSON("a" << BSON("b" << BSON("c" << BSON("d" << 1)))))); ASSERT_TRUE(objMatch.matchesBSON(BSON("a" << BSON("b" << BSON("c" << BSON("d" << "foo")))))); } TEST(InternalSchemaObjectMatchExpression, EmptyMatchAcceptsAllObjects) { boost::intrusive_ptr expCtx(new ExpressionContextForTest()); auto subExpr = MatchExpressionParser::parse(BSONObj(), expCtx); ASSERT_OK(subExpr.getStatus()); InternalSchemaObjectMatchExpression objMatch("a"_sd, std::move(subExpr.getValue())); ASSERT_FALSE(objMatch.matchesBSON(BSON("a" << 1))); ASSERT_FALSE(objMatch.matchesBSON(BSON("a" << "string"))); ASSERT_TRUE(objMatch.matchesBSON(BSON("a" << BSONObj()))); ASSERT_TRUE(objMatch.matchesBSON(BSON("a" << BSON("b" << "string")))); ASSERT_FALSE(objMatch.matchesBSON(BSON("a" << BSON_ARRAY(BSONObj())))); } TEST(InternalSchemaObjectMatchExpression, NestedObjectMatchReturnsCorrectPath) { auto query = fromjson( " {a: {$_internalSchemaObjectMatch: {" " b: {$_internalSchemaObjectMatch: {" " $or: [{c: {$type: 'string'}}, {c: {$gt: 0}}]" " }}}" " }}}"); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); auto objMatch = MatchExpressionParser::parse(query, expCtx); ASSERT_OK(objMatch.getStatus()); ASSERT_EQ(objMatch.getValue()->path(), "a"); ASSERT_EQ(objMatch.getValue()->getChild(0)->path(), "b"); } TEST(InternalSchemaObjectMatchExpression, MatchesNestedObjectMatch) { auto query = fromjson( " {a: {$_internalSchemaObjectMatch: {" " b: {$_internalSchemaObjectMatch: {" " c: 3" " }}}" " }}}"); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); auto objMatch = MatchExpressionParser::parse(query, expCtx); ASSERT_OK(objMatch.getStatus()); ASSERT_FALSE(objMatch.getValue()->matchesBSON(fromjson("{a: 1}"))); ASSERT_FALSE(objMatch.getValue()->matchesBSON(fromjson("{a: {b: 1}}"))); ASSERT_FALSE(objMatch.getValue()->matchesBSON(fromjson("{a: {b: {c: 1}}}"))); ASSERT_TRUE(objMatch.getValue()->matchesBSON(fromjson("{a: {b: {c: 3}}}"))); } TEST(InternalSchemaObjectMatchExpression, EquivalentReturnsCorrectResults) { auto query = fromjson( " {a: {$_internalSchemaObjectMatch: {" " b: 3" " }}}"); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); Matcher objectMatch(query, expCtx); query = fromjson( " {a: {$_internalSchemaObjectMatch: {" " b: {$eq: 3}" " }}}"); Matcher objectMatchEq(query, expCtx); ASSERT_TRUE(objectMatch.getMatchExpression()->equivalent(objectMatchEq.getMatchExpression())); query = fromjson( " {a: {$_internalSchemaObjectMatch: {" " c: {$eq: 3}" " }}}"); Matcher objectMatchNotEq(query, expCtx); ASSERT_FALSE( objectMatch.getMatchExpression()->equivalent(objectMatchNotEq.getMatchExpression())); } TEST(InternalSchemaObjectMatchExpression, SubExpressionRespectsCollator) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kToLowerString); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); expCtx->setCollator(&collator); auto query = fromjson( "{a: {$_internalSchemaObjectMatch: {" " b: {$eq: 'FOO'}" "}}}"); auto objectMatch = MatchExpressionParser::parse(query, expCtx); ASSERT_OK(objectMatch.getStatus()); ASSERT_TRUE(objectMatch.getValue()->matchesBSON(fromjson("{a: {b: 'FOO'}}"))); ASSERT_TRUE(objectMatch.getValue()->matchesBSON(fromjson("{a: {b: 'foO'}}"))); ASSERT_TRUE(objectMatch.getValue()->matchesBSON(fromjson("{a: {b: 'foo'}}"))); } TEST(InternalSchemaObjectMatchExpression, RejectsArraysContainingMatchingSubObject) { auto query = fromjson("{a: {$_internalSchemaObjectMatch: {b: 1}}}"); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); auto objMatch = MatchExpressionParser::parse(query, expCtx); ASSERT_OK(objMatch.getStatus()); ASSERT_FALSE(objMatch.getValue()->matchesBSON(fromjson("{a: 1}"))); ASSERT_TRUE(objMatch.getValue()->matchesBSON(fromjson("{a: {b: 1}}"))); ASSERT_FALSE(objMatch.getValue()->matchesBSON(fromjson("{a: [{b: 1}]}"))); ASSERT_FALSE(objMatch.getValue()->matchesBSON(fromjson("{a: [{b: 1}, {b: 2}]}"))); } TEST(InternalSchemaObjectMatchExpression, HasSingleChild) { auto query = fromjson( " {a: {$_internalSchemaObjectMatch: {" " c: {$eq: 3}" " }}}"); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); auto objMatch = MatchExpressionParser::parse(query, expCtx); ASSERT_OK(objMatch.getStatus()); ASSERT_EQ(objMatch.getValue()->numChildren(), 1U); ASSERT(objMatch.getValue()->getChild(0)); } DEATH_TEST(InternalSchemaObjectMatchExpression, GetChildFailsIndexGreaterThanZero, "Invariant failure i == 0") { auto query = fromjson( " {a: {$_internalSchemaObjectMatch: {" " c: {$eq: 3}" " }}}"); boost::intrusive_ptr expCtx(new ExpressionContextForTest()); auto objMatch = MatchExpressionParser::parse(query, expCtx); ASSERT_OK(objMatch.getStatus()); objMatch.getValue()->getChild(1); } } // namespace } // namespace mongo