// key_string_test.cpp /** * Copyright (C) 2014 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 . * * 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. */ #define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kStorage #include #include "mongo/platform/basic.h" #include "mongo/config.h" #include "mongo/db/storage/key_string.h" #include "mongo/unittest/unittest.h" #include "mongo/util/hex.h" #include "mongo/util/log.h" #include "mongo/base/owned_pointer_vector.h" using std::string; using namespace mongo; BSONObj toBson(const KeyString& ks, Ordering ord) { return KeyString::toBson(ks.getBuffer(), ks.getSize(), ord, ks.getTypeBits()); } Ordering ALL_ASCENDING = Ordering::make(BSONObj()); Ordering ONE_ASCENDING = Ordering::make(BSON("a" << 1)); Ordering ONE_DESCENDING = Ordering::make(BSON("a" << -1)); TEST(KeyStringTest, Simple1) { BSONObj a = BSON("" << 5); BSONObj b = BSON("" << 6); ASSERT_LESS_THAN(a, b); ASSERT_LESS_THAN(KeyString(a, ALL_ASCENDING, RecordId()), KeyString(b, ALL_ASCENDING, RecordId())); } #define ROUNDTRIP_ORDER(x, order) do { \ const BSONObj _orig = x; \ const KeyString _ks(_orig, order); \ const BSONObj _converted = toBson(_ks, order); \ ASSERT_EQ(_converted, _orig); \ ASSERT(_converted.binaryEqual(_orig)); \ } while (0) #define ROUNDTRIP(x) do { \ ROUNDTRIP_ORDER(x, ALL_ASCENDING); \ ROUNDTRIP_ORDER(x, ONE_DESCENDING); \ } while (0) #define COMPARES_SAME(_x,_y) do { \ KeyString _xKS(_x, ONE_ASCENDING); \ KeyString _yKS(_y, ONE_ASCENDING); \ if (_x == _y) { \ ASSERT_EQUALS(_xKS, _yKS); \ } \ else if (_x < _y) { \ ASSERT_LESS_THAN(_xKS, _yKS); \ } \ else { \ ASSERT_LESS_THAN(_yKS, _xKS); \ } \ \ _xKS.resetToKey(_x, ONE_DESCENDING); \ _yKS.resetToKey(_y, ONE_DESCENDING); \ if (_x == _y) { \ ASSERT_EQUALS(_xKS, _yKS); \ } \ else if (_x < _y) { \ ASSERT_GREATER_THAN(_xKS, _yKS); \ } \ else { \ ASSERT_GREATER_THAN(_yKS, _xKS); \ } \ } while (0) TEST(KeyStringTest, ActualBytesDouble) { // just one test like this for utter sanity BSONObj a = BSON("" << 5.5 ); KeyString ks(a, ALL_ASCENDING); log() << "size: " << ks.getSize() << " hex [" << toHex(ks.getBuffer(), ks.getSize()) << "]"; ASSERT_EQUALS(10U, ks.getSize()); string hex = "2B" // kNumericPositive1ByteInt "0B" // (5 << 1) | 1 "02000000000000" // fractional bytes of double "04"; // kEnd ASSERT_EQUALS(hex, toHex(ks.getBuffer(), ks.getSize())); ks.resetToKey(a, Ordering::make(BSON("a" << -1))); ASSERT_EQUALS(10U, ks.getSize()); // last byte (kEnd) doesn't get flipped string hexFlipped; for ( size_t i = 0; i < hex.size()-2; i += 2 ) { char c = fromHex(hex.c_str() + i); c = ~c; hexFlipped += toHex(&c, 1); } hexFlipped += hex.substr(hex.size()-2); ASSERT_EQUALS(hexFlipped, toHex(ks.getBuffer(), ks.getSize())); } TEST(KeyStringTest, AllTypesSimple) { ROUNDTRIP(BSON("" << 5.5)); ROUNDTRIP(BSON("" << "abc")); ROUNDTRIP(BSON("" << BSON("a" << 5))); ROUNDTRIP(BSON("" << BSON_ARRAY("a" << 5))); ROUNDTRIP(BSON("" << BSONBinData( "abc", 3, bdtCustom ))); ROUNDTRIP(BSON("" << BSONUndefined)); ROUNDTRIP(BSON("" << OID("abcdefabcdefabcdefabcdef"))); ROUNDTRIP(BSON("" << true)); ROUNDTRIP(BSON("" << Date_t::fromMillisSinceEpoch(123123123))); ROUNDTRIP(BSON("" << BSONRegEx("asdf", "x"))); ROUNDTRIP(BSON("" << BSONDBRef("db.c", OID("010203040506070809101112")))); ROUNDTRIP(BSON("" << BSONCode("abc_code"))); ROUNDTRIP(BSON("" << BSONCodeWScope("def_code", BSON("x_scope" << "a")))); ROUNDTRIP(BSON("" << 5)); ROUNDTRIP(BSON("" << Timestamp(123123, 123))); ROUNDTRIP(BSON("" << 1235123123123LL)); } TEST(KeyStringTest, Array1) { BSONObj emptyArray = BSON("" << BSONArray()); ASSERT_EQUALS(Array, emptyArray.firstElement().type()); ROUNDTRIP(emptyArray); ROUNDTRIP(BSON("" << BSON_ARRAY(emptyArray.firstElement()))); ROUNDTRIP(BSON("" << BSON_ARRAY(1))); ROUNDTRIP(BSON("" << BSON_ARRAY(1 << 2))); ROUNDTRIP(BSON("" << BSON_ARRAY(1 << 2 << 3))); { KeyString a(emptyArray, ALL_ASCENDING, RecordId::min()); KeyString b(emptyArray, ALL_ASCENDING, RecordId(5)); ASSERT_LESS_THAN(a, b); } { KeyString a(emptyArray, ALL_ASCENDING, RecordId(0)); KeyString b(emptyArray, ALL_ASCENDING, RecordId(5)); ASSERT_LESS_THAN(a, b); } } TEST(KeyStringTest, SubDoc1) { ROUNDTRIP(BSON("" << BSON("foo" << 2))); ROUNDTRIP(BSON("" << BSON("foo" << 2 << "bar" << "asd"))); ROUNDTRIP(BSON("" << BSON("foo" << BSON_ARRAY(2 << 4)))); } TEST(KeyStringTest, SubDoc2) { BSONObj a = BSON("" << BSON("a" << "foo")); BSONObj b = BSON("" << BSON("b" << 5.5)); BSONObj c = BSON("" << BSON("c" << BSON("x" << 5))); ROUNDTRIP(a); ROUNDTRIP(b); ROUNDTRIP(c); COMPARES_SAME(a,b); COMPARES_SAME(a,c); COMPARES_SAME(b,c); } TEST(KeyStringTest, Compound1) { ROUNDTRIP(BSON("" << BSON("a" << 5) << "" << 1)); ROUNDTRIP(BSON("" << BSON("" << 5) << "" << 1)); } TEST(KeyStringTest, Undef1) { ROUNDTRIP(BSON("" << BSONUndefined)); } TEST(KeyStringTest, NumberLong0) { double d = (1ll << 52) - 1; long long ll = static_cast(d); double d2 = static_cast(ll); ASSERT_EQUALS(d, d2); } TEST(KeyStringTest, NumbersNearInt32Max) { int64_t start = std::numeric_limits::max(); for (int64_t i = -1000; i < 1000; i++) { long long toTest = start + i; ROUNDTRIP(BSON("" << toTest)); ROUNDTRIP(BSON("" << static_cast(toTest))); ROUNDTRIP(BSON("" << static_cast(toTest))); } } TEST(KeyStringTest, LotsOfNumbers1) { for (int i = 0; i < 64; i++) { int64_t x = 1LL << i; ROUNDTRIP(BSON("" << static_cast(x))); ROUNDTRIP(BSON("" << static_cast(x))); ROUNDTRIP(BSON("" << static_cast(x))); ROUNDTRIP(BSON("" << (static_cast(x) + .1))); ROUNDTRIP(BSON("" << (static_cast(x) - .1))); ROUNDTRIP(BSON("" << (static_cast(x) + 1))); ROUNDTRIP(BSON("" << (static_cast(x) + 1))); ROUNDTRIP(BSON("" << (static_cast(x) + 1))); ROUNDTRIP(BSON("" << (static_cast(x) + 1.1))); ROUNDTRIP(BSON("" << -static_cast(x))); ROUNDTRIP(BSON("" << -static_cast(x))); ROUNDTRIP(BSON("" << -static_cast(x))); ROUNDTRIP(BSON("" << -(static_cast(x) + .1))); ROUNDTRIP(BSON("" << -(static_cast(x) + 1))); ROUNDTRIP(BSON("" << -(static_cast(x) + 1))); ROUNDTRIP(BSON("" << -(static_cast(x) + 1))); ROUNDTRIP(BSON("" << -(static_cast(x) + 1.1))); } } TEST(KeyStringTest, LotsOfNumbers2) { for (double i = -1100; i < 1100; i++) { double x = pow(2, i); ROUNDTRIP(BSON("" << x)); } for (double i = -1100; i < 1100; i++) { double x = pow(2.1, i); ROUNDTRIP(BSON("" << x)); } } TEST(KeyStringTest, RecordIdOrder1) { Ordering ordering = Ordering::make(BSON("a" << 1)); KeyString a(BSON("" << 5), ordering, RecordId::min()); KeyString b(BSON("" << 5), ordering, RecordId(2)); KeyString c(BSON("" << 5), ordering, RecordId(3)); KeyString d(BSON("" << 6), ordering, RecordId()); KeyString e(BSON("" << 6), ordering, RecordId(1)); ASSERT_LESS_THAN(a, b); ASSERT_LESS_THAN(b, c); ASSERT_LESS_THAN(c, d); ASSERT_LESS_THAN(d, e); } TEST(KeyStringTest, RecordIdOrder2) { Ordering ordering = Ordering::make(BSON("a" << -1 << "b" << -1)); KeyString a(BSON("" << 5 << "" << 6), ordering, RecordId::min()); KeyString b(BSON("" << 5 << "" << 6), ordering, RecordId(5)); KeyString c(BSON("" << 5 << "" << 5), ordering, RecordId(4)); KeyString d(BSON("" << 3 << "" << 4), ordering, RecordId(3)); ASSERT_LESS_THAN(a, b); ASSERT_LESS_THAN(b, c); ASSERT_LESS_THAN(c, d); ASSERT_LESS_THAN(a, c); ASSERT_LESS_THAN(a, d); ASSERT_LESS_THAN(b, d); } TEST(KeyStringTest, RecordIdOrder2Double) { Ordering ordering = Ordering::make(BSON("a" << -1 << "b" << -1)); KeyString a(BSON("" << 5.0 << "" << 6.0), ordering, RecordId::min()); KeyString b(BSON("" << 5.0 << "" << 6.0), ordering, RecordId(5)); KeyString c(BSON("" << 3.0 << "" << 4.0), ordering, RecordId(3)); ASSERT_LESS_THAN(a, b); ASSERT_LESS_THAN(b, c); ASSERT_LESS_THAN(a, c); } TEST(KeyStringTest, Timestamp) { BSONObj a = BSON("" << Timestamp(0, 0)); BSONObj b = BSON("" << Timestamp(1234, 1)); BSONObj c = BSON("" << Timestamp(1234, 2)); BSONObj d = BSON("" << Timestamp(1235, 1)); { ROUNDTRIP(a); ROUNDTRIP(b); ROUNDTRIP(c); ASSERT_LESS_THAN(a, b); ASSERT_LESS_THAN(b, c); ASSERT_LESS_THAN(c, d); KeyString ka(a, ALL_ASCENDING); KeyString kb(b, ALL_ASCENDING); KeyString kc(c, ALL_ASCENDING); KeyString kd(d, ALL_ASCENDING); ASSERT(ka.compare(kb) < 0); ASSERT(kb.compare(kc) < 0); ASSERT(kc.compare(kd) < 0); } { Ordering ALL_ASCENDING = Ordering::make(BSON("a" << -1)); ROUNDTRIP(a); ROUNDTRIP(b); ROUNDTRIP(c); ASSERT(d.woCompare(c, ALL_ASCENDING) < 0); ASSERT(c.woCompare(b, ALL_ASCENDING) < 0); ASSERT(b.woCompare(a, ALL_ASCENDING) < 0); KeyString ka(a, ALL_ASCENDING); KeyString kb(b, ALL_ASCENDING); KeyString kc(c, ALL_ASCENDING); KeyString kd(d, ALL_ASCENDING); ASSERT(ka.compare(kb) > 0); ASSERT(kb.compare(kc) > 0); ASSERT(kc.compare(kd) > 0); } } TEST(KeyStringTest, AllTypesRoundtrip) { for ( int i = 1; i <= JSTypeMax; i++ ) { { BSONObjBuilder b; b.appendMinForType("", i ); BSONObj o = b.obj(); ROUNDTRIP(o); } { BSONObjBuilder b; b.appendMaxForType("", i ); BSONObj o = b.obj(); ROUNDTRIP(o); } } } const std::vector& getInterestingElements() { static std::vector elements; if (!elements.empty()) { return elements; } // These are used to test strings that include NUL bytes. const StringData ball("ball", StringData::LiteralTag()); const StringData ball00n("ball\0\0n", StringData::LiteralTag()); elements.push_back(BSON("" << 1)); elements.push_back(BSON("" << 1.0)); elements.push_back(BSON("" << 1LL)); elements.push_back(BSON("" << 123456789123456789LL)); elements.push_back(BSON("" << -123456789123456789LL)); elements.push_back(BSON("" << 112353998331165715LL)); elements.push_back(BSON("" << 112353998331165710LL)); elements.push_back(BSON("" << 1123539983311657199LL)); elements.push_back(BSON("" << 123456789123456789.123)); elements.push_back(BSON("" << -123456789123456789.123)); elements.push_back(BSON("" << 112353998331165715.0)); elements.push_back(BSON("" << 112353998331165710.0)); elements.push_back(BSON("" << 1123539983311657199.0)); elements.push_back(BSON("" << 5.0)); elements.push_back(BSON("" << 5)); elements.push_back(BSON("" << 2)); elements.push_back(BSON("" << -2)); elements.push_back(BSON("" << -2.2)); elements.push_back(BSON("" << -12312312.2123123123123)); elements.push_back(BSON("" << 12312312.2123123123123)); elements.push_back(BSON("" << "aaa")); elements.push_back(BSON("" << "AAA")); elements.push_back(BSON("" << ball)); elements.push_back(BSON("" << ball00n)); elements.push_back(BSON("" << BSONSymbol(ball))); elements.push_back(BSON("" << BSONSymbol(ball00n))); elements.push_back(BSON("" << BSON("a" << 5))); elements.push_back(BSON("" << BSON("a" << 6))); elements.push_back(BSON("" << BSON("b" << 6))); elements.push_back(BSON("" << BSON_ARRAY("a" << 5))); elements.push_back(BSON("" << BSONNULL)); elements.push_back(BSON("" << BSONUndefined)); elements.push_back(BSON("" << OID("abcdefabcdefabcdefabcdef"))); elements.push_back(BSON("" << Date_t::fromMillisSinceEpoch(123))); elements.push_back(BSON("" << BSONCode("abc_code"))); elements.push_back(BSON("" << BSONCode(ball))); elements.push_back(BSON("" << BSONCode(ball00n))); elements.push_back(BSON("" << BSONCodeWScope("def_code1", BSON("x_scope" << "a")))); elements.push_back(BSON("" << BSONCodeWScope("def_code2", BSON("x_scope" << "a")))); elements.push_back(BSON("" << BSONCodeWScope("def_code2", BSON("x_scope" << "b")))); elements.push_back(BSON("" << BSONCodeWScope(ball, BSON("a" << 1)))); elements.push_back(BSON("" << BSONCodeWScope(ball00n, BSON("a" << 1)))); elements.push_back(BSON("" << true)); elements.push_back(BSON("" << false)); // Something that needs multiple bytes of typeBits elements.push_back(BSON("" << BSON_ARRAY("" << BSONSymbol("") << 0 << 0ll << 0.0 << -0.0 ))); // // Interesting numeric cases // elements.push_back(BSON("" << 0)); elements.push_back(BSON("" << 0ll)); elements.push_back(BSON("" << 0.0)); elements.push_back(BSON("" << -0.0)); elements.push_back(BSON("" << std::numeric_limits::quiet_NaN())); elements.push_back(BSON("" << std::numeric_limits::infinity())); elements.push_back(BSON("" << -std::numeric_limits::infinity())); elements.push_back(BSON("" << std::numeric_limits::max())); elements.push_back(BSON("" << -std::numeric_limits::max())); elements.push_back(BSON("" << std::numeric_limits::min())); elements.push_back(BSON("" << -std::numeric_limits::min())); elements.push_back(BSON("" << std::numeric_limits::denorm_min())); elements.push_back(BSON("" << -std::numeric_limits::denorm_min())); elements.push_back(BSON("" << std::numeric_limits::denorm_min())); elements.push_back(BSON("" << -std::numeric_limits::denorm_min())); elements.push_back(BSON("" << std::numeric_limits::max())); elements.push_back(BSON("" << -std::numeric_limits::max())); elements.push_back(BSON("" << std::numeric_limits::min())); elements.push_back(BSON("" << std::numeric_limits::max())); elements.push_back(BSON("" << -std::numeric_limits::max())); elements.push_back(BSON("" << std::numeric_limits::min())); for (int powerOfTwo = 0; powerOfTwo < 63; powerOfTwo++) { const long long lNum = 1ll << powerOfTwo; const double dNum = double(lNum); // All powers of two in this range can be represented exactly as doubles. invariant(lNum == static_cast(dNum)); elements.push_back(BSON("" << lNum)); elements.push_back(BSON("" << -lNum)); elements.push_back(BSON("" << dNum)); elements.push_back(BSON("" << -dNum)); elements.push_back(BSON("" << (lNum + 1))); elements.push_back(BSON("" << (lNum - 1))); elements.push_back(BSON("" << (-lNum + 1))); elements.push_back(BSON("" << (-lNum - 1))); if (powerOfTwo <= 52) { // is dNum - 0.5 representable? elements.push_back(BSON("" << (dNum - 0.5))); elements.push_back(BSON("" << -(dNum - 0.5))); } if (powerOfTwo <= 51) { // is dNum + 0.5 representable? elements.push_back(BSON("" << (dNum + 0.5))); elements.push_back(BSON("" << -(dNum + 0.5))); } } { // Numbers around +/- numeric_limits::max() which can't be represented // precisely as a double. const long long maxLL = std::numeric_limits::max(); const double closestAbove = 9223372036854775808.0; // 2**63 const double closestBelow = 9223372036854774784.0; // 2**63 - epsilon elements.push_back(BSON("" << maxLL)); elements.push_back(BSON("" << (maxLL - 1))); elements.push_back(BSON("" << closestAbove)); elements.push_back(BSON("" << closestBelow)); elements.push_back(BSON("" << -maxLL)); elements.push_back(BSON("" << -(maxLL - 1))); elements.push_back(BSON("" << -closestAbove)); elements.push_back(BSON("" << -closestBelow)); } { // Numbers around numeric_limits::min() which can be represented precisely as // a double, but not as a positive long long. const long long minLL = std::numeric_limits::min(); const double closestBelow = -9223372036854777856.0; // -2**63 - epsilon const double equal = -9223372036854775808.0; // 2**63 const double closestAbove = -9223372036854774784.0; // -2**63 + epsilon elements.push_back(BSON("" << minLL)); elements.push_back(BSON("" << equal)); elements.push_back(BSON("" << closestAbove)); elements.push_back(BSON("" << closestBelow)); } return elements; } void testPermutation(const std::vector& elementsOrig, const std::vector& orderings, bool debug) { // Since KeyStrings are compared using memcmp we can assume it provides a total ordering such // that there won't be cases where (a < b && b < c && !(a < c)). This test still needs to ensure // that it provides the *correct* total ordering. for (size_t k = 0; k < orderings.size(); k++) { BSONObj orderObj = orderings[k]; Ordering ordering = Ordering::make(orderObj); if (debug) log() << "ordering: " << orderObj; std::vector elements = elementsOrig; std::stable_sort(elements.begin(), elements.end(), BSONObjCmp(orderObj)); for (size_t i = 0; i < elements.size(); i++) { const BSONObj& o1 = elements[i]; if (debug) log() << "\to1: " << o1; ROUNDTRIP_ORDER(o1, ordering); KeyString k1(o1, ordering); KeyString l1(BSON("l" << o1.firstElement()), ordering); // kLess KeyString g1(BSON("g" << o1.firstElement()), ordering); // kGreater ASSERT_LT(l1, k1); ASSERT_GT(g1, k1); if (i + 1 < elements.size()) { const BSONObj& o2 = elements[i + 1]; if (debug) log() << "\t\t o2: " << o2; KeyString k2(o2, ordering); KeyString g2(BSON("g" << o2.firstElement()), ordering); KeyString l2(BSON("l" << o2.firstElement()), ordering); int bsonCmp = o1.woCompare(o2, ordering); invariant(bsonCmp <= 0); // We should be sorted... if (bsonCmp == 0) { ASSERT_EQ(k1, k2); } else { ASSERT_LT(k1, k2); } // Test the query encodings using kLess and kGreater int firstElementComp = o1.firstElement().woCompare(o2.firstElement()); if (ordering.descending(1)) firstElementComp = -firstElementComp; invariant(firstElementComp <= 0); if (firstElementComp == 0) { // If they share a first element then l1/g1 should equal l2/g2 and l1 should be // less than both and g1 should be greater than both. ASSERT_EQ(l1, l2); ASSERT_EQ(g1, g2); ASSERT_LT(l1, k2); ASSERT_GT(g1, k2); } else { // k1 is less than k2. Less(k2) and Greater(k1) should be between them. ASSERT_LT(g1, k2); ASSERT_GT(l2, k1); } } } } } TEST(KeyStringTest, AllPermCompare) { const std::vector& elements = getInterestingElements(); for (size_t i = 0; i < elements.size(); i++) { const BSONObj& o = elements[i]; ROUNDTRIP(o); } std::vector orderings; orderings.push_back(BSON("a" << 1)); orderings.push_back(BSON("a" << -1)); testPermutation(elements, orderings, false); } TEST(KeyStringTest, AllPerm2Compare) { // This test can take over a minute without optimizations. Re-enable if you need to debug it. #if !defined(MONGO_CONFIG_OPTIMIZED_BUILD) log() << "\t\t\tskipping test on non-optimized build"; return; #endif const std::vector& baseElements = getInterestingElements(); std::vector elements; for (size_t i = 0; i < baseElements.size(); i++) { for (size_t j = 0; j < baseElements.size(); j++) { BSONObjBuilder b; b.appendElements(baseElements[i]); b.appendElements(baseElements[j]); BSONObj o = b.obj(); elements.push_back(o); } } log() << "AllPrem2Compare size:" << elements.size(); for (size_t i = 0; i < elements.size(); i++) { const BSONObj& o = elements[i]; ROUNDTRIP(o); } std::vector orderings; orderings.push_back(BSON("a" << 1 << "b" << 1)); orderings.push_back(BSON("a" << -1 << "b" << 1)); orderings.push_back(BSON("a" << 1 << "b" << -1)); orderings.push_back(BSON("a" << -1 << "b" << -1)); testPermutation(elements, orderings, false); } #define COMPARE_HELPER(LHS, RHS) \ (((LHS) < (RHS)) ? -1 : (((LHS) == (RHS)) ? 0 : 1)) int compareLongToDouble(long long lhs, double rhs) { if (rhs >= std::numeric_limits::max()) return -1; if (rhs < std::numeric_limits::min() ) return 1; if (fabs(rhs) >= (1LL << 52)) { return COMPARE_HELPER(lhs, static_cast(rhs)); } return COMPARE_HELPER(static_cast(lhs), rhs); } int compareNumbers(const BSONElement& lhs, const BSONElement& rhs ) { invariant(lhs.isNumber()); invariant(rhs.isNumber()); if (lhs.type() == NumberInt || lhs.type() == NumberLong) { if (rhs.type() == NumberInt || rhs.type() == NumberLong) { return COMPARE_HELPER(lhs.numberLong(), rhs.numberLong()); } return compareLongToDouble(lhs.numberLong(), rhs.Double()); } else { // double if (rhs.type() == NumberDouble) { return COMPARE_HELPER(lhs.Double(), rhs.Double()); } return -compareLongToDouble(rhs.numberLong(), lhs.Double()); } } TEST(KeyStringTest, NaNs) { // TODO use hex floats to force distinct NaNs const double nan1 = std::numeric_limits::quiet_NaN(); const double nan2 = std::numeric_limits::signaling_NaN(); // Since only output a single NaN, we can't use the normal ROUNDTRIP testing here. const KeyString ks1a(BSON("" << nan1), ONE_ASCENDING); const KeyString ks1d(BSON("" << nan1), ONE_DESCENDING); const KeyString ks2a(BSON("" << nan2), ONE_ASCENDING); const KeyString ks2d(BSON("" << nan2), ONE_DESCENDING); ASSERT_EQ(ks1a, ks2a); ASSERT_EQ(ks1d, ks2d); ASSERT(std::isnan(toBson(ks1a, ONE_ASCENDING)[""].Double())); ASSERT(std::isnan(toBson(ks2a, ONE_ASCENDING)[""].Double())); ASSERT(std::isnan(toBson(ks1d, ONE_DESCENDING)[""].Double())); ASSERT(std::isnan(toBson(ks2d, ONE_DESCENDING)[""].Double())); } TEST(KeyStringTest, NumberOrderLots) { std::vector numbers; { numbers.push_back(BSON("" << 0)); numbers.push_back(BSON("" << 0.0)); numbers.push_back(BSON("" << -0.0)); numbers.push_back(BSON("" << std::numeric_limits::min())); numbers.push_back(BSON("" << std::numeric_limits::max())); numbers.push_back(BSON("" << static_cast(std::numeric_limits::min()))); numbers.push_back(BSON("" << static_cast(std::numeric_limits::max()))); numbers.push_back(BSON("" << std::numeric_limits::min())); numbers.push_back(BSON("" << std::numeric_limits::max())); numbers.push_back(BSON("" << std::numeric_limits::min())); numbers.push_back(BSON("" << std::numeric_limits::max())); numbers.push_back(BSON("" << std::numeric_limits::min())); numbers.push_back(BSON("" << std::numeric_limits::max())); for (int i = 0; i < 64; i++) { int64_t x = 1LL << i; numbers.push_back(BSON("" << static_cast(x))); numbers.push_back(BSON("" << static_cast(x))); numbers.push_back(BSON("" << static_cast(x))); numbers.push_back(BSON("" << (static_cast(x) + .1))); numbers.push_back(BSON("" << (static_cast(x) + 1))); numbers.push_back(BSON("" << (static_cast(x) + 1))); numbers.push_back(BSON("" << (static_cast(x) + 1))); numbers.push_back(BSON("" << (static_cast(x) + 1.1))); numbers.push_back(BSON("" << -static_cast(x))); numbers.push_back(BSON("" << -static_cast(x))); numbers.push_back(BSON("" << -static_cast(x))); numbers.push_back(BSON("" << -(static_cast(x) + .1))); numbers.push_back(BSON("" << -(static_cast(x) + 1))); numbers.push_back(BSON("" << -(static_cast(x) + 1))); numbers.push_back(BSON("" << -(static_cast(x) + 1))); numbers.push_back(BSON("" << -(static_cast(x) + 1.1))); } for (double i = 0; i < 1000; i++) { double x = pow(2.1, i); numbers.push_back(BSON("" << x)); } } Ordering ordering = Ordering::make(BSON("a" << 1)); OwnedPointerVector keyStrings; for (size_t i = 0; i < numbers.size(); i++) { keyStrings.push_back(new KeyString(numbers[i], ordering)); } for (size_t i = 0; i < numbers.size(); i++) { for (size_t j = 0; j < numbers.size(); j++) { const KeyString& a = *keyStrings[i]; const KeyString& b = *keyStrings[j]; ASSERT_EQUALS(a.compare(b), -b.compare(a)); if (a.compare(b) != compareNumbers(numbers[i].firstElement(), numbers[j].firstElement())) { log() << numbers[i] << " " << numbers[j]; } ASSERT_EQUALS(a.compare(b), compareNumbers(numbers[i].firstElement(), numbers[j].firstElement())); } } } TEST(KeyStringTest, RecordIds) { for (int i = 0; i < 63; i++) { const RecordId rid = RecordId(1ll << i); { // Test encoding / decoding of single RecordIds const KeyString ks(rid); ASSERT_GTE(ks.getSize(), 2u); ASSERT_LTE(ks.getSize(), 10u); ASSERT_EQ(KeyString::decodeRecordIdAtEnd(ks.getBuffer(), ks.getSize()), rid); { BufReader reader(ks.getBuffer(), ks.getSize()); ASSERT_EQ(KeyString::decodeRecordId(&reader), rid); ASSERT(reader.atEof()); } if (rid.isNormal()) { ASSERT_GT(ks, KeyString(RecordId())); ASSERT_GT(ks, KeyString(RecordId::min())); ASSERT_LT(ks, KeyString(RecordId::max())); ASSERT_GT(ks, KeyString(RecordId(rid.repr() - 1))); ASSERT_LT(ks, KeyString(RecordId(rid.repr() + 1))); } } for (int j = 0; j < 63; j++) { RecordId other = RecordId(1ll << j); if (rid == other) ASSERT_EQ(KeyString(rid), KeyString(other)); if (rid < other) ASSERT_LT(KeyString(rid), KeyString(other)); if (rid > other) ASSERT_GT(KeyString(rid), KeyString(other)); { // Test concatenating RecordIds like in a unique index. KeyString ks; ks.appendRecordId(RecordId::max()); // uses all bytes ks.appendRecordId(rid); ks.appendRecordId(RecordId(0xDEADBEEF)); // uses some extra bytes ks.appendRecordId(rid); ks.appendRecordId(RecordId(1)); // uses no extra bytes ks.appendRecordId(rid); ks.appendRecordId(other); ASSERT_EQ(KeyString::decodeRecordIdAtEnd(ks.getBuffer(), ks.getSize()), other); // forward scan BufReader reader(ks.getBuffer(), ks.getSize()); ASSERT_EQ(KeyString::decodeRecordId(&reader), RecordId::max()); ASSERT_EQ(KeyString::decodeRecordId(&reader), rid); ASSERT_EQ(KeyString::decodeRecordId(&reader), RecordId(0xDEADBEEF)); ASSERT_EQ(KeyString::decodeRecordId(&reader), rid); ASSERT_EQ(KeyString::decodeRecordId(&reader), RecordId(1)); ASSERT_EQ(KeyString::decodeRecordId(&reader), rid); ASSERT_EQ(KeyString::decodeRecordId(&reader), other); ASSERT(reader.atEof()); } } } }