summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorNick Zolnierz <nicholas.zolnierz@mongodb.com>2017-08-24 13:23:41 -0400
committerNick Zolnierz <nicholas.zolnierz@mongodb.com>2017-09-19 13:47:58 -0400
commit4574a7c8dd12650eceee1e197b35ba47dfbb7f8e (patch)
tree9709578bb7347d0395e900b7ccace7b4cb5c156d /src
parentaf7387affece778a2507666b8bc5f502778fe1b8 (diff)
downloadmongo-4574a7c8dd12650eceee1e197b35ba47dfbb7f8e.tar.gz
SERVER-30176: Extend the JSON Schema parser to handle logical restriction keywords (enum only)
Diffstat (limited to 'src')
-rw-r--r--src/mongo/bson/bson_comparator_interface_base.cpp24
-rw-r--r--src/mongo/bson/bson_comparator_interface_base.h37
-rw-r--r--src/mongo/bson/bson_obj_test.cpp47
-rw-r--r--src/mongo/bson/bsonelement.cpp326
-rw-r--r--src/mongo/bson/bsonelement.h44
-rw-r--r--src/mongo/bson/bsonelement_comparator.h12
-rw-r--r--src/mongo/bson/bsonobj.cpp90
-rw-r--r--src/mongo/bson/bsonobj.h30
-rw-r--r--src/mongo/bson/bsonobj_comparator.h13
-rw-r--r--src/mongo/bson/simple_bsonelement_comparator.h5
-rw-r--r--src/mongo/bson/simple_bsonobj_comparator.h5
-rw-r--r--src/mongo/bson/unordered_fields_bsonelement_comparator.h55
-rw-r--r--src/mongo/bson/unordered_fields_bsonobj_comparator.h61
-rw-r--r--src/mongo/db/matcher/SConscript4
-rw-r--r--src/mongo/db/matcher/expression.h2
-rw-r--r--src/mongo/db/matcher/expression_algo.cpp5
-rw-r--r--src/mongo/db/matcher/expression_leaf.cpp3
-rw-r--r--src/mongo/db/matcher/expression_parser.cpp29
-rw-r--r--src/mongo/db/matcher/expression_parser.h1
-rw-r--r--src/mongo/db/matcher/expression_serialization_test.cpp16
-rw-r--r--src/mongo/db/matcher/schema/expression_internal_schema_eq.cpp89
-rw-r--r--src/mongo/db/matcher/schema/expression_internal_schema_eq.h82
-rw-r--r--src/mongo/db/matcher/schema/expression_internal_schema_eq_test.cpp145
-rw-r--r--src/mongo/db/matcher/schema/expression_internal_schema_root_doc_eq.cpp79
-rw-r--r--src/mongo/db/matcher/schema/expression_internal_schema_root_doc_eq.h94
-rw-r--r--src/mongo/db/matcher/schema/expression_internal_schema_root_doc_eq_test.cpp119
-rw-r--r--src/mongo/db/matcher/schema/expression_parser_schema_test.cpp42
-rw-r--r--src/mongo/db/matcher/schema/json_schema_parser.cpp67
-rw-r--r--src/mongo/db/matcher/schema/json_schema_parser_test.cpp62
-rw-r--r--src/mongo/db/pipeline/document_source_match.cpp1
-rw-r--r--src/mongo/db/pipeline/value.cpp11
-rw-r--r--src/mongo/db/query/plan_cache.cpp6
32 files changed, 1332 insertions, 274 deletions
diff --git a/src/mongo/bson/bson_comparator_interface_base.cpp b/src/mongo/bson/bson_comparator_interface_base.cpp
index 6dfb379fd67..4605aa0fe24 100644
--- a/src/mongo/bson/bson_comparator_interface_base.cpp
+++ b/src/mongo/bson/bson_comparator_interface_base.cpp
@@ -43,10 +43,18 @@ template <typename T>
void BSONComparatorInterfaceBase<T>::hashCombineBSONObj(
size_t& seed,
const BSONObj& objToHash,
- bool considerFieldName,
+ ComparisonRulesSet rules,
const StringData::ComparatorInterface* stringComparator) {
- for (auto elem : objToHash) {
- hashCombineBSONElement(seed, elem, considerFieldName, stringComparator);
+
+ if (rules & ComparisonRules::kIgnoreFieldOrder) {
+ BSONObjIteratorSorted iter(objToHash);
+ while (iter.more()) {
+ hashCombineBSONElement(seed, iter.next(), rules, stringComparator);
+ }
+ } else {
+ for (auto elem : objToHash) {
+ hashCombineBSONElement(seed, elem, rules, stringComparator);
+ }
}
}
@@ -54,18 +62,16 @@ template <typename T>
void BSONComparatorInterfaceBase<T>::hashCombineBSONElement(
size_t& hash,
BSONElement elemToHash,
- bool considerFieldName,
+ ComparisonRulesSet rules,
const StringData::ComparatorInterface* stringComparator) {
boost::hash_combine(hash, elemToHash.canonicalType());
const StringData fieldName = elemToHash.fieldNameStringData();
- if (considerFieldName && !fieldName.empty()) {
+ if ((rules & ComparisonRules::kConsiderFieldName) && !fieldName.empty()) {
SimpleStringDataComparator::kInstance.hash_combine(hash, fieldName);
}
switch (elemToHash.type()) {
- // Order of types is the same as in compareElementValues().
-
case mongo::EOO:
case mongo::Undefined:
case mongo::jstNULL:
@@ -145,7 +151,7 @@ void BSONComparatorInterfaceBase<T>::hashCombineBSONElement(
case mongo::Array:
hashCombineBSONObj(hash,
elemToHash.embeddedObject(),
- true, // considerFieldName
+ rules | ComparisonRules::kConsiderFieldName,
stringComparator);
break;
@@ -166,7 +172,7 @@ void BSONComparatorInterfaceBase<T>::hashCombineBSONElement(
hash, StringData(elemToHash.codeWScopeCode(), elemToHash.codeWScopeCodeLen()));
hashCombineBSONObj(hash,
elemToHash.codeWScopeObject(),
- true, // considerFieldName
+ rules | ComparisonRules::kConsiderFieldName,
&SimpleStringDataComparator::kInstance);
break;
}
diff --git a/src/mongo/bson/bson_comparator_interface_base.h b/src/mongo/bson/bson_comparator_interface_base.h
index 29d80916f72..341994aa67c 100644
--- a/src/mongo/bson/bson_comparator_interface_base.h
+++ b/src/mongo/bson/bson_comparator_interface_base.h
@@ -57,6 +57,39 @@ public:
BSONComparatorInterfaceBase& operator=(BSONComparatorInterfaceBase&& other) = default;
/**
+ * Set of rules used in the comparison of BSON Objects and Elements.
+ */
+ enum ComparisonRules {
+ // Set this bit to consider the field name in element comparisons.
+ // if (kConsiderFieldName = 0) --> 'a: 1' == 'b: 1'
+ // if (kConsiderFieldName = 1) --> 'a: 1' != 'b: 1'
+ kConsiderFieldName = 1 << 0,
+
+ // Set this bit to ignore the element order in BSON Object comparisons. This field will
+ // remain set/unset for nested objects.
+ //
+ // e.g. if kIgnoreFieldOrder == 1, then the following objects are considered equal:
+ //
+ // obj1: {
+ // a: {
+ // b: 1,
+ // c: 1
+ // },
+ // d: 1
+ // }
+ //
+ // obj2: {
+ // d: 1,
+ // a: {
+ // c: 1,
+ // b: 1,
+ // },
+ // }
+ kIgnoreFieldOrder = 1 << 1,
+ };
+ using ComparisonRulesSet = uint32_t;
+
+ /**
* A deferred comparison between two objects of type T, which can be converted into a boolean
* via the evaluate() method.
*/
@@ -240,7 +273,7 @@ protected:
*/
static void hashCombineBSONObj(size_t& seed,
const BSONObj& objToHash,
- bool considerFieldName,
+ ComparisonRulesSet rules,
const StringData::ComparatorInterface* stringComparator);
/**
@@ -250,7 +283,7 @@ protected:
*/
static void hashCombineBSONElement(size_t& seed,
BSONElement elemToHash,
- bool considerFieldName,
+ ComparisonRulesSet rules,
const StringData::ComparatorInterface* stringComparator);
};
diff --git a/src/mongo/bson/bson_obj_test.cpp b/src/mongo/bson/bson_obj_test.cpp
index 5a9360605d5..82f95be072a 100644
--- a/src/mongo/bson/bson_obj_test.cpp
+++ b/src/mongo/bson/bson_obj_test.cpp
@@ -29,6 +29,7 @@
#include "mongo/bson/bsonobj_comparator.h"
#include "mongo/bson/simple_bsonelement_comparator.h"
#include "mongo/bson/simple_bsonobj_comparator.h"
+#include "mongo/bson/unordered_fields_bsonobj_comparator.h"
#include "mongo/db/jsobj.h"
#include "mongo/db/json.h"
#include "mongo/platform/decimal128.h"
@@ -525,6 +526,8 @@ TEST(BSONObjCompare, BSONObjHashingIgnoresTopLevelFieldNamesWhenRequested) {
ASSERT_NE(bsonCmpConsiderFieldNames.hash(obj1), bsonCmpConsiderFieldNames.hash(obj2));
ASSERT_EQ(bsonCmpIgnoreFieldNames.hash(obj1), bsonCmpIgnoreFieldNames.hash(obj2));
ASSERT_NE(bsonCmpIgnoreFieldNames.hash(obj1), bsonCmpIgnoreFieldNames.hash(obj3));
+ ASSERT_NE(bsonCmpIgnoreFieldNames.hash(fromjson("{a: {b: 1, c: 1}, d: 1}")),
+ bsonCmpIgnoreFieldNames.hash(fromjson("{a: {b: 1, c: 2}, d: 1}")));
}
TEST(BSONObjCompare, BSONElementHashingIgnoresEltFieldNameWhenRequested) {
@@ -544,6 +547,50 @@ TEST(BSONObjCompare, BSONElementHashingIgnoresEltFieldNameWhenRequested) {
bsonCmpIgnoreFieldNames.hash(obj3.firstElement()));
}
+TEST(BSONObjCompare, WoCompareWithIdxKey) {
+ BSONObj obj = fromjson("{a: 1, b: 1, c: 1}");
+ BSONObj objEq = fromjson("{a: 1, b: 1, c: 1}");
+ BSONObj objGt = fromjson("{a: 2, b: 2, c: 2}");
+ BSONObj objLt = fromjson("{a: 0, b: 0, c: 0}");
+ BSONObj idxKeyAsc = fromjson("{a: 1, b: 1}");
+ BSONObj idxKeyDesc = fromjson("{a: -1, b: 1}");
+ BSONObj idxKeyShort = fromjson("{a: 1}");
+
+ ASSERT_EQ(obj.woCompare(objEq, idxKeyAsc), 0);
+ ASSERT_EQ(obj.woCompare(objEq, idxKeyDesc), 0);
+ ASSERT_EQ(obj.woCompare(objEq, idxKeyShort), 0);
+ ASSERT_EQ(obj.woCompare(objGt, idxKeyAsc), -1);
+ ASSERT_EQ(obj.woCompare(objGt, idxKeyDesc), 1);
+ ASSERT_EQ(obj.woCompare(objGt, idxKeyShort), -1);
+ ASSERT_EQ(obj.woCompare(objLt, idxKeyAsc), 1);
+ ASSERT_EQ(obj.woCompare(objLt, idxKeyDesc), -1);
+ ASSERT_EQ(obj.woCompare(objLt, idxKeyShort), 1);
+}
+
+TEST(BSONObjCompare, UnorderedFieldsBSONObjComparison) {
+ BSONObj obj = fromjson("{a: {b: 1}, c: 1}");
+
+ UnorderedFieldsBSONObjComparator bsonCmp;
+
+ ASSERT_TRUE(bsonCmp.evaluate(obj == fromjson("{c: 1, a: {b: 1}}")));
+ ASSERT_FALSE(bsonCmp.evaluate(obj == fromjson("{a: {b: 1}, c: 1, d: 1}")));
+ ASSERT_FALSE(bsonCmp.evaluate(obj == fromjson("{a: {b: 1}}")));
+ ASSERT_FALSE(bsonCmp.evaluate(obj == fromjson("{a: {b: 2}, c: 1}")));
+}
+
+TEST(BSONObjCompare, UnorderedFieldsBSONObjHashing) {
+ BSONObj obj = fromjson("{a: {b: 1, c: 1}, d: 1}");
+
+ UnorderedFieldsBSONObjComparator bsonCmp;
+
+ ASSERT_EQ(bsonCmp.hash(obj), bsonCmp.hash(obj));
+ ASSERT_EQ(bsonCmp.hash(obj), bsonCmp.hash(fromjson("{d: 1, a: {b: 1, c: 1}}")));
+ ASSERT_EQ(bsonCmp.hash(obj), bsonCmp.hash(fromjson("{a: {c: 1, b: 1}, d: 1}")));
+ ASSERT_NE(bsonCmp.hash(obj), bsonCmp.hash(fromjson("{a: {b: 1, c: 1}}")));
+ ASSERT_NE(bsonCmp.hash(obj), bsonCmp.hash(fromjson("{a: {b: 1, c: 1}, d: 2}")));
+ ASSERT_NE(bsonCmp.hash(obj), bsonCmp.hash(fromjson("{a: {b: 1}, d: 1}")));
+}
+
TEST(Looping, Cpp11Basic) {
int count = 0;
for (BSONElement e : BSON("a" << 1 << "a" << 2 << "a" << 3)) {
diff --git a/src/mongo/bson/bsonelement.cpp b/src/mongo/bson/bsonelement.cpp
index de5d1e6d303..38ca7772058 100644
--- a/src/mongo/bson/bsonelement.cpp
+++ b/src/mongo/bson/bsonelement.cpp
@@ -321,6 +321,160 @@ int compareElementStringValues(const BSONElement& leftStr, const BSONElement& ri
} // namespace
+int BSONElement::compareElements(const BSONElement& l,
+ const BSONElement& r,
+ ComparisonRulesSet rules,
+ const StringData::ComparatorInterface* comparator) {
+ switch (l.type()) {
+ case BSONType::EOO:
+ case BSONType::Undefined: // EOO and Undefined are same canonicalType
+ case BSONType::jstNULL:
+ case BSONType::MaxKey:
+ case BSONType::MinKey: {
+ auto f = l.canonicalType() - r.canonicalType();
+ if (f < 0)
+ return -1;
+ return f == 0 ? 0 : 1;
+ }
+ case BSONType::Bool:
+ return *l.value() - *r.value();
+ case BSONType::bsonTimestamp:
+ // unsigned compare for timestamps - note they are not really dates but (ordinal +
+ // time_t)
+ if (l.timestamp() < r.timestamp())
+ return -1;
+ return l.timestamp() == r.timestamp() ? 0 : 1;
+ case BSONType::Date:
+ // Signed comparisons for Dates.
+ {
+ const Date_t a = l.Date();
+ const Date_t b = r.Date();
+ if (a < b)
+ return -1;
+ return a == b ? 0 : 1;
+ }
+
+ case BSONType::NumberInt: {
+ // All types can precisely represent all NumberInts, so it is safe to simply convert to
+ // whatever rhs's type is.
+ switch (r.type()) {
+ case NumberInt:
+ return compareInts(l._numberInt(), r._numberInt());
+ case NumberLong:
+ return compareLongs(l._numberInt(), r._numberLong());
+ case NumberDouble:
+ return compareDoubles(l._numberInt(), r._numberDouble());
+ case NumberDecimal:
+ return compareIntToDecimal(l._numberInt(), r._numberDecimal());
+ default:
+ invariant(false);
+ }
+ }
+
+ case BSONType::NumberLong: {
+ switch (r.type()) {
+ case NumberLong:
+ return compareLongs(l._numberLong(), r._numberLong());
+ case NumberInt:
+ return compareLongs(l._numberLong(), r._numberInt());
+ case NumberDouble:
+ return compareLongToDouble(l._numberLong(), r._numberDouble());
+ case NumberDecimal:
+ return compareLongToDecimal(l._numberLong(), r._numberDecimal());
+ default:
+ invariant(false);
+ }
+ }
+
+ case BSONType::NumberDouble: {
+ switch (r.type()) {
+ case NumberDouble:
+ return compareDoubles(l._numberDouble(), r._numberDouble());
+ case NumberInt:
+ return compareDoubles(l._numberDouble(), r._numberInt());
+ case NumberLong:
+ return compareDoubleToLong(l._numberDouble(), r._numberLong());
+ case NumberDecimal:
+ return compareDoubleToDecimal(l._numberDouble(), r._numberDecimal());
+ default:
+ invariant(false);
+ }
+ }
+
+ case BSONType::NumberDecimal: {
+ switch (r.type()) {
+ case NumberDecimal:
+ return compareDecimals(l._numberDecimal(), r._numberDecimal());
+ case NumberInt:
+ return compareDecimalToInt(l._numberDecimal(), r._numberInt());
+ case NumberLong:
+ return compareDecimalToLong(l._numberDecimal(), r._numberLong());
+ case NumberDouble:
+ return compareDecimalToDouble(l._numberDecimal(), r._numberDouble());
+ default:
+ invariant(false);
+ }
+ }
+
+ case BSONType::jstOID:
+ return memcmp(l.value(), r.value(), OID::kOIDSize);
+ case BSONType::Code:
+ return compareElementStringValues(l, r);
+ case BSONType::Symbol:
+ case BSONType::String: {
+ if (comparator) {
+ return comparator->compare(l.valueStringData(), r.valueStringData());
+ } else {
+ return compareElementStringValues(l, r);
+ }
+ }
+ case BSONType::Object:
+ case BSONType::Array: {
+ return l.embeddedObject().woCompare(
+ r.embeddedObject(),
+ BSONObj(),
+ rules | BSONElement::ComparisonRules::kConsiderFieldName,
+ comparator);
+ }
+ case BSONType::DBRef: {
+ int lsz = l.valuesize();
+ int rsz = r.valuesize();
+ if (lsz - rsz != 0)
+ return lsz - rsz;
+ return memcmp(l.value(), r.value(), lsz);
+ }
+ case BSONType::BinData: {
+ int lsz = l.objsize(); // our bin data size in bytes, not including the subtype byte
+ int rsz = r.objsize();
+ if (lsz - rsz != 0)
+ return lsz - rsz;
+ return memcmp(l.value() + 4, r.value() + 4, lsz + 1 /*+1 for subtype byte*/);
+ }
+ case BSONType::RegEx: {
+ int c = strcmp(l.regex(), r.regex());
+ if (c)
+ return c;
+ return strcmp(l.regexFlags(), r.regexFlags());
+ }
+ case BSONType::CodeWScope: {
+ int cmp = StringData(l.codeWScopeCode(), l.codeWScopeCodeLen() - 1)
+ .compare(StringData(r.codeWScopeCode(), r.codeWScopeCodeLen() - 1));
+ if (cmp)
+ return cmp;
+
+ // When comparing the scope object, we should consider field names. Special string
+ // comparison semantics do not apply to strings nested inside the CodeWScope scope
+ // object, so we do not pass through the string comparator.
+ return l.codeWScopeObject().woCompare(
+ r.codeWScopeObject(),
+ BSONObj(),
+ rules | BSONElement::ComparisonRules::kConsiderFieldName);
+ }
+ }
+
+ MONGO_UNREACHABLE;
+}
+
/** transform a BSON array into a vector of BSONElements.
we match array # positions with their vector position, and ignore
any fields with non-numeric field names.
@@ -347,23 +501,20 @@ std::vector<BSONElement> BSONElement::Array() const {
return v;
}
-/* wo = "well ordered"
- note: (mongodb related) : this can only change in behavior when index version # changes
-*/
-int BSONElement::woCompare(const BSONElement& e,
- bool considerFieldName,
+int BSONElement::woCompare(const BSONElement& elem,
+ ComparisonRulesSet rules,
const StringData::ComparatorInterface* comparator) const {
- if (type() != e.type()) {
+ if (type() != elem.type()) {
int lt = (int)canonicalType();
- int rt = (int)e.canonicalType();
+ int rt = (int)elem.canonicalType();
if (int diff = lt - rt)
return diff;
}
- if (considerFieldName) {
- if (int diff = strcmp(fieldName(), e.fieldName()))
+ if (rules & ComparisonRules::kConsiderFieldName) {
+ if (int diff = fieldNameStringData().compare(elem.fieldNameStringData()))
return diff;
}
- return compareElementValues(*this, e, comparator);
+ return compareElements(*this, elem, rules, comparator);
}
bool BSONElement::binaryEqual(const BSONElement& rhs) const {
@@ -810,159 +961,4 @@ bool BSONObj::coerceVector(std::vector<T>* out) const {
return true;
}
-/**
- * l and r must be same canonicalType when called.
- */
-int compareElementValues(const BSONElement& l,
- const BSONElement& r,
- const StringData::ComparatorInterface* comparator) {
- int f;
-
- switch (l.type()) {
- case EOO:
- case Undefined: // EOO and Undefined are same canonicalType
- case jstNULL:
- case MaxKey:
- case MinKey:
- f = l.canonicalType() - r.canonicalType();
- if (f < 0)
- return -1;
- return f == 0 ? 0 : 1;
- case Bool:
- return *l.value() - *r.value();
- case bsonTimestamp:
- // unsigned compare for timestamps - note they are not really dates but (ordinal +
- // time_t)
- if (l.timestamp() < r.timestamp())
- return -1;
- return l.timestamp() == r.timestamp() ? 0 : 1;
- case Date:
- // Signed comparisons for Dates.
- {
- const Date_t a = l.Date();
- const Date_t b = r.Date();
- if (a < b)
- return -1;
- return a == b ? 0 : 1;
- }
-
- case NumberInt: {
- // All types can precisely represent all NumberInts, so it is safe to simply convert to
- // whatever rhs's type is.
- switch (r.type()) {
- case NumberInt:
- return compareInts(l._numberInt(), r._numberInt());
- case NumberLong:
- return compareLongs(l._numberInt(), r._numberLong());
- case NumberDouble:
- return compareDoubles(l._numberInt(), r._numberDouble());
- case NumberDecimal:
- return compareIntToDecimal(l._numberInt(), r._numberDecimal());
- default:
- invariant(false);
- }
- }
-
- case NumberLong: {
- switch (r.type()) {
- case NumberLong:
- return compareLongs(l._numberLong(), r._numberLong());
- case NumberInt:
- return compareLongs(l._numberLong(), r._numberInt());
- case NumberDouble:
- return compareLongToDouble(l._numberLong(), r._numberDouble());
- case NumberDecimal:
- return compareLongToDecimal(l._numberLong(), r._numberDecimal());
- default:
- invariant(false);
- }
- }
-
- case NumberDouble: {
- switch (r.type()) {
- case NumberDouble:
- return compareDoubles(l._numberDouble(), r._numberDouble());
- case NumberInt:
- return compareDoubles(l._numberDouble(), r._numberInt());
- case NumberLong:
- return compareDoubleToLong(l._numberDouble(), r._numberLong());
- case NumberDecimal:
- return compareDoubleToDecimal(l._numberDouble(), r._numberDecimal());
- default:
- invariant(false);
- }
- }
-
- case NumberDecimal: {
- switch (r.type()) {
- case NumberDecimal:
- return compareDecimals(l._numberDecimal(), r._numberDecimal());
- case NumberInt:
- return compareDecimalToInt(l._numberDecimal(), r._numberInt());
- case NumberLong:
- return compareDecimalToLong(l._numberDecimal(), r._numberLong());
- case NumberDouble:
- return compareDecimalToDouble(l._numberDecimal(), r._numberDouble());
- default:
- invariant(false);
- }
- }
-
- case jstOID:
- return memcmp(l.value(), r.value(), OID::kOIDSize);
- case Code:
- return compareElementStringValues(l, r);
- case Symbol:
- case String: {
- if (comparator) {
- return comparator->compare(l.valueStringData(), r.valueStringData());
- } else {
- return compareElementStringValues(l, r);
- }
- }
- case Object:
- case Array:
- // woCompare parameters: r, ordering, considerFieldName, comparator.
- // r: the BSONObj to compare with.
- // ordering: the sort directions for each key.
- // considerFieldName: whether field names should be considered in comparison.
- // comparator: used for all string comparisons, if non-null.
- return l.embeddedObject().woCompare(r.embeddedObject(), BSONObj(), true, comparator);
- case DBRef: {
- int lsz = l.valuesize();
- int rsz = r.valuesize();
- if (lsz - rsz != 0)
- return lsz - rsz;
- return memcmp(l.value(), r.value(), lsz);
- }
- case BinData: {
- int lsz = l.objsize(); // our bin data size in bytes, not including the subtype byte
- int rsz = r.objsize();
- if (lsz - rsz != 0)
- return lsz - rsz;
- return memcmp(l.value() + 4, r.value() + 4, lsz + 1 /*+1 for subtype byte*/);
- }
- case RegEx: {
- int c = strcmp(l.regex(), r.regex());
- if (c)
- return c;
- return strcmp(l.regexFlags(), r.regexFlags());
- }
- case CodeWScope: {
- int cmp = StringData(l.codeWScopeCode(), l.codeWScopeCodeLen() - 1)
- .compare(StringData(r.codeWScopeCode(), r.codeWScopeCodeLen() - 1));
- if (cmp)
- return cmp;
-
- // When comparing the scope object, we should consider field names. Special string
- // comparison semantics do not apply to strings nested inside the CodeWScope scope
- // object, so we do not pass through the string comparator.
- return l.codeWScopeObject().woCompare(r.codeWScopeObject(), BSONObj(), true);
- }
- default:
- verify(false);
- }
- return -1;
-}
-
} // namespace mongo
diff --git a/src/mongo/bson/bsonelement.h b/src/mongo/bson/bsonelement.h
index 7d58f4653aa..c86a50a0154 100644
--- a/src/mongo/bson/bsonelement.h
+++ b/src/mongo/bson/bsonelement.h
@@ -57,13 +57,6 @@ typedef BSONElement be;
typedef BSONObj bo;
typedef BSONObjBuilder bob;
-/** l and r MUST have same type when called: check that first.
- If comparator is non-null, it is used for all comparisons between two strings.
-*/
-int compareElementValues(const BSONElement& l,
- const BSONElement& r,
- const StringData::ComparatorInterface* comparator = nullptr);
-
/** BSONElement represents an "element" in a BSONObj. So for the object { a : 3, b : "abc" },
'a : 3' is the first element (key+value).
@@ -88,6 +81,25 @@ public:
*/
using DeferredComparison = BSONComparatorInterfaceBase<BSONElement>::DeferredComparison;
+ /**
+ * Set of rules that dictate the behavior of the comparison APIs.
+ */
+ using ComparisonRules = BSONComparatorInterfaceBase<BSONElement>::ComparisonRules;
+ using ComparisonRulesSet = BSONComparatorInterfaceBase<BSONElement>::ComparisonRulesSet;
+
+ /**
+ * Compares two BSON elements of the same canonical type.
+ *
+ * Returns <0 if 'l' is less than the element 'r'.
+ * >0 if 'l' is greater than the element 'r'.
+ * 0 if 'l' is equal to the element 'r'.
+ */
+ static int compareElements(const BSONElement& l,
+ const BSONElement& r,
+ ComparisonRulesSet rules,
+ const StringData::ComparatorInterface* comparator);
+
+
/** These functions, which start with a capital letter, throw if the
element is not of the required type. Example:
@@ -518,14 +530,16 @@ public:
*/
bool binaryEqualValues(const BSONElement& rhs) const;
- /** Well ordered comparison.
- @return <0: l<r. 0:l==r. >0:l>r
- order by type, field name, and field value.
- If considerFieldName is true, pay attention to the field name.
- If comparator is non-null, it is used for all comparisons between two strings.
- */
- int woCompare(const BSONElement& e,
- bool considerFieldName = true,
+ /**
+ * Compares two BSON Elements using the rules specified by 'rules' and the 'comparator' for
+ * string comparisons.
+ *
+ * Returns <0 if 'this' is less than 'elem'.
+ * >0 if 'this' is greater than 'elem'.
+ * 0 if 'this' is equal to 'elem'.
+ */
+ int woCompare(const BSONElement& elem,
+ ComparisonRulesSet rules = ComparisonRules::kConsiderFieldName,
const StringData::ComparatorInterface* comparator = nullptr) const;
DeferredComparison operator<(const BSONElement& other) const {
diff --git a/src/mongo/bson/bsonelement_comparator.h b/src/mongo/bson/bsonelement_comparator.h
index 609bbb4e5bd..9125f2c37f4 100644
--- a/src/mongo/bson/bsonelement_comparator.h
+++ b/src/mongo/bson/bsonelement_comparator.h
@@ -55,21 +55,21 @@ public:
*/
BSONElementComparator(FieldNamesMode fieldNamesMode,
const StringData::ComparatorInterface* stringComparator)
- : _fieldNamesMode(fieldNamesMode), _stringComparator(stringComparator) {}
+ : _stringComparator(stringComparator),
+ _rules((fieldNamesMode == FieldNamesMode::kConsider) ? ComparisonRules::kConsiderFieldName
+ : 0) {}
int compare(const BSONElement& lhs, const BSONElement& rhs) const final {
- const bool considerFieldName = (_fieldNamesMode == FieldNamesMode::kConsider);
- return lhs.woCompare(rhs, considerFieldName, _stringComparator);
+ return lhs.woCompare(rhs, _rules, _stringComparator);
}
void hash_combine(size_t& seed, const BSONElement& toHash) const final {
- const bool considerFieldName = (_fieldNamesMode == FieldNamesMode::kConsider);
- hashCombineBSONElement(seed, toHash, considerFieldName, _stringComparator);
+ hashCombineBSONElement(seed, toHash, _rules, _stringComparator);
}
private:
- FieldNamesMode _fieldNamesMode;
const StringData::ComparatorInterface* _stringComparator;
+ ComparisonRulesSet _rules;
};
} // namespace mongo
diff --git a/src/mongo/bson/bsonobj.cpp b/src/mongo/bson/bsonobj.cpp
index 12bcb5f6747..5ec83659f63 100644
--- a/src/mongo/bson/bsonobj.cpp
+++ b/src/mongo/bson/bsonobj.cpp
@@ -42,7 +42,49 @@
#include "mongo/util/stringutils.h"
namespace mongo {
+
+namespace {
+
+template <class ObjectIterator>
+int compareObjects(const BSONObj& firstObj,
+ const BSONObj& secondObj,
+ const BSONObj& idxKey,
+ BSONObj::ComparisonRulesSet rules,
+ const StringData::ComparatorInterface* comparator) {
+ if (firstObj.isEmpty())
+ return secondObj.isEmpty() ? 0 : -1;
+ if (secondObj.isEmpty())
+ return 1;
+
+ ObjectIterator firstIter(firstObj);
+ ObjectIterator secondIter(secondObj);
+ ObjectIterator idxKeyIter(idxKey);
+
+ while (true) {
+ BSONElement l = firstIter.next();
+ BSONElement r = secondIter.next();
+
+ if (l.eoo())
+ return r.eoo() ? 0 : -1;
+ if (r.eoo())
+ return 1;
+
+ auto x = l.woCompare(r, rules, comparator);
+
+ if (idxKeyIter.more() && idxKeyIter.next().number() < 0)
+ x = -x;
+
+ if (x != 0)
+ return x;
+ }
+
+ MONGO_UNREACHABLE;
+}
+
+} // namespace
+
using namespace std;
+
/* BSONObj ------------------------------------------------------------*/
void BSONObj::_assertInvalid() const {
@@ -104,7 +146,7 @@ bool BSONObj::valid(BSONVersion version) const {
int BSONObj::woCompare(const BSONObj& r,
const Ordering& o,
- bool considerFieldName,
+ ComparisonRulesSet rules,
const StringData::ComparatorInterface* comparator) const {
if (isEmpty())
return r.isEmpty() ? 0 : -1;
@@ -126,7 +168,7 @@ int BSONObj::woCompare(const BSONObj& r,
int x;
{
- x = l.woCompare(r, considerFieldName, comparator);
+ x = l.woCompare(r, rules, comparator);
if (o.descending(mask))
x = -x;
}
@@ -140,47 +182,11 @@ int BSONObj::woCompare(const BSONObj& r,
/* well ordered compare */
int BSONObj::woCompare(const BSONObj& r,
const BSONObj& idxKey,
- bool considerFieldName,
+ ComparisonRulesSet rules,
const StringData::ComparatorInterface* comparator) const {
- if (isEmpty())
- return r.isEmpty() ? 0 : -1;
- if (r.isEmpty())
- return 1;
-
- bool ordered = !idxKey.isEmpty();
-
- BSONObjIterator i(*this);
- BSONObjIterator j(r);
- BSONObjIterator k(idxKey);
- while (1) {
- // so far, equal...
-
- BSONElement l = i.next();
- BSONElement r = j.next();
- BSONElement o;
- if (ordered)
- o = k.next();
- if (l.eoo())
- return r.eoo() ? 0 : -1;
- if (r.eoo())
- return 1;
-
- int x;
- /*
- if( ordered && o.type() == String && strcmp(o.valuestr(), "ascii-proto") == 0 &&
- l.type() == String && r.type() == String ) {
- // note: no negative support yet, as this is just sort of a POC
- x = _stricmp(l.valuestr(), r.valuestr());
- }
- else*/ {
- x = l.woCompare(r, considerFieldName, comparator);
- if (ordered && o.number() < 0)
- x = -x;
- }
- if (x != 0)
- return x;
- }
- return -1;
+ return (rules & ComparisonRules::kIgnoreFieldOrder)
+ ? compareObjects<BSONObjIteratorSorted>(*this, r, idxKey, rules, comparator)
+ : compareObjects<BSONObjIterator>(*this, r, idxKey, rules, comparator);
}
bool BSONObj::isPrefixOf(const BSONObj& otherObj,
diff --git a/src/mongo/bson/bsonobj.h b/src/mongo/bson/bsonobj.h
index a6d7abea953..6898c63a840 100644
--- a/src/mongo/bson/bsonobj.h
+++ b/src/mongo/bson/bsonobj.h
@@ -102,6 +102,12 @@ public:
*/
using DeferredComparison = BSONComparatorInterfaceBase<BSONObj>::DeferredComparison;
+ /**
+ * Set of rules that dictate the behavior of the comparison APIs.
+ */
+ using ComparisonRules = BSONComparatorInterfaceBase<BSONObj>::ComparisonRules;
+ using ComparisonRulesSet = BSONComparatorInterfaceBase<BSONObj>::ComparisonRulesSet;
+
static const char kMinBSONLength = 5;
/** Construct an empty BSONObj -- that is, {}. */
@@ -393,26 +399,22 @@ public:
// See bsonobj_comparator_interface.h for details.
//
- /**wo='well ordered'. fields must be in same order in each object.
- Ordering is with respect to the signs of the elements
- and allows ascending / descending key mixing.
- If comparator is non-null, it is used for all comparisons between two strings.
- @return <0 if l<r. 0 if l==r. >0 if l>r
- */
+ /**
+ * Compares two BSON Objects using the rules specified by 'rules', 'comparator' for
+ * string comparisons, and 'o' for ascending vs. descending ordering.
+ *
+ * Returns <0 if 'this' is less than 'obj'.
+ * >0 if 'this' is greater than 'obj'.
+ * 0 if 'this' is equal to 'obj'.
+ */
int woCompare(const BSONObj& r,
const Ordering& o,
- bool considerFieldName = true,
+ ComparisonRulesSet rules = ComparisonRules::kConsiderFieldName,
const StringData::ComparatorInterface* comparator = nullptr) const;
- /**wo='well ordered'. fields must be in same order in each object.
- Ordering is with respect to the signs of the elements
- and allows ascending / descending key mixing.
- If comparator is non-null, it is used for all comparisons between two strings.
- @return <0 if l<r. 0 if l==r. >0 if l>r
- */
int woCompare(const BSONObj& r,
const BSONObj& ordering = BSONObj(),
- bool considerFieldName = true,
+ ComparisonRulesSet rules = ComparisonRules::kConsiderFieldName,
const StringData::ComparatorInterface* comparator = nullptr) const;
DeferredComparison operator<(const BSONObj& other) const {
diff --git a/src/mongo/bson/bsonobj_comparator.h b/src/mongo/bson/bsonobj_comparator.h
index fac94464e94..a50c5cd3b36 100644
--- a/src/mongo/bson/bsonobj_comparator.h
+++ b/src/mongo/bson/bsonobj_comparator.h
@@ -58,23 +58,22 @@ public:
FieldNamesMode fieldNamesMode,
const StringData::ComparatorInterface* stringComparator)
: _ordering(std::move(ordering)),
- _fieldNamesMode(fieldNamesMode),
- _stringComparator(stringComparator) {}
+ _stringComparator(stringComparator),
+ _rules((fieldNamesMode == FieldNamesMode::kConsider) ? ComparisonRules::kConsiderFieldName
+ : 0) {}
int compare(const BSONObj& lhs, const BSONObj& rhs) const final {
- const bool considerFieldName = (_fieldNamesMode == FieldNamesMode::kConsider);
- return lhs.woCompare(rhs, _ordering, considerFieldName, _stringComparator);
+ return lhs.woCompare(rhs, _ordering, _rules, _stringComparator);
}
void hash_combine(size_t& seed, const BSONObj& toHash) const final {
- const bool considerFieldName = (_fieldNamesMode == FieldNamesMode::kConsider);
- hashCombineBSONObj(seed, toHash, considerFieldName, _stringComparator);
+ hashCombineBSONObj(seed, toHash, _rules, _stringComparator);
}
private:
BSONObj _ordering;
- FieldNamesMode _fieldNamesMode;
const StringData::ComparatorInterface* _stringComparator;
+ ComparisonRulesSet _rules;
};
} // namespace mongo
diff --git a/src/mongo/bson/simple_bsonelement_comparator.h b/src/mongo/bson/simple_bsonelement_comparator.h
index b05204c3f50..8fafbc3659f 100644
--- a/src/mongo/bson/simple_bsonelement_comparator.h
+++ b/src/mongo/bson/simple_bsonelement_comparator.h
@@ -43,12 +43,11 @@ public:
static const SimpleBSONElementComparator kInstance;
int compare(const BSONElement& lhs, const BSONElement& rhs) const final {
- return lhs.woCompare(rhs, true, nullptr);
+ return lhs.woCompare(rhs, ComparisonRules::kConsiderFieldName, nullptr);
}
void hash_combine(size_t& seed, const BSONElement& toHash) const final {
- const bool considerFieldName = true;
- hashCombineBSONElement(seed, toHash, considerFieldName, nullptr);
+ hashCombineBSONElement(seed, toHash, ComparisonRules::kConsiderFieldName, nullptr);
}
};
diff --git a/src/mongo/bson/simple_bsonobj_comparator.h b/src/mongo/bson/simple_bsonobj_comparator.h
index 2e88586746f..2202e221696 100644
--- a/src/mongo/bson/simple_bsonobj_comparator.h
+++ b/src/mongo/bson/simple_bsonobj_comparator.h
@@ -42,12 +42,11 @@ public:
static const SimpleBSONObjComparator kInstance;
int compare(const BSONObj& lhs, const BSONObj& rhs) const final {
- return lhs.woCompare(rhs, BSONObj(), true, nullptr);
+ return lhs.woCompare(rhs, BSONObj(), ComparisonRules::kConsiderFieldName, nullptr);
}
void hash_combine(size_t& seed, const BSONObj& toHash) const final {
- const bool considerFieldName = true;
- hashCombineBSONObj(seed, toHash, considerFieldName, nullptr);
+ hashCombineBSONObj(seed, toHash, ComparisonRules::kConsiderFieldName, nullptr);
}
};
diff --git a/src/mongo/bson/unordered_fields_bsonelement_comparator.h b/src/mongo/bson/unordered_fields_bsonelement_comparator.h
new file mode 100644
index 00000000000..23bbe8ed4f8
--- /dev/null
+++ b/src/mongo/bson/unordered_fields_bsonelement_comparator.h
@@ -0,0 +1,55 @@
+/**
+ * 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 <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.
+ */
+
+#pragma once
+
+#include "mongo/base/string_data_comparator_interface.h"
+#include "mongo/bson/bsonelement_comparator_interface.h"
+
+namespace mongo {
+
+/**
+ * A BSONElement comparator that supports unordered element comparisons for objects. Does not
+ * support using a non-simple string comparator.
+ */
+class UnorderedFieldsBSONElementComparator final : public BSONElement::ComparatorInterface {
+public:
+ static constexpr StringData::ComparatorInterface* kStringComparator = nullptr;
+
+ UnorderedFieldsBSONElementComparator() = default;
+
+ int compare(const BSONElement& lhs, const BSONElement& rhs) const final {
+ return lhs.woCompare(rhs, ComparisonRules::kIgnoreFieldOrder, kStringComparator);
+ }
+
+ void hash_combine(size_t& seed, const BSONElement& toHash) const final {
+ hashCombineBSONElement(seed, toHash, ComparisonRules::kIgnoreFieldOrder, kStringComparator);
+ }
+};
+
+} // namespace mongo
diff --git a/src/mongo/bson/unordered_fields_bsonobj_comparator.h b/src/mongo/bson/unordered_fields_bsonobj_comparator.h
new file mode 100644
index 00000000000..248331407f0
--- /dev/null
+++ b/src/mongo/bson/unordered_fields_bsonobj_comparator.h
@@ -0,0 +1,61 @@
+/**
+ * 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 <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.
+ */
+
+#pragma once
+
+#include "mongo/base/string_data_comparator_interface.h"
+#include "mongo/bson/bsonobj_comparator_interface.h"
+
+namespace mongo {
+
+/**
+ * A BSONObj comparator that supports unordered element comparison. Does not support using a
+ * non-simple string comparator.
+ */
+class UnorderedFieldsBSONObjComparator final : public BSONObj::ComparatorInterface {
+public:
+ static constexpr StringData::ComparatorInterface* kStringComparator = nullptr;
+
+ UnorderedFieldsBSONObjComparator() = default;
+
+ int compare(const BSONObj& lhs, const BSONObj& rhs) const final {
+ return lhs.woCompare(rhs,
+ BSONObj(),
+ ComparisonRules::kIgnoreFieldOrder |
+ ComparisonRules::kConsiderFieldName,
+ kStringComparator);
+ }
+
+ void hash_combine(size_t& seed, const BSONObj& toHash) const final {
+ hashCombineBSONObj(seed,
+ toHash,
+ ComparisonRules::kIgnoreFieldOrder | ComparisonRules::kConsiderFieldName,
+ kStringComparator);
+ }
+};
+} // namespace mongo
diff --git a/src/mongo/db/matcher/SConscript b/src/mongo/db/matcher/SConscript
index e1b4008f208..f3096cfc1c1 100644
--- a/src/mongo/db/matcher/SConscript
+++ b/src/mongo/db/matcher/SConscript
@@ -52,11 +52,13 @@ env.Library(
'schema/expression_internal_schema_all_elem_match_from_index.cpp',
'schema/expression_internal_schema_allowed_properties.cpp',
'schema/expression_internal_schema_cond.cpp',
+ 'schema/expression_internal_schema_eq.cpp',
'schema/expression_internal_schema_fmod.cpp',
'schema/expression_internal_schema_match_array_index.cpp',
'schema/expression_internal_schema_num_array_items.cpp',
'schema/expression_internal_schema_num_properties.cpp',
'schema/expression_internal_schema_object_match.cpp',
+ 'schema/expression_internal_schema_root_doc_eq.cpp',
'schema/expression_internal_schema_str_length.cpp',
'schema/expression_internal_schema_unique_items.cpp',
'schema/expression_internal_schema_xor.cpp',
@@ -94,6 +96,7 @@ env.CppUnitTest(
'schema/expression_internal_schema_all_elem_match_from_index_test.cpp',
'schema/expression_internal_schema_allowed_properties_test.cpp',
'schema/expression_internal_schema_cond_test.cpp',
+ 'schema/expression_internal_schema_eq_test.cpp',
'schema/expression_internal_schema_fmod_test.cpp',
'schema/expression_internal_schema_match_array_index_test.cpp',
'schema/expression_internal_schema_max_items_test.cpp',
@@ -103,6 +106,7 @@ env.CppUnitTest(
'schema/expression_internal_schema_min_length_test.cpp',
'schema/expression_internal_schema_min_properties_test.cpp',
'schema/expression_internal_schema_object_match_test.cpp',
+ 'schema/expression_internal_schema_root_doc_eq_test.cpp',
'schema/expression_internal_schema_unique_items_test.cpp',
'schema/expression_internal_schema_xor_test.cpp',
],
diff --git a/src/mongo/db/matcher/expression.h b/src/mongo/db/matcher/expression.h
index a7e89a59c9e..768c265d3ea 100644
--- a/src/mongo/db/matcher/expression.h
+++ b/src/mongo/db/matcher/expression.h
@@ -104,6 +104,7 @@ public:
INTERNAL_SCHEMA_ALLOWED_PROPERTIES,
INTERNAL_SCHEMA_ALL_ELEM_MATCH_FROM_INDEX,
INTERNAL_SCHEMA_COND,
+ INTERNAL_SCHEMA_EQ,
INTERNAL_SCHEMA_FMOD,
INTERNAL_SCHEMA_MATCH_ARRAY_INDEX,
INTERNAL_SCHEMA_MAX_ITEMS,
@@ -113,6 +114,7 @@ public:
INTERNAL_SCHEMA_MIN_LENGTH,
INTERNAL_SCHEMA_MIN_PROPERTIES,
INTERNAL_SCHEMA_OBJECT_MATCH,
+ INTERNAL_SCHEMA_ROOT_DOC_EQ,
INTERNAL_SCHEMA_TYPE,
INTERNAL_SCHEMA_UNIQUE_ITEMS,
INTERNAL_SCHEMA_XOR,
diff --git a/src/mongo/db/matcher/expression_algo.cpp b/src/mongo/db/matcher/expression_algo.cpp
index 609eef099e6..3bd30006f00 100644
--- a/src/mongo/db/matcher/expression_algo.cpp
+++ b/src/mongo/db/matcher/expression_algo.cpp
@@ -89,9 +89,10 @@ bool _isSubsetOf(const ComparisonMatchExpression* lhs, const ComparisonMatchExpr
return false;
}
- // Either collator may be used by compareElementValues() here, since either the collators are
+ // Either collator may be used by compareElements() here, since either the collators are
// the same or lhsData does not contain string comparison.
- int cmp = compareElementValues(lhsData, rhsData, rhs->getCollator());
+ int cmp = BSONElement::compareElements(
+ lhsData, rhsData, BSONElement::ComparisonRules::kConsiderFieldName, rhs->getCollator());
// Check whether the two expressions are equivalent.
if (lhs->matchType() == rhs->matchType() && cmp == 0) {
diff --git a/src/mongo/db/matcher/expression_leaf.cpp b/src/mongo/db/matcher/expression_leaf.cpp
index 56238d995b4..6a3502566e8 100644
--- a/src/mongo/db/matcher/expression_leaf.cpp
+++ b/src/mongo/db/matcher/expression_leaf.cpp
@@ -122,7 +122,8 @@ bool ComparisonMatchExpression::matchesSingleElement(const BSONElement& e,
}
}
- int x = compareElementValues(e, _rhs, _collator);
+ int x = BSONElement::compareElements(
+ e, _rhs, BSONElement::ComparisonRules::kConsiderFieldName, _collator);
switch (matchType()) {
case LT:
diff --git a/src/mongo/db/matcher/expression_parser.cpp b/src/mongo/db/matcher/expression_parser.cpp
index 50747bebfbd..b1dec5b6a33 100644
--- a/src/mongo/db/matcher/expression_parser.cpp
+++ b/src/mongo/db/matcher/expression_parser.cpp
@@ -50,6 +50,7 @@
#include "mongo/db/matcher/schema/expression_internal_schema_all_elem_match_from_index.h"
#include "mongo/db/matcher/schema/expression_internal_schema_allowed_properties.h"
#include "mongo/db/matcher/schema/expression_internal_schema_cond.h"
+#include "mongo/db/matcher/schema/expression_internal_schema_eq.h"
#include "mongo/db/matcher/schema/expression_internal_schema_fmod.h"
#include "mongo/db/matcher/schema/expression_internal_schema_match_array_index.h"
#include "mongo/db/matcher/schema/expression_internal_schema_max_items.h"
@@ -59,6 +60,7 @@
#include "mongo/db/matcher/schema/expression_internal_schema_min_length.h"
#include "mongo/db/matcher/schema/expression_internal_schema_min_properties.h"
#include "mongo/db/matcher/schema/expression_internal_schema_object_match.h"
+#include "mongo/db/matcher/schema/expression_internal_schema_root_doc_eq.h"
#include "mongo/db/matcher/schema/expression_internal_schema_unique_items.h"
#include "mongo/db/matcher/schema/expression_internal_schema_xor.h"
#include "mongo/db/matcher/schema/json_schema_parser.h"
@@ -458,6 +460,15 @@ StatusWithMatchExpression MatchExpressionParser::_parseSubField(
case PathAcceptingKeyword::INTERNAL_SCHEMA_TYPE: {
return _parseType<InternalSchemaTypeExpression>(name, e);
}
+
+ case PathAcceptingKeyword::INTERNAL_SCHEMA_EQ: {
+ auto eqExpr = stdx::make_unique<InternalSchemaEqMatchExpression>();
+ auto status = eqExpr->init(name, e);
+ if (!status.isOK()) {
+ return status;
+ }
+ return {std::move(eqExpr)};
+ }
}
return {Status(ErrorCodes::BadValue,
@@ -639,6 +650,22 @@ StatusWithMatchExpression MatchExpressionParser::_parse(
auto alwaysTrueExpr = stdx::make_unique<AlwaysTrueMatchExpression>();
root->add(alwaysTrueExpr.release());
+ } else if (mongoutils::str::equals("_internalSchemaRootDocEq", rest)) {
+ if (!topLevel) {
+ return {Status(ErrorCodes::FailedToParse,
+ str::stream() << InternalSchemaRootDocEqMatchExpression::kName
+ << " must be at the top level")};
+ }
+
+ if (e.type() != BSONType::Object) {
+ return {Status(ErrorCodes::TypeMismatch,
+ str::stream() << InternalSchemaRootDocEqMatchExpression::kName
+ << " must be an object, found type "
+ << e.type())};
+ }
+ auto rootDocEq = stdx::make_unique<InternalSchemaRootDocEqMatchExpression>();
+ rootDocEq->init(e.embeddedObject());
+ root->add(rootDocEq.release());
} else {
return {Status(ErrorCodes::BadValue,
mongoutils::str::stream() << "unknown top level operator: "
@@ -1704,12 +1731,12 @@ MONGO_INITIALIZER(MatchExpressionParser)(InitializerContext* context) {
// TODO: SERVER-19565 Add $eq after auditing callers.
{"_internalSchemaAllElemMatchFromIndex",
PathAcceptingKeyword::INTERNAL_SCHEMA_ALL_ELEM_MATCH_FROM_INDEX},
+ {"_internalSchemaEq", PathAcceptingKeyword::INTERNAL_SCHEMA_EQ},
{"_internalSchemaFmod", PathAcceptingKeyword::INTERNAL_SCHEMA_FMOD},
{"_internalSchemaMatchArrayIndex",
PathAcceptingKeyword::INTERNAL_SCHEMA_MATCH_ARRAY_INDEX},
{"_internalSchemaMaxItems", PathAcceptingKeyword::INTERNAL_SCHEMA_MAX_ITEMS},
{"_internalSchemaMaxLength", PathAcceptingKeyword::INTERNAL_SCHEMA_MAX_LENGTH},
- {"_internalSchemaMaxLength", PathAcceptingKeyword::INTERNAL_SCHEMA_MAX_LENGTH},
{"_internalSchemaMinItems", PathAcceptingKeyword::INTERNAL_SCHEMA_MIN_ITEMS},
{"_internalSchemaMinItems", PathAcceptingKeyword::INTERNAL_SCHEMA_MIN_ITEMS},
{"_internalSchemaMinLength", PathAcceptingKeyword::INTERNAL_SCHEMA_MIN_LENGTH},
diff --git a/src/mongo/db/matcher/expression_parser.h b/src/mongo/db/matcher/expression_parser.h
index f69eafbff12..095bd79212d 100644
--- a/src/mongo/db/matcher/expression_parser.h
+++ b/src/mongo/db/matcher/expression_parser.h
@@ -61,6 +61,7 @@ enum class PathAcceptingKeyword {
GREATER_THAN,
GREATER_THAN_OR_EQUAL,
INTERNAL_SCHEMA_ALL_ELEM_MATCH_FROM_INDEX,
+ INTERNAL_SCHEMA_EQ,
INTERNAL_SCHEMA_FMOD,
INTERNAL_SCHEMA_MATCH_ARRAY_INDEX,
INTERNAL_SCHEMA_MAX_ITEMS,
diff --git a/src/mongo/db/matcher/expression_serialization_test.cpp b/src/mongo/db/matcher/expression_serialization_test.cpp
index 4ae5a56848d..1971087ed30 100644
--- a/src/mongo/db/matcher/expression_serialization_test.cpp
+++ b/src/mongo/db/matcher/expression_serialization_test.cpp
@@ -1747,5 +1747,21 @@ TEST(SerializeInternalSchema,
}})"));
ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
}
+
+TEST(SerializeInternalSchema, ExpressionInternalSchemaEqSerializesCorrectly) {
+ Matcher original(fromjson("{x: {$_internalSchemaEq: {y: 1}}}"), kSimpleCollator);
+ Matcher reserialized(serialize(original.getMatchExpression()), kSimpleCollator);
+
+ ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{x: {$_internalSchemaEq: {y: 1}}}"));
+ ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+}
+
+TEST(SerializeInternalSchema, ExpressionInternalSchemaRootDocEqSerializesCorrectly) {
+ Matcher original(fromjson("{$_internalSchemaRootDocEq: {y: 1}}"), kSimpleCollator);
+ Matcher reserialized(serialize(original.getMatchExpression()), kSimpleCollator);
+
+ ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{$_internalSchemaRootDocEq: {y: 1}}"));
+ ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression()));
+}
} // namespace
} // namespace mongo
diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_eq.cpp b/src/mongo/db/matcher/schema/expression_internal_schema_eq.cpp
new file mode 100644
index 00000000000..20de5d876e9
--- /dev/null
+++ b/src/mongo/db/matcher/schema/expression_internal_schema_eq.cpp
@@ -0,0 +1,89 @@
+/**
+ * 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 <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.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/db/matcher/schema/expression_internal_schema_eq.h"
+
+#include "mongo/bson/bsonmisc.h"
+#include "mongo/bson/bsonobj.h"
+#include "mongo/bson/bsonobjbuilder.h"
+
+namespace mongo {
+
+constexpr StringData InternalSchemaEqMatchExpression::kName;
+
+Status InternalSchemaEqMatchExpression::init(StringData path, BSONElement rhs) {
+ invariant(rhs);
+ _rhsElem = rhs;
+ return setPath(path);
+}
+
+bool InternalSchemaEqMatchExpression::matchesSingleElement(const BSONElement& elem,
+ MatchDetails* details) const {
+ return _eltCmp.evaluate(_rhsElem == elem);
+}
+
+void InternalSchemaEqMatchExpression::debugString(StringBuilder& debug, int level) const {
+ _debugAddSpace(debug, level);
+ debug << path() << " " << kName << " " << _rhsElem.toString(false);
+
+ auto td = getTag();
+ if (td) {
+ debug << " ";
+ td->debugString(&debug);
+ }
+
+ debug << "\n";
+}
+
+void InternalSchemaEqMatchExpression::serialize(BSONObjBuilder* out) const {
+ BSONObjBuilder eqObj(out->subobjStart(path()));
+ eqObj.appendAs(_rhsElem, kName);
+ eqObj.doneFast();
+}
+
+bool InternalSchemaEqMatchExpression::equivalent(const MatchExpression* other) const {
+ if (other->matchType() != matchType()) {
+ return false;
+ }
+
+ auto realOther = static_cast<const InternalSchemaEqMatchExpression*>(other);
+ return path() == realOther->path() && _eltCmp.evaluate(_rhsElem == realOther->_rhsElem);
+}
+
+std::unique_ptr<MatchExpression> InternalSchemaEqMatchExpression::shallowClone() const {
+ auto clone = stdx::make_unique<InternalSchemaEqMatchExpression>();
+ invariantOK(clone->init(path(), _rhsElem));
+ if (getTag()) {
+ clone->setTag(getTag()->clone());
+ }
+ return std::move(clone);
+}
+
+} // namespace mongo
diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_eq.h b/src/mongo/db/matcher/schema/expression_internal_schema_eq.h
new file mode 100644
index 00000000000..7905671c7a8
--- /dev/null
+++ b/src/mongo/db/matcher/schema/expression_internal_schema_eq.h
@@ -0,0 +1,82 @@
+/**
+ * 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 <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.
+ */
+
+#pragma once
+
+#include "mongo/bson/unordered_fields_bsonelement_comparator.h"
+#include "mongo/db/matcher/expression_leaf.h"
+
+namespace mongo {
+
+/**
+ * MatchExpression for $_internalSchemaEq, which behaves similar to $eq except:
+ *
+ * - leaf arrays are not traversed.
+ * - comparisons between objects do not consider field order.
+ * - null element values only match the literal null, and not missing or undefined values.
+ * - always uses simple string comparison semantics, even if the query has a non-simple collation.
+ */
+class InternalSchemaEqMatchExpression final : public LeafMatchExpression {
+public:
+ static constexpr StringData kName = "$_internalSchemaEq"_sd;
+
+ InternalSchemaEqMatchExpression() : LeafMatchExpression(MatchType::INTERNAL_SCHEMA_EQ) {}
+
+ Status init(StringData path, BSONElement rhs);
+
+ std::unique_ptr<MatchExpression> shallowClone() const final;
+
+ bool matchesSingleElement(const BSONElement&, MatchDetails*) const final;
+
+ void debugString(StringBuilder& debug, int level) const final;
+
+ void serialize(BSONObjBuilder* out) const final;
+
+ bool equivalent(const MatchExpression* other) const final;
+
+ size_t numChildren() const final {
+ return 0;
+ }
+
+ MatchExpression* getChild(size_t i) const final {
+ MONGO_UNREACHABLE;
+ }
+
+ std::vector<MatchExpression*>* getChildVector() final {
+ return nullptr;
+ }
+
+ bool shouldExpandLeafArray() const final {
+ return false;
+ }
+
+private:
+ UnorderedFieldsBSONElementComparator _eltCmp;
+ BSONElement _rhsElem;
+};
+} // namespace mongo
diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_eq_test.cpp b/src/mongo/db/matcher/schema/expression_internal_schema_eq_test.cpp
new file mode 100644
index 00000000000..f82f0b34bf0
--- /dev/null
+++ b/src/mongo/db/matcher/schema/expression_internal_schema_eq_test.cpp
@@ -0,0 +1,145 @@
+/**
+ * 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 <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.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/bson/json.h"
+#include "mongo/db/matcher/matcher.h"
+#include "mongo/db/matcher/schema/expression_internal_schema_eq.h"
+#include "mongo/unittest/unittest.h"
+
+namespace mongo {
+namespace {
+
+TEST(InternalSchemaEqMatchExpression, CorrectlyMatchesScalarElements) {
+ BSONObj numberOperand = BSON("a" << 5);
+
+ InternalSchemaEqMatchExpression eq;
+ ASSERT_OK(eq.init("a", numberOperand["a"]));
+ ASSERT_TRUE(eq.matchesBSON(BSON("a" << 5.0)));
+ ASSERT_FALSE(eq.matchesBSON(BSON("a" << 6)));
+
+ BSONObj stringOperand = BSON("a"
+ << "str");
+
+ ASSERT_OK(eq.init("a", stringOperand["a"]));
+ ASSERT_TRUE(eq.matchesBSON(BSON("a"
+ << "str")));
+ ASSERT_FALSE(eq.matchesBSON(BSON("a"
+ << "string")));
+}
+
+TEST(InternalSchemaEqMatchExpression, CorrectlyMatchesArrayElement) {
+ BSONObj operand = BSON("a" << BSON_ARRAY("b" << 5));
+
+ InternalSchemaEqMatchExpression eq;
+ ASSERT_OK(eq.init("a", operand["a"]));
+ ASSERT_TRUE(eq.matchesBSON(BSON("a" << BSON_ARRAY("b" << 5))));
+ ASSERT_FALSE(eq.matchesBSON(BSON("a" << BSON_ARRAY(5 << "b"))));
+ ASSERT_FALSE(eq.matchesBSON(BSON("a" << BSON_ARRAY("b" << 5 << 5))));
+ ASSERT_FALSE(eq.matchesBSON(BSON("a" << BSON_ARRAY("b" << 6))));
+}
+
+TEST(InternalSchemaEqMatchExpression, CorrectlyMatchesNullElement) {
+ BSONObj operand = BSON("a" << BSONNULL);
+
+ InternalSchemaEqMatchExpression eq;
+ ASSERT_OK(eq.init("a", operand["a"]));
+ ASSERT_TRUE(eq.matchesBSON(BSON("a" << BSONNULL)));
+ ASSERT_FALSE(eq.matchesBSON(BSON("a" << 4)));
+}
+
+TEST(InternalSchemaEqMatchExpression, NullElementDoesNotMatchMissing) {
+ BSONObj operand = BSON("a" << BSONNULL);
+
+ InternalSchemaEqMatchExpression eq;
+ ASSERT_OK(eq.init("a", operand["a"]));
+ ASSERT_FALSE(eq.matchesBSON(BSONObj()));
+ ASSERT_FALSE(eq.matchesBSON(BSON("b" << 4)));
+}
+
+TEST(InternalSchemaEqMatchExpression, NullElementDoesNotMatchUndefinedOrMissing) {
+ BSONObj operand = BSON("a" << BSONNULL);
+
+ InternalSchemaEqMatchExpression eq;
+ ASSERT_OK(eq.init("a", operand["a"]));
+ ASSERT_FALSE(eq.matchesBSON(BSONObj()));
+ ASSERT_FALSE(eq.matchesBSON(fromjson("{a: undefined}")));
+}
+
+TEST(InternalSchemaEqMatchExpression, DoesNotTraverseLeafArrays) {
+ BSONObj operand = BSON("a" << 5);
+
+ InternalSchemaEqMatchExpression eq;
+ ASSERT_OK(eq.init("a", operand["a"]));
+ ASSERT_TRUE(eq.matchesBSON(BSON("a" << 5.0)));
+ ASSERT_FALSE(eq.matchesBSON(BSON("a" << BSON_ARRAY(5))));
+}
+
+TEST(InternalSchemaEqMatchExpression, MatchesObjectsIndependentOfFieldOrder) {
+ BSONObj operand = fromjson("{a: {b: 1, c: {d: 2, e: 3}}}");
+
+ InternalSchemaEqMatchExpression eq;
+ ASSERT_OK(eq.init("a", operand["a"]));
+ ASSERT_TRUE(eq.matchesBSON(fromjson("{a: {b: 1, c: {d: 2, e: 3}}}")));
+ ASSERT_TRUE(eq.matchesBSON(fromjson("{a: {c: {e: 3, d: 2}, b: 1}}")));
+ ASSERT_FALSE(eq.matchesBSON(fromjson("{a: {b: 1, c: {d: 2}, e: 3}}")));
+ ASSERT_FALSE(eq.matchesBSON(fromjson("{a: {b: 2, c: {d: 2}}}")));
+ ASSERT_FALSE(eq.matchesBSON(fromjson("{a: {b: 1}}")));
+}
+
+TEST(InternalSchemaEqMatchExpression, EquivalentReturnsCorrectResults) {
+ auto query = fromjson(R"(
+ {a: {$_internalSchemaEq: {
+ b: {c: 1, d: 1}
+ }}})");
+ Matcher eqExpr(query, nullptr);
+
+ query = fromjson(R"(
+ {a: {$_internalSchemaEq: {
+ b: {d: 1, c: 1}
+ }}})");
+ Matcher eqExprEq(query, nullptr);
+ ASSERT_TRUE(eqExpr.getMatchExpression()->equivalent(eqExprEq.getMatchExpression()));
+
+ query = fromjson(R"(
+ {a: {$_internalSchemaEq: {
+ b: {d: 1}
+ }}})");
+ Matcher eqExprNotEq(query, nullptr);
+ ASSERT_FALSE(eqExpr.getMatchExpression()->equivalent(eqExprNotEq.getMatchExpression()));
+}
+
+TEST(InternalSchemaEqMatchExpression, EquivalentToClone) {
+ auto query = fromjson("{a: {$_internalSchemaEq: {a:1, b: {c: 1, d: [1]}}}}");
+ Matcher rootDocEq(query, nullptr);
+ auto clone = rootDocEq.getMatchExpression()->shallowClone();
+ ASSERT_TRUE(rootDocEq.getMatchExpression()->equivalent(clone.get()));
+}
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_root_doc_eq.cpp b/src/mongo/db/matcher/schema/expression_internal_schema_root_doc_eq.cpp
new file mode 100644
index 00000000000..c0a5bbf8922
--- /dev/null
+++ b/src/mongo/db/matcher/schema/expression_internal_schema_root_doc_eq.cpp
@@ -0,0 +1,79 @@
+/**
+ * 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 <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.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/db/matcher/schema/expression_internal_schema_root_doc_eq.h"
+
+namespace mongo {
+
+constexpr StringData InternalSchemaRootDocEqMatchExpression::kName;
+
+bool InternalSchemaRootDocEqMatchExpression::matches(const MatchableDocument* doc,
+ MatchDetails* details) const {
+ return _objCmp.evaluate(doc->toBSON() == _rhsObj);
+}
+
+void InternalSchemaRootDocEqMatchExpression::debugString(StringBuilder& debug, int level) const {
+ _debugAddSpace(debug, level);
+ debug << kName << " " << _rhsObj.toString();
+
+ auto td = getTag();
+ if (td) {
+ debug << " ";
+ td->debugString(&debug);
+ }
+
+ debug << "\n";
+}
+
+void InternalSchemaRootDocEqMatchExpression::serialize(BSONObjBuilder* out) const {
+ BSONObjBuilder subObj(out->subobjStart(kName));
+ subObj.appendElements(_rhsObj);
+ subObj.doneFast();
+}
+
+bool InternalSchemaRootDocEqMatchExpression::equivalent(const MatchExpression* other) const {
+ if (matchType() != other->matchType()) {
+ return false;
+ }
+
+ auto realOther = static_cast<const InternalSchemaRootDocEqMatchExpression*>(other);
+ return _objCmp.evaluate(_rhsObj == realOther->_rhsObj);
+}
+
+std::unique_ptr<MatchExpression> InternalSchemaRootDocEqMatchExpression::shallowClone() const {
+ auto clone = stdx::make_unique<InternalSchemaRootDocEqMatchExpression>();
+ clone->init(_rhsObj);
+ if (getTag()) {
+ clone->setTag(getTag()->clone());
+ }
+ return std::move(clone);
+}
+
+} // namespace mongo
diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_root_doc_eq.h b/src/mongo/db/matcher/schema/expression_internal_schema_root_doc_eq.h
new file mode 100644
index 00000000000..0255f39236e
--- /dev/null
+++ b/src/mongo/db/matcher/schema/expression_internal_schema_root_doc_eq.h
@@ -0,0 +1,94 @@
+/**
+ * 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 <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.
+ */
+
+#pragma once
+
+#include "mongo/bson/unordered_fields_bsonobj_comparator.h"
+#include "mongo/db/matcher/expression.h"
+
+namespace mongo {
+
+/**
+ * MatchExpression for $_internalSchemaRootDocEq, which matches the root document with the
+ * following equality semantics:
+ *
+ * - comparisons between objects do not consider field order.
+ * - null element values only match the literal null, and not missing or undefined values.
+ * - always uses simple string comparison semantics, even if the query has a non-simple collation.
+ */
+class InternalSchemaRootDocEqMatchExpression final : public MatchExpression {
+public:
+ static constexpr StringData kName = "$_internalSchemaRootDocEq"_sd;
+
+ InternalSchemaRootDocEqMatchExpression()
+ : MatchExpression(MatchExpression::INTERNAL_SCHEMA_ROOT_DOC_EQ) {}
+
+ void init(BSONObj obj) {
+ _rhsObj = std::move(obj);
+ }
+
+ bool matches(const MatchableDocument* doc, MatchDetails* details = nullptr) const final;
+
+ /**
+ * This expression should only be used to match full documents, not objects within an array
+ * in the case of $elemMatch.
+ */
+ bool matchesSingleElement(const BSONElement& elem,
+ MatchDetails* details = nullptr) const final {
+ MONGO_UNREACHABLE;
+ }
+
+ std::unique_ptr<MatchExpression> shallowClone() const final;
+
+ void debugString(StringBuilder& debug, int level = 0) const final;
+
+ void serialize(BSONObjBuilder* out) const final;
+
+ bool equivalent(const MatchExpression* other) const final;
+
+ size_t numChildren() const final {
+ return 0;
+ }
+
+ MatchExpression* getChild(size_t i) const final {
+ MONGO_UNREACHABLE;
+ }
+
+ std::vector<MatchExpression*>* getChildVector() final {
+ return nullptr;
+ }
+
+ MatchCategory getCategory() const final {
+ return MatchCategory::kOther;
+ }
+
+private:
+ UnorderedFieldsBSONObjComparator _objCmp;
+ BSONObj _rhsObj;
+};
+} // namespace mongo
diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_root_doc_eq_test.cpp b/src/mongo/db/matcher/schema/expression_internal_schema_root_doc_eq_test.cpp
new file mode 100644
index 00000000000..4b46779b173
--- /dev/null
+++ b/src/mongo/db/matcher/schema/expression_internal_schema_root_doc_eq_test.cpp
@@ -0,0 +1,119 @@
+/**
+ * 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 <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.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/bson/json.h"
+#include "mongo/db/matcher/matcher.h"
+#include "mongo/db/matcher/schema/expression_internal_schema_root_doc_eq.h"
+#include "mongo/unittest/unittest.h"
+
+namespace mongo {
+namespace {
+
+TEST(InternalSchemaRootDocEqMatchExpression, MatchesObject) {
+ InternalSchemaRootDocEqMatchExpression rootDocEq;
+ rootDocEq.init(BSON("a" << 1 << "b"
+ << "string"));
+ ASSERT_TRUE(rootDocEq.matchesBSON(BSON("a" << 1 << "b"
+ << "string")));
+ ASSERT_FALSE(rootDocEq.matchesBSON(BSON("a" << 2 << "b"
+ << "string")));
+}
+
+TEST(InternalSchemaRootDocEqMatchExpression, MatchesNestedObject) {
+ InternalSchemaRootDocEqMatchExpression rootDocEq;
+ rootDocEq.init(BSON("a" << 1 << "b" << BSON("c" << 1)));
+ ASSERT_TRUE(rootDocEq.matchesBSON(BSON("a" << 1 << "b" << BSON("c" << 1))));
+ ASSERT_FALSE(rootDocEq.matchesBSON(BSON("a" << 1 << "b" << BSON("c" << 2))));
+}
+
+TEST(InternalSchemaRootDocEqMatchExpression, MatchesObjectIgnoresElementOrder) {
+ InternalSchemaRootDocEqMatchExpression rootDocEq;
+ rootDocEq.init(BSON("a" << 1 << "b" << BSON("c" << 1)));
+ ASSERT_TRUE(rootDocEq.matchesBSON(BSON("b" << BSON("c" << 1) << "a" << 1)));
+}
+
+TEST(InternalSchemaRootDocEqMatchExpression, MatchesNestedObjectIgnoresElementOrder) {
+ InternalSchemaRootDocEqMatchExpression rootDocEq;
+ rootDocEq.init(BSON("a" << BSON("b" << 1 << "c" << 1)));
+ ASSERT_TRUE(rootDocEq.matchesBSON(BSON("a" << BSON("c" << 1 << "b" << 1))));
+}
+
+TEST(InternalSchemaRootDocEqMatchExpression, MatchesEmptyObject) {
+ InternalSchemaRootDocEqMatchExpression rootDocEq;
+ rootDocEq.init(BSONObj());
+ ASSERT_TRUE(rootDocEq.matchesBSON(BSONObj()));
+}
+
+TEST(InternalSchemaRootDocEqMatchExpression, MatchesNestedArray) {
+ InternalSchemaRootDocEqMatchExpression rootDocEq;
+ rootDocEq.init(BSON("a" << BSON_ARRAY(1 << 2 << 3)));
+ ASSERT_TRUE(rootDocEq.matchesBSON(BSON("a" << BSON_ARRAY(1 << 2 << 3))));
+ ASSERT_FALSE(rootDocEq.matchesBSON(BSON("a" << BSON_ARRAY(1 << 3 << 2))));
+}
+
+TEST(InternalSchemaRootDocEqMatchExpression, MatchesObjectWithNullElement) {
+ InternalSchemaRootDocEqMatchExpression rootDocEq;
+ rootDocEq.init(fromjson("{a: null}"));
+ ASSERT_TRUE(rootDocEq.matchesBSON(fromjson("{a: null}")));
+ ASSERT_FALSE(rootDocEq.matchesBSON(fromjson("{a: 1}")));
+ ASSERT_FALSE(rootDocEq.matchesBSON(fromjson("{}")));
+ ASSERT_FALSE(rootDocEq.matchesBSON(fromjson("{a: undefined}")));
+}
+
+TEST(InternalSchemaRootDocEqMatchExpression, EquivalentReturnsCorrectResults) {
+ auto query = fromjson(R"(
+ {$_internalSchemaRootDocEq: {
+ b: 1, c: 1
+ }})");
+ Matcher rootDocEq(query, nullptr);
+
+ query = fromjson(R"(
+ {$_internalSchemaRootDocEq: {
+ c: 1, b: 1
+ }})");
+ Matcher exprEq(query, nullptr);
+ ASSERT_TRUE(rootDocEq.getMatchExpression()->equivalent(exprEq.getMatchExpression()));
+
+ query = fromjson(R"(
+ {$_internalSchemaRootDocEq: {
+ c: 1
+ }})");
+ Matcher exprNotEq(query, nullptr);
+ ASSERT_FALSE(rootDocEq.getMatchExpression()->equivalent(exprNotEq.getMatchExpression()));
+}
+
+TEST(InternalSchemaRootDocEqMatchExpression, EquivalentToClone) {
+ auto query = fromjson("{$_internalSchemaRootDocEq: {a:1, b: {c: 1, d: [1]}}}");
+ Matcher rootDocEq(query, nullptr);
+ auto clone = rootDocEq.getMatchExpression()->shallowClone();
+ ASSERT_TRUE(rootDocEq.getMatchExpression()->equivalent(clone.get()));
+}
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/db/matcher/schema/expression_parser_schema_test.cpp b/src/mongo/db/matcher/schema/expression_parser_schema_test.cpp
index 4b6c8009df6..154e395a125 100644
--- a/src/mongo/db/matcher/schema/expression_parser_schema_test.cpp
+++ b/src/mongo/db/matcher/schema/expression_parser_schema_test.cpp
@@ -808,5 +808,47 @@ TEST(MatchExpressionParserSchemaTest, AllowedPropertiesAcceptsEmptyOtherwiseExpr
ASSERT_TRUE(allowedProperties.getValue()->matchesBSON(BSONObj()));
}
+TEST(MatchExpressionParserSchemaTest, EqParsesSuccessfully) {
+ auto query = fromjson("{foo: {$_internalSchemaEq: 1}}");
+ auto eqExpr = MatchExpressionParser::parse(query, kSimpleCollator);
+ ASSERT_OK(eqExpr.getStatus());
+
+ ASSERT_TRUE(eqExpr.getValue()->matchesBSON(fromjson("{foo: 1}")));
+ ASSERT_FALSE(eqExpr.getValue()->matchesBSON(fromjson("{foo: [1]}")));
+ ASSERT_FALSE(eqExpr.getValue()->matchesBSON(fromjson("{not_foo: 1}")));
+}
+
+TEST(MatchExpressionParserSchemaTest, RootDocEqFailsToParseNonObjects) {
+ auto query = fromjson("{$_internalSchemaRootDocEq: 1}");
+ auto rootDocEq = MatchExpressionParser::parse(query, kSimpleCollator);
+ ASSERT_EQ(rootDocEq.getStatus(), ErrorCodes::TypeMismatch);
+
+ query = fromjson("{$_internalSchemaRootDocEq: [{}]}");
+ rootDocEq = MatchExpressionParser::parse(query, kSimpleCollator);
+ ASSERT_EQ(rootDocEq.getStatus(), ErrorCodes::TypeMismatch);
+}
+
+TEST(MatchExpressionParserSchemaTest, RootDocEqMustBeTopLevel) {
+ auto query = fromjson("{a: {$_internalSchemaRootDocEq: 1}}");
+ auto rootDocEq = MatchExpressionParser::parse(query, kSimpleCollator);
+ ASSERT_EQ(rootDocEq.getStatus(), ErrorCodes::BadValue);
+
+ query = fromjson("{$or: [{a: 1}, {$_internalSchemaRootDocEq: 1}]}");
+ rootDocEq = MatchExpressionParser::parse(query, kSimpleCollator);
+ ASSERT_EQ(rootDocEq.getStatus(), ErrorCodes::FailedToParse);
+
+ query = fromjson("{a: {$elemMatch: {$_internalSchemaRootDocEq: 1}}}");
+ rootDocEq = MatchExpressionParser::parse(query, kSimpleCollator);
+ ASSERT_EQ(rootDocEq.getStatus(), ErrorCodes::BadValue);
+}
+
+TEST(MatchExpressionParserSchemaTest, RootDocEqParsesSuccessfully) {
+ auto query = fromjson("{$_internalSchemaRootDocEq: {}}");
+ auto eqExpr = MatchExpressionParser::parse(query, kSimpleCollator);
+ ASSERT_OK(eqExpr.getStatus());
+
+ ASSERT_TRUE(eqExpr.getValue()->matchesBSON(fromjson("{}")));
+}
+
} // namespace
} // namespace mongo
diff --git a/src/mongo/db/matcher/schema/json_schema_parser.cpp b/src/mongo/db/matcher/schema/json_schema_parser.cpp
index e0b4d78a168..8c107f0b759 100644
--- a/src/mongo/db/matcher/schema/json_schema_parser.cpp
+++ b/src/mongo/db/matcher/schema/json_schema_parser.cpp
@@ -35,12 +35,14 @@
#include <boost/container/flat_set.hpp>
#include "mongo/bson/bsontypes.h"
+#include "mongo/bson/unordered_fields_bsonelement_comparator.h"
#include "mongo/db/matcher/expression_always_boolean.h"
#include "mongo/db/matcher/expression_parser.h"
#include "mongo/db/matcher/matcher_type_set.h"
#include "mongo/db/matcher/schema/expression_internal_schema_all_elem_match_from_index.h"
#include "mongo/db/matcher/schema/expression_internal_schema_allowed_properties.h"
#include "mongo/db/matcher/schema/expression_internal_schema_cond.h"
+#include "mongo/db/matcher/schema/expression_internal_schema_eq.h"
#include "mongo/db/matcher/schema/expression_internal_schema_fmod.h"
#include "mongo/db/matcher/schema/expression_internal_schema_match_array_index.h"
#include "mongo/db/matcher/schema/expression_internal_schema_max_items.h"
@@ -50,6 +52,7 @@
#include "mongo/db/matcher/schema/expression_internal_schema_min_length.h"
#include "mongo/db/matcher/schema/expression_internal_schema_min_properties.h"
#include "mongo/db/matcher/schema/expression_internal_schema_object_match.h"
+#include "mongo/db/matcher/schema/expression_internal_schema_root_doc_eq.h"
#include "mongo/db/matcher/schema/expression_internal_schema_unique_items.h"
#include "mongo/db/matcher/schema/expression_internal_schema_xor.h"
#include "mongo/logger/log_component_settings.h"
@@ -70,6 +73,7 @@ constexpr StringData kSchemaAllOfKeyword = "allOf"_sd;
constexpr StringData kSchemaAnyOfKeyword = "anyOf"_sd;
constexpr StringData kSchemaDependenciesKeyword = "dependencies"_sd;
constexpr StringData kSchemaDescriptionKeyword = "description"_sd;
+constexpr StringData kSchemaEnumKeyword = "enum"_sd;
constexpr StringData kSchemaExclusiveMaximumKeyword = "exclusiveMaximum"_sd;
constexpr StringData kSchemaExclusiveMinimumKeyword = "exclusiveMinimum"_sd;
constexpr StringData kSchemaItemsKeyword = "items"_sd;
@@ -410,6 +414,60 @@ StatusWithMatchExpression parseLogicalKeyword(StringData path,
return {std::move(listOfExpr)};
}
+StatusWithMatchExpression parseEnum(StringData path, BSONElement enumElement) {
+ if (enumElement.type() != BSONType::Array) {
+ return {ErrorCodes::TypeMismatch,
+ str::stream() << "$jsonSchema keyword '" << kSchemaEnumKeyword
+ << "' must be an array, but found an element of type "
+ << enumElement.type()};
+ }
+
+ auto enumArray = enumElement.embeddedObject();
+ if (enumArray.isEmpty()) {
+ return {ErrorCodes::FailedToParse,
+ str::stream() << "$jsonSchema keyword '" << kSchemaEnumKeyword
+ << "' cannot be an empty array"};
+ }
+
+ auto orExpr = stdx::make_unique<OrMatchExpression>();
+ UnorderedFieldsBSONElementComparator eltComp;
+ BSONEltSet eqSet = eltComp.makeBSONEltSet();
+ for (auto&& arrayElem : enumArray) {
+ auto insertStatus = eqSet.insert(arrayElem);
+ if (!insertStatus.second) {
+ return {ErrorCodes::FailedToParse,
+ str::stream() << "$jsonSchema keyword '" << kSchemaEnumKeyword
+ << "' array cannot contain duplicate values."};
+ }
+
+ // 'enum' at the top-level implies a literal object match on the root document.
+ if (path.empty()) {
+ // Top-level non-object enum values can be safely ignored, since MongoDB only stores
+ // objects, not scalars or arrays.
+ if (arrayElem.type() == BSONType::Object) {
+ auto rootDocEq = stdx::make_unique<InternalSchemaRootDocEqMatchExpression>();
+ rootDocEq->init(arrayElem.embeddedObject());
+ orExpr->add(rootDocEq.release());
+ }
+ } else {
+ auto eqExpr = stdx::make_unique<InternalSchemaEqMatchExpression>();
+ auto initStatus = eqExpr->init(path, arrayElem);
+ if (!initStatus.isOK()) {
+ return initStatus;
+ }
+
+ orExpr->add(eqExpr.release());
+ }
+ }
+
+ // Make sure that the OR expression has at least 1 child.
+ if (orExpr->numChildren() == 0) {
+ return {stdx::make_unique<AlwaysFalseMatchExpression>()};
+ }
+
+ return {std::move(orExpr)};
+}
+
/**
* Given a BSON element corresponding to the $jsonSchema "required" keyword, returns the set of
* required property names. If the contents of the "required" keyword are invalid, returns a non-OK
@@ -1079,6 +1137,14 @@ Status translateLogicalKeywords(StringMap<BSONElement>* keywordMap,
andExpr->add(notMatchExpr.release());
}
+ if (auto enumElt = keywordMap->get(kSchemaEnumKeyword)) {
+ auto enumExpr = parseEnum(path, enumElt);
+ if (!enumExpr.isOK()) {
+ return enumExpr.getStatus();
+ }
+ andExpr->add(enumExpr.getValue().release());
+ }
+
return Status::OK();
}
@@ -1361,6 +1427,7 @@ StatusWithMatchExpression _parse(StringData path, BSONObj schema, bool ignoreUnk
{kSchemaBsonTypeKeyword, {}},
{kSchemaDependenciesKeyword, {}},
{kSchemaDescriptionKeyword, {}},
+ {kSchemaEnumKeyword, {}},
{kSchemaExclusiveMaximumKeyword, {}},
{kSchemaExclusiveMinimumKeyword, {}},
{kSchemaItemsKeyword, {}},
diff --git a/src/mongo/db/matcher/schema/json_schema_parser_test.cpp b/src/mongo/db/matcher/schema/json_schema_parser_test.cpp
index c43f9c5c1b4..87ee0483c34 100644
--- a/src/mongo/db/matcher/schema/json_schema_parser_test.cpp
+++ b/src/mongo/db/matcher/schema/json_schema_parser_test.cpp
@@ -2292,5 +2292,67 @@ TEST(JSONSchemaParserTest, AdditionalItemsGeneratesEmptyExpressionIfItemsAnObjec
}]
}]})"));
}
+
+TEST(JSONSchemaParserTest, FailsToParseIfEnumIsNotAnArray) {
+ BSONObj schema = fromjson("{properties: {foo: {enum: 'foo'}}}");
+ auto result = JSONSchemaParser::parse(schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::TypeMismatch);
+}
+
+TEST(JSONSchemaParserTest, FailsToParseEnumIfArrayIsEmpty) {
+ BSONObj schema = fromjson("{properties: {foo: {enum: []}}}");
+ auto result = JSONSchemaParser::parse(schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
+}
+
+TEST(JSONSchemaParserTest, FailsToParseEnumIfArrayContainsDuplicateValue) {
+ BSONObj schema = fromjson("{properties: {foo: {enum: [1, 2, 1]}}}");
+ auto result = JSONSchemaParser::parse(schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
+
+ schema = fromjson("{properties: {foo: {enum: [{a: 1, b: 1}, {b: 1, a: 1}]}}}");
+ result = JSONSchemaParser::parse(schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
+}
+
+TEST(JSONSchemaParserTest, EnumTranslatesCorrectly) {
+ BSONObj schema = fromjson("{properties: {foo: {enum: [1, '2', [3]]}}}");
+ auto result = JSONSchemaParser::parse(schema);
+ ASSERT_OK(result.getStatus());
+ ASSERT_SERIALIZES_TO(result.getValue().get(), fromjson(R"({
+ $and: [{
+ $and: [{
+ $or: [
+ {$nor: [{foo: {$exists: true}}]},
+ {
+ $and: [{
+ $or: [
+ {foo: {$_internalSchemaEq: 1}},
+ {foo: {$_internalSchemaEq: "2"}},
+ {foo: {$_internalSchemaEq: [3]}}
+ ]
+ }]
+ }
+ ]
+ }]
+ }]
+ })"));
+}
+
+TEST(JSONSchemaParserTest, TopLevelEnumTranslatesCorrectly) {
+ BSONObj schema = fromjson("{enum: [1, {foo: 1}]}}}");
+ auto result = JSONSchemaParser::parse(schema);
+ ASSERT_OK(result.getStatus());
+ ASSERT_SERIALIZES_TO(result.getValue().get(),
+ fromjson("{$and: [{$or: [{$_internalSchemaRootDocEq: {foo: 1}}]}]}"));
+}
+
+TEST(JSONSchemaParserTest, TopLevelEnumWithZeroObjectsTranslatesCorrectly) {
+ BSONObj schema = fromjson("{enum: [1, 'impossible', true]}}}");
+ auto result = JSONSchemaParser::parse(schema);
+ ASSERT_OK(result.getStatus());
+ ASSERT_SERIALIZES_TO(result.getValue().get(), fromjson("{$and: [{$alwaysFalse: 1}]}"));
+}
+
} // namespace
} // namespace mongo
diff --git a/src/mongo/db/pipeline/document_source_match.cpp b/src/mongo/db/pipeline/document_source_match.cpp
index 7bc49ef0ac3..84e35a01c82 100644
--- a/src/mongo/db/pipeline/document_source_match.cpp
+++ b/src/mongo/db/pipeline/document_source_match.cpp
@@ -258,6 +258,7 @@ Document redactSafePortionDollarOps(BSONObj expr) {
case PathAcceptingKeyword::GEO_INTERSECTS:
case PathAcceptingKeyword::GEO_NEAR:
case PathAcceptingKeyword::INTERNAL_SCHEMA_ALL_ELEM_MATCH_FROM_INDEX:
+ case PathAcceptingKeyword::INTERNAL_SCHEMA_EQ:
case PathAcceptingKeyword::INTERNAL_SCHEMA_FMOD:
case PathAcceptingKeyword::INTERNAL_SCHEMA_MATCH_ARRAY_INDEX:
case PathAcceptingKeyword::INTERNAL_SCHEMA_MAX_ITEMS:
diff --git a/src/mongo/db/pipeline/value.cpp b/src/mongo/db/pipeline/value.cpp
index a7bce3fc777..2695da381d0 100644
--- a/src/mongo/db/pipeline/value.cpp
+++ b/src/mongo/db/pipeline/value.cpp
@@ -648,7 +648,7 @@ inline static int cmp(const T& left, const T& right) {
int Value::compare(const Value& rL,
const Value& rR,
const StringData::ComparatorInterface* stringComparator) {
- // Note, this function needs to behave identically to BSON's compareElementValues().
+ // Note, this function needs to behave identically to BSONElement::compareElements().
// Additionally, any changes here must be replicated in hash_combine().
BSONType lType = rL.getType();
BSONType rType = rR.getType();
@@ -660,7 +660,8 @@ int Value::compare(const Value& rL,
return ret;
switch (lType) {
- // Order of types is the same as in compareElementValues() to make it easier to verify
+ // Order of types is the same as in BSONElement::compareElements() to make it easier to
+ // verify.
// These are valueless types
case EOO:
@@ -802,7 +803,9 @@ int Value::compare(const Value& rL,
return rL.getStringData().compare(rR.getStringData());
}
- case RegEx: // same as String in this impl but keeping order same as compareElementValues
+ case RegEx:
+ // same as String in this impl but keeping order same as
+ // BSONElement::compareElements().
return rL.getStringData().compare(rR.getStringData());
case CodeWScope: {
@@ -826,7 +829,7 @@ void Value::hash_combine(size_t& seed,
boost::hash_combine(seed, canonicalizeBSONType(type));
switch (type) {
- // Order of types is the same as in Value::compare() and compareElementValues().
+ // Order of types is the same as in Value::compare() and BSONElement::compareElements().
// These are valueless types
case EOO:
diff --git a/src/mongo/db/query/plan_cache.cpp b/src/mongo/db/query/plan_cache.cpp
index 092a3bf6512..43502d96c0b 100644
--- a/src/mongo/db/query/plan_cache.cpp
+++ b/src/mongo/db/query/plan_cache.cpp
@@ -186,6 +186,9 @@ const char* encodeMatchType(MatchExpression::MatchType mt) {
case MatchExpression::INTERNAL_SCHEMA_COND:
return "internalSchemaCond";
+ case MatchExpression::INTERNAL_SCHEMA_EQ:
+ return "internalSchemaEq";
+
case MatchExpression::INTERNAL_SCHEMA_FMOD:
return "internalSchemaFmod";
@@ -204,6 +207,9 @@ const char* encodeMatchType(MatchExpression::MatchType mt) {
case MatchExpression::INTERNAL_SCHEMA_OBJECT_MATCH:
return "internalSchemaObjectMatch";
+ case MatchExpression::INTERNAL_SCHEMA_ROOT_DOC_EQ:
+ return "internalSchemaRootDocEq";
+
case MatchExpression::INTERNAL_SCHEMA_MIN_LENGTH:
return "internalSchemaMinLength";