summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenjamin Murphy <benjamin_murphy@me.com>2016-02-25 10:19:29 -0500
committerBenjamin Murphy <benjamin_murphy@me.com>2016-03-14 18:03:04 -0400
commit488f48f2f497a4e844aa3057e49f96d2a4be1bcb (patch)
treeb73cd307dcfeb06e241d63195df243314b8fb91e
parent4b6952e97e74d8c7bd16ebfc5fe6e412ccf0f48c (diff)
downloadmongo-488f48f2f497a4e844aa3057e49f96d2a4be1bcb.tar.gz
SERVER-22833 Rename MatchExpression::toBSON() to serialize(), and ensure it produces parseable output.
-rw-r--r--src/mongo/db/exec/collection_scan.cpp2
-rw-r--r--src/mongo/db/exec/fetch.cpp2
-rw-r--r--src/mongo/db/exec/geo_near.cpp2
-rw-r--r--src/mongo/db/exec/index_scan.cpp2
-rw-r--r--src/mongo/db/exec/or.cpp2
-rw-r--r--src/mongo/db/exec/text_or.cpp2
-rw-r--r--src/mongo/db/matcher/SConscript11
-rw-r--r--src/mongo/db/matcher/expression.cpp11
-rw-r--r--src/mongo/db/matcher/expression.h19
-rw-r--r--src/mongo/db/matcher/expression_array.cpp26
-rw-r--r--src/mongo/db/matcher/expression_array.h6
-rw-r--r--src/mongo/db/matcher/expression_geo.cpp4
-rw-r--r--src/mongo/db/matcher/expression_geo.h4
-rw-r--r--src/mongo/db/matcher/expression_leaf.cpp26
-rw-r--r--src/mongo/db/matcher/expression_leaf.h16
-rw-r--r--src/mongo/db/matcher/expression_leaf_test.cpp11
-rw-r--r--src/mongo/db/matcher/expression_parser.cpp2
-rw-r--r--src/mongo/db/matcher/expression_serialization_test.cpp791
-rw-r--r--src/mongo/db/matcher/expression_text_base.cpp2
-rw-r--r--src/mongo/db/matcher/expression_text_base.h2
-rw-r--r--src/mongo/db/matcher/expression_tree.cpp30
-rw-r--r--src/mongo/db/matcher/expression_tree.h8
-rw-r--r--src/mongo/db/matcher/expression_where_base.cpp2
-rw-r--r--src/mongo/db/matcher/expression_where_base.h2
-rw-r--r--src/mongo/db/matcher/matcher.h4
-rw-r--r--src/mongo/db/query/explain.cpp2
-rw-r--r--src/mongo/db/query/parsed_projection_test.cpp2
-rw-r--r--src/mongo/dbtests/extensions_callback_real_test.cpp8
28 files changed, 910 insertions, 91 deletions
diff --git a/src/mongo/db/exec/collection_scan.cpp b/src/mongo/db/exec/collection_scan.cpp
index 696a05a71ea..e6ead6b431c 100644
--- a/src/mongo/db/exec/collection_scan.cpp
+++ b/src/mongo/db/exec/collection_scan.cpp
@@ -234,7 +234,7 @@ unique_ptr<PlanStageStats> CollectionScan::getStats() {
// Add a BSON representation of the filter to the stats tree, if there is one.
if (NULL != _filter) {
BSONObjBuilder bob;
- _filter->toBSON(&bob);
+ _filter->serialize(&bob);
_commonStats.filter = bob.obj();
}
diff --git a/src/mongo/db/exec/fetch.cpp b/src/mongo/db/exec/fetch.cpp
index 79b70ceba83..d2309d1217b 100644
--- a/src/mongo/db/exec/fetch.cpp
+++ b/src/mongo/db/exec/fetch.cpp
@@ -216,7 +216,7 @@ unique_ptr<PlanStageStats> FetchStage::getStats() {
// Add a BSON representation of the filter to the stats tree, if there is one.
if (NULL != _filter) {
BSONObjBuilder bob;
- _filter->toBSON(&bob);
+ _filter->serialize(&bob);
_commonStats.filter = bob.obj();
}
diff --git a/src/mongo/db/exec/geo_near.cpp b/src/mongo/db/exec/geo_near.cpp
index bfad1618b62..f15ab7ac59c 100644
--- a/src/mongo/db/exec/geo_near.cpp
+++ b/src/mongo/db/exec/geo_near.cpp
@@ -462,7 +462,7 @@ public:
initPath(twoDPath);
}
- void toBSON(BSONObjBuilder* out) const final {
+ void serialize(BSONObjBuilder* out) const final {
out->append("TwoDPtInAnnulusExpression", true);
}
diff --git a/src/mongo/db/exec/index_scan.cpp b/src/mongo/db/exec/index_scan.cpp
index 4e7410eeb13..de3f8d8f327 100644
--- a/src/mongo/db/exec/index_scan.cpp
+++ b/src/mongo/db/exec/index_scan.cpp
@@ -277,7 +277,7 @@ std::unique_ptr<PlanStageStats> IndexScan::getStats() {
// Add a BSON representation of the filter to the stats tree, if there is one.
if (NULL != _filter) {
BSONObjBuilder bob;
- _filter->toBSON(&bob);
+ _filter->serialize(&bob);
_commonStats.filter = bob.obj();
}
diff --git a/src/mongo/db/exec/or.cpp b/src/mongo/db/exec/or.cpp
index 327ec1b31fd..f9d51880c2f 100644
--- a/src/mongo/db/exec/or.cpp
+++ b/src/mongo/db/exec/or.cpp
@@ -143,7 +143,7 @@ unique_ptr<PlanStageStats> OrStage::getStats() {
// Add a BSON representation of the filter to the stats tree, if there is one.
if (NULL != _filter) {
BSONObjBuilder bob;
- _filter->toBSON(&bob);
+ _filter->serialize(&bob);
_commonStats.filter = bob.obj();
}
diff --git a/src/mongo/db/exec/text_or.cpp b/src/mongo/db/exec/text_or.cpp
index 23011388369..195af913a99 100644
--- a/src/mongo/db/exec/text_or.cpp
+++ b/src/mongo/db/exec/text_or.cpp
@@ -115,7 +115,7 @@ std::unique_ptr<PlanStageStats> TextOrStage::getStats() {
if (_filter) {
BSONObjBuilder bob;
- _filter->toBSON(&bob);
+ _filter->serialize(&bob);
_commonStats.filter = bob.obj();
}
diff --git a/src/mongo/db/matcher/SConscript b/src/mongo/db/matcher/SConscript
index e9353127805..cb267e448d2 100644
--- a/src/mongo/db/matcher/SConscript
+++ b/src/mongo/db/matcher/SConscript
@@ -141,3 +141,14 @@ env.Library(
'incomplete',
],
)
+
+env.CppUnitTest(
+ target='expression_serialization_test',
+ source=[
+ 'expression_serialization_test.cpp',
+ ],
+ LIBDEPS=[
+ 'expressions',
+ 'expressions_geo',
+ ],
+)
diff --git a/src/mongo/db/matcher/expression.cpp b/src/mongo/db/matcher/expression.cpp
index 989e075d1de..187050c5eb5 100644
--- a/src/mongo/db/matcher/expression.cpp
+++ b/src/mongo/db/matcher/expression.cpp
@@ -57,10 +57,15 @@ bool MatchExpression::matchesBSON(const BSONObj& doc, MatchDetails* details) con
void FalseMatchExpression::debugString(StringBuilder& debug, int level) const {
_debugAddSpace(debug, level);
- debug << "$false\n";
+ debug << "$all: []\n";
}
-void FalseMatchExpression::toBSON(BSONObjBuilder* out) const {
- out->append("$false", 1);
+void FalseMatchExpression::serialize(BSONObjBuilder* out) const {
+ // Our query language has no "always false" operator aside from a $all with no children, so use
+ // that as a proxy here.
+ BSONObjBuilder child(out->subobjStart(_path));
+ BSONArrayBuilder allChild(child.subarrayStart("$all"));
+ allChild.doneFast();
+ child.doneFast();
}
}
diff --git a/src/mongo/db/matcher/expression.h b/src/mongo/db/matcher/expression.h
index 43433346fa7..ad10d17621c 100644
--- a/src/mongo/db/matcher/expression.h
+++ b/src/mongo/db/matcher/expression.h
@@ -233,12 +233,18 @@ public:
}
}
+ /**
+ * Serialize the MatchExpression to BSON, appending to 'out'. Output of this method is expected
+ * to be a valid query object, that, when parsed, produces a logically equivalent
+ * MatchExpression.
+ */
+ virtual void serialize(BSONObjBuilder* out) const = 0;
+
//
// Debug information
//
virtual std::string toString() const;
virtual void debugString(StringBuilder& debug, int level = 0) const = 0;
- virtual void toBSON(BSONObjBuilder* out) const = 0;
protected:
void _debugAddSpace(StringBuilder& debug, int level) const;
@@ -250,7 +256,9 @@ private:
class FalseMatchExpression : public MatchExpression {
public:
- FalseMatchExpression() : MatchExpression(ALWAYS_FALSE) {}
+ FalseMatchExpression(StringData path) : MatchExpression(ALWAYS_FALSE) {
+ _path = path;
+ }
virtual bool matches(const MatchableDocument* doc, MatchDetails* details = 0) const {
return false;
@@ -261,15 +269,18 @@ public:
}
virtual std::unique_ptr<MatchExpression> shallowClone() const {
- return stdx::make_unique<FalseMatchExpression>();
+ return stdx::make_unique<FalseMatchExpression>(_path);
}
virtual void debugString(StringBuilder& debug, int level = 0) const;
- virtual void toBSON(BSONObjBuilder* out) const;
+ virtual void serialize(BSONObjBuilder* out) const;
virtual bool equivalent(const MatchExpression* other) const {
return other->matchType() == ALWAYS_FALSE;
}
+
+private:
+ StringData _path;
};
}
diff --git a/src/mongo/db/matcher/expression_array.cpp b/src/mongo/db/matcher/expression_array.cpp
index 4ca68653e3d..9f0b11f1424 100644
--- a/src/mongo/db/matcher/expression_array.cpp
+++ b/src/mongo/db/matcher/expression_array.cpp
@@ -128,14 +128,10 @@ void ElemMatchObjectMatchExpression::debugString(StringBuilder& debug, int level
_sub->debugString(debug, level + 1);
}
-void ElemMatchObjectMatchExpression::toBSON(BSONObjBuilder* out) const {
+void ElemMatchObjectMatchExpression::serialize(BSONObjBuilder* out) const {
BSONObjBuilder subBob;
- _sub->toBSON(&subBob);
- if (path().empty()) {
- out->append("$elemMatch", subBob.obj());
- } else {
- out->append(path(), BSON("$elemMatch" << subBob.obj()));
- }
+ _sub->serialize(&subBob);
+ out->append(path(), BSON("$elemMatch" << subBob.obj()));
}
@@ -202,16 +198,16 @@ void ElemMatchValueMatchExpression::debugString(StringBuilder& debug, int level)
}
}
-void ElemMatchValueMatchExpression::toBSON(BSONObjBuilder* out) const {
+void ElemMatchValueMatchExpression::serialize(BSONObjBuilder* out) const {
BSONObjBuilder emBob;
+
for (unsigned i = 0; i < _subs.size(); i++) {
- _subs[i]->toBSON(&emBob);
- }
- if (path().empty()) {
- out->append("$elemMatch", emBob.obj());
- } else {
- out->append(path(), BSON("$elemMatch" << emBob.obj()));
+ BSONObjBuilder predicate;
+ _subs[i]->serialize(&predicate);
+ BSONObj predObj = predicate.obj();
+ emBob.appendElements(predObj.firstElement().embeddedObject());
}
+ out->append(path(), BSON("$elemMatch" << emBob.obj()));
}
@@ -239,7 +235,7 @@ void SizeMatchExpression::debugString(StringBuilder& debug, int level) const {
}
}
-void SizeMatchExpression::toBSON(BSONObjBuilder* out) const {
+void SizeMatchExpression::serialize(BSONObjBuilder* out) const {
out->append(path(), BSON("$size" << _size));
}
diff --git a/src/mongo/db/matcher/expression_array.h b/src/mongo/db/matcher/expression_array.h
index 0776bd5a3cf..76459c93b8c 100644
--- a/src/mongo/db/matcher/expression_array.h
+++ b/src/mongo/db/matcher/expression_array.h
@@ -86,7 +86,7 @@ public:
virtual void debugString(StringBuilder& debug, int level) const;
- virtual void toBSON(BSONObjBuilder* out) const;
+ virtual void serialize(BSONObjBuilder* out) const;
virtual size_t numChildren() const {
return 1;
@@ -126,7 +126,7 @@ public:
virtual void debugString(StringBuilder& debug, int level) const;
- virtual void toBSON(BSONObjBuilder* out) const;
+ virtual void serialize(BSONObjBuilder* out) const;
virtual std::vector<MatchExpression*>* getChildVector() {
return &_subs;
@@ -164,7 +164,7 @@ public:
virtual void debugString(StringBuilder& debug, int level) const;
- virtual void toBSON(BSONObjBuilder* out) const;
+ virtual void serialize(BSONObjBuilder* out) const;
virtual bool equivalent(const MatchExpression* other) const;
diff --git a/src/mongo/db/matcher/expression_geo.cpp b/src/mongo/db/matcher/expression_geo.cpp
index ed5c78f4fb6..15a538609b4 100644
--- a/src/mongo/db/matcher/expression_geo.cpp
+++ b/src/mongo/db/matcher/expression_geo.cpp
@@ -369,7 +369,7 @@ void GeoMatchExpression::debugString(StringBuilder& debug, int level) const {
debug << "\n";
}
-void GeoMatchExpression::toBSON(BSONObjBuilder* out) const {
+void GeoMatchExpression::serialize(BSONObjBuilder* out) const {
out->appendElements(_rawObj);
}
@@ -426,7 +426,7 @@ void GeoNearMatchExpression::debugString(StringBuilder& debug, int level) const
debug << "\n";
}
-void GeoNearMatchExpression::toBSON(BSONObjBuilder* out) const {
+void GeoNearMatchExpression::serialize(BSONObjBuilder* out) const {
out->appendElements(_rawObj);
}
diff --git a/src/mongo/db/matcher/expression_geo.h b/src/mongo/db/matcher/expression_geo.h
index d10ec5954d2..856b31045f8 100644
--- a/src/mongo/db/matcher/expression_geo.h
+++ b/src/mongo/db/matcher/expression_geo.h
@@ -91,7 +91,7 @@ public:
virtual void debugString(StringBuilder& debug, int level = 0) const;
- virtual void toBSON(BSONObjBuilder* out) const;
+ virtual void serialize(BSONObjBuilder* out) const;
virtual bool equivalent(const MatchExpression* other) const;
@@ -174,7 +174,7 @@ public:
virtual void debugString(StringBuilder& debug, int level = 0) const;
- virtual void toBSON(BSONObjBuilder* out) const;
+ virtual void serialize(BSONObjBuilder* out) const;
virtual bool equivalent(const MatchExpression* other) const;
diff --git a/src/mongo/db/matcher/expression_leaf.cpp b/src/mongo/db/matcher/expression_leaf.cpp
index 1389fd8f98a..042e4169609 100644
--- a/src/mongo/db/matcher/expression_leaf.cpp
+++ b/src/mongo/db/matcher/expression_leaf.cpp
@@ -185,8 +185,7 @@ void ComparisonMatchExpression::debugString(StringBuilder& debug, int level) con
debug << "$gte";
break;
default:
- debug << " UNKNOWN - should be impossible";
- break;
+ invariant(false);
}
debug << " " << _rhs.toString(false);
@@ -199,7 +198,7 @@ void ComparisonMatchExpression::debugString(StringBuilder& debug, int level) con
debug << "\n";
}
-void ComparisonMatchExpression::toBSON(BSONObjBuilder* out) const {
+void ComparisonMatchExpression::serialize(BSONObjBuilder* out) const {
string opString = "";
switch (matchType()) {
case LT:
@@ -218,8 +217,7 @@ void ComparisonMatchExpression::toBSON(BSONObjBuilder* out) const {
opString = "$gte";
break;
default:
- opString = " UNKNOWN - should be impossible";
- break;
+ invariant(false);
}
out->append(path(), BSON(opString << _rhs));
@@ -307,7 +305,7 @@ void RegexMatchExpression::debugString(StringBuilder& debug, int level) const {
debug << "\n";
}
-void RegexMatchExpression::toBSON(BSONObjBuilder* out) const {
+void RegexMatchExpression::serialize(BSONObjBuilder* out) const {
out->appendRegex(path(), _regex, _flags);
}
@@ -342,7 +340,7 @@ void ModMatchExpression::debugString(StringBuilder& debug, int level) const {
debug << "\n";
}
-void ModMatchExpression::toBSON(BSONObjBuilder* out) const {
+void ModMatchExpression::serialize(BSONObjBuilder* out) const {
out->append(path(), BSON("$mod" << BSON_ARRAY(_divisor << _remainder)));
}
@@ -377,7 +375,7 @@ void ExistsMatchExpression::debugString(StringBuilder& debug, int level) const {
debug << "\n";
}
-void ExistsMatchExpression::toBSON(BSONObjBuilder* out) const {
+void ExistsMatchExpression::serialize(BSONObjBuilder* out) const {
out->append(path(), BSON("$exists" << true));
}
@@ -494,7 +492,7 @@ void TypeMatchExpression::debugString(StringBuilder& debug, int level) const {
debug << "\n";
}
-void TypeMatchExpression::toBSON(BSONObjBuilder* out) const {
+void TypeMatchExpression::serialize(BSONObjBuilder* out) const {
if (matchesAllNumbers()) {
out->append(path(), BSON("$type" << kMatchesAllNumbersAlias));
} else {
@@ -591,13 +589,13 @@ void ArrayFilterEntries::debugString(StringBuilder& debug) const {
debug << "]";
}
-void ArrayFilterEntries::toBSON(BSONArrayBuilder* out) const {
+void ArrayFilterEntries::serialize(BSONArrayBuilder* out) const {
for (BSONElementSet::const_iterator it = _equalities.begin(); it != _equalities.end(); ++it) {
out->append(*it);
}
for (size_t i = 0; i < _regexes.size(); ++i) {
BSONObjBuilder regexBob;
- _regexes[i]->toBSON(&regexBob);
+ _regexes[i]->serialize(&regexBob);
out->append(regexBob.obj().firstElement());
}
out->doneFast();
@@ -654,10 +652,10 @@ void InMatchExpression::debugString(StringBuilder& debug, int level) const {
debug << "\n";
}
-void InMatchExpression::toBSON(BSONObjBuilder* out) const {
+void InMatchExpression::serialize(BSONObjBuilder* out) const {
BSONObjBuilder inBob(out->subobjStart(path()));
BSONArrayBuilder arrBob(inBob.subarrayStart("$in"));
- _arrayEntries.toBSON(&arrBob);
+ _arrayEntries.serialize(&arrBob);
inBob.doneFast();
}
@@ -878,7 +876,7 @@ void BitTestMatchExpression::debugString(StringBuilder& debug, int level) const
}
}
-void BitTestMatchExpression::toBSON(BSONObjBuilder* out) const {
+void BitTestMatchExpression::serialize(BSONObjBuilder* out) const {
string opString = "";
switch (matchType()) {
diff --git a/src/mongo/db/matcher/expression_leaf.h b/src/mongo/db/matcher/expression_leaf.h
index 2b382768788..1f1b305e141 100644
--- a/src/mongo/db/matcher/expression_leaf.h
+++ b/src/mongo/db/matcher/expression_leaf.h
@@ -97,7 +97,7 @@ public:
virtual void debugString(StringBuilder& debug, int level = 0) const;
- virtual void toBSON(BSONObjBuilder* out) const;
+ virtual void serialize(BSONObjBuilder* out) const;
virtual bool equivalent(const MatchExpression* other) const;
@@ -210,7 +210,7 @@ public:
virtual void debugString(StringBuilder& debug, int level) const;
- virtual void toBSON(BSONObjBuilder* out) const;
+ virtual void serialize(BSONObjBuilder* out) const;
void shortDebugString(StringBuilder& debug) const;
@@ -248,7 +248,7 @@ public:
virtual void debugString(StringBuilder& debug, int level) const;
- virtual void toBSON(BSONObjBuilder* out) const;
+ virtual void serialize(BSONObjBuilder* out) const;
virtual bool equivalent(const MatchExpression* other) const;
@@ -283,7 +283,7 @@ public:
virtual void debugString(StringBuilder& debug, int level) const;
- virtual void toBSON(BSONObjBuilder* out) const;
+ virtual void serialize(BSONObjBuilder* out) const;
virtual bool equivalent(const MatchExpression* other) const;
};
@@ -337,7 +337,7 @@ public:
void debugString(StringBuilder& debug) const;
- void toBSON(BSONArrayBuilder* out) const;
+ void serialize(BSONArrayBuilder* out) const;
private:
bool _hasNull; // if _equalities has a jstNULL element in it
@@ -364,7 +364,7 @@ public:
virtual void debugString(StringBuilder& debug, int level) const;
- virtual void toBSON(BSONObjBuilder* out) const;
+ virtual void serialize(BSONObjBuilder* out) const;
virtual bool equivalent(const MatchExpression* other) const;
@@ -425,7 +425,7 @@ public:
virtual void debugString(StringBuilder& debug, int level) const;
- virtual void toBSON(BSONObjBuilder* out) const;
+ virtual void serialize(BSONObjBuilder* out) const;
virtual bool equivalent(const MatchExpression* other) const;
@@ -481,7 +481,7 @@ public:
virtual void debugString(StringBuilder& debug, int level) const;
- virtual void toBSON(BSONObjBuilder* out) const;
+ virtual void serialize(BSONObjBuilder* out) const;
virtual bool equivalent(const MatchExpression* other) const;
diff --git a/src/mongo/db/matcher/expression_leaf_test.cpp b/src/mongo/db/matcher/expression_leaf_test.cpp
index 43fdd47e380..b2946f5f546 100644
--- a/src/mongo/db/matcher/expression_leaf_test.cpp
+++ b/src/mongo/db/matcher/expression_leaf_test.cpp
@@ -1373,17 +1373,6 @@ TEST(TypeMatchExpression, MatchesElementNumber) {
ASSERT_FALSE(type.matchesSingleElement(notMatch["a"]));
}
-TEST(TypeMatchExpression, ElementNumberToBSONIsFormattedCorrectly) {
- TypeMatchExpression type;
- ASSERT_OK(type.initAsMatchingAllNumbers("a"));
- ASSERT_EQ("a", type.path());
-
- BSONObjBuilder objBuilder;
- type.toBSON(&objBuilder);
- ASSERT_EQ(objBuilder.obj(),
- BSON("a" << BSON("$type" << TypeMatchExpression::kMatchesAllNumbersAlias)));
-}
-
TEST(TypeMatchExpression, InvalidTypeMatchExpressionTypeCode) {
TypeMatchExpression type;
ASSERT_NOT_OK(type.initWithBSONType("", JSTypeMax + 1));
diff --git a/src/mongo/db/matcher/expression_parser.cpp b/src/mongo/db/matcher/expression_parser.cpp
index c249f28a4be..fd6b8802524 100644
--- a/src/mongo/db/matcher/expression_parser.cpp
+++ b/src/mongo/db/matcher/expression_parser.cpp
@@ -804,7 +804,7 @@ StatusWithMatchExpression MatchExpressionParser::_parseAll(const char* name,
}
if (myAnd->numChildren() == 0) {
- return {stdx::make_unique<FalseMatchExpression>()};
+ return {stdx::make_unique<FalseMatchExpression>(name)};
}
return {std::move(myAnd)};
diff --git a/src/mongo/db/matcher/expression_serialization_test.cpp b/src/mongo/db/matcher/expression_serialization_test.cpp
new file mode 100644
index 00000000000..39a472adef6
--- /dev/null
+++ b/src/mongo/db/matcher/expression_serialization_test.cpp
@@ -0,0 +1,791 @@
+/**
+ * Copyright (C) 2016 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 <http://www.gnu.org/licenses/>.
+ *
+ * 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.
+ */
+
+// Unit tests for MatchExpression::serialize serialization.
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/db/json.h"
+#include "mongo/db/matcher/expression.h"
+#include "mongo/db/matcher/expression_parser.h"
+#include "mongo/db/matcher/extensions_callback_noop.h"
+#include "mongo/db/matcher/matcher.h"
+#include "mongo/unittest/unittest.h"
+
+namespace mongo {
+namespace {
+
+using std::pair;
+using std::string;
+using std::unique_ptr;
+
+BSONObj serialize(MatchExpression* match) {
+ BSONObjBuilder bob;
+ match->serialize(&bob);
+ return bob.obj();
+}
+
+TEST(SerializeBasic, AndExpressionWithOneChildSerializesCorrectly) {
+ Matcher original(fromjson("{$and: [{x: 0}]}"), ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(), fromjson("{$and: [{x: {$eq: 0}}]}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+
+ BSONObj obj = fromjson("{x: 1}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson("{x: 0}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+}
+
+TEST(SerializeBasic, AndExpressionWithTwoChildrenSerializesCorrectly) {
+ Matcher original(fromjson("{$and: [{x: 1}, {x: 2}]}"), ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(), fromjson("{$and: [{x: {$eq: 1}}, {x: {$eq: 2}}]}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+
+ BSONObj obj = fromjson("{x: 1}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson("{x: [1, 2]}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+}
+
+TEST(SerializeBasic, AndExpressionWithTwoIdenticalChildrenSerializesCorrectly) {
+ Matcher original(fromjson("{$and: [{x: 1}, {x: 1}]}"), ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(), fromjson("{$and: [{x: {$eq: 1}}, {x: {$eq: 1}}]}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+
+ BSONObj obj = fromjson("{x: 1}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson("{x: -1}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+}
+
+TEST(SerializeBasic, ExpressionOr) {
+ Matcher original(fromjson("{$or: [{x: 'A'}, {x: 'B'}]}"), ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(), fromjson("{$or: [{x: {$eq: 'A'}}, {x: {$eq: 'B'}}]}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+
+ BSONObj obj = fromjson("{x: 'A'}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson("{x: 'a'}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+}
+
+TEST(SerializeBasic, ExpressionElemMatchObjectSerializesCorrectly) {
+ Matcher original(fromjson("{x: {$elemMatch: {a: {$gt: 0}, b: {$gt: 0}}}}"),
+ ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(),
+ fromjson("{x: {$elemMatch: {$and: [{a: {$gt: 0}}, {b: {$gt: 0}}]}}}"));
+ ASSERT_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));
+}
+
+TEST(SerializeBasic, ExpressionElemMatchObjectWithEmptyStringSerializesCorrectly) {
+ Matcher original(fromjson("{'': {$elemMatch: {a: {$gt: 0}, b: {$gt: 0}}}}"),
+ ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(),
+ fromjson("{'': {$elemMatch: {$and: [{a: {$gt: 0}}, {b: {$gt: 0}}]}}}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+
+ BSONObj obj = fromjson("{'': [{a: 1, b: -1}, {a: -1, b: 1}]}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson("{'': [{a: 1, b: 1}, {a: 0, b: 0}]}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+}
+
+TEST(SerializeBasic, ExpressionElemMatchValueSerializesCorrectly) {
+ Matcher original(fromjson("{x: {$elemMatch: {$lt: 1, $gt: -1}}}"), ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(), fromjson("{x: {$elemMatch: {$lt: 1, $gt: -1}}}"));
+ ASSERT_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, ExpressionElemMatchValueWithEmptyStringSerializesCorrectly) {
+ Matcher original(fromjson("{x: {$elemMatch: {$lt: 1, $gt: -1}}}"), ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(), fromjson("{x: {$elemMatch: {$lt: 1, $gt: -1}}}"));
+ ASSERT_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, ExpressionSizeSerializesCorrectly) {
+ Matcher original(fromjson("{x: {$size: 2}}"), ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(), fromjson("{x: {$size: 2}}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+
+ BSONObj obj = fromjson("{x: [1, 2, 3]}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson("{x: [1, 2]}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+}
+
+TEST(SerializeBasic, ExpressionAllSerializesCorrectly) {
+ Matcher original(fromjson("{x: {$all: [1, 2]}}"), ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(), fromjson("{$and: [{x: {$eq: 1}}, {x: {$eq: 2}}]}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+
+ BSONObj obj = fromjson("{x: [1, 2, 3]}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson("{x: [1, 3]}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+}
+
+TEST(SerializeBasic, ExpressionAllWithEmptyArraySerializesCorrectly) {
+ Matcher original(fromjson("{x: {$all: []}}"), ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(), fromjson("{x: {$all: []}}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+
+ BSONObj obj = fromjson("{x: [1, 2, 3]}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+}
+
+TEST(SerializeBasic, ExpressionAllWithRegex) {
+ Matcher original(fromjson("{x: {$all: [/a.b.c/, /.d.e./]}}"), ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(), fromjson("{$and: [{x: /a.b.c/}, {x: /.d.e./}]}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+
+ BSONObj obj = fromjson("{x: 'abcde'}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson("{x: 'adbec'}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+}
+
+TEST(SerializeBasic, ExpressionEqSerializesCorrectly) {
+ Matcher original(fromjson("{x: {$eq: {a: 1}}}"), ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(), fromjson("{x: {$eq: {a: 1}}}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+
+ BSONObj obj = fromjson("{x: {a: 1}}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson("{x: {a: [1, 2]}}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson("{x: {a: 2}}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+}
+
+TEST(SerializeBasic, ExpressionNeSerializesCorrectly) {
+ Matcher original(fromjson("{x: {$ne: {a: 1}}}"), ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(), fromjson("{$nor: [{x: {$eq: {a: 1}}}]}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+
+ BSONObj obj = fromjson("{x: {a: 1}}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson("{x: {a: [1, 2]}}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+}
+
+TEST(SerializeBasic, ExpressionLtSerializesCorrectly) {
+ Matcher original(fromjson("{x: {$lt: 3}}"), ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(), fromjson("{x: {$lt: 3}}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+
+ BSONObj obj = fromjson("{x: 3}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson("{x: 2.9}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+}
+
+TEST(SerializeBasic, ExpressionGtSerializesCorrectly) {
+ Matcher original(fromjson("{x: {$gt: 3}}"), ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(), fromjson("{x: {$gt: 3}}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+
+ BSONObj obj = fromjson("{x: 3}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson("{x: 3.1}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+}
+
+TEST(SerializeBasic, ExpressionGteSerializesCorrectly) {
+ Matcher original(fromjson("{x: {$gte: 3}}"), ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(), fromjson("{x: {$gte: 3}}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+
+ BSONObj obj = fromjson("{x: 3}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson("{x: 2}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+}
+
+TEST(SerializeBasic, ExpressionLteSerializesCorrectly) {
+ Matcher original(fromjson("{x: {$lte: 3}}"), ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(), fromjson("{x: {$lte: 3}}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+
+ BSONObj obj = fromjson("{x: 3}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson("{x: 4}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+}
+
+TEST(SerializeBasic, ExpressionRegexWithObjSerializesCorrectly) {
+ Matcher original(fromjson("{x: {$regex: 'a.b'}}"), ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(), fromjson("{x: {$regex: 'a.b'}}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+
+ BSONObj obj = fromjson("{x: 'abc'}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson("{x: 'acb'}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+}
+
+TEST(SerializeBasic, ExpressionRegexWithValueSerializesCorrectly) {
+ Matcher original(fromjson("{x: /a.b/i}"), ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(), fromjson("{x: {$regex: 'a.b', $options: 'i'}}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+
+ BSONObj obj = fromjson("{x: 'abc'}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson("{x: 'acb'}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+}
+
+TEST(SerializeBasic, ExpressionRegexWithValueAndOptionsSerializesCorrectly) {
+ Matcher original(fromjson("{x: /a.b/}"), ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(), fromjson("{x: {$regex: 'a.b'}}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+
+ BSONObj obj = fromjson("{x: 'abc'}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson("{x: 'acb'}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+}
+
+TEST(SerializeBasic, ExpressionRegexWithEqObjSerializesCorrectly) {
+ Matcher original(fromjson("{x: {$eq: {$regex: 'a.b'}}}"), ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(), fromjson("{x: {$eq: {$regex: 'a.b'}}}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+
+ BSONObj obj = fromjson("{x: 'abc'}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson("{x: 'acb'}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson("{x: /a.b.c/}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+}
+
+TEST(SerializeBasic, ExpressionModSerializesCorrectly) {
+ Matcher original(fromjson("{x: {$mod: [2, 1]}}"), ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(), fromjson("{x: {$mod: [2, 1]}}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+
+ BSONObj obj = fromjson("{x: 1}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson("{x: 2}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+}
+
+TEST(SerializeBasic, ExpressionExistsTrueSerializesCorrectly) {
+ Matcher original(fromjson("{x: {$exists: true}}"), ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(), fromjson("{x: {$exists: true}}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+
+ BSONObj obj = fromjson("{x: 1}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson("{a: 1}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+}
+
+TEST(SerializeBasic, ExpressionExistsFalseSerializesCorrectly) {
+ Matcher original(fromjson("{x: {$exists: false}}"), ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(), fromjson("{$nor: [{x: {$exists: true}}]}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+
+ BSONObj obj = fromjson("{x: 1}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson("{a: 1}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+}
+
+TEST(SerializeBasic, ExpressionInSerializesCorrectly) {
+ Matcher original(fromjson("{x: {$in: [1, 2, 3]}}"), ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(), fromjson("{x: {$in: [1, 2, 3]}}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+
+ BSONObj obj = fromjson("{x: 1}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson("{x: 4}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson("{x: [1, 2]}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+}
+
+TEST(SerializeBasic, ExpressionInWithEmptyArraySerializesCorrectly) {
+ Matcher original(fromjson("{x: {$in: []}}"), ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(), fromjson("{x: {$in: []}}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+
+ BSONObj obj = fromjson("{x: 1}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+}
+
+TEST(SerializeBasic, ExpressionInWithRegexSerializesCorrectly) {
+ Matcher original(fromjson("{x: {$in: [/\\d+/, /\\w+/]}}"), ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(), fromjson("{x: {$in: [/\\d+/, /\\w+/]}}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+
+ BSONObj obj = fromjson("{x: '1234'}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson("{x: 'abcd'}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson("{x: '1a2b'}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+}
+
+TEST(SerializeBasic, ExpressionNinSerializesCorrectly) {
+ Matcher original(fromjson("{x: {$nin: [1, 2, 3]}}"), ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(), fromjson("{$nor: [{x: {$in: [1, 2, 3]}}]}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+
+ BSONObj obj = fromjson("{x: 1}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson("{x: 4}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson("{x: [1, 2]}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+}
+
+TEST(SerializeBasic, ExpressionBitsAllSetSerializesCorrectly) {
+ Matcher original(fromjson("{x: {$bitsAllSet: [1, 3]}}"), ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(), fromjson("{x: {$bitsAllSet: [1, 3]}}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+
+ BSONObj obj = fromjson("{x: 2}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson("{x: 10}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+}
+
+TEST(SerializeBasic, ExpressionBitsAllClearSerializesCorrectly) {
+ Matcher original(fromjson("{x: {$bitsAllClear: [1, 3]}}"), ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(), fromjson("{x: {$bitsAllClear: [1, 3]}}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+
+ BSONObj obj = fromjson("{x: 2}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson("{x: 1}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+}
+
+TEST(SerializeBasic, ExpressionBitsAnySetSerializesCorrectly) {
+ Matcher original(fromjson("{x: {$bitsAnySet: [1, 3]}}"), ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(), fromjson("{x: {$bitsAnySet: [1, 3]}}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+
+ BSONObj obj = fromjson("{x: 2}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson("{x: 4}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+}
+
+TEST(SerializeBasic, ExpressionBitsAnyClearSerializesCorrectly) {
+ Matcher original(fromjson("{x: {$bitsAnyClear: [1, 3]}}"), ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(), fromjson("{x: {$bitsAnyClear: [1, 3]}}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+
+ BSONObj obj = fromjson("{x: 2}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson("{x: 1}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson("{x: 10}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+}
+
+TEST(SerializeBasic, ExpressionNotSerializesCorrectly) {
+ Matcher original(fromjson("{x: {$not: {$eq: 3}}}"), ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(), fromjson("{$nor: [{$and: [{x: {$eq: 3}}]}]}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+
+ BSONObj obj = fromjson("{x: 3}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson("{x: 4}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+}
+
+TEST(SerializeBasic, ExpressionNotWithMultipleChildrenSerializesCorrectly) {
+ Matcher original(fromjson("{x: {$not: {$lt: 1, $gt: 3}}}"), ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(),
+ fromjson("{$nor: [{$and: [{x: {$lt: 1}}, {x: {$gt: 3}}]}]}}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+
+ BSONObj obj = fromjson("{x: 2}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson("{x: 4}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+}
+
+TEST(SerializeBasic, ExpressionNotWithBitTestSerializesCorrectly) {
+ Matcher original(fromjson("{x: {$not: {$bitsAnySet: [1, 3]}}}"), ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(), fromjson("{$nor: [{$and: [{x: {$bitsAnySet: [1, 3]}}]}]}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+
+ BSONObj obj = fromjson("{x: 2}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson("{x: 4}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+}
+
+TEST(SerializeBasic, ExpressionNotWithRegexObjSerializesCorrectly) {
+ Matcher original(fromjson("{x: {$not: {$regex: 'a.b'}}}"), ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(), fromjson("{$nor: [{x: /a.b/}]}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+
+ BSONObj obj = fromjson("{x: 'abc'}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson("{x: 'acb'}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+}
+
+TEST(SerializeBasic, ExpressionNotWithRegexValueSerializesCorrectly) {
+ Matcher original(fromjson("{x: {$not: /a.b/}}"), ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(), fromjson("{$nor: [{x: /a.b/}]}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+
+ BSONObj obj = fromjson("{x: 'abc'}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson("{x: 'acb'}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+}
+
+TEST(SerializeBasic, ExpressionNotWithRegexValueAndOptionsSerializesCorrectly) {
+ Matcher original(fromjson("{x: {$not: /a.b/i}}"), ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(), fromjson("{$nor: [{x: /a.b/i}]}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+
+ BSONObj obj = fromjson("{x: 'abc'}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson("{x: 'acb'}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+}
+
+TEST(SerializeBasic, ExpressionNotWithGeoSerializesCorrectly) {
+ Matcher original(fromjson(
+ "{x: {$not: {$geoIntersects: {$geometry: {type: 'Polygon', "
+ "coordinates: [[[0,0], [5,0], "
+ "[5, 5], [0, 5], [0, 0]]]}}}}}"),
+ ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(),
+ fromjson(
+ "{$nor: [{$and: [{x: {$geoIntersects: {$geometry: {type: 'Polygon', coordinates: "
+ "[[[0,0], "
+ "[5,0], [5, 5], [0, 5], [0, 0]]]}}}}]}]}"));
+
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+ BSONObj obj =
+ fromjson("{x: {type: 'Polygon', coordinates: [[4, 4], [4, 6], [6, 6], [6, 4], [4, 4]]}}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson(
+ "{x: {type: 'Polygon', coordinates: [[4, 4], [4, 4.5], [4.5, 4.5], [4.5, 4], [4, 4]]}}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson(
+ "{x: {type: 'Polygon', coordinates: [[5.5, 5.5], [5.5, 6], [6, 6], [6, 5.5], [5.5, "
+ "5.5]]}}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+}
+
+TEST(SerializeBasic, ExpressionNorSerializesCorrectly) {
+ Matcher original(fromjson("{$nor: [{x: 3}, {x: {$lt: 1}}]}"), ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(), fromjson("{$nor: [{x: {$eq: 3}}, {x: {$lt: 1}}]}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+
+ BSONObj obj = fromjson("{x: 3}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson("{x: 0}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson("{x: 2}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+}
+
+TEST(SerializeBasic, ExpressionTypeSerializesCorrectly) {
+ Matcher original(fromjson("{x: {$type: 2}}"), ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(), fromjson("{x: {$type: 2}}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+
+ BSONObj obj = fromjson("{x: 3}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson("{x: '3'}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+}
+
+TEST(SerializeBasic, ExpressionTypeWithNumberSerializesCorrectly) {
+ Matcher original(fromjson("{x: {$type: 'number'}}"), ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(), fromjson("{x: {$type: 'number'}}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+
+ BSONObj obj = fromjson("{x: 3}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson("{x: '3'}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+}
+
+TEST(SerializeBasic, ExpressionEmptySerializesCorrectly) {
+ Matcher original(fromjson("{}"), ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(), fromjson("{}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+
+ BSONObj obj = fromjson("{x: 3}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+}
+
+TEST(SerializeBasic, ExpressionWhereSerializesCorrectly) {
+ Matcher original(fromjson("{$where: 'this.a == this.b'}"), ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(),
+ BSONObjBuilder().appendCodeWScope("$where", "this.a == this.b", BSONObj()).obj());
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+}
+
+TEST(SerializeBasic, ExpressionWhereWithScopeSerializesCorrectly) {
+ Matcher original(BSON("$where" << BSONCodeWScope("this.a == this.b", BSON("x" << 3))),
+ ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(),
+ BSON("$where" << BSONCodeWScope("this.a == this.b", BSON("x" << 3))));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+}
+
+TEST(SerializeBasic, ExpressionCommentSerializesCorrectly) {
+ Matcher original(fromjson("{$comment: 'Hello'}"), ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(), fromjson("{}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+
+ BSONObj obj = fromjson("{a: 1, b: 2}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson("{a: 'z', b: 'z'}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+}
+
+TEST(SerializeBasic, ExpressionGeoWithinSerializesCorrectly) {
+ Matcher original(
+ fromjson(
+ "{x: {$geoWithin: {$geometry: "
+ "{type: 'Polygon', coordinates: [[[0, 0], [10, 0], [10, 10], [0, 10], [0, 0]]]}}}}"),
+ ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(),
+ fromjson(
+ "{x: {$geoWithin: {$geometry: {type: 'Polygon', coordinates: [[[0,0], [10,0], "
+ "[10, 10], [0, 10], [0, 0]]]}}}}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+
+ BSONObj obj = fromjson("{x: {type: 'Point', coordinates: [5, 5]}}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson("{x: {type: 'Point', coordinates: [50, 50]}}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+}
+
+TEST(SerializeBasic, ExpressionGeoIntersectsSerializesCorrectly) {
+ Matcher original(
+ fromjson(
+ "{x: {$geoIntersects: {$geometry: {type: 'Polygon', coordinates: [[[0,0], [5,0], [5, "
+ "5], [0, 5], [0, 0]]]}}}}"),
+ ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(),
+ fromjson(
+ "{x: {$geoIntersects: {$geometry: {type: 'Polygon', coordinates: [[[0,0], [5,0], "
+ "[5, 5], [0, 5], [0, 0]]]}}}}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+
+ BSONObj obj =
+ fromjson("{x: {type: 'Polygon', coordinates: [[4, 4], [4, 6], [6, 6], [6, 4], [4, 4]]}}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson(
+ "{x: {type: 'Polygon', coordinates: [[4, 4], [4, 4.5], [4.5, 4.5], [4.5, 4], [4, 4]]}}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+
+ obj = fromjson(
+ "{x: {type: 'Polygon', coordinates: [[5.5, 5.5], [5.5, 6], [6, 6], [6, 5.5], [5.5, "
+ "5.5]]}}");
+ ASSERT_EQ(original.matches(obj), reserialized.matches(obj));
+}
+
+TEST(SerializeBasic, ExpressionNearSerializesCorrectly) {
+ Matcher original(
+ fromjson(
+ "{x: {$near: {$geometry: {type: 'Point', coordinates: [0, 0]}, $maxDistance: 10, "
+ "$minDistance: 1}}}"),
+ ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(),
+ fromjson(
+ "{x: {$near: {$geometry: {type: 'Point', coordinates: [0, 0]}, $maxDistance: 10, "
+ "$minDistance: 1}}}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+}
+
+TEST(SerializeBasic, ExpressionNearSphereSerializesCorrectly) {
+ Matcher original(
+ fromjson(
+ "{x: {$nearSphere: {$geometry: {type: 'Point', coordinates: [0, 0]}, $maxDistance: 10, "
+ "$minDistance: 1}}}"),
+ ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(),
+ fromjson(
+ "{x: {$nearSphere: {$geometry: {type: 'Point', coordinates: [0, 0]}, "
+ "$maxDistance: 10, $minDistance: 1}}}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+}
+
+TEST(SerializeBasic, ExpressionTextSerializesCorrectly) {
+ Matcher original(fromjson("{$text: {$search: 'a', $language: 'en', $caseSensitive: true}}"),
+ ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(),
+ fromjson(
+ "{$text: {$search: 'a', $language: 'en', $caseSensitive: true, "
+ "$diacriticSensitive: false}}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+}
+
+TEST(SerializeBasic, ExpressionTextWithDefaultLanguageSerializesCorrectly) {
+ Matcher original(fromjson("{$text: {$search: 'a', $caseSensitive: false}}"),
+ ExtensionsCallbackNoop());
+ Matcher reserialized(serialize(original.getMatchExpression()), ExtensionsCallbackNoop());
+ ASSERT_EQ(*reserialized.getQuery(),
+ fromjson(
+ "{$text: {$search: 'a', $language: '', $caseSensitive: false, "
+ "$diacriticSensitive: false}}"));
+ ASSERT_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+}
+
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/db/matcher/expression_text_base.cpp b/src/mongo/db/matcher/expression_text_base.cpp
index 82fa7f9fd3e..afed1a33e6f 100644
--- a/src/mongo/db/matcher/expression_text_base.cpp
+++ b/src/mongo/db/matcher/expression_text_base.cpp
@@ -54,7 +54,7 @@ void TextMatchExpressionBase::debugString(StringBuilder& debug, int level) const
debug << "\n";
}
-void TextMatchExpressionBase::toBSON(BSONObjBuilder* out) const {
+void TextMatchExpressionBase::serialize(BSONObjBuilder* out) const {
const fts::FTSQuery& ftsQuery = getFTSQuery();
out->append("$text",
BSON("$search" << ftsQuery.getQuery() << "$language" << ftsQuery.getLanguage()
diff --git a/src/mongo/db/matcher/expression_text_base.h b/src/mongo/db/matcher/expression_text_base.h
index 187a91bb2d4..06a23a010bf 100644
--- a/src/mongo/db/matcher/expression_text_base.h
+++ b/src/mongo/db/matcher/expression_text_base.h
@@ -79,7 +79,7 @@ public:
void debugString(StringBuilder& debug, int level = 0) const final;
- void toBSON(BSONObjBuilder* out) const final;
+ void serialize(BSONObjBuilder* out) const final;
bool equivalent(const MatchExpression* other) const final;
};
diff --git a/src/mongo/db/matcher/expression_tree.cpp b/src/mongo/db/matcher/expression_tree.cpp
index bc07445d0cc..02da8991465 100644
--- a/src/mongo/db/matcher/expression_tree.cpp
+++ b/src/mongo/db/matcher/expression_tree.cpp
@@ -56,7 +56,7 @@ void ListOfMatchExpression::_debugList(StringBuilder& debug, int level) const {
void ListOfMatchExpression::_listToBSON(BSONArrayBuilder* out) const {
for (unsigned i = 0; i < _expressions.size(); i++) {
BSONObjBuilder childBob(out->subobjStart());
- _expressions[i]->toBSON(&childBob);
+ _expressions[i]->serialize(&childBob);
}
out->doneFast();
}
@@ -107,9 +107,16 @@ void AndMatchExpression::debugString(StringBuilder& debug, int level) const {
_debugList(debug, level);
}
-void AndMatchExpression::toBSON(BSONObjBuilder* out) const {
+void AndMatchExpression::serialize(BSONObjBuilder* out) const {
+ if (!numChildren()) {
+ // It is possible for an AndMatchExpression to have no children, resulting in the serialized
+ // expression {$and: []}, which is not a valid query object.
+ return;
+ }
+
BSONArrayBuilder arrBob(out->subarrayStart("$and"));
_listToBSON(&arrBob);
+ arrBob.doneFast();
}
// -----
@@ -139,7 +146,7 @@ void OrMatchExpression::debugString(StringBuilder& debug, int level) const {
_debugList(debug, level);
}
-void OrMatchExpression::toBSON(BSONObjBuilder* out) const {
+void OrMatchExpression::serialize(BSONObjBuilder* out) const {
BSONArrayBuilder arrBob(out->subarrayStart("$or"));
_listToBSON(&arrBob);
}
@@ -170,7 +177,7 @@ void NorMatchExpression::debugString(StringBuilder& debug, int level) const {
_debugList(debug, level);
}
-void NorMatchExpression::toBSON(BSONObjBuilder* out) const {
+void NorMatchExpression::serialize(BSONObjBuilder* out) const {
BSONArrayBuilder arrBob(out->subarrayStart("$nor"));
_listToBSON(&arrBob);
}
@@ -183,10 +190,17 @@ void NotMatchExpression::debugString(StringBuilder& debug, int level) const {
_exp->debugString(debug, level + 1);
}
-void NotMatchExpression::toBSON(BSONObjBuilder* out) const {
- BSONObjBuilder childBob(out->subobjStart("$not"));
- _exp->toBSON(&childBob);
- childBob.doneFast();
+void NotMatchExpression::serialize(BSONObjBuilder* out) const {
+ BSONObjBuilder childBob;
+ _exp->serialize(&childBob);
+
+ BSONObj tempObj = childBob.obj();
+
+ // We don't know what the inner object is, and thus whether serializing to $not will result in a
+ // parseable MatchExpression. As a fix, we change it to $nor, which is always parseable.
+ BSONArrayBuilder tBob(out->subarrayStart("$nor"));
+ tBob.append(tempObj);
+ tBob.doneFast();
}
bool NotMatchExpression::equivalent(const MatchExpression* other) const {
diff --git a/src/mongo/db/matcher/expression_tree.h b/src/mongo/db/matcher/expression_tree.h
index 0f4b8018523..2b65bc9d2f6 100644
--- a/src/mongo/db/matcher/expression_tree.h
+++ b/src/mongo/db/matcher/expression_tree.h
@@ -101,7 +101,7 @@ public:
virtual void debugString(StringBuilder& debug, int level = 0) const;
- virtual void toBSON(BSONObjBuilder* out) const;
+ virtual void serialize(BSONObjBuilder* out) const;
};
class OrMatchExpression : public ListOfMatchExpression {
@@ -125,7 +125,7 @@ public:
virtual void debugString(StringBuilder& debug, int level = 0) const;
- virtual void toBSON(BSONObjBuilder* out) const;
+ virtual void serialize(BSONObjBuilder* out) const;
};
class NorMatchExpression : public ListOfMatchExpression {
@@ -149,7 +149,7 @@ public:
virtual void debugString(StringBuilder& debug, int level = 0) const;
- virtual void toBSON(BSONObjBuilder* out) const;
+ virtual void serialize(BSONObjBuilder* out) const;
};
class NotMatchExpression : public MatchExpression {
@@ -183,7 +183,7 @@ public:
virtual void debugString(StringBuilder& debug, int level = 0) const;
- virtual void toBSON(BSONObjBuilder* out) const;
+ virtual void serialize(BSONObjBuilder* out) const;
bool equivalent(const MatchExpression* other) const;
diff --git a/src/mongo/db/matcher/expression_where_base.cpp b/src/mongo/db/matcher/expression_where_base.cpp
index 1bd7c2bde22..21b746e13ab 100644
--- a/src/mongo/db/matcher/expression_where_base.cpp
+++ b/src/mongo/db/matcher/expression_where_base.cpp
@@ -46,7 +46,7 @@ void WhereMatchExpressionBase::debugString(StringBuilder& debug, int level) cons
debug << "scope: " << getScope() << "\n";
}
-void WhereMatchExpressionBase::toBSON(BSONObjBuilder* out) const {
+void WhereMatchExpressionBase::serialize(BSONObjBuilder* out) const {
out->appendCodeWScope("$where", getCode(), getScope());
}
diff --git a/src/mongo/db/matcher/expression_where_base.h b/src/mongo/db/matcher/expression_where_base.h
index 8aabbed669d..ac845559bb2 100644
--- a/src/mongo/db/matcher/expression_where_base.h
+++ b/src/mongo/db/matcher/expression_where_base.h
@@ -54,7 +54,7 @@ public:
void debugString(StringBuilder& debug, int level = 0) const final;
- void toBSON(BSONObjBuilder* out) const final;
+ void serialize(BSONObjBuilder* out) const final;
bool equivalent(const MatchExpression* other) const final;
diff --git a/src/mongo/db/matcher/matcher.h b/src/mongo/db/matcher/matcher.h
index 7de034a654c..99b54b37a8a 100644
--- a/src/mongo/db/matcher/matcher.h
+++ b/src/mongo/db/matcher/matcher.h
@@ -60,6 +60,10 @@ public:
return _pattern.toString();
}
+ MatchExpression* getMatchExpression() {
+ return _expression.get();
+ }
+
private:
BSONObj _pattern;
diff --git a/src/mongo/db/query/explain.cpp b/src/mongo/db/query/explain.cpp
index 1dd784ffd04..890d50e9021 100644
--- a/src/mongo/db/query/explain.cpp
+++ b/src/mongo/db/query/explain.cpp
@@ -552,7 +552,7 @@ void Explain::generatePlannerInfo(PlanExecutor* exec,
// does not canonicalize for idhack updates). In these cases, 'query' is NULL.
if (NULL != query) {
BSONObjBuilder parsedQueryBob(plannerBob.subobjStart("parsedQuery"));
- query->root()->toBSON(&parsedQueryBob);
+ query->root()->serialize(&parsedQueryBob);
parsedQueryBob.doneFast();
}
diff --git a/src/mongo/db/query/parsed_projection_test.cpp b/src/mongo/db/query/parsed_projection_test.cpp
index 6f4066c406b..8d88c628639 100644
--- a/src/mongo/db/query/parsed_projection_test.cpp
+++ b/src/mongo/db/query/parsed_projection_test.cpp
@@ -181,7 +181,7 @@ TEST(ParsedProjectionTest, ValidPositionalOperatorProjections) {
// to achieve the same effect.
// Projection parser should handle this the same way as an empty path.
TEST(ParsedProjectionTest, InvalidPositionalProjectionDefaultPathMatchExpression) {
- unique_ptr<MatchExpression> queryMatchExpr(new FalseMatchExpression());
+ unique_ptr<MatchExpression> queryMatchExpr(new FalseMatchExpression(""));
ASSERT(NULL == queryMatchExpr->path().rawData());
ParsedProjection* out = NULL;
diff --git a/src/mongo/dbtests/extensions_callback_real_test.cpp b/src/mongo/dbtests/extensions_callback_real_test.cpp
index 56465cb2c7e..d1284f7aea4 100644
--- a/src/mongo/dbtests/extensions_callback_real_test.cpp
+++ b/src/mongo/dbtests/extensions_callback_real_test.cpp
@@ -247,13 +247,13 @@ TEST_F(ExtensionsCallbackRealTest, WhereExpressionsWithSameScopeHaveSameBSONRepr
auto expr1 =
unittest::assertGet(ExtensionsCallbackReal(&_txn, &_nss).parseWhere(query1.firstElement()));
BSONObjBuilder builder1;
- expr1->toBSON(&builder1);
+ expr1->serialize(&builder1);
BSONObj query2 = BSON("$where" << BSONCodeWScope(code, BSON("a" << true)));
auto expr2 =
unittest::assertGet(ExtensionsCallbackReal(&_txn, &_nss).parseWhere(query2.firstElement()));
BSONObjBuilder builder2;
- expr2->toBSON(&builder2);
+ expr2->serialize(&builder2);
ASSERT_EQ(builder1.obj(), builder2.obj());
}
@@ -266,13 +266,13 @@ TEST_F(ExtensionsCallbackRealTest,
auto expr1 =
unittest::assertGet(ExtensionsCallbackReal(&_txn, &_nss).parseWhere(query1.firstElement()));
BSONObjBuilder builder1;
- expr1->toBSON(&builder1);
+ expr1->serialize(&builder1);
BSONObj query2 = BSON("$where" << BSONCodeWScope(code, BSON("a" << false)));
auto expr2 =
unittest::assertGet(ExtensionsCallbackReal(&_txn, &_nss).parseWhere(query2.firstElement()));
BSONObjBuilder builder2;
- expr2->toBSON(&builder2);
+ expr2->serialize(&builder2);
ASSERT_NE(builder1.obj(), builder2.obj());
}