diff options
author | James Wahlin <james.wahlin@mongodb.com> | 2019-09-11 21:18:26 +0000 |
---|---|---|
committer | evergreen <evergreen@mongodb.com> | 2019-09-11 21:18:26 +0000 |
commit | 3db787da9dc453192adcf7706991d2d09f0ff0c7 (patch) | |
tree | 45eafe4287f66d68fc82f9b9b99241c1973f844c | |
parent | 08274a638ff8e3e750704c907de5ebd1def46865 (diff) | |
download | mongo-3db787da9dc453192adcf7706991d2d09f0ff0c7.tar.gz |
SERVER-39019 $elemMatch $ne serialization is incorrect, doesn't roundtrip
-rw-r--r-- | src/mongo/db/matcher/expression_array.cpp | 34 | ||||
-rw-r--r-- | src/mongo/db/matcher/expression_serialization_test.cpp | 62 |
2 files changed, 96 insertions, 0 deletions
diff --git a/src/mongo/db/matcher/expression_array.cpp b/src/mongo/db/matcher/expression_array.cpp index ad5c846f7a1..b1436c0345c 100644 --- a/src/mongo/db/matcher/expression_array.cpp +++ b/src/mongo/db/matcher/expression_array.cpp @@ -185,6 +185,40 @@ void ElemMatchValueMatchExpression::debugString(StringBuilder& debug, int level) void ElemMatchValueMatchExpression::serialize(BSONObjBuilder* out) const { BSONObjBuilder emBob; + // NotMatchExpression will serialize to a $nor. This serialization is incorrect for + // ElemMatchValue however as $nor is a top-level expression and expects that contained + // expressions have path information. For this case we will serialize to $not. + if (_subs.size() == 1 && _subs[0]->matchType() == MatchType::NOT) { + std::vector<MatchExpression*> childList{_subs[0]->getChild(0)}; + + MatchExpression* notChildExpr = _subs[0]->getChild(0); + if (notChildExpr->matchType() == MatchType::AND) { + childList = *notChildExpr->getChildVector(); + } + + const bool allChildrenArePathMatchExpression = + std::all_of(childList.begin(), childList.end(), [](MatchExpression* child) { + return dynamic_cast<PathMatchExpression*>(child); + }); + + if (allChildrenArePathMatchExpression) { + BSONObjBuilder pathBuilder(out->subobjStart(path())); + BSONObjBuilder elemMatchBuilder(pathBuilder.subobjStart("$elemMatch")); + BSONObjBuilder notBuilder(elemMatchBuilder.subobjStart("$not")); + + for (auto&& child : childList) { + BSONObjBuilder predicate; + child->serialize(&predicate); + notBuilder.appendElements(predicate.obj().firstElement().embeddedObject()); + } + + notBuilder.doneFast(); + elemMatchBuilder.doneFast(); + pathBuilder.doneFast(); + return; + } + } + for (unsigned i = 0; i < _subs.size(); i++) { BSONObjBuilder predicate; _subs[i]->serialize(&predicate); diff --git a/src/mongo/db/matcher/expression_serialization_test.cpp b/src/mongo/db/matcher/expression_serialization_test.cpp index 5b0cd3beff3..b34afa725b4 100644 --- a/src/mongo/db/matcher/expression_serialization_test.cpp +++ b/src/mongo/db/matcher/expression_serialization_test.cpp @@ -260,6 +260,68 @@ TEST(SerializeBasic, ExpressionElemMatchValueWithEmptyStringSerializesCorrectly) ASSERT_EQ(original.matches(obj), reserialized.matches(obj)); } +TEST(SerializeBasic, ExpressionElemMatchValueWithNotEqualSerializesCorrectly) { + boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + Matcher original(fromjson("{x: {$elemMatch: {$ne: 1}}}"), + expCtx, + ExtensionsCallbackNoop(), + MatchExpressionParser::kAllowAllSpecialFeatures); + Matcher reserialized(serialize(original.getMatchExpression()), + expCtx, + ExtensionsCallbackNoop(), + MatchExpressionParser::kAllowAllSpecialFeatures); + ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{x: {$elemMatch: {$not: {$eq: 1}}}}")); + ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); + + BSONObj obj = fromjson("{x: [{a: 1, b: -1}, {a: -1, b: 1}]}"); + ASSERT_EQ(original.matches(obj), reserialized.matches(obj)); + + obj = fromjson("{x: [{a: 1, b: 1}, {a: 0, b: 0}]}"); + ASSERT_EQ(original.matches(obj), reserialized.matches(obj)); + + obj = fromjson("{x: [1, 0]}"); + ASSERT_EQ(original.matches(obj), reserialized.matches(obj)); +} + +TEST(SerializeBasic, ExpressionElemMatchValueWithNotLessThanGreaterThanSerializesCorrectly) { + boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + Matcher original(fromjson("{x: {$elemMatch: {$not: {$lt: 10, $gt: 5}}}}"), + expCtx, + ExtensionsCallbackNoop(), + MatchExpressionParser::kAllowAllSpecialFeatures); + Matcher reserialized(serialize(original.getMatchExpression()), + expCtx, + ExtensionsCallbackNoop(), + MatchExpressionParser::kAllowAllSpecialFeatures); + ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), + fromjson("{x: {$elemMatch: {$not: {$lt: 10, $gt: 5}}}}")); + ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); + + auto obj = fromjson("{x: [5]}"); + ASSERT_EQ(original.matches(obj), reserialized.matches(obj)); +} + +TEST(SerializeBasic, ExpressionElemMatchObjectWithNorSerializesCorrectly) { + boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + Matcher original(fromjson("{x: {$elemMatch: {$nor: [{y: 2}]}}}"), + expCtx, + ExtensionsCallbackNoop(), + MatchExpressionParser::kAllowAllSpecialFeatures); + Matcher reserialized(serialize(original.getMatchExpression()), + expCtx, + ExtensionsCallbackNoop(), + MatchExpressionParser::kAllowAllSpecialFeatures); + ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), + fromjson("{x: {$elemMatch: {$nor: [{y: {$eq: 2}}]}}}")); + ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); + + BSONObj obj = fromjson("{x: [{y: 100}]}"); + ASSERT_EQ(original.matches(obj), reserialized.matches(obj)); + + obj = fromjson("{x: [{y: 2}]}"); + ASSERT_EQ(original.matches(obj), reserialized.matches(obj)); +} + TEST(SerializeBasic, ExpressionSizeSerializesCorrectly) { boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); Matcher original(fromjson("{x: {$size: 2}}"), |