summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames Wahlin <james.wahlin@mongodb.com>2019-09-24 12:49:28 +0000
committerevergreen <evergreen@mongodb.com>2019-09-24 12:49:28 +0000
commit523f08103d6317dff1700828d939b09d11c2fd2d (patch)
treeedd31775295d5c0d2bbf1dfed913c170fcf32410
parentdd0bc4758718abc47050f0b91dfc777a54b67899 (diff)
downloadmongo-523f08103d6317dff1700828d939b09d11c2fd2d.tar.gz
SERVER-39019 $elemMatch $ne serialization is incorrect, doesn't roundtrip
(cherry picked from commit 3db787da9dc453192adcf7706991d2d09f0ff0c7)
-rw-r--r--src/mongo/db/matcher/expression_array.cpp34
-rw-r--r--src/mongo/db/matcher/expression_serialization_test.cpp50
2 files changed, 84 insertions, 0 deletions
diff --git a/src/mongo/db/matcher/expression_array.cpp b/src/mongo/db/matcher/expression_array.cpp
index 86fbc384eae..b6a9734233d 100644
--- a/src/mongo/db/matcher/expression_array.cpp
+++ b/src/mongo/db/matcher/expression_array.cpp
@@ -201,6 +201,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 allChildrenAreLeafMatchExpression =
+ std::all_of(childList.begin(), childList.end(), [](MatchExpression* child) {
+ return dynamic_cast<LeafMatchExpression*>(child);
+ });
+
+ if (allChildrenAreLeafMatchExpression) {
+ 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 13a2aaa172d..cac2f5155ba 100644
--- a/src/mongo/db/matcher/expression_serialization_test.cpp
+++ b/src/mongo/db/matcher/expression_serialization_test.cpp
@@ -207,6 +207,56 @@ TEST(SerializeBasic, ExpressionElemMatchValueWithEmptyStringSerializesCorrectly)
ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
}
+TEST(SerializeBasic, ExpressionElemMatchValueWithNotEqualSerializesCorrectly) {
+ const CollatorInterface* collator = nullptr;
+ Matcher original(fromjson("{x: {$elemMatch: {$ne: 1}}}"), ExtensionsCallbackNoop(), collator);
+ Matcher reserialized(
+ serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator);
+ 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) {
+ const CollatorInterface* collator = nullptr;
+ Matcher original(fromjson("{x: {$elemMatch: {$not: {$lt: 10, $gt: 5}}}}"),
+ ExtensionsCallbackNoop(),
+ collator);
+ Matcher reserialized(
+ serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator);
+ 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) {
+ const CollatorInterface* collator = nullptr;
+ Matcher original(
+ fromjson("{x: {$elemMatch: {$nor: [{y: 2}]}}}"), ExtensionsCallbackNoop(), collator);
+ Matcher reserialized(
+ serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator);
+ 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) {
const CollatorInterface* collator = nullptr;
Matcher original(fromjson("{x: {$size: 2}}"), ExtensionsCallbackNoop(), collator);