diff options
-rw-r--r-- | src/mongo/db/index/btree_key_generator.cpp | 39 | ||||
-rw-r--r-- | src/mongo/db/index/btree_key_generator.h | 1 | ||||
-rw-r--r-- | src/mongo/db/query/collation/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/db/query/collation/collation_index_key_test.cpp | 105 | ||||
-rw-r--r-- | src/mongo/db/query/collation/collator_interface.cpp | 4 | ||||
-rw-r--r-- | src/mongo/db/query/collation/collator_interface.h | 6 | ||||
-rw-r--r-- | src/mongo/db/storage/key_string.cpp | 51 | ||||
-rw-r--r-- | src/mongo/db/storage/key_string.h | 51 |
8 files changed, 201 insertions, 57 deletions
diff --git a/src/mongo/db/index/btree_key_generator.cpp b/src/mongo/db/index/btree_key_generator.cpp index 71b557cb4c9..c0daddfc3a5 100644 --- a/src/mongo/db/index/btree_key_generator.cpp +++ b/src/mongo/db/index/btree_key_generator.cpp @@ -152,23 +152,17 @@ void BtreeKeyGenerator::getKeys(const BSONObj& obj, BSONElement e = obj["_id"]; if (e.eoo()) { keys->insert(_nullKeyString); - } else if (_collator) { - BSONObjBuilder b; - CollationIndexKey::collationAwareIndexKeyAppend(e, _collator, &b); - - KeyString::Builder keyString(_keyStringVersion, b.obj(), _ordering); - if (id) { - keyString.appendRecordId(*id); - } - /* - * Insert a copy so its buffer size fits the key size. - */ - keys->insert(keyString.getValueCopy()); } else { - int size = e.size() + 5 /* bson over head*/ - 3 /* remove _id string */; - BSONObjBuilder b(size); - b.appendAs(e, ""); - KeyString::Builder keyString(_keyStringVersion, b.obj(), _ordering); + KeyString::Builder keyString(_keyStringVersion, _ordering); + + if (_collator) { + keyString.appendBSONElement(e, [&](StringData stringData) { + return _collator->getComparisonString(stringData); + }); + } else { + keyString.appendBSONElement(e); + } + if (id) { keyString.appendRecordId(*id); } @@ -274,11 +268,16 @@ void BtreeKeyGenerator::_getKeysWithArray(std::vector<const char*> fieldNames, if (_isSparse && numNotFound == fieldNames.size()) { return; } - BSONObjBuilder b(_sizeTracker); - for (std::vector<BSONElement>::iterator i = fixed.begin(); i != fixed.end(); ++i) { - CollationIndexKey::collationAwareIndexKeyAppend(*i, _collator, &b); + KeyString::HeapBuilder keyString(_keyStringVersion, _ordering); + for (const auto& elem : fixed) { + if (_collator) { + keyString.appendBSONElement(elem, [&](StringData stringData) { + return _collator->getComparisonString(stringData); + }); + } else { + keyString.appendBSONElement(elem); + } } - KeyString::HeapBuilder keyString(_keyStringVersion, b.obj(), _ordering); if (id) { keyString.appendRecordId(*id); } diff --git a/src/mongo/db/index/btree_key_generator.h b/src/mongo/db/index/btree_key_generator.h index 08b960acfc9..303927a1fe7 100644 --- a/src/mongo/db/index/btree_key_generator.h +++ b/src/mongo/db/index/btree_key_generator.h @@ -82,7 +82,6 @@ private: const bool _isIdIndex; const bool _isSparse; const KeyString::Value _nullKeyString; // A full key with all fields null. - const BSONSizeTracker _sizeTracker; std::vector<BSONElement> _fixed; /** diff --git a/src/mongo/db/query/collation/SConscript b/src/mongo/db/query/collation/SConscript index 1a2c36d3ace..4304071e676 100644 --- a/src/mongo/db/query/collation/SConscript +++ b/src/mongo/db/query/collation/SConscript @@ -109,6 +109,7 @@ icuEnv.CppUnitTest( "collator_interface_mock_test.cpp", ], LIBDEPS=[ + '$BUILD_DIR/mongo/db/storage/key_string', "collator_factory_mock", "collator_icu", "collator_interface", diff --git a/src/mongo/db/query/collation/collation_index_key_test.cpp b/src/mongo/db/query/collation/collation_index_key_test.cpp index 20a788d7df4..1f184778fe2 100644 --- a/src/mongo/db/query/collation/collation_index_key_test.cpp +++ b/src/mongo/db/query/collation/collation_index_key_test.cpp @@ -34,12 +34,36 @@ #include "mongo/bson/bsonobjbuilder.h" #include "mongo/bson/json.h" #include "mongo/db/query/collation/collator_interface_mock.h" +#include "mongo/db/storage/key_string.h" #include "mongo/unittest/unittest.h" namespace { using namespace mongo; +void assertKeyStringCollatorOutput(const CollatorInterfaceMock& collator, + const BSONObj& dataObj, + const BSONObj& expected) { + KeyString::Builder ks(KeyString::Version::kLatestVersion, KeyString::ALL_ASCENDING); + ks.appendBSONElement(dataObj.firstElement(), [&](StringData stringData) { + return collator.getComparisonString(stringData); + }); + + ASSERT_EQ( + ks.getValueCopy(), + KeyString::Builder(KeyString::Version::kLatestVersion, expected, KeyString::ALL_ASCENDING)); +} + +void assertKeyStringCollatorThrows(const CollatorInterfaceMock& collator, const BSONObj& dataObj) { + KeyString::Builder ks(KeyString::Version::kLatestVersion, KeyString::ALL_ASCENDING); + ASSERT_THROWS_CODE(ks.appendBSONElement(dataObj.firstElement(), + [&](StringData stringData) { + return collator.getComparisonString(stringData); + }), + AssertionException, + ErrorCodes::CannotBuildIndexKeys); +} + TEST(CollationIndexKeyTest, IsCollatableTypeShouldBeTrueForString) { BSONObj obj = BSON("foo" << "string"); @@ -79,6 +103,16 @@ TEST(CollationIndexKeyTest, CollationAwareAppendReversesStringWithReverseMockCol << "gnirts")); } +TEST(CollationIndexKeyTest, KeyStringAppendReversesStringWithReverseMockCollator) { + CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); + BSONObj dataObj = BSON("foo" + << "string"); + assertKeyStringCollatorOutput(collator, + dataObj, + BSON("" + << "gnirts")); +} + TEST(CollationIndexKeyTest, CollationAwareAppendCorrectlySerializesEmptyComparisonKey) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); BSONObjBuilder builder; @@ -94,6 +128,16 @@ TEST(CollationIndexKeyTest, CollationAwareAppendCorrectlySerializesEmptyComparis ASSERT_BSONOBJ_EQ(out.obj(), expectedObj); } +TEST(CollationIndexKeyTest, KeyStringAppendCorrectlySerializesEmptyComparisonKey) { + CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); + BSONObjBuilder builder; + builder.append("foo", StringData()); + + BSONObjBuilder expectedBuilder; + expectedBuilder.append("", StringData()); + assertKeyStringCollatorOutput(collator, builder.obj(), expectedBuilder.obj()); +} + TEST(CollationIndexKeyTest, CollationAwareAppendCorrectlySerializesWithEmbeddedNullByte) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); BSONObjBuilder builder; @@ -109,6 +153,16 @@ TEST(CollationIndexKeyTest, CollationAwareAppendCorrectlySerializesWithEmbeddedN ASSERT_BSONOBJ_EQ(out.obj(), expectedObj); } +TEST(CollationIndexKeyTest, KeyStringAppendCorrectlySerializesWithEmbeddedNullByte) { + CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); + BSONObjBuilder builder; + builder.append("foo", "a\0b"_sd); + + BSONObjBuilder expectedBuilder; + expectedBuilder.append("", "b\0a"_sd); + assertKeyStringCollatorOutput(collator, builder.obj(), expectedBuilder.obj()); +} + TEST(CollationIndexKeyTest, CollationAwareAppendCorrectlyReversesSimpleEmbeddedObject) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); BSONObj dataObj = BSON("" << BSON("a" @@ -121,6 +175,15 @@ TEST(CollationIndexKeyTest, CollationAwareAppendCorrectlyReversesSimpleEmbeddedO ASSERT_BSONOBJ_EQ(out.obj(), expected); } +TEST(CollationIndexKeyTest, KeyStringAppendCorrectlyReversesSimpleEmbeddedObject) { + CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); + BSONObj dataObj = BSON("" << BSON("a" + << "!foo")); + BSONObj expected = BSON("" << BSON("a" + << "oof!")); + assertKeyStringCollatorOutput(collator, dataObj, expected); +} + TEST(CollationIndexKeyTest, CollationAwareAppendCorrectlyReversesSimpleEmbeddedArray) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); BSONObj dataObj = BSON("" << BSON_ARRAY("foo" @@ -133,6 +196,15 @@ TEST(CollationIndexKeyTest, CollationAwareAppendCorrectlyReversesSimpleEmbeddedA ASSERT_BSONOBJ_EQ(out.obj(), expected); } +TEST(CollationIndexKeyTest, KeyStringAppendCorrectlyReversesSimpleEmbeddedArray) { + CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); + BSONObj dataObj = BSON("" << BSON_ARRAY("foo" + << "bar")); + BSONObj expected = BSON("" << BSON_ARRAY("oof" + << "rab")); + assertKeyStringCollatorOutput(collator, dataObj, expected); +} + TEST(CollationIndexKeyTest, CollationAwareAppendCorrectlyReversesComplexNesting) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); BSONObj dataObj = fromjson( @@ -149,6 +221,19 @@ TEST(CollationIndexKeyTest, CollationAwareAppendCorrectlyReversesComplexNesting) ASSERT_BSONOBJ_EQ(out.obj(), expected); } +TEST(CollationIndexKeyTest, KeyStringAppendCorrectlyReversesComplexNesting) { + CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); + BSONObj dataObj = fromjson( + "{ '' : [{'a': 'ha', 'b': 2}," + "'bar'," + "{'c': 2, 'd': 'ah', 'e': 'abc', 'f': ['cba', 'xyz']}]})"); + BSONObj expected = fromjson( + "{ '' : [{'a': 'ah', 'b': 2}," + "'rab'," + "{'c': 2, 'd': 'ha', 'e': 'cba', 'f': ['abc', 'zyx']}]})"); + assertKeyStringCollatorOutput(collator, dataObj, expected); +} + TEST(CollationIndexKeyTest, CollationAwareAppendThrowsIfSymbol) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); BSONObj dataObj = BSON("" << BSONSymbol("mySymbol")); @@ -159,6 +244,12 @@ TEST(CollationIndexKeyTest, CollationAwareAppendThrowsIfSymbol) { ErrorCodes::CannotBuildIndexKeys); } +TEST(CollationIndexKeyTest, KeyStringAppendThrowsIfSymbol) { + CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); + BSONObj dataObj = BSON("" << BSONSymbol("mySymbol")); + assertKeyStringCollatorThrows(collator, dataObj); +} + TEST(CollationIndexKeyTest, CollationAwareAppendDoesNotThrowOnSymbolIfNoCollation) { BSONObj dataObj = BSON("" << BSONSymbol("mySymbol")); BSONObj expected = BSON("" << BSONSymbol("mySymbol")); @@ -179,6 +270,14 @@ TEST(CollationIndexKeyTest, CollationAwareAppendThrowsIfSymbolInsideObject) { ErrorCodes::CannotBuildIndexKeys); } +TEST(CollationIndexKeyTest, KeyStringAppendThrowsIfSymbolInsideObject) { + CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); + BSONObj dataObj = BSON("" << BSON("a" + << "foo" + << "b" << BSONSymbol("mySymbol"))); + assertKeyStringCollatorThrows(collator, dataObj); +} + TEST(CollationIndexKeyTest, CollationAwareAppendThrowsIfSymbolInsideArray) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); BSONObj dataObj = BSON("" << BSON_ARRAY("foo" << BSONSymbol("mySymbol"))); @@ -189,4 +288,10 @@ TEST(CollationIndexKeyTest, CollationAwareAppendThrowsIfSymbolInsideArray) { ErrorCodes::CannotBuildIndexKeys); } +TEST(CollationIndexKeyTest, KeyStringAppendThrowsIfSymbolInsideArray) { + CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); + BSONObj dataObj = BSON("" << BSON_ARRAY("foo" << BSONSymbol("mySymbol"))); + assertKeyStringCollatorThrows(collator, dataObj); +} + } // namespace diff --git a/src/mongo/db/query/collation/collator_interface.cpp b/src/mongo/db/query/collation/collator_interface.cpp index d000ae85527..ae2dedbcde9 100644 --- a/src/mongo/db/query/collation/collator_interface.cpp +++ b/src/mongo/db/query/collation/collator_interface.cpp @@ -40,4 +40,8 @@ void CollatorInterface::hash_combine(size_t& seed, StringData stringToHash) cons SimpleStringDataComparator::kInstance.hash_combine(seed, comparisonKey.getKeyData()); } +std::string CollatorInterface::getComparisonString(StringData stringData) const { + return getComparisonKey(stringData).getKeyData().toString(); +} + } // namespace mongo diff --git a/src/mongo/db/query/collation/collator_interface.h b/src/mongo/db/query/collation/collator_interface.h index 7cba6aa2bb1..0e19137f8df 100644 --- a/src/mongo/db/query/collation/collator_interface.h +++ b/src/mongo/db/query/collation/collator_interface.h @@ -115,6 +115,12 @@ public: virtual ComparisonKey getComparisonKey(StringData stringData) const = 0; /** + * Returns the comparison key string for 'stringData', according to this collation. See + * ComparisonKey's comments for details. + */ + std::string getComparisonString(StringData stringData) const; + + /** * Returns whether this collation has the same matching and sorting semantics as 'other'. */ bool operator==(const CollatorInterface& other) const { diff --git a/src/mongo/db/storage/key_string.cpp b/src/mongo/db/storage/key_string.cpp index de20dc242c6..f8ec7b19180 100644 --- a/src/mongo/db/storage/key_string.cpp +++ b/src/mongo/db/storage/key_string.cpp @@ -342,14 +342,16 @@ void BuilderBase<BufferT>::resetToKey(const BSONObj& obj, } template <class BufferT> -void BuilderBase<BufferT>::appendBSONElement(const BSONElement& elem) { +void BuilderBase<BufferT>::appendBSONElement(const BSONElement& elem, const StringTransformFn& f) { + invariant(_state == BuildState::kEmpty || _state == BuildState::kAppendingBSONElements); + const int elemIdx = _elemCount++; const bool invert = (_ordering.get(elemIdx) == -1); if (_state == BuildState::kEmpty) { _transition(BuildState::kAppendingBSONElements); } - _appendBsonValue(elem, invert, nullptr); + _appendBsonValue(elem, invert, nullptr, f); } template <class BufferT> @@ -409,6 +411,7 @@ void BuilderBase<BufferT>::_appendAllElementsForIndexing(const BSONObj& obj, template <class BufferT> void BuilderBase<BufferT>::appendRecordId(RecordId loc) { + _doneAppending(); _transition(BuildState::kAppendedRecordID); // The RecordId encoding must be able to determine the full length starting from the last // byte, without knowing where the first byte is since it is stored at the end of a @@ -494,10 +497,14 @@ void BuilderBase<BufferT>::_appendOID(OID val, bool invert) { } template <class BufferT> -void BuilderBase<BufferT>::_appendString(StringData val, bool invert) { +void BuilderBase<BufferT>::_appendString(StringData val, bool invert, const StringTransformFn& f) { _typeBits.appendString(); _append(CType::kStringLike, invert); - _appendStringLike(val, invert); + if (f) { + _appendStringLike(f(val), invert); + } else { + _appendStringLike(val, invert); + } } template <class BufferT> @@ -517,7 +524,7 @@ template <class BufferT> void BuilderBase<BufferT>::_appendCodeWString(const BSONCodeWScope& val, bool invert) { _append(CType::kCodeWithScope, invert); _appendStringLike(val.code, invert); - _appendBson(val.scope, invert); + _appendBson(val.scope, invert, nullptr); } template <class BufferT> @@ -554,19 +561,23 @@ void BuilderBase<BufferT>::_appendDBRef(const BSONDBRef& val, bool invert) { } template <class BufferT> -void BuilderBase<BufferT>::_appendArray(const BSONArray& val, bool invert) { +void BuilderBase<BufferT>::_appendArray(const BSONArray& val, + bool invert, + const StringTransformFn& f) { _append(CType::kArray, invert); BSONForEach(elem, val) { // No generic ctype byte needed here since no name is encoded. - _appendBsonValue(elem, invert, nullptr); + _appendBsonValue(elem, invert, nullptr, f); } _append(int8_t(0), invert); } template <class BufferT> -void BuilderBase<BufferT>::_appendObject(const BSONObj& val, bool invert) { +void BuilderBase<BufferT>::_appendObject(const BSONObj& val, + bool invert, + const StringTransformFn& f) { _append(CType::kObject, invert); - _appendBson(val, invert); + _appendBson(val, invert, f); } template <class BufferT> @@ -823,7 +834,8 @@ void BuilderBase<BufferT>::_appendNumberDecimal(const Decimal128 dec, bool inver template <class BufferT> void BuilderBase<BufferT>::_appendBsonValue(const BSONElement& elem, bool invert, - const StringData* name) { + const StringData* name, + const StringTransformFn& f) { if (name) { _appendBytes(name->rawData(), name->size() + 1, invert); // + 1 for NUL } @@ -841,13 +853,13 @@ void BuilderBase<BufferT>::_appendBsonValue(const BSONElement& elem, _appendNumberDouble(elem._numberDouble(), invert); break; case String: - _appendString(elem.valueStringData(), invert); + _appendString(elem.valueStringData(), invert, f); break; case Object: - _appendObject(elem.Obj(), invert); + _appendObject(elem.Obj(), invert, f); break; case Array: - _appendArray(BSONArray(elem.Obj()), invert); + _appendArray(BSONArray(elem.Obj()), invert, f); break; case BinData: { int len; @@ -873,6 +885,13 @@ void BuilderBase<BufferT>::_appendBsonValue(const BSONElement& elem, _appendDBRef(BSONDBRef(elem.dbrefNS(), elem.dbrefOID()), invert); break; case Symbol: + if (f) { + uasserted( + ErrorCodes::CannotBuildIndexKeys, + str::stream() + << "Cannot index type Symbol with a collation. Failed to index element: " + << elem << "."); + } _appendSymbol(elem.valueStringData(), invert); break; case Code: @@ -924,12 +943,14 @@ void BuilderBase<BufferT>::_appendStringLike(StringData str, bool invert) { } template <class BufferT> -void BuilderBase<BufferT>::_appendBson(const BSONObj& obj, bool invert) { +void BuilderBase<BufferT>::_appendBson(const BSONObj& obj, + bool invert, + const StringTransformFn& f) { BSONForEach(elem, obj) { // Force the order to be based on (ctype, name, value). _append(bsonTypeToGenericKeyStringType(elem.type()), invert); StringData name = elem.fieldNameStringData(); - _appendBsonValue(elem, invert, &name); + _appendBsonValue(elem, invert, &name, f); } _append(int8_t(0), invert); } diff --git a/src/mongo/db/storage/key_string.h b/src/mongo/db/storage/key_string.h index aefa78c75a2..48529162659 100644 --- a/src/mongo/db/storage/key_string.h +++ b/src/mongo/db/storage/key_string.h @@ -354,6 +354,8 @@ enum DecimalContinuationMarker { kDCMHasContinuationLargerThanDoubleRoundedUpTo15Digits = 0x3 }; +using StringTransformFn = std::function<std::string(StringData)>; + template <class BufferT> class BuilderBase { public: @@ -424,7 +426,7 @@ public: */ template <typename T = BufferT> typename std::enable_if<std::is_same<T, BufBuilder>::value, Value>::type release() { - _prepareForRelease(); + _doneAppending(); _transition(BuildState::kReleased); return {version, _typeBits, static_cast<size_t>(_buffer.len()), _buffer.release()}; } @@ -434,7 +436,7 @@ public: * buffer. */ Value getValueCopy() { - _prepareForRelease(); + _doneAppending(); invariant(_state == BuildState::kEndAdded || _state == BuildState::kAppendedRecordID || _state == BuildState::kAppendedTypeBits); BufBuilder newBuf(_buffer.len()); @@ -444,7 +446,11 @@ public: void appendRecordId(RecordId loc); void appendTypeBits(const TypeBits& bits); - void appendBSONElement(const BSONElement& elem); + + /* + * Function 'f' will be applied to all string elements contained in 'elem'. + */ + void appendBSONElement(const BSONElement& elem, const StringTransformFn& f = nullptr); /** * Resets to an empty state. @@ -519,15 +525,15 @@ private: void _appendDate(Date_t val, bool invert); void _appendTimestamp(Timestamp val, bool invert); void _appendOID(OID val, bool invert); - void _appendString(StringData val, bool invert); + void _appendString(StringData val, bool invert, const StringTransformFn& f); void _appendSymbol(StringData val, bool invert); void _appendCode(StringData val, bool invert); void _appendCodeWString(const BSONCodeWScope& val, bool invert); void _appendBinData(const BSONBinData& val, bool invert); void _appendRegex(const BSONRegEx& val, bool invert); void _appendDBRef(const BSONDBRef& val, bool invert); - void _appendArray(const BSONArray& val, bool invert); - void _appendObject(const BSONObj& val, bool invert); + void _appendArray(const BSONArray& val, bool invert, const StringTransformFn& f); + void _appendObject(const BSONObj& val, bool invert, const StringTransformFn& f); void _appendNumberDouble(const double num, bool invert); void _appendNumberLong(const long long num, bool invert); void _appendNumberInt(const int num, bool invert); @@ -538,10 +544,13 @@ private: * if NULL, not included in encoding * if not NULL, put in after type, before value */ - void _appendBsonValue(const BSONElement& elem, bool invert, const StringData* name); + void _appendBsonValue(const BSONElement& elem, + bool invert, + const StringData* name, + const StringTransformFn& f); void _appendStringLike(StringData str, bool invert); - void _appendBson(const BSONObj& obj, bool invert); + void _appendBson(const BSONObj& obj, bool invert, const StringTransformFn& f); void _appendSmallDouble(double value, DecimalContinuationMarker dcm, bool invert); void _appendLargeDouble(double value, DecimalContinuationMarker dcm, bool invert); void _appendInteger(const long long num, bool invert); @@ -560,7 +569,7 @@ private: void _appendBytes(const void* source, size_t bytes, bool invert); - void _prepareForRelease() { + void _doneAppending() { if (_state == BuildState::kAppendingBSONElements) { _appendDiscriminator(_discriminator); } @@ -631,39 +640,39 @@ struct isKeyString<BuilderBase<BufferT>> : public std::true_type {}; template <> struct isKeyString<Value> : public std::true_type {}; -template <class T> +template <class T, class U> inline typename std::enable_if<isKeyString<T>::value, bool>::type operator<(const T& lhs, - const T& rhs) { + const U& rhs) { return lhs.compare(rhs) < 0; } -template <class T> +template <class T, class U> inline typename std::enable_if<isKeyString<T>::value, bool>::type operator<=(const T& lhs, - const T& rhs) { + const U& rhs) { return lhs.compare(rhs) <= 0; } -template <class T> +template <class T, class U> inline typename std::enable_if<isKeyString<T>::value, bool>::type operator==(const T& lhs, - const T& rhs) { + const U& rhs) { return lhs.compare(rhs) == 0; } -template <class T> +template <class T, class U> inline typename std::enable_if<isKeyString<T>::value, bool>::type operator>(const T& lhs, - const T& rhs) { + const U& rhs) { return lhs.compare(rhs) > 0; } -template <class T> +template <class T, class U> inline typename std::enable_if<isKeyString<T>::value, bool>::type operator>=(const T& lhs, - const T& rhs) { + const U& rhs) { return lhs.compare(rhs) >= 0; } -template <class T> +template <class T, class U> inline typename std::enable_if<isKeyString<T>::value, bool>::type operator!=(const T& lhs, - const T& rhs) { + const U& rhs) { return !(lhs == rhs); } |