diff options
author | David Storch <david.storch@10gen.com> | 2016-09-06 18:16:43 -0400 |
---|---|---|
committer | David Storch <david.storch@10gen.com> | 2016-09-09 15:05:13 -0400 |
commit | 613134ad5960767276df350db1f90cc919225dd5 (patch) | |
tree | b3af405211503717a2524813237734b3af2613a8 /src/mongo | |
parent | f6397011fa6f607a80a6bde1408bf6afddaf20a7 (diff) | |
download | mongo-613134ad5960767276df350db1f90cc919225dd5.tar.gz |
SERVER-23990 move BSONObj/BSONElement hashing into {BSONObj,BSONElement}::ComparatorInterface
Diffstat (limited to 'src/mongo')
-rw-r--r-- | src/mongo/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/bson/bson_comparator_interface_base.cpp | 179 | ||||
-rw-r--r-- | src/mongo/bson/bson_comparator_interface_base.h | 67 | ||||
-rw-r--r-- | src/mongo/bson/bson_obj_test.cpp | 35 | ||||
-rw-r--r-- | src/mongo/bson/bsonelement.cpp | 110 | ||||
-rw-r--r-- | src/mongo/bson/bsonelement.h | 9 | ||||
-rw-r--r-- | src/mongo/bson/bsonelement_comparator.h | 5 | ||||
-rw-r--r-- | src/mongo/bson/bsonobj.cpp | 10 | ||||
-rw-r--r-- | src/mongo/bson/bsonobj.h | 9 | ||||
-rw-r--r-- | src/mongo/bson/bsonobj_comparator.h | 5 | ||||
-rw-r--r-- | src/mongo/bson/simple_bsonelement_comparator.h | 5 | ||||
-rw-r--r-- | src/mongo/bson/simple_bsonobj_comparator.h | 5 | ||||
-rw-r--r-- | src/mongo/db/pipeline/value.cpp | 5 | ||||
-rw-r--r-- | src/mongo/db/query/collation/collator_interface_mock_test.cpp | 156 | ||||
-rw-r--r-- | src/mongo/db/repl/sync_tail.cpp | 3 |
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); } |