summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Storch <david.storch@10gen.com>2016-09-06 18:16:43 -0400
committerDavid Storch <david.storch@10gen.com>2016-09-09 15:05:13 -0400
commit613134ad5960767276df350db1f90cc919225dd5 (patch)
treeb3af405211503717a2524813237734b3af2613a8
parentf6397011fa6f607a80a6bde1408bf6afddaf20a7 (diff)
downloadmongo-613134ad5960767276df350db1f90cc919225dd5.tar.gz
SERVER-23990 move BSONObj/BSONElement hashing into {BSONObj,BSONElement}::ComparatorInterface
-rw-r--r--src/mongo/SConscript1
-rw-r--r--src/mongo/bson/bson_comparator_interface_base.cpp179
-rw-r--r--src/mongo/bson/bson_comparator_interface_base.h67
-rw-r--r--src/mongo/bson/bson_obj_test.cpp35
-rw-r--r--src/mongo/bson/bsonelement.cpp110
-rw-r--r--src/mongo/bson/bsonelement.h9
-rw-r--r--src/mongo/bson/bsonelement_comparator.h5
-rw-r--r--src/mongo/bson/bsonobj.cpp10
-rw-r--r--src/mongo/bson/bsonobj.h9
-rw-r--r--src/mongo/bson/bsonobj_comparator.h5
-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/db/pipeline/value.cpp5
-rw-r--r--src/mongo/db/query/collation/collator_interface_mock_test.cpp156
-rw-r--r--src/mongo/db/repl/sync_tail.cpp3
15 files changed, 453 insertions, 151 deletions
diff --git a/src/mongo/SConscript b/src/mongo/SConscript
index efb9955a979..d071aab2d8f 100644
--- a/src/mongo/SConscript
+++ b/src/mongo/SConscript
@@ -69,6 +69,7 @@ baseSource=[
'base/status.cpp',
'base/string_data.cpp',
'base/validate_locale.cpp',
+ 'bson/bson_comparator_interface_base.cpp',
'bson/bson_validate.cpp',
'bson/bsonelement.cpp',
'bson/bsonmisc.cpp',
diff --git a/src/mongo/bson/bson_comparator_interface_base.cpp b/src/mongo/bson/bson_comparator_interface_base.cpp
new file mode 100644
index 00000000000..6dfb379fd67
--- /dev/null
+++ b/src/mongo/bson/bson_comparator_interface_base.cpp
@@ -0,0 +1,179 @@
+/**
+ * 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.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/bson/bson_comparator_interface_base.h"
+
+#include <boost/functional/hash.hpp>
+
+#include "mongo/base/simple_string_data_comparator.h"
+#include "mongo/bson/bsonelement.h"
+#include "mongo/bson/bsonobj.h"
+#include "mongo/bson/simple_bsonobj_comparator.h"
+
+namespace mongo {
+
+template <typename T>
+void BSONComparatorInterfaceBase<T>::hashCombineBSONObj(
+ size_t& seed,
+ const BSONObj& objToHash,
+ bool considerFieldName,
+ const StringData::ComparatorInterface* stringComparator) {
+ for (auto elem : objToHash) {
+ hashCombineBSONElement(seed, elem, considerFieldName, stringComparator);
+ }
+}
+
+template <typename T>
+void BSONComparatorInterfaceBase<T>::hashCombineBSONElement(
+ size_t& hash,
+ BSONElement elemToHash,
+ bool considerFieldName,
+ const StringData::ComparatorInterface* stringComparator) {
+ boost::hash_combine(hash, elemToHash.canonicalType());
+
+ const StringData fieldName = elemToHash.fieldNameStringData();
+ if (considerFieldName && !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:
+ case mongo::MaxKey:
+ case mongo::MinKey:
+ // These are valueless types
+ break;
+
+ case mongo::Bool:
+ boost::hash_combine(hash, elemToHash.boolean());
+ break;
+
+ case mongo::bsonTimestamp:
+ boost::hash_combine(hash, elemToHash.timestamp().asULL());
+ break;
+
+ case mongo::Date:
+ boost::hash_combine(hash, elemToHash.date().asInt64());
+ break;
+
+ case mongo::NumberDecimal: {
+ const Decimal128 dcml = elemToHash.numberDecimal();
+ if (dcml.toAbs().isGreater(Decimal128(std::numeric_limits<double>::max(),
+ Decimal128::kRoundTo34Digits,
+ Decimal128::kRoundTowardZero)) &&
+ !dcml.isInfinite() && !dcml.isNaN()) {
+ // Normalize our decimal to force equivalent decimals
+ // in the same cohort to hash to the same value
+ Decimal128 dcmlNorm(dcml.normalize());
+ boost::hash_combine(hash, dcmlNorm.getValue().low64);
+ boost::hash_combine(hash, dcmlNorm.getValue().high64);
+ break;
+ }
+ // Else, fall through and convert the decimal to a double and hash.
+ // At this point the decimal fits into the range of doubles, is infinity, or is NaN,
+ // which doubles have a cheaper representation for.
+ }
+ case mongo::NumberDouble:
+ case mongo::NumberLong:
+ case mongo::NumberInt: {
+ // This converts all numbers to doubles, which ignores the low-order bits of
+ // NumberLongs > 2**53 and precise decimal numbers without double representations,
+ // but that is ok since the hash will still be the same for equal numbers and is
+ // still likely to be different for different numbers. (Note: this issue only
+ // applies for decimals when they are outside of the valid double range. See
+ // the above case.)
+ // SERVER-16851
+ const double dbl = elemToHash.numberDouble();
+ if (std::isnan(dbl)) {
+ boost::hash_combine(hash, std::numeric_limits<double>::quiet_NaN());
+ } else {
+ boost::hash_combine(hash, dbl);
+ }
+ break;
+ }
+
+ case mongo::jstOID:
+ elemToHash.__oid().hash_combine(hash);
+ break;
+
+ case mongo::String: {
+ if (stringComparator) {
+ stringComparator->hash_combine(hash, elemToHash.valueStringData());
+ } else {
+ SimpleStringDataComparator::kInstance.hash_combine(hash,
+ elemToHash.valueStringData());
+ }
+ break;
+ }
+
+ case mongo::Code:
+ case mongo::Symbol:
+ SimpleStringDataComparator::kInstance.hash_combine(hash, elemToHash.valueStringData());
+ break;
+
+ case mongo::Object:
+ case mongo::Array:
+ hashCombineBSONObj(hash,
+ elemToHash.embeddedObject(),
+ true, // considerFieldName
+ stringComparator);
+ break;
+
+ case mongo::DBRef:
+ case mongo::BinData:
+ // All bytes of the value are required to be identical.
+ SimpleStringDataComparator::kInstance.hash_combine(
+ hash, StringData(elemToHash.value(), elemToHash.valuesize()));
+ break;
+
+ case mongo::RegEx:
+ SimpleStringDataComparator::kInstance.hash_combine(hash, elemToHash.regex());
+ SimpleStringDataComparator::kInstance.hash_combine(hash, elemToHash.regexFlags());
+ break;
+
+ case mongo::CodeWScope: {
+ SimpleStringDataComparator::kInstance.hash_combine(
+ hash, StringData(elemToHash.codeWScopeCode(), elemToHash.codeWScopeCodeLen()));
+ hashCombineBSONObj(hash,
+ elemToHash.codeWScopeObject(),
+ true, // considerFieldName
+ &SimpleStringDataComparator::kInstance);
+ break;
+ }
+ }
+}
+
+template class BSONComparatorInterfaceBase<BSONObj>;
+template class BSONComparatorInterfaceBase<BSONElement>;
+
+} // namespace mongo
diff --git a/src/mongo/bson/bson_comparator_interface_base.h b/src/mongo/bson/bson_comparator_interface_base.h
index 461199f6431..f70bec7b872 100644
--- a/src/mongo/bson/bson_comparator_interface_base.h
+++ b/src/mongo/bson/bson_comparator_interface_base.h
@@ -33,12 +33,16 @@
#include <set>
#include "mongo/base/disallow_copying.h"
+#include "mongo/base/string_data_comparator_interface.h"
#include "mongo/stdx/unordered_map.h"
#include "mongo/stdx/unordered_set.h"
#include "mongo/util/assert_util.h"
namespace mongo {
+class BSONElement;
+class BSONObj;
+
/**
* Base class for the BSONObj and BSONElement comparator interfaces.
*/
@@ -101,18 +105,19 @@ public:
};
/**
- * Function object for hashing with respect to this comparator.
- *
- * TODO SERVER-23990: Make BSONObj/BSONElement hasher collation-aware.
+ * Function object for hashing with respect to this comparator. Compatible with the hash concept
+ * from std::hash.
*/
class Hasher {
public:
+ explicit Hasher(const BSONComparatorInterfaceBase* comparator) : _comparator(comparator) {}
+
size_t operator()(const T& toHash) const {
- return _hasher(toHash);
+ return _comparator->hash(toHash);
}
private:
- typename T::Hasher _hasher;
+ const BSONComparatorInterfaceBase* _comparator;
};
using Set = std::set<T, LessThan>;
@@ -128,12 +133,34 @@ public:
virtual ~BSONComparatorInterfaceBase() = default;
/**
- * Compares two BSONObj objects. Returns <0, 0, >0 if 'lhs' < 'rhs', 'lhs' == 'rhs', or 'lhs' >
- * 'rhs' respectively.
+ * Compares two BSONObj/BSONElement objects. Returns <0, 0, >0 if 'lhs' < 'rhs', 'lhs' == 'rhs',
+ * or 'lhs' > 'rhs' respectively.
*/
virtual int compare(const T& lhs, const T& rhs) const = 0;
/**
+ * Produces a hash for a BSONObj or BSONElement in such a way that respects this comparator.
+ *
+ * The hash function is subject to change. Do not use in cases where hashes need to be
+ * consistent across versions.
+ */
+ size_t hash(const T& toHash) const {
+ size_t seed = 0;
+ hash_combine(seed, toHash);
+ return seed;
+ }
+
+ /**
+ * Produces a hash for a BSONObj or BSONElement in such a way that respects this comparator. The
+ * resulting hash is combined with 'seed', allowing the caller to create a composite hash out of
+ * several BSONObj/BSONElement objects.
+ *
+ * The hash function is subject to change. Do not use in cases where hashes need to be
+ * consistent across versions.
+ */
+ virtual void hash_combine(size_t& seed, const T& toHash) const = 0;
+
+ /**
* Evaluates a deferred comparison object generated by invocation of one of the BSONObj operator
* overloads for relops.
*/
@@ -181,7 +208,7 @@ protected:
}
UnorderedSet makeUnorderedSet(std::initializer_list<T> init = {}) const {
- return UnorderedSet(init, 0, Hasher(), EqualTo(this));
+ return UnorderedSet(init, 0, Hasher(this), EqualTo(this));
}
template <typename ValueType>
@@ -190,10 +217,30 @@ protected:
}
template <typename ValueType>
- UnorderedMap<ValueType> makeBSONObjIndexedUnorderedMap(
+ UnorderedMap<ValueType> makeUnorderedMap(
std::initializer_list<std::pair<const T, ValueType>> init = {}) const {
- return UnorderedMap<ValueType>(init, 0, Hasher(), EqualTo(this));
+ return UnorderedMap<ValueType>(init, 0, Hasher(this), EqualTo(this));
}
+
+ /**
+ * Hashes 'objToHash', respecting the equivalence classes given by 'stringComparator'.
+ *
+ * Helper intended for use by subclasses.
+ */
+ static void hashCombineBSONObj(size_t& seed,
+ const BSONObj& objToHash,
+ bool considerFieldName,
+ const StringData::ComparatorInterface* stringComparator);
+
+ /**
+ * Hashes 'elemToHash', respecting the equivalence classes given by 'stringComparator'.
+ *
+ * Helper intended for use by subclasses.
+ */
+ static void hashCombineBSONElement(size_t& seed,
+ BSONElement elemToHash,
+ bool considerFieldName,
+ const StringData::ComparatorInterface* stringComparator);
};
} // namespace mongo
diff --git a/src/mongo/bson/bson_obj_test.cpp b/src/mongo/bson/bson_obj_test.cpp
index bf38662a321..890d3c4580f 100644
--- a/src/mongo/bson/bson_obj_test.cpp
+++ b/src/mongo/bson/bson_obj_test.cpp
@@ -25,6 +25,10 @@
* then also delete it in the license file.
*/
+#include "mongo/bson/bsonelement_comparator.h"
+#include "mongo/bson/bsonobj_comparator.h"
+#include "mongo/bson/simple_bsonelement_comparator.h"
+#include "mongo/bson/simple_bsonobj_comparator.h"
#include "mongo/db/jsobj.h"
#include "mongo/db/json.h"
#include "mongo/platform/decimal128.h"
@@ -509,6 +513,37 @@ TEST(BSONObjCompare, NumericBounds) {
ASSERT_GT(r.woCompare(l), 0);
}
+TEST(BSONObjCompare, BSONObjHashingIgnoresTopLevelFieldNamesWhenRequested) {
+ BSONObj obj1 = fromjson("{a: {b: 1}, c: {d: 1}}");
+ BSONObj obj2 = fromjson("{A: {b: 1}, C: {d: 1}}");
+ BSONObj obj3 = fromjson("{A: {B: 1}, C: {D: 1}}");
+
+ SimpleBSONObjComparator bsonCmpConsiderFieldNames;
+ BSONObjComparator bsonCmpIgnoreFieldNames(
+ BSONObj(), BSONObjComparator::FieldNamesMode::kIgnore, nullptr);
+
+ ASSERT_NE(bsonCmpConsiderFieldNames.hash(obj1), bsonCmpConsiderFieldNames.hash(obj2));
+ ASSERT_EQ(bsonCmpIgnoreFieldNames.hash(obj1), bsonCmpIgnoreFieldNames.hash(obj2));
+ ASSERT_NE(bsonCmpIgnoreFieldNames.hash(obj1), bsonCmpIgnoreFieldNames.hash(obj3));
+}
+
+TEST(BSONObjCompare, BSONElementHashingIgnoresEltFieldNameWhenRequested) {
+ BSONObj obj1 = fromjson("{a: {b: 1}}");
+ BSONObj obj2 = fromjson("{A: {b: 1}}");
+ BSONObj obj3 = fromjson("{A: {B: 1}}");
+
+ SimpleBSONElementComparator bsonCmpConsiderFieldNames;
+ BSONElementComparator bsonCmpIgnoreFieldNames(BSONElementComparator::FieldNamesMode::kIgnore,
+ nullptr);
+
+ ASSERT_NE(bsonCmpConsiderFieldNames.hash(obj1.firstElement()),
+ bsonCmpConsiderFieldNames.hash(obj2.firstElement()));
+ ASSERT_EQ(bsonCmpIgnoreFieldNames.hash(obj1.firstElement()),
+ bsonCmpIgnoreFieldNames.hash(obj2.firstElement()));
+ ASSERT_NE(bsonCmpIgnoreFieldNames.hash(obj1.firstElement()),
+ bsonCmpIgnoreFieldNames.hash(obj3.firstElement()));
+}
+
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 929af2878f5..34b4b07576d 100644
--- a/src/mongo/bson/bsonelement.cpp
+++ b/src/mongo/bson/bsonelement.cpp
@@ -1010,114 +1010,4 @@ int compareElementValues(const BSONElement& l,
return -1;
}
-size_t BSONElement::Hasher::operator()(const BSONElement& elem) const {
- size_t hash = 0;
-
- boost::hash_combine(hash, elem.canonicalType());
-
- const StringData fieldName = elem.fieldNameStringData();
- if (!fieldName.empty()) {
- boost::hash_combine(hash, SimpleStringDataComparator::kInstance.hash(fieldName));
- }
-
- switch (elem.type()) {
- // Order of types is the same as in compareElementValues().
-
- case mongo::EOO:
- case mongo::Undefined:
- case mongo::jstNULL:
- case mongo::MaxKey:
- case mongo::MinKey:
- // These are valueless types
- break;
-
- case mongo::Bool:
- boost::hash_combine(hash, elem.boolean());
- break;
-
- case mongo::bsonTimestamp:
- boost::hash_combine(hash, elem.timestamp().asULL());
- break;
-
- case mongo::Date:
- boost::hash_combine(hash, elem.date().asInt64());
- break;
-
- case mongo::NumberDecimal: {
- const Decimal128 dcml = elem.numberDecimal();
- if (dcml.toAbs().isGreater(Decimal128(std::numeric_limits<double>::max(),
- Decimal128::kRoundTo34Digits,
- Decimal128::kRoundTowardZero)) &&
- !dcml.isInfinite() && !dcml.isNaN()) {
- // Normalize our decimal to force equivalent decimals
- // in the same cohort to hash to the same value
- Decimal128 dcmlNorm(dcml.normalize());
- boost::hash_combine(hash, dcmlNorm.getValue().low64);
- boost::hash_combine(hash, dcmlNorm.getValue().high64);
- break;
- }
- // Else, fall through and convert the decimal to a double and hash.
- // At this point the decimal fits into the range of doubles, is infinity, or is NaN,
- // which doubles have a cheaper representation for.
- }
- case mongo::NumberDouble:
- case mongo::NumberLong:
- case mongo::NumberInt: {
- // This converts all numbers to doubles, which ignores the low-order bits of
- // NumberLongs > 2**53 and precise decimal numbers without double representations,
- // but that is ok since the hash will still be the same for equal numbers and is
- // still likely to be different for different numbers. (Note: this issue only
- // applies for decimals when they are outside of the valid double range. See
- // the above case.)
- // SERVER-16851
- const double dbl = elem.numberDouble();
- if (std::isnan(dbl)) {
- boost::hash_combine(hash, std::numeric_limits<double>::quiet_NaN());
- } else {
- boost::hash_combine(hash, dbl);
- }
- break;
- }
-
- case mongo::jstOID:
- elem.__oid().hash_combine(hash);
- break;
-
- case mongo::Code:
- case mongo::Symbol:
- case mongo::String:
- boost::hash_combine(hash,
- SimpleStringDataComparator::kInstance.hash(elem.valueStringData()));
- break;
-
- case mongo::Object:
- case mongo::Array:
- boost::hash_combine(hash, BSONObj::Hasher()(elem.embeddedObject()));
- break;
-
- case mongo::DBRef:
- case mongo::BinData:
- // All bytes of the value are required to be identical.
- boost::hash_combine(hash,
- SimpleStringDataComparator::kInstance.hash(
- StringData(elem.value(), elem.valuesize())));
- break;
-
- case mongo::RegEx:
- boost::hash_combine(hash, SimpleStringDataComparator::kInstance.hash(elem.regex()));
- boost::hash_combine(hash,
- SimpleStringDataComparator::kInstance.hash(elem.regexFlags()));
- break;
-
- case mongo::CodeWScope: {
- boost::hash_combine(hash,
- SimpleStringDataComparator::kInstance.hash(
- StringData(elem.codeWScopeCode(), elem.codeWScopeCodeLen())));
- boost::hash_combine(hash, BSONObj::Hasher()(elem.codeWScopeObject()));
- break;
- }
- }
- return hash;
-}
-
} // namespace mongo
diff --git a/src/mongo/bson/bsonelement.h b/src/mongo/bson/bsonelement.h
index 830e857deab..9e9ee9e60da 100644
--- a/src/mongo/bson/bsonelement.h
+++ b/src/mongo/bson/bsonelement.h
@@ -542,15 +542,6 @@ public:
return DeferredComparison(DeferredComparison::Type::kNE, *this, other);
}
- /**
- * Functor compatible with std::hash for std::unordered_{map,set}
- * Warning: The hash function is subject to change. Do not use in cases where hashes need
- * to be consistent across versions.
- */
- struct Hasher {
- size_t operator()(const BSONElement& elem) const;
- };
-
const char* rawdata() const {
return data;
}
diff --git a/src/mongo/bson/bsonelement_comparator.h b/src/mongo/bson/bsonelement_comparator.h
index c1e1d9bfd7a..609bbb4e5bd 100644
--- a/src/mongo/bson/bsonelement_comparator.h
+++ b/src/mongo/bson/bsonelement_comparator.h
@@ -62,6 +62,11 @@ public:
return lhs.woCompare(rhs, considerFieldName, _stringComparator);
}
+ void hash_combine(size_t& seed, const BSONElement& toHash) const final {
+ const bool considerFieldName = (_fieldNamesMode == FieldNamesMode::kConsider);
+ hashCombineBSONElement(seed, toHash, considerFieldName, _stringComparator);
+ }
+
private:
FieldNamesMode _fieldNamesMode;
const StringData::ComparatorInterface* _stringComparator;
diff --git a/src/mongo/bson/bsonobj.cpp b/src/mongo/bson/bsonobj.cpp
index 0d965e364a5..d9af2c706a9 100644
--- a/src/mongo/bson/bsonobj.cpp
+++ b/src/mongo/bson/bsonobj.cpp
@@ -31,8 +31,6 @@
#include "mongo/db/jsobj.h"
-#include <boost/functional/hash.hpp>
-
#include "mongo/base/data_range.h"
#include "mongo/bson/bson_validate.h"
#include "mongo/db/json.h"
@@ -184,14 +182,6 @@ int BSONObj::woCompare(const BSONObj& r,
return -1;
}
-size_t BSONObj::Hasher::operator()(const BSONObj& obj) const {
- size_t hash = 0;
- BSONForEach(elem, obj) {
- boost::hash_combine(hash, BSONElement::Hasher()(elem));
- }
- return hash;
-}
-
bool BSONObj::isPrefixOf(const BSONObj& otherObj,
const BSONElement::ComparatorInterface& eltCmp) const {
BSONObjIterator a(*this);
diff --git a/src/mongo/bson/bsonobj.h b/src/mongo/bson/bsonobj.h
index a39bdfb64a6..22ab6ff182f 100644
--- a/src/mongo/bson/bsonobj.h
+++ b/src/mongo/bson/bsonobj.h
@@ -461,15 +461,6 @@ public:
}
/**
- * Functor compatible with std::hash for std::unordered_{map,set}
- * Warning: The hash function is subject to change. Do not use in cases where hashes need
- * to be consistent across versions.
- */
- struct Hasher {
- size_t operator()(const BSONObj& obj) const;
- };
-
- /**
* Returns true if 'this' is a prefix of otherObj- in other words if otherObj contains the same
* field names and field vals in the same order as 'this', plus optionally some additional
* elements.
diff --git a/src/mongo/bson/bsonobj_comparator.h b/src/mongo/bson/bsonobj_comparator.h
index e5e2fd94e2b..fac94464e94 100644
--- a/src/mongo/bson/bsonobj_comparator.h
+++ b/src/mongo/bson/bsonobj_comparator.h
@@ -66,6 +66,11 @@ public:
return lhs.woCompare(rhs, _ordering, considerFieldName, _stringComparator);
}
+ void hash_combine(size_t& seed, const BSONObj& toHash) const final {
+ const bool considerFieldName = (_fieldNamesMode == FieldNamesMode::kConsider);
+ hashCombineBSONObj(seed, toHash, considerFieldName, _stringComparator);
+ }
+
private:
BSONObj _ordering;
FieldNamesMode _fieldNamesMode;
diff --git a/src/mongo/bson/simple_bsonelement_comparator.h b/src/mongo/bson/simple_bsonelement_comparator.h
index 001f70d21fe..b05204c3f50 100644
--- a/src/mongo/bson/simple_bsonelement_comparator.h
+++ b/src/mongo/bson/simple_bsonelement_comparator.h
@@ -45,6 +45,11 @@ public:
int compare(const BSONElement& lhs, const BSONElement& rhs) const final {
return lhs.woCompare(rhs, true, nullptr);
}
+
+ void hash_combine(size_t& seed, const BSONElement& toHash) const final {
+ const bool considerFieldName = true;
+ hashCombineBSONElement(seed, toHash, considerFieldName, nullptr);
+ }
};
} // namespace mongo
diff --git a/src/mongo/bson/simple_bsonobj_comparator.h b/src/mongo/bson/simple_bsonobj_comparator.h
index 76d4070f059..2e88586746f 100644
--- a/src/mongo/bson/simple_bsonobj_comparator.h
+++ b/src/mongo/bson/simple_bsonobj_comparator.h
@@ -44,6 +44,11 @@ public:
int compare(const BSONObj& lhs, const BSONObj& rhs) const final {
return lhs.woCompare(rhs, BSONObj(), true, nullptr);
}
+
+ void hash_combine(size_t& seed, const BSONObj& toHash) const final {
+ const bool considerFieldName = true;
+ hashCombineBSONObj(seed, toHash, considerFieldName, nullptr);
+ }
};
} // namespace mongo
diff --git a/src/mongo/db/pipeline/value.cpp b/src/mongo/db/pipeline/value.cpp
index 5eea4d314d1..f6cd179f1a6 100644
--- a/src/mongo/db/pipeline/value.cpp
+++ b/src/mongo/db/pipeline/value.cpp
@@ -37,6 +37,7 @@
#include "mongo/base/compare_numbers.h"
#include "mongo/base/data_type_endian.h"
#include "mongo/base/simple_string_data_comparator.h"
+#include "mongo/bson/simple_bsonobj_comparator.h"
#include "mongo/db/jsobj.h"
#include "mongo/db/pipeline/document.h"
#include "mongo/platform/decimal128.h"
@@ -928,8 +929,8 @@ void Value::hash_combine(size_t& seed,
case CodeWScope: {
intrusive_ptr<const RCCodeWScope> cws = _storage.getCodeWScope();
- boost::hash_combine(seed, SimpleStringDataComparator::kInstance.hash(cws->code));
- boost::hash_combine(seed, BSONObj::Hasher()(cws->scope));
+ SimpleStringDataComparator::kInstance.hash_combine(seed, cws->code);
+ SimpleBSONObjComparator::kInstance.hash_combine(seed, cws->scope);
break;
}
}
diff --git a/src/mongo/db/query/collation/collator_interface_mock_test.cpp b/src/mongo/db/query/collation/collator_interface_mock_test.cpp
index 146183e5ae9..e8f3f5af7ea 100644
--- a/src/mongo/db/query/collation/collator_interface_mock_test.cpp
+++ b/src/mongo/db/query/collation/collator_interface_mock_test.cpp
@@ -30,6 +30,10 @@
#include "mongo/db/query/collation/collator_interface_mock.h"
+#include "mongo/bson/bsonelement_comparator.h"
+#include "mongo/bson/bsonobj_comparator.h"
+#include "mongo/bson/simple_bsonelement_comparator.h"
+#include "mongo/bson/simple_bsonobj_comparator.h"
#include "mongo/db/jsobj.h"
#include "mongo/db/json.h"
#include "mongo/unittest/unittest.h"
@@ -219,4 +223,156 @@ TEST(CollatorInterfaceMockSelfTest, CollatorGeneratedUnorderedMapOfStringsRespec
ASSERT_EQ(map["fooZ"], 3);
}
+TEST(CollatorInterfaceMockSelfTest, BSONObjsEqualUnderCollatorHashEqually) {
+ CollatorInterfaceMock toLowerCollator(CollatorInterfaceMock::MockType::kToLowerString);
+ SimpleBSONObjComparator bsonCmpConsiderCase;
+ BSONObjComparator bsonCmpIgnoreCase(
+ BSONObj(), BSONObjComparator::FieldNamesMode::kConsider, &toLowerCollator);
+ BSONObj obj1 = BSON("a"
+ << "foo");
+ BSONObj obj2 = BSON("a"
+ << "FOO");
+ ASSERT_NE(bsonCmpConsiderCase.hash(obj1), bsonCmpConsiderCase.hash(obj2));
+ ASSERT_EQ(bsonCmpIgnoreCase.hash(obj1), bsonCmpIgnoreCase.hash(obj2));
+}
+
+TEST(CollatorInterfaceMockSelfTest, BSONObjsEqualUnderCollatorHashEquallyNested) {
+ CollatorInterfaceMock toLowerCollator(CollatorInterfaceMock::MockType::kToLowerString);
+ SimpleBSONObjComparator bsonCmpConsiderCase;
+ BSONObjComparator bsonCmpIgnoreCase(
+ BSONObj(), BSONObjComparator::FieldNamesMode::kConsider, &toLowerCollator);
+ BSONObj obj1 = BSON("a" << 1 << "b" << BSON("c"
+ << "foo"));
+ BSONObj obj2 = BSON("a" << 1 << "b" << BSON("c"
+ << "FOO"));
+ ASSERT_NE(bsonCmpConsiderCase.hash(obj1), bsonCmpConsiderCase.hash(obj2));
+ ASSERT_EQ(bsonCmpIgnoreCase.hash(obj1), bsonCmpIgnoreCase.hash(obj2));
+}
+
+TEST(CollatorInterfaceMockSelfTest, BSONElementsEqualUnderCollatorHashEqually) {
+ CollatorInterfaceMock toLowerCollator(CollatorInterfaceMock::MockType::kToLowerString);
+ SimpleBSONElementComparator bsonCmpConsiderCase;
+ BSONElementComparator bsonCmpIgnoreCase(BSONElementComparator::FieldNamesMode::kConsider,
+ &toLowerCollator);
+ BSONObj obj1 = BSON("a"
+ << "foo");
+ BSONObj obj2 = BSON("a"
+ << "FOO");
+ BSONElement elt1 = obj1.firstElement();
+ BSONElement elt2 = obj2.firstElement();
+ ASSERT_NE(bsonCmpConsiderCase.hash(elt1), bsonCmpConsiderCase.hash(elt2));
+ ASSERT_EQ(bsonCmpIgnoreCase.hash(elt1), bsonCmpIgnoreCase.hash(elt2));
+}
+
+TEST(CollatorInterfaceMockSelfTest, BSONElementsEqualUnderCollatorHashEquallyNested) {
+ CollatorInterfaceMock toLowerCollator(CollatorInterfaceMock::MockType::kToLowerString);
+ SimpleBSONElementComparator bsonCmpConsiderCase;
+ BSONElementComparator bsonCmpIgnoreCase(BSONElementComparator::FieldNamesMode::kConsider,
+ &toLowerCollator);
+ BSONObj obj1 = BSON("a" << BSON("b"
+ << "foo"
+ << "c"
+ << BSON("d"
+ << "BaR")));
+ BSONObj obj2 = BSON("a" << BSON("b"
+ << "FOO"
+ << "c"
+ << BSON("d"
+ << "bar")));
+ BSONElement elt1 = obj1.firstElement();
+ BSONElement elt2 = obj2.firstElement();
+ ASSERT_NE(bsonCmpConsiderCase.hash(elt1), bsonCmpConsiderCase.hash(elt2));
+ ASSERT_EQ(bsonCmpIgnoreCase.hash(elt1), bsonCmpIgnoreCase.hash(elt2));
+}
+
+TEST(CollatorInterfaceMockSelfTest, CollatorGeneratedUnorderedSetOfBSONObjRespectsCollation) {
+ CollatorInterfaceMock toLowerCollator(CollatorInterfaceMock::MockType::kToLowerString);
+ BSONObjComparator bsonCmpIgnoreCase(
+ BSONObj(), BSONObjComparator::FieldNamesMode::kConsider, &toLowerCollator);
+ auto set = bsonCmpIgnoreCase.makeBSONObjUnorderedSet();
+ set.insert(BSON("a"
+ << "foo"));
+ set.insert(BSON("a"
+ << "FOO"));
+ set.insert(BSON("a"
+ << "FOOz"));
+ ASSERT_EQ(set.size(), 2U);
+ ASSERT_EQ(set.count(BSON("a"
+ << "FoO")),
+ 1U);
+ ASSERT_EQ(set.count(BSON("a"
+ << "fooZ")),
+ 1U);
+}
+
+TEST(CollatorInterfaceMockSelfTest, CollatorGeneratedUnorderedMapOfBSONObjRespectsCollation) {
+ CollatorInterfaceMock toLowerCollator(CollatorInterfaceMock::MockType::kToLowerString);
+ BSONObjComparator bsonCmpIgnoreCase(
+ BSONObj(), BSONObjComparator::FieldNamesMode::kConsider, &toLowerCollator);
+ auto map = bsonCmpIgnoreCase.makeBSONObjIndexedUnorderedMap<int>();
+ map[BSON("a"
+ << "foo")] = 1;
+ map[BSON("a"
+ << "FOO")] = 2;
+ map[BSON("a"
+ << "FOOz")] = 3;
+ ASSERT_EQ(map.size(), 2U);
+ ASSERT_EQ(map[BSON("a"
+ << "FoO")],
+ 2);
+ ASSERT_EQ(map[BSON("a"
+ << "fooZ")],
+ 3);
+}
+
+TEST(CollatorInterfaceMockSelfTest, CollatorGeneratedUnorderedSetOfBSONElementRespectsCollation) {
+ CollatorInterfaceMock toLowerCollator(CollatorInterfaceMock::MockType::kToLowerString);
+ BSONElementComparator bsonCmpIgnoreCase(BSONElementComparator::FieldNamesMode::kConsider,
+ &toLowerCollator);
+ auto set = bsonCmpIgnoreCase.makeBSONEltUnorderedSet();
+
+ BSONObj obj1 = BSON("a" << BSON("b"
+ << "foo"));
+ set.insert(obj1.firstElement());
+ BSONObj obj2 = BSON("a" << BSON("b"
+ << "FOO"));
+ set.insert(obj2.firstElement());
+ BSONObj obj3 = BSON("a" << BSON("b"
+ << "FOOz"));
+ set.insert(obj3.firstElement());
+
+ ASSERT_EQ(set.size(), 2U);
+ BSONObj obj4 = BSON("a" << BSON("b"
+ << "FoO"));
+ ASSERT_EQ(set.count(obj4.firstElement()), 1U);
+ BSONObj obj5 = BSON("a" << BSON("b"
+ << "fooZ"));
+ ASSERT_EQ(set.count(obj5.firstElement()), 1U);
+}
+
+TEST(CollatorInterfaceMockSelfTest, CollatorGeneratedUnorderedMapOfBSONElementRespectsCollation) {
+ CollatorInterfaceMock toLowerCollator(CollatorInterfaceMock::MockType::kToLowerString);
+ BSONElementComparator bsonCmpIgnoreCase(BSONElementComparator::FieldNamesMode::kConsider,
+ &toLowerCollator);
+ auto map = bsonCmpIgnoreCase.makeBSONEltIndexedUnorderedMap<int>();
+
+ BSONObj obj1 = BSON("a" << BSON("b"
+ << "foo"));
+ map[obj1.firstElement()] = 1;
+ BSONObj obj2 = BSON("a" << BSON("b"
+ << "FOO"));
+ map[obj2.firstElement()] = 2;
+ BSONObj obj3 = BSON("a" << BSON("b"
+ << "FOOz"));
+ map[obj3.firstElement()] = 3;
+
+ ASSERT_EQ(map.size(), 2U);
+ BSONObj obj4 = BSON("a" << BSON("b"
+ << "FoO"));
+ ASSERT_EQ(map[obj4.firstElement()], 2);
+ BSONObj obj5 = BSON("a" << BSON("b"
+ << "fooZ"));
+ ASSERT_EQ(map[obj5.firstElement()], 3);
+}
+
} // namespace
diff --git a/src/mongo/db/repl/sync_tail.cpp b/src/mongo/db/repl/sync_tail.cpp
index 3a8861da3cd..2ad593da9e3 100644
--- a/src/mongo/db/repl/sync_tail.cpp
+++ b/src/mongo/db/repl/sync_tail.cpp
@@ -38,6 +38,7 @@
#include <memory>
#include "mongo/base/counter.h"
+#include "mongo/bson/simple_bsonelement_comparator.h"
#include "mongo/db/auth/authorization_session.h"
#include "mongo/db/catalog/collection.h"
#include "mongo/db/catalog/database.h"
@@ -593,7 +594,7 @@ void fillWriterVectors(OperationContext* txn,
// collation-aware hashing of BSONElement.
if (supportsDocLocking && !collProperties.isCapped && !collProperties.collator) {
BSONElement id = op.getIdElement();
- const size_t idHash = BSONElement::Hasher()(id);
+ const size_t idHash = SimpleBSONElementComparator::kInstance.hash(id);
MurmurHash3_x86_32(&idHash, sizeof(idHash), hash, &hash);
}