diff --git a/src/mongo/db/catalog/index_catalog_entry.cpp b/src/mongo/db/catalog/index_catalog_entry.cpp index 9b6f5f63d6d23..1b91c5710d083 100644 --- a/src/mongo/db/catalog/index_catalog_entry.cpp +++ b/src/mongo/db/catalog/index_catalog_entry.cpp @@ -44,11 +44,11 @@ namespace mongo { HeadManagerImpl(IndexCatalogEntry* ice) : _catalogEntry(ice) { } virtual ~HeadManagerImpl() { } - const DiskLoc& getHead() const { + const DiskLoc getHead() const { return _catalogEntry->head(); } - void setHead(OperationContext* txn, const DiskLoc& newHead) { + void setHead(OperationContext* txn, const DiskLoc newHead) { _catalogEntry->setHead(txn, newHead); } diff --git a/src/mongo/db/structure/btree/SConscript b/src/mongo/db/structure/btree/SConscript index 93a0594ebb747..d672599c50eca 100644 --- a/src/mongo/db/structure/btree/SConscript +++ b/src/mongo/db/structure/btree/SConscript @@ -1,15 +1,35 @@ -# -*- mode: python -*- - -Import("env") - -env.Library( - target= 'btree', - source= [ - 'btree_logic.cpp', - 'btree_interface.cpp', - 'key.cpp' - ], - LIBDEPS= [ - '$BUILD_DIR/mongo/bson' - ] - ) +# -*- mode: python -*- + +Import("env") + +env.Library( + target= 'btree', + source= [ + 'btree_logic.cpp', + 'btree_interface.cpp', + 'key.cpp' + ], + LIBDEPS= [ + '$BUILD_DIR/mongo/bson' + ] + ) + +env.Library( + target= 'btree_test_help', + source= [ + 'btree_test_help.cpp' + ], + LIBDEPS= [ + 'btree', + '$BUILD_DIR/mongo/db/structure/record_store_v1_test_help' + ] + ) + +env.CppUnitTest( + target='btree_logic_test', + source=['btree_logic_test.cpp' + ], + LIBDEPS=[ + 'btree_test_help' + ] + ) diff --git a/src/mongo/db/structure/btree/btree_logic.cpp b/src/mongo/db/structure/btree/btree_logic.cpp index 49b2f9fb464fc..e8a2245cba682 100644 --- a/src/mongo/db/structure/btree/btree_logic.cpp +++ b/src/mongo/db/structure/btree/btree_logic.cpp @@ -56,7 +56,7 @@ namespace mongo { _numAdded(0), _txn(txn) { - _first = _cur = _logic->addBucket(txn); + _first = _cur = _logic->_addBucket(txn); _b = _getModifiableBucket(_cur); _committed = false; } @@ -113,7 +113,7 @@ namespace mongo { template void BtreeLogic::Builder::newBucket() { - DiskLoc newBucketLoc = _logic->addBucket(_txn); + DiskLoc newBucketLoc = _logic->_addBucket(_txn); _b->parent = newBucketLoc; _cur = newBucketLoc; _b = _getModifiableBucket(_cur); @@ -128,7 +128,7 @@ namespace mongo { break; } - DiskLoc upLoc = _logic->addBucket(_txn); + DiskLoc upLoc = _logic->_addBucket(_txn); DiskLoc upStart = upLoc; BucketType* up = _getModifiableBucket(upLoc); @@ -152,7 +152,7 @@ namespace mongo { if (!_logic->_pushBack(up, r, k, keepLoc)) { // current bucket full - DiskLoc n = _logic->addBucket(_txn); + DiskLoc n = _logic->_addBucket(_txn); up->parent = n; upLoc = n; up = _getModifiableBucket(upLoc); @@ -251,17 +251,6 @@ namespace mongo { return (int) (BtreeLayout::BucketSize - (bucket->data - (char*)bucket)); } - template - int BtreeLogic::headerSize() { - const BucketType* b = NULL; - return (char*)&(b->data) - (char*)&(b->parent); - } - - template - int BtreeLogic::bodySize() { - return BtreeLayout::BucketSize - headerSize(); - } - // We define this value as the maximum number of bytes such that, if we have // fewer than this many bytes, we must be able to either merge with or receive // keys from any neighboring node. If our utilization goes below this value we @@ -270,25 +259,19 @@ namespace mongo { // is a lower bound on bucket utilization for non root buckets. // // Note that the exact value here depends on the implementation of - // rebalancedSeparatorPos(). The conditions for lowWaterMark - 1 are as + // _rebalancedSeparatorPos(). The conditions for lowWaterMark - 1 are as // follows: We know we cannot merge with the neighbor, so the total data size // for us, the neighbor, and the separator must be at least // BucketType::bodySize() + 1. We must be able to accept one key of any // allowed size, so our size plus storage for that additional key must be // <= BucketType::bodySize() / 2. This way, with the extra key we'll have a // new bucket data size < half the total data size and by the implementation - // of rebalancedSeparatorPos() the key must be added. + // of _rebalancedSeparatorPos() the key must be added. template int BtreeLogic::lowWaterMark() { - return bodySize() / 2 - BtreeLayout::KeyMax - sizeof(KeyHeaderType) + 1; + return BtreeLayout::BucketBodySize / 2 - BtreeLayout::KeyMax - sizeof(KeyHeaderType) + 1; } - // XXX - - enum Flags { - Packed = 1 - }; - template void BtreeLogic::init(BucketType* bucket) { BtreeLayout::initBucket(bucket); @@ -489,9 +472,9 @@ namespace mongo { } template - int BtreeLogic::packedDataSize(BucketType* bucket, int refPos) { + int BtreeLogic::_packedDataSize(BucketType* bucket, int refPos) { if (bucket->flags & Packed) { - return BtreeLayout::BucketSize - bucket->emptySize - headerSize(); + return BtreeLayout::BucketSize - bucket->emptySize - BucketType::HeaderSize; } int size = 0; @@ -1300,14 +1283,14 @@ namespace mongo { invariant(bucket); invariant(BtreeLayout::INVALID_N_SENTINEL != bucket->n); - if (keyIsAt(savedKey, savedLoc, bucket, *keyOffsetInOut)) { + if (_keyIsAt(savedKey, savedLoc, bucket, *keyOffsetInOut)) { skipUnusedKeys(bucketLocInOut, keyOffsetInOut, direction); return; } if (*keyOffsetInOut > 0) { (*keyOffsetInOut)--; - if (keyIsAt(savedKey, savedLoc, bucket, *keyOffsetInOut)) { + if (_keyIsAt(savedKey, savedLoc, bucket, *keyOffsetInOut)) { skipUnusedKeys(bucketLocInOut, keyOffsetInOut, direction); return; } @@ -1317,10 +1300,10 @@ namespace mongo { } template - bool BtreeLogic::keyIsAt(const BSONObj& savedKey, - const DiskLoc& savedLoc, - BucketType* bucket, - int keyPos) const { + bool BtreeLogic::_keyIsAt(const BSONObj& savedKey, + const DiskLoc& savedLoc, + BucketType* bucket, + int keyPos) const { if (keyPos >= bucket->n) { return false; } @@ -1468,9 +1451,9 @@ namespace mongo { BucketType* leftBucket = getBucket(leftNodeLoc); BucketType* rightBucket = getBucket(rightNodeLoc); - int sum = headerSize() - + packedDataSize(leftBucket, pos) - + packedDataSize(rightBucket, pos) + int sum = BucketType::HeaderSize + + _packedDataSize(leftBucket, pos) + + _packedDataSize(rightBucket, pos) + getFullKey(bucket, leftIndex).data.dataSize() + sizeof(KeyHeaderType); @@ -1482,9 +1465,7 @@ namespace mongo { * splitPos(). */ template - int BtreeLogic::rebalancedSeparatorPos(BucketType* bucket, - const DiskLoc bucketLoc, - int leftIndex) { + int BtreeLogic::_rebalancedSeparatorPos(BucketType* bucket, int leftIndex) { int split = -1; int rightSize = 0; const BucketType* l = childForPos(bucket, leftIndex); @@ -1500,7 +1481,7 @@ namespace mongo { // This constraint should be ensured by only calling this function // if we go below the low water mark. - invariant(rightSizeLimit < bodySize()); + invariant(rightSizeLimit < BtreeLayout::BucketBodySize); for (int i = r->n - 1; i > -1; --i) { rightSize += getFullKey(r, i).data.dataSize() + KNS; @@ -1725,7 +1706,7 @@ namespace mongo { BucketType* r = btreemod(txn, getBucket(rchild)); _packReadyForMod(r, zeropos); - int split = rebalancedSeparatorPos(bucket, bucketLoc, leftIndex); + int split = _rebalancedSeparatorPos(bucket, leftIndex); // By definition, if we are below the low water mark and cannot merge // then we must actively balance. @@ -1746,7 +1727,7 @@ namespace mongo { return false; } - if (packedDataSize(bucket, 0) >= lowWaterMark()) { + if (_packedDataSize(bucket, 0) >= lowWaterMark()) { return false; } @@ -1803,15 +1784,6 @@ namespace mongo { return getRoot()->n == 0; } - template - inline void BtreeLogic::fix(OperationContext* txn, - const DiskLoc bucketLoc, - const DiskLoc child) { - if (!child.isNull()) { - *txn->recoveryUnit()->writing(&getBucket(child)->parent) = bucketLoc; - } - } - /** * This can cause a lot of additional page writes when we assign buckets to different parents. * Maybe get rid of parent ptrs? @@ -1830,7 +1802,10 @@ namespace mongo { } for (int i = firstIndex; i <= lastIndex; i++) { - fix(txn, bucketLoc, childLocForPos(bucket, i)); + const DiskLoc childLoc = childLocForPos(bucket, i); + if (!childLoc.isNull()) { + *txn->recoveryUnit()->writing(&getBucket(childLoc)->parent) = bucketLoc; + } } } @@ -1925,7 +1900,7 @@ namespace mongo { const DiskLoc rchild) { int split = splitPos(bucket, keypos); - DiskLoc rLoc = addBucket(txn); + DiskLoc rLoc = _addBucket(txn); BucketType* r = btreemod(txn, getBucket(rLoc)); for (int i = split + 1; i < bucket->n; i++) { @@ -1947,7 +1922,7 @@ namespace mongo { if (bucket->parent.isNull()) { // promote splitkey to a parent this->node make a new parent if we were the root - DiskLoc L = addBucket(txn); + DiskLoc L = _addBucket(txn); BucketType* p = btreemod(txn, getBucket(L)); pushBack(p, splitkey.recordLoc, splitkey.data, bucketLoc); p->nextChild = rLoc; @@ -2000,12 +1975,12 @@ namespace mongo { return Status(ErrorCodes::InternalError, "index already initialized"); } - _headManager->setHead(txn, addBucket(txn)); + _headManager->setHead(txn, _addBucket(txn)); return Status::OK(); } template - DiskLoc BtreeLogic::addBucket(OperationContext* txn) { + DiskLoc BtreeLogic::_addBucket(OperationContext* txn) { DummyDocWriter docWriter(BtreeLayout::BucketSize); StatusWith loc = _recordStore->insertRecord(txn, &docWriter, 0); // XXX: remove this(?) or turn into massert or sanely bubble it back up. @@ -2017,11 +1992,10 @@ namespace mongo { // static template - void BtreeLogic::dump(BucketType* bucket, int depth) { - log() << "BUCKET n:" << bucket->n; - log() << " parent:" << hex << bucket->parent.getOfs() << dec; + void BtreeLogic::dumpBucket(const BucketType* bucket, int indentLength) { + log() << "BUCKET n:" << bucket->n << ", parent:" << hex << bucket->parent.getOfs() << dec; - string indent = string(depth, ' '); + const string indent = string(indentLength, ' '); for (int i = 0; i < bucket->n; i++) { log() << '\n' << indent; @@ -2029,7 +2003,7 @@ namespace mongo { string ks = k.data.toString(); log() << " " << hex << k.prevChildBucket.getOfs() << "<-- prevChildBucket for " << i << '\n'; log() << indent << " " << i << ' ' << ks.substr(0, 30) - << " Loc:" << k.recordLoc.toString() << dec; + << " Loc:" << k.recordLoc.toString() << dec; if (getKeyHeader(bucket, i).isUnused()) { log() << " UNUSED"; } @@ -2072,11 +2046,11 @@ namespace mongo { bool strict, bool dumpBuckets, unsigned depth) { - return fullValidate(getRootLoc(), unusedCount, strict, dumpBuckets, depth); + return _fullValidate(getRootLoc(), unusedCount, strict, dumpBuckets, depth); } template - long long BtreeLogic::fullValidate(const DiskLoc bucketLoc, + long long BtreeLogic::_fullValidate(const DiskLoc bucketLoc, long long *unusedCount, bool strict, bool dumpBuckets, @@ -2086,7 +2060,7 @@ namespace mongo { if (dumpBuckets) { log() << bucketLoc.toString() << ' '; - dump(bucket, depth); + dumpBucket(bucket, depth); } long long keyCount = 0; @@ -2112,7 +2086,7 @@ namespace mongo { wassert(b->parent == bucketLoc); } - keyCount += fullValidate(left, unusedCount, strict, dumpBuckets, depth + 1); + keyCount += _fullValidate(left, unusedCount, strict, dumpBuckets, depth + 1); } } @@ -2125,7 +2099,7 @@ namespace mongo { wassert(b->parent == bucketLoc); } - keyCount += fullValidate(bucket->nextChild, unusedCount, strict, dumpBuckets, depth + 1); + keyCount += _fullValidate(bucket->nextChild, unusedCount, strict, dumpBuckets, depth + 1); } return keyCount; @@ -2164,7 +2138,7 @@ namespace mongo { for (int j = 0; j < bucket->n; j++) { out() << " " << getFullKey(bucket, j).data.toString() << endl; } - dump(bucket); + dumpBucket(bucket); } wassert(false); break; @@ -2191,7 +2165,7 @@ namespace mongo { if (z > 0) { problem() << "Btree keys out of order in collection " << ns; ONCE { - dump(bucket); + dumpBucket(bucket); } invariant(false); } diff --git a/src/mongo/db/structure/btree/btree_logic.h b/src/mongo/db/structure/btree/btree_logic.h index 3cbc5e3524138..da6d48c3a65d9 100644 --- a/src/mongo/db/structure/btree/btree_logic.h +++ b/src/mongo/db/structure/btree/btree_logic.h @@ -44,6 +44,10 @@ namespace mongo { class BucketDeletionNotification; class RecordStore; + // Used for unit-testing only + template class BtreeLogicTestBase; + template class ArtificialTreeBuilder; + /** * This is the logic for manipulating the Btree. It is (mostly) independent of the on-disk * format. @@ -202,11 +206,6 @@ namespace mongo { DiskLoc* bucketInOut, int* keyOffsetInOut) const; - bool keyIsAt(const BSONObj& savedKey, - const DiskLoc& savedLoc, - BucketType* bucket, - int keyPos) const; - // // Creation and deletion // @@ -216,9 +215,19 @@ namespace mongo { */ Status initAsEmpty(OperationContext* txn); + // + // Size constants + // + + static int lowWaterMark(); + private: friend class BtreeLogic::Builder; + // Used for unit-testing only + friend class BtreeLogicTestBase; + friend class ArtificialTreeBuilder; + /** * This is an in memory wrapper for the variable length data associated with a * KeyHeaderType. It points to on-disk data but is not itself on-disk data. @@ -249,12 +258,6 @@ namespace mongo { // Functions that depend on the templated type info but nothing in 'this'. // - static int headerSize(); - - static int bodySize(); - - static int lowWaterMark(); - static LocType& childLocForPos(BucketType* bucket, int pos); static FullKey getFullKey(const BucketType* bucket, int i); @@ -281,7 +284,7 @@ namespace mongo { static bool mayDropKey(BucketType* bucket, int index, int refPos); - static int packedDataSize(BucketType* bucket, int refPos); + static int _packedDataSize(BucketType* bucket, int refPos); static void setPacked(BucketType* bucket); @@ -301,7 +304,7 @@ namespace mongo { static bool isHead(BucketType* bucket); - static void dump(BucketType* bucket, int depth = 0); + static void dumpBucket(const BucketType* bucket, int indentLength = 0); static void assertValid(const std::string& ns, BucketType* bucket, @@ -378,22 +381,20 @@ namespace mongo { const DiskLoc& recordLoc, const int direction) const; - long long fullValidate(const DiskLoc bucketLoc, + long long _fullValidate(const DiskLoc bucketLoc, long long *unusedCount, bool strict, bool dumpBuckets, unsigned depth); - DiskLoc addBucket(OperationContext* txn); + DiskLoc _addBucket(OperationContext* txn); bool canMergeChildren(BucketType* bucket, const DiskLoc bucketLoc, const int leftIndex); // has to look in children of 'bucket' and requires record store - int rebalancedSeparatorPos(BucketType* bucket, - const DiskLoc bucketLoc, - int leftIndex); + int _rebalancedSeparatorPos(BucketType* bucket, int leftIndex); void _packReadyForMod(BucketType* bucket, int &refPos); @@ -437,9 +438,7 @@ namespace mongo { const DiskLoc lchild, const DiskLoc rchild); - void fix(OperationContext* txn, const DiskLoc bucketLoc, const DiskLoc child); - - void fixParentPtrs(OperationContext* txn, + void fixParentPtrs(OperationContext* trans, BucketType* bucket, const DiskLoc bucketLoc, int firstIndex = 0, @@ -506,6 +505,11 @@ namespace mongo { BucketType* bucket, const DiskLoc bucketLoc); + bool _keyIsAt(const BSONObj& savedKey, + const DiskLoc& savedLoc, + BucketType* bucket, + int keyPos) const; + // TODO 'this' for _ordering(?) int customBSONCmp(const BSONObj& l, const BSONObj& rBegin, diff --git a/src/mongo/db/structure/btree/btree_logic_test.cpp b/src/mongo/db/structure/btree/btree_logic_test.cpp new file mode 100644 index 0000000000000..deabd340184ad --- /dev/null +++ b/src/mongo/db/structure/btree/btree_logic_test.cpp @@ -0,0 +1,2205 @@ +// btree_logic_test.cpp : Btree unit tests +// + +/** + * Copyright (C) 2014 MongoDB + * + * 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. + */ + +// This file contains simple single-threaded tests, which check various aspects of the Btree logic +// + +#include "mongo/db/instance.h" +#include "mongo/db/operation_context_noop.h" +#include "mongo/db/structure/btree/btree_test_help.h" +#include "mongo/unittest/unittest.h" + + +namespace mongo { + + /** + * This class is made friend of BtreeLogic so we can add whatever private method accesses we + * need to it, to be used by the tests. + */ + template + class BtreeLogicTestBase { + public: + typedef typename BtreeLayoutType::BucketType BucketType; + typedef typename BtreeLayoutType::FixedWidthKeyType FixedWidthKeyType; + + typedef typename BtreeLogic::FullKey FullKey; + typedef typename BtreeLogic::KeyDataOwnedType KeyDataOwnedType; + + BtreeLogicTestBase() : _helper(BSON("TheKey" << 1)) { + + } + + virtual ~BtreeLogicTestBase() { + + } + + protected: + void checkValidNumKeys(int nKeys) { + ASSERT_EQUALS(nKeys, _helper.btree.fullValidate(NULL, true, true, 0)); + } + + void insert(const BSONObj &key, const DiskLoc dl) { + OperationContextNoop txn; + _helper.btree.insert(&txn, key, dl, true); + } + + bool unindex(const BSONObj &key) { + OperationContextNoop txn; + return _helper.btree.unindex(&txn, key, _helper.dummyDiskLoc); + } + + void locate(const BSONObj &key, + int expectedPos, + bool expectedFound, + const DiskLoc &expectedLocation, + int direction) { + int pos; + DiskLoc loc; + ASSERT_EQUALS(expectedFound, + _helper.btree.locate(key, _helper.dummyDiskLoc, direction, &pos, &loc)); + ASSERT_EQUALS(expectedLocation, loc); + ASSERT_EQUALS(expectedPos, pos); + } + + const BucketType* child(const BucketType* bucket, int i) const { + verify(i <= bucket->n); + + DiskLoc diskLoc; + if (i == bucket->n) { + diskLoc = bucket->nextChild; + } + else { + FullKey fullKey = BtreeLogic::getFullKey(bucket, i); + diskLoc = fullKey.prevChildBucket; + } + + verify(!diskLoc.isNull()); + + return _helper.btree.getBucket(diskLoc); + } + + BucketType* head() const { + return _helper.btree.getBucket(_helper.headManager.getHead()); + } + + void forcePackBucket(const DiskLoc bucketLoc) { + BucketType* bucket = _helper.btree.getBucket(bucketLoc); + + bucket->topSize += bucket->emptySize; + bucket->emptySize = 0; + BtreeLogic::setNotPacked(bucket); + } + + void truncateBucket(BucketType* bucket, int N, int &refPos) { + _helper.btree.truncateTo(bucket, N, refPos); + } + + int bucketPackedDataSize(BucketType* bucket, int refPos) { + return _helper.btree._packedDataSize(bucket, refPos); + } + + int bucketRebalancedSeparatorPos(const DiskLoc bucketLoc, int leftIndex) { + BucketType* bucket = _helper.btree.getBucket(bucketLoc); + + return _helper.btree._rebalancedSeparatorPos(bucket, leftIndex); + } + + FullKey getKey(const DiskLoc bucketLoc, int pos) const { + const BucketType* bucket = _helper.btree.getBucket(bucketLoc); + return BtreeLogic::getFullKey(bucket, pos); + } + + void markKeyUnused(const DiskLoc bucketLoc, int keyPos) { + BucketType* bucket = _helper.btree.getBucket(bucketLoc); + invariant(keyPos >= 0 && keyPos < bucket->n); + + _helper.btree.getKeyHeader(bucket, keyPos).setUnused(); + } + + DiskLoc newBucket() { + OperationContextNoop txn; + return _helper.btree._addBucket(&txn); + } + + /** + * Sets the nextChild pointer for the bucket at the specified location. + */ + void setBucketNextChild(const DiskLoc bucketLoc, const DiskLoc nextChild) { + OperationContextNoop txn; + + BucketType* bucket = _helper.btree.getBucket(bucketLoc); + bucket->nextChild = nextChild; + + _helper.btree.fixParentPtrs(&txn, bucket, bucketLoc); + } + + protected: + BtreeLogicTestHelper _helper; + }; + + // + // TESTS + // + + template + class SimpleCreate : public BtreeLogicTestBase { + public: + void run() { + OperationContextNoop txn; + this->_helper.btree.initAsEmpty(&txn); + + this->checkValidNumKeys(0); + } + }; + + template + class SimpleInsertDelete : public BtreeLogicTestBase { + public: + void run() { + OperationContextNoop txn; + this->_helper.btree.initAsEmpty(&txn); + + BSONObj key = simpleKey('z'); + this->insert(key, this->_helper.dummyDiskLoc); + + this->checkValidNumKeys(1); + this->locate(key, 0, true, this->_helper.headManager.getHead(), 1); + + this->unindex(key); + + this->checkValidNumKeys(0); + this->locate(key, 0, false, DiskLoc(), 1); + } + }; + + template + class SplitUnevenBucketBase : public BtreeLogicTestBase { + public: + void run() { + OperationContextNoop txn; + this->_helper.btree.initAsEmpty(&txn); + + for (int i = 0; i < 10; ++i) { + BSONObj shortKey = simpleKey(shortToken(i), 1); + this->insert(shortKey, this->_helper.dummyDiskLoc); + + BSONObj longKey = simpleKey(longToken(i), 800); + this->insert(longKey, this->_helper.dummyDiskLoc); + } + + this->checkValidNumKeys(20); + ASSERT_EQUALS(1, this->head()->n); + checkSplit(); + } + + protected: + virtual char shortToken(int i) const = 0; + virtual char longToken(int i) const = 0; + virtual void checkSplit() = 0; + + static char leftToken(int i) { + return 'a' + i; + } + + static char rightToken(int i) { + return 'z' - i; + } + }; + + template + class SplitRightHeavyBucket : public SplitUnevenBucketBase { + private: + virtual char shortToken(int i) const { + return this->leftToken(i); + } + virtual char longToken(int i) const { + return this->rightToken(i); + } + virtual void checkSplit() { + ASSERT_EQUALS(15, this->child(this->head(), 0)->n); + ASSERT_EQUALS(4, this->child(this->head(), 1)->n); + } + }; + + template + class SplitLeftHeavyBucket : public SplitUnevenBucketBase { + private: + virtual char shortToken(int i) const { + return this->rightToken(i); + } + virtual char longToken(int i) const { + return this->leftToken(i); + } + virtual void checkSplit() { + ASSERT_EQUALS(4, this->child(this->head(), 0)->n); + ASSERT_EQUALS(15, this->child(this->head(), 1)->n); + } + }; + + template + class MissingLocate : public BtreeLogicTestBase { + public: + void run() { + OperationContextNoop txn; + this->_helper.btree.initAsEmpty(&txn); + + for (int i = 0; i < 3; ++i) { + BSONObj k = simpleKey('b' + 2 * i); + this->insert(k, this->_helper.dummyDiskLoc); + } + + locateExtended(1, 'a', 'b', this->_helper.headManager.getHead()); + locateExtended(1, 'c', 'd', this->_helper.headManager.getHead()); + locateExtended(1, 'e', 'f', this->_helper.headManager.getHead()); + locateExtended(1, 'g', 'g' + 1, DiskLoc()); // of course, 'h' isn't in the index. + + // old behavior + // locateExtended( -1, 'a', 'b', dl() ); + // locateExtended( -1, 'c', 'd', dl() ); + // locateExtended( -1, 'e', 'f', dl() ); + // locateExtended( -1, 'g', 'f', dl() ); + + locateExtended(-1, 'a', 'a' - 1, DiskLoc()); // of course, 'a' - 1 isn't in the index + locateExtended(-1, 'c', 'b', this->_helper.headManager.getHead()); + locateExtended(-1, 'e', 'd', this->_helper.headManager.getHead()); + locateExtended(-1, 'g', 'f', this->_helper.headManager.getHead()); + } + + private: + void locateExtended( + int direction, char token, char expectedMatch, DiskLoc expectedLocation) { + const BSONObj k = simpleKey(token); + int expectedPos = (expectedMatch - 'b') / 2; + + this->locate(k, expectedPos, false, expectedLocation, direction); + } + }; + + template + class MissingLocateMultiBucket : public BtreeLogicTestBase { + public: + void run() { + OperationContextNoop txn; + this->_helper.btree.initAsEmpty(&txn); + + this->insert(simpleKey('A', 800), this->_helper.dummyDiskLoc); + this->insert(simpleKey('B', 800), this->_helper.dummyDiskLoc); + this->insert(simpleKey('C', 800), this->_helper.dummyDiskLoc); + this->insert(simpleKey('D', 800), this->_helper.dummyDiskLoc); + this->insert(simpleKey('E', 800), this->_helper.dummyDiskLoc); + this->insert(simpleKey('F', 800), this->_helper.dummyDiskLoc); + this->insert(simpleKey('G', 800), this->_helper.dummyDiskLoc); + this->insert(simpleKey('H', 800), this->_helper.dummyDiskLoc); + this->insert(simpleKey('J', 800), this->_helper.dummyDiskLoc); + + // This causes split + this->insert(simpleKey('I', 800), this->_helper.dummyDiskLoc); + + int pos; + DiskLoc loc; + + // 'E' is the split point and should be in the head the rest should be ~50/50 + const BSONObj splitPoint = simpleKey('E', 800); + this->_helper.btree.locate(splitPoint, this->_helper.dummyDiskLoc, 1, &pos, &loc); + ASSERT_EQUALS(this->_helper.headManager.getHead(), loc); + ASSERT_EQUALS(0, pos); + + // Find the one before 'E' + int largePos; + DiskLoc largeLoc; + this->_helper.btree.locate(splitPoint, this->_helper.dummyDiskLoc, 1, &largePos, &largeLoc); + this->_helper.btree.advance(&largeLoc, &largePos, -1); + + // Find the one after 'E' + int smallPos; + DiskLoc smallLoc; + this->_helper.btree.locate(splitPoint, this->_helper.dummyDiskLoc, 1, &smallPos, &smallLoc); + this->_helper.btree.advance(&smallLoc, &smallPos, 1); + + ASSERT_NOT_EQUALS(smallLoc, largeLoc); + ASSERT_NOT_EQUALS(smallLoc, loc); + ASSERT_NOT_EQUALS(largeLoc, loc); + } + }; + + /** + * Validates that adding keys incrementally produces buckets, which are 90%/10% full. + */ + template + class SERVER983 : public BtreeLogicTestBase { + public: + void run() { + OperationContextNoop txn; + this->_helper.btree.initAsEmpty(&txn); + + this->insert(simpleKey('A', 800), this->_helper.dummyDiskLoc); + this->insert(simpleKey('B', 800), this->_helper.dummyDiskLoc); + this->insert(simpleKey('C', 800), this->_helper.dummyDiskLoc); + this->insert(simpleKey('D', 800), this->_helper.dummyDiskLoc); + this->insert(simpleKey('E', 800), this->_helper.dummyDiskLoc); + this->insert(simpleKey('F', 800), this->_helper.dummyDiskLoc); + this->insert(simpleKey('G', 800), this->_helper.dummyDiskLoc); + this->insert(simpleKey('H', 800), this->_helper.dummyDiskLoc); + this->insert(simpleKey('I', 800), this->_helper.dummyDiskLoc); + + // This will cause split + this->insert(simpleKey('J', 800), this->_helper.dummyDiskLoc); + + int pos; + DiskLoc loc; + + // 'H' is the maximum 'large' interval key, 90% should be < 'H' and 10% larger + const BSONObj splitPoint = simpleKey('H', 800); + this->_helper.btree.locate(splitPoint, this->_helper.dummyDiskLoc, 1, &pos, &loc); + ASSERT_EQUALS(this->_helper.headManager.getHead(), loc); + ASSERT_EQUALS(0, pos); + + // Find the one before 'H' + int largePos; + DiskLoc largeLoc; + this->_helper.btree.locate( + splitPoint, this->_helper.dummyDiskLoc, 1, &largePos, &largeLoc); + this->_helper.btree.advance(&largeLoc, &largePos, -1); + + // Find the one after 'H' + int smallPos; + DiskLoc smallLoc; + this->_helper.btree.locate( + splitPoint, this->_helper.dummyDiskLoc, 1, &smallPos, &smallLoc); + this->_helper.btree.advance(&smallLoc, &smallPos, 1); + + ASSERT_NOT_EQUALS(smallLoc, largeLoc); + ASSERT_NOT_EQUALS(smallLoc, loc); + ASSERT_NOT_EQUALS(largeLoc, loc); + } + }; + + template + class DontReuseUnused : public BtreeLogicTestBase { + public: + void run() { + OperationContextNoop txn; + this->_helper.btree.initAsEmpty(&txn); + + for (int i = 0; i < 10; ++i) { + const BSONObj k = simpleKey('b' + 2 * i, 800); + this->insert(k, this->_helper.dummyDiskLoc); + } + + const BSONObj root = simpleKey('p', 800); + this->unindex(root); + + this->insert(root, this->_helper.dummyDiskLoc); + this->locate(root, 0, true, this->head()->nextChild, 1); + } + }; + + template + class MergeBucketsTestBase : public BtreeLogicTestBase { + public: + void run() { + OperationContextNoop txn; + this->_helper.btree.initAsEmpty(&txn); + + for (int i = 0; i < 10; ++i) { + const BSONObj k = simpleKey('b' + 2 * i, 800); + this->insert(k, this->_helper.dummyDiskLoc); + } + + // numRecords() - 1, because this->_helper.dummyDiskLoc is actually in the record store too + ASSERT_EQUALS(3, this->_helper.recordStore.numRecords() - 1); + + long long expectedCount = 10 - unindexKeys(); + ASSERT_EQUALS(1, this->_helper.recordStore.numRecords() - 1); + + long long unusedCount = 0; + ASSERT_EQUALS(expectedCount, this->_helper.btree.fullValidate(&unusedCount, true, true, 0)); + ASSERT_EQUALS(0, unusedCount); + } + + protected: + virtual int unindexKeys() = 0; + }; + + template + class MergeBucketsLeft : public MergeBucketsTestBase { + virtual int unindexKeys() { + BSONObj k = simpleKey('b', 800); + this->unindex(k); + + k = simpleKey('b' + 2, 800); + this->unindex(k); + + k = simpleKey('b' + 4, 800); + this->unindex(k); + + k = simpleKey('b' + 6, 800); + this->unindex(k); + + return 4; + } + }; + + template + class MergeBucketsRight : public MergeBucketsTestBase { + virtual int unindexKeys() { + const BSONObj k = simpleKey('b' + 2 * 9, 800); + this->unindex(k); + return 1; + } + }; + + template + class MergeBucketsDontReplaceHead : public BtreeLogicTestBase { + public: + void run() { + OperationContextNoop txn; + this->_helper.btree.initAsEmpty(&txn); + + for (int i = 0; i < 18; ++i) { + const BSONObj k = simpleKey('a' + i, 800); + this->insert(k, this->_helper.dummyDiskLoc); + } + + // numRecords() - 1, because fixedDiskLoc is actually in the record store too + ASSERT_EQUALS(4, this->_helper.recordStore.numRecords() - 1); + + const BSONObj k = simpleKey('a' + 17, 800); + this->unindex(k); + ASSERT_EQUALS(3, this->_helper.recordStore.numRecords() - 1); + + long long unusedCount = 0; + ASSERT_EQUALS(17, this->_helper.btree.fullValidate(&unusedCount, true, true, 0)); + ASSERT_EQUALS(0, unusedCount); + } + }; + + template + class MergeBucketsDelInternal : public BtreeLogicTestBase { + public: + void run() { + OperationContextNoop txn; + ArtificialTreeBuilder builder(&txn, &this->_helper); + + builder.makeTree("{d:{b:{a:null},bb:null,_:{c:null}},_:{f:{e:null},_:{g:null}}}"); + ASSERT_EQUALS(8, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 7 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(8, this->_helper.recordStore.numRecords()); + + const BSONObj k = BSON("" << "bb"); + verify(this->unindex(k)); + + ASSERT_EQUALS(7, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 5 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(6, this->_helper.recordStore.numRecords()); + + builder.checkStructure("{b:{a:null},d:{c:null},f:{e:null},_:{g:null}}"); + } + }; + + template + class MergeBucketsRightNull : public BtreeLogicTestBase { + public: + void run() { + OperationContextNoop txn; + ArtificialTreeBuilder builder(&txn, &this->_helper); + + builder.makeTree("{d:{b:{a:null},bb:null,cc:{c:null}},_:{f:{e:null},h:{g:null}}}"); + ASSERT_EQUALS(10, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 7 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(8, this->_helper.recordStore.numRecords()); + + const BSONObj k = BSON("" << "bb"); + verify(this->unindex(k)); + + ASSERT_EQUALS(9, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 5 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(6, this->_helper.recordStore.numRecords()); + + builder.checkStructure("{b:{a:null},cc:{c:null},d:null,f:{e:null},h:{g:null}}"); + } + }; + + // This comment was here during porting, not sure what it means: + // + // "Not yet handling this case" + template + class DontMergeSingleBucket : public BtreeLogicTestBase { + public: + void run() { + OperationContextNoop txn; + ArtificialTreeBuilder builder(&txn, &this->_helper); + + builder.makeTree("{d:{b:{a:null},c:null}}"); + + ASSERT_EQUALS(4, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 3 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(4, this->_helper.recordStore.numRecords()); + + const BSONObj k = BSON("" << "c"); + verify(this->unindex(k)); + + ASSERT_EQUALS(3, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 3 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(4, this->_helper.recordStore.numRecords()); + + builder.checkStructure("{d:{b:{a:null}}}"); + } + }; + + template + class ParentMergeNonRightToLeft : public BtreeLogicTestBase { + public: + void run() { + OperationContextNoop txn; + ArtificialTreeBuilder builder(&txn, &this->_helper); + + builder.makeTree("{d:{b:{a:null},bb:null,cc:{c:null}},i:{f:{e:null},h:{g:null}}}"); + + ASSERT_EQUALS(11, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 7 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(8, this->_helper.recordStore.numRecords()); + + const BSONObj k = BSON("" << "bb"); + verify(this->unindex(k)); + + ASSERT_EQUALS(10, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // Child does not currently replace parent in this case. Also, the tree + // has 6 buckets + 1 for the this->_helper.dummyDiskLoc. + ASSERT_EQUALS(7, this->_helper.recordStore.numRecords()); + + builder.checkStructure("{i:{b:{a:null},cc:{c:null},d:null,f:{e:null},h:{g:null}}}"); + } + }; + + template + class ParentMergeNonRightToRight : public BtreeLogicTestBase { + public: + void run() { + OperationContextNoop txn; + ArtificialTreeBuilder builder(&txn, &this->_helper); + + builder.makeTree("{d:{b:{a:null},cc:{c:null}},i:{f:{e:null},ff:null,h:{g:null}}}"); + + ASSERT_EQUALS(11, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 7 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(8, this->_helper.recordStore.numRecords()); + + const BSONObj k = BSON("" << "ff"); + verify(this->unindex(k)); + + ASSERT_EQUALS(10, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // Child does not currently replace parent in this case. Also, the tree + // has 6 buckets + 1 for the this->_helper.dummyDiskLoc. + ASSERT_EQUALS(7, this->_helper.recordStore.numRecords()); + + builder.checkStructure("{i:{b:{a:null},cc:{c:null},d:null,f:{e:null},h:{g:null}}}"); + } + }; + + template + class CantMergeRightNoMerge : public BtreeLogicTestBase { + public: + void run() { + OperationContextNoop txn; + ArtificialTreeBuilder builder(&txn, &this->_helper); + + builder.makeTree("{d:{b:{a:null},bb:null,cc:{c:null}}," + "dd:null," + "_:{f:{e:null},h:{g:null}}}"); + + ASSERT_EQUALS(11, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 7 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(8, this->_helper.recordStore.numRecords()); + + const BSONObj k = BSON("" << "bb"); + verify(this->unindex(k)); + + ASSERT_EQUALS(10, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 7 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(8, this->_helper.recordStore.numRecords()); + + builder.checkStructure("{d:{b:{a:null},cc:{c:null}}," + "dd:null," + "_:{f:{e:null},h:{g:null}}}"); + } + }; + + template + class CantMergeLeftNoMerge : public BtreeLogicTestBase { + public: + void run() { + OperationContextNoop txn; + ArtificialTreeBuilder builder(&txn, &this->_helper); + + builder.makeTree("{c:{b:{a:null}},d:null,_:{f:{e:null},g:null}}"); + + ASSERT_EQUALS(7, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 5 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(6, this->_helper.recordStore.numRecords()); + + const BSONObj k = BSON("" << "g"); + verify(this->unindex(k)); + + ASSERT_EQUALS(6, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 5 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(6, this->_helper.recordStore.numRecords()); + + builder.checkStructure("{c:{b:{a:null}},d:null,_:{f:{e:null}}}"); + } + }; + + template + class MergeOption : public BtreeLogicTestBase { + public: + void run() { + OperationContextNoop txn; + ArtificialTreeBuilder builder(&txn, &this->_helper); + + builder.makeTree("{c:{b:{a:null}},f:{e:{d:null},ee:null},_:{h:{g:null}}}"); + + ASSERT_EQUALS(9, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 7 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(8, this->_helper.recordStore.numRecords()); + + const BSONObj k = BSON("" << "ee"); + verify(this->unindex(k)); + + ASSERT_EQUALS(8, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 6 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(7, this->_helper.recordStore.numRecords()); + + builder.checkStructure("{c:{b:{a:null}},_:{e:{d:null},f:null,h:{g:null}}}"); + } + }; + + template + class ForceMergeLeft : public BtreeLogicTestBase { + public: + void run() { + OperationContextNoop txn; + ArtificialTreeBuilder builder(&txn, &this->_helper); + + builder.makeTree("{c:{b:{a:null}},f:{e:{d:null},ee:null},ff:null,_:{h:{g:null}}}"); + + ASSERT_EQUALS(10, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 7 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(8, this->_helper.recordStore.numRecords()); + + const BSONObj k = BSON("" << "ee"); + verify(this->unindex(k)); + + ASSERT_EQUALS(9, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 6 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(7, this->_helper.recordStore.numRecords()); + + builder.checkStructure("{f:{b:{a:null},c:null,e:{d:null}},ff:null,_:{h:{g:null}}}"); + } + }; + + template + class ForceMergeRight : public BtreeLogicTestBase { + public: + void run() { + OperationContextNoop txn; + ArtificialTreeBuilder builder(&txn, &this->_helper); + + builder.makeTree("{c:{b:{a:null}},cc:null,f:{e:{d:null},ee:null},_:{h:{g:null}}}"); + + ASSERT_EQUALS(10, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 7 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(8, this->_helper.recordStore.numRecords()); + + const BSONObj k = BSON("" << "ee"); + verify(this->unindex(k)); + + ASSERT_EQUALS(9, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 6 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(7, this->_helper.recordStore.numRecords()); + + builder.checkStructure("{c:{b:{a:null}},cc:null,_:{e:{d:null},f:null,h:{g:null}}}"); + } + }; + + template + class RecursiveMerge : public BtreeLogicTestBase { + public: + void run() { + OperationContextNoop txn; + ArtificialTreeBuilder builder(&txn, &this->_helper); + + builder.makeTree("{h:{e:{b:{a:null},c:null,d:null},g:{f:null}},j:{i:null}}"); + + ASSERT_EQUALS(10, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 6 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(7, this->_helper.recordStore.numRecords()); + + const BSONObj k = BSON("" << "c"); + verify(this->unindex(k)); + + ASSERT_EQUALS(9, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 4 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(5, this->_helper.recordStore.numRecords()); + + // Height is not currently reduced in this case + builder.checkStructure("{j:{g:{b:{a:null},d:null,e:null,f:null},h:null,i:null}}"); + } + }; + + template + class RecursiveMergeRightBucket : public BtreeLogicTestBase { + public: + void run() { + OperationContextNoop txn; + ArtificialTreeBuilder builder(&txn, &this->_helper); + + builder.makeTree("{h:{e:{b:{a:null},c:null,d:null},g:{f:null}},_:{i:null}}"); + + ASSERT_EQUALS(9, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 6 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(7, this->_helper.recordStore.numRecords()); + + const BSONObj k = BSON("" << "c"); + verify(this->unindex(k)); + + ASSERT_EQUALS(8, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 3 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(4, this->_helper.recordStore.numRecords()); + + builder.checkStructure("{g:{b:{a:null},d:null,e:null,f:null},h:null,i:null}"); + } + }; + + template + class RecursiveMergeDoubleRightBucket : public BtreeLogicTestBase { + public: + void run() { + OperationContextNoop txn; + ArtificialTreeBuilder builder(&txn, &this->_helper); + + builder.makeTree("{h:{e:{b:{a:null},c:null,d:null},_:{f:null}},_:{i:null}}"); + + ASSERT_EQUALS(8, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 6 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(7, this->_helper.recordStore.numRecords()); + + const BSONObj k = BSON("" << "c"); + verify(this->unindex(k)); + + ASSERT_EQUALS(7, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 4 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(5, this->_helper.recordStore.numRecords()); + + // no recursion currently in this case + builder.checkStructure("{h:{b:{a:null},d:null,e:null,f:null},_:{i:null}}"); + } + }; + + template + class MergeSizeTestBase : public BtreeLogicTestBase { + public: + MergeSizeTestBase() : _count(0) { + + } + + void run() { + OperationContextNoop txn; + this->_helper.btree.initAsEmpty(&txn); + + ArtificialTreeBuilder builder(&txn, &this->_helper); + + const BSONObj& topKey = biggestKey('m'); + + DiskLoc leftChild = this->newBucket(); + builder.push(this->_helper.headManager.getHead(), topKey, leftChild); + _count++; + + DiskLoc rightChild = this->newBucket(); + this->setBucketNextChild(this->_helper.headManager.getHead(), rightChild); + + _count += builder.fillBucketToExactSize(leftChild, leftSize(), 'a'); + _count += builder.fillBucketToExactSize(rightChild, rightSize(), 'n'); + + ASSERT(leftAdditional() <= 2); + if (leftAdditional() >= 2) { + builder.push(leftChild, bigKey('k'), DiskLoc()); + } + if (leftAdditional() >= 1) { + builder.push(leftChild, bigKey('l'), DiskLoc()); + } + + ASSERT(rightAdditional() <= 2); + if (rightAdditional() >= 2) { + builder.push(rightChild, bigKey('y'), DiskLoc()); + } + if (rightAdditional() >= 1) { + builder.push(rightChild, bigKey('z'), DiskLoc()); + } + + _count += leftAdditional() + rightAdditional(); + + initCheck(); + + const char *keys = delKeys(); + for (const char *i = keys; *i; ++i) { + long long unused = 0; + ASSERT_EQUALS(_count, this->_helper.btree.fullValidate(&unused, true, true, 0)); + ASSERT_EQUALS(0, unused); + + // The tree has 3 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(4, this->_helper.recordStore.numRecords()); + + const BSONObj k = bigKey(*i); + this->unindex(k); + + --_count; + } + + long long unused = 0; + ASSERT_EQUALS(_count, this->_helper.btree.fullValidate(&unused, true, true, 0)); + ASSERT_EQUALS(0, unused); + + validate(); + + if (!merge()) { + // The tree has 3 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(4, this->_helper.recordStore.numRecords()); + } + else { + // The tree has 1 bucket + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(2, this->_helper.recordStore.numRecords()); + } + } + + protected: + virtual int leftAdditional() const { return 2; } + virtual int rightAdditional() const { return 2; } + virtual void initCheck() {} + virtual void validate() {} + virtual int leftSize() const = 0; + virtual int rightSize() const = 0; + virtual const char * delKeys() const { return "klyz"; } + virtual bool merge() const { return true; } + + static BSONObj bigKey(char a) { + return simpleKey(a, 801); + } + + static BSONObj biggestKey(char a) { + int size = OnDiskFormat::KeyMax - bigSize() + 801; + return simpleKey(a, size); + } + + static int bigSize() { + return typename BtreeLogicTestBase::KeyDataOwnedType(bigKey('a')).dataSize(); + } + + static int biggestSize() { + return typename BtreeLogicTestBase::KeyDataOwnedType(biggestKey('a')).dataSize(); + } + + int _count; + }; + + template + class MergeSizeJustRightRight : public MergeSizeTestBase { + protected: + virtual int rightSize() const { + return BtreeLogic::lowWaterMark() - 1; + } + + virtual int leftSize() const { + return OnDiskFormat::BucketBodySize - + MergeSizeTestBase::biggestSize() - + sizeof(typename BtreeLogicTestBase::FixedWidthKeyType) - + (BtreeLogic::lowWaterMark() - 1); + } + }; + + template + class MergeSizeJustRightLeft : public MergeSizeTestBase { + protected: + virtual int leftSize() const { + return BtreeLogic::lowWaterMark() - 1; + } + + virtual int rightSize() const { + return OnDiskFormat::BucketBodySize - + MergeSizeTestBase::biggestSize() - + sizeof(typename BtreeLogicTestBase::FixedWidthKeyType) - + (BtreeLogic::lowWaterMark() - 1); + } + + virtual const char * delKeys() const { return "yzkl"; } + }; + + template + class MergeSizeRight : public MergeSizeJustRightRight { + virtual int rightSize() const { return MergeSizeJustRightRight::rightSize() - 1; } + virtual int leftSize() const { return MergeSizeJustRightRight::leftSize() + 1; } + }; + + template + class MergeSizeLeft : public MergeSizeJustRightLeft { + virtual int rightSize() const { return MergeSizeJustRightLeft::rightSize() + 1; } + virtual int leftSize() const { return MergeSizeJustRightLeft::leftSize() - 1; } + }; + + template + class NoMergeBelowMarkRight : public MergeSizeJustRightRight { + virtual int rightSize() const { return MergeSizeJustRightRight::rightSize() + 1; } + virtual int leftSize() const { return MergeSizeJustRightRight::leftSize() - 1; } + virtual bool merge() const { return false; } + }; + + template + class NoMergeBelowMarkLeft : public MergeSizeJustRightLeft { + virtual int rightSize() const { return MergeSizeJustRightLeft::rightSize() - 1; } + virtual int leftSize() const { return MergeSizeJustRightLeft::leftSize() + 1; } + virtual bool merge() const { return false; } + }; + + template + class MergeSizeRightTooBig : public MergeSizeJustRightLeft { + virtual int rightSize() const { return MergeSizeJustRightLeft::rightSize() + 1; } + virtual bool merge() const { return false; } + }; + + template + class MergeSizeLeftTooBig : public MergeSizeJustRightRight { + virtual int leftSize() const { return MergeSizeJustRightRight::leftSize() + 1; } + virtual bool merge() const { return false; } + }; + + template + class MergeRightEmpty : public MergeSizeTestBase { + protected: + virtual int rightAdditional() const { return 1; } + virtual int leftAdditional() const { return 1; } + virtual const char * delKeys() const { return "lz"; } + virtual int rightSize() const { return 0; } + virtual int leftSize() const { + return OnDiskFormat::BucketBodySize - + MergeSizeTestBase::biggestSize() - + sizeof(typename BtreeLogicTestBase::FixedWidthKeyType); + } + }; + + template + class MergeMinRightEmpty : public MergeSizeTestBase { + protected: + virtual int rightAdditional() const { return 1; } + virtual int leftAdditional() const { return 0; } + virtual const char * delKeys() const { return "z"; } + virtual int rightSize() const { return 0; } + virtual int leftSize() const { + return MergeSizeTestBase::bigSize() + + sizeof(typename BtreeLogicTestBase::FixedWidthKeyType); + } + }; + + template + class MergeLeftEmpty : public MergeSizeTestBase { + protected: + virtual int rightAdditional() const { return 1; } + virtual int leftAdditional() const { return 1; } + virtual const char * delKeys() const { return "zl"; } + virtual int leftSize() const { return 0; } + virtual int rightSize() const { + return OnDiskFormat::BucketBodySize - + MergeSizeTestBase::biggestSize() - + sizeof(typename BtreeLogicTestBase::FixedWidthKeyType); + } + }; + + template + class MergeMinLeftEmpty : public MergeSizeTestBase { + protected: + virtual int leftAdditional() const { return 1; } + virtual int rightAdditional() const { return 0; } + virtual const char * delKeys() const { return "l"; } + virtual int leftSize() const { return 0; } + virtual int rightSize() const { + return MergeSizeTestBase::bigSize() + + sizeof(typename BtreeLogicTestBase::FixedWidthKeyType); + } + }; + + template + class BalanceRightEmpty : public MergeRightEmpty { + protected: + virtual int leftSize() const { + return OnDiskFormat::BucketBodySize - + MergeSizeTestBase::biggestSize() - + sizeof(typename BtreeLogicTestBase::FixedWidthKeyType) + 1; + } + + virtual bool merge() const { return false; } + + virtual void initCheck() { + _oldTop = this->getKey(this->_helper.headManager.getHead(), 0).data.toBson(); + } + + virtual void validate() { + ASSERT_NOT_EQUALS(_oldTop, this->getKey(this->_helper.headManager.getHead(), 0).data.toBson()); + } + + private: + BSONObj _oldTop; + }; + + template + class BalanceLeftEmpty : public MergeLeftEmpty { + protected: + virtual int rightSize() const { + return OnDiskFormat::BucketBodySize - + MergeSizeTestBase::biggestSize() - + sizeof(typename BtreeLogicTestBase::FixedWidthKeyType) + 1; + } + + virtual bool merge() const { return false; } + + virtual void initCheck() { + _oldTop = this->getKey(this->_helper.headManager.getHead(), 0).data.toBson(); + } + + virtual void validate() { + ASSERT_TRUE(_oldTop != this->getKey(this->_helper.headManager.getHead(), 0).data.toBson()); + } + + private: + BSONObj _oldTop; + }; + + template + class BalanceOneLeftToRight : public BtreeLogicTestBase { + public: + void run() { + OperationContextNoop txn; + ArtificialTreeBuilder builder(&txn, &this->_helper); + + builder.makeTree("{$10:{$1:null,$2:null,$3:null,$4:null,$5:null,$6:null}," + "b:{$20:null,$30:null,$40:null,$50:null,a:null}," + "_:{c:null}}"); + + ASSERT_EQUALS(14, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 4 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(5, this->_helper.recordStore.numRecords()); + + const BSONObj k = BSON("" << bigNumString(0x40, 800)); + ASSERT(this->unindex(k)); + + ASSERT_EQUALS(13, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 4 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(5, this->_helper.recordStore.numRecords()); + + builder.checkStructure("{$6:{$1:null,$2:null,$3:null,$4:null,$5:null}," + "b:{$10:null,$20:null,$30:null,$50:null,a:null}," + "_:{c:null}}"); + } + }; + + template + class BalanceOneRightToLeft : public BtreeLogicTestBase { + public: + void run() { + OperationContextNoop txn; + ArtificialTreeBuilder builder(&txn, &this->_helper); + + builder.makeTree("{$10:{$1:null,$2:null,$3:null,$4:null}," + "b:{$20:null,$30:null,$40:null,$50:null,$60:null,$70:null}," + "_:{c:null}}"); + + ASSERT_EQUALS(13, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 4 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(5, this->_helper.recordStore.numRecords()); + + const BSONObj k = BSON("" << bigNumString(0x3, 800)); + ASSERT(this->unindex(k)); + + ASSERT_EQUALS(12, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 4 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(5, this->_helper.recordStore.numRecords()); + + builder.checkStructure("{$20:{$1:null,$2:null,$4:null,$10:null}," + "b:{$30:null,$40:null,$50:null,$60:null,$70:null}," + "_:{c:null}}"); + } + }; + + template + class BalanceThreeLeftToRight : public BtreeLogicTestBase { + public: + void run() { + OperationContextNoop txn; + ArtificialTreeBuilder builder(&txn, &this->_helper); + + builder.makeTree("{$20:{$1:{$0:null},$3:{$2:null},$5:{$4:null},$7:{$6:null}," + "$9:{$8:null},$11:{$10:null},$13:{$12:null},_:{$14:null}}," + "b:{$30:null,$40:{$35:null},$50:{$45:null}}," + "_:{c:null}}"); + + ASSERT_EQUALS(23, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 14 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(15, this->_helper.recordStore.numRecords()); + + const BSONObj k = BSON("" << bigNumString(0x30, 800)); + ASSERT(this->unindex(k)); + + ASSERT_EQUALS(22, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 14 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(15, this->_helper.recordStore.numRecords()); + + builder.checkStructure("{$9:{$1:{$0:null},$3:{$2:null}," + "$5:{$4:null},$7:{$6:null},_:{$8:null}}," + "b:{$11:{$10:null},$13:{$12:null},$20:{$14:null}," + "$40:{$35:null},$50:{$45:null}}," + "_:{c:null}}"); + } + }; + + template + class BalanceThreeRightToLeft : public BtreeLogicTestBase { + public: + void run() { + OperationContextNoop txn; + ArtificialTreeBuilder builder(&txn, &this->_helper); + + builder.makeTree("{$20:{$1:{$0:null},$3:{$2:null},$5:null,_:{$14:null}}," + "b:{$30:{$25:null},$40:{$35:null},$50:{$45:null},$60:{$55:null}," + "$70:{$65:null},$80:{$75:null}," + "$90:{$85:null},$100:{$95:null}}," + "_:{c:null}}"); + + ASSERT_EQUALS(25, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 15 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(16, this->_helper.recordStore.numRecords()); + + const BSONObj k = BSON("" << bigNumString(0x5, 800)); + ASSERT(this->unindex(k)); + + ASSERT_EQUALS(24, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 15 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(16, this->_helper.recordStore.numRecords()); + + builder.checkStructure("{$50:{$1:{$0:null},$3:{$2:null},$20:{$14:null}," + "$30:{$25:null},$40:{$35:null},_:{$45:null}}," + "b:{$60:{$55:null},$70:{$65:null},$80:{$75:null}," + "$90:{$85:null},$100:{$95:null}}," + "_:{c:null}}"); + } + }; + + template + class BalanceSingleParentKey : public BtreeLogicTestBase { + public: + void run() { + OperationContextNoop txn; + ArtificialTreeBuilder builder(&txn, &this->_helper); + + builder.makeTree("{$10:{$1:null,$2:null,$3:null,$4:null,$5:null,$6:null}," + "_:{$20:null,$30:null,$40:null,$50:null,a:null}}"); + + ASSERT_EQUALS(12, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 3 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(4, this->_helper.recordStore.numRecords()); + + const BSONObj k = BSON("" << bigNumString(0x40, 800)); + ASSERT(this->unindex(k)); + + ASSERT_EQUALS(11, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 3 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(4, this->_helper.recordStore.numRecords()); + + builder.checkStructure("{$6:{$1:null,$2:null,$3:null,$4:null,$5:null}," + "_:{$10:null,$20:null,$30:null,$50:null,a:null}}"); + } + }; + + template + class PackEmptyBucket : public BtreeLogicTestBase { + public: + void run() { + OperationContextNoop txn; + ArtificialTreeBuilder builder(&txn, &this->_helper); + + builder.makeTree("{a:null}"); + + const BSONObj k = BSON("" << "a"); + ASSERT(this->unindex(k)); + + this->forcePackBucket(this->_helper.headManager.getHead()); + + typename BtreeLogicTestBase::BucketType* headBucket = this->head(); + + ASSERT_EQUALS(0, headBucket->n); + ASSERT_FALSE(headBucket->flags & Packed); + + int unused = 0; + this->truncateBucket(headBucket, 0, unused); + + ASSERT_EQUALS(0, headBucket->n); + ASSERT_EQUALS(0, headBucket->topSize); + ASSERT_EQUALS((int)OnDiskFormat::BucketBodySize, headBucket->emptySize); + ASSERT_TRUE(headBucket->flags & Packed); + } + }; + + template + class PackedDataSizeEmptyBucket : public BtreeLogicTestBase { + public: + void run() { + OperationContextNoop txn; + ArtificialTreeBuilder builder(&txn, &this->_helper); + + builder.makeTree("{a:null}"); + + const BSONObj k = BSON("" << "a"); + ASSERT(this->unindex(k)); + + this->forcePackBucket(this->_helper.headManager.getHead()); + + typename BtreeLogicTestBase::BucketType* headBucket = this->head(); + + ASSERT_EQUALS(0, headBucket->n); + ASSERT_FALSE(headBucket->flags & Packed); + ASSERT_EQUALS(0, this->bucketPackedDataSize(headBucket, 0)); + ASSERT_FALSE(headBucket->flags & Packed); + } + }; + + template + class BalanceSingleParentKeyPackParent : public BtreeLogicTestBase { + public: + void run() { + OperationContextNoop txn; + ArtificialTreeBuilder builder(&txn, &this->_helper); + + builder.makeTree("{$10:{$1:null,$2:null,$3:null,$4:null,$5:null,$6:null}," + "_:{$20:null,$30:null,$40:null,$50:null,a:null}}"); + + ASSERT_EQUALS(12, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 3 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(4, this->_helper.recordStore.numRecords()); + + // force parent pack + this->forcePackBucket(this->_helper.headManager.getHead()); + + const BSONObj k = BSON("" << bigNumString(0x40, 800)); + ASSERT(this->unindex(k)); + + ASSERT_EQUALS(11, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 3 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(4, this->_helper.recordStore.numRecords()); + + builder.checkStructure("{$6:{$1:null,$2:null,$3:null,$4:null,$5:null}," + "_:{$10:null,$20:null,$30:null,$50:null,a:null}}"); + } + }; + + template + class BalanceSplitParent : public BtreeLogicTestBase { + public: + void run() { + OperationContextNoop txn; + ArtificialTreeBuilder builder(&txn, &this->_helper); + + builder.makeTree( + "{$10$10:{$1:null,$2:null,$3:null,$4:null}," + "$100:{$20:null,$30:null,$40:null,$50:null,$60:null,$70:null,$80:null}," + "$200:null,$300:null,$400:null,$500:null,$600:null," + "$700:null,$800:null,$900:null,_:{c:null}}"); + + ASSERT_EQUALS(22, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 4 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(5, this->_helper.recordStore.numRecords()); + + const BSONObj k = BSON("" << bigNumString(0x3, 800)); + ASSERT(this->unindex(k)); + + ASSERT_EQUALS(21, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 6 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(7, this->_helper.recordStore.numRecords()); + + builder.checkStructure("{$500:{ $30:{$1:null,$2:null,$4:null,$10$10:null,$20:null}," + "$100:{$40:null,$50:null,$60:null,$70:null,$80:null}," + "$200:null,$300:null,$400:null}," + "_:{$600:null,$700:null,$800:null,$900:null,_:{c:null}}}"); + } + }; + + template + class RebalancedSeparatorBase : public BtreeLogicTestBase { + public: + void run() { + OperationContextNoop txn; + ArtificialTreeBuilder builder(&txn, &this->_helper); + + builder.makeTree(treeSpec()); + modTree(); + + ASSERT_EQUALS(expectedSeparator(), + this->bucketRebalancedSeparatorPos( + this->_helper.headManager.getHead(), 0)); + } + + virtual string treeSpec() const = 0; + virtual int expectedSeparator() const = 0; + virtual void modTree() {} + }; + + template + class EvenRebalanceLeft : public RebalancedSeparatorBase { + virtual string treeSpec() const { return "{$7:{$1:null,$2$31f:null,$3:null," + "$4$31f:null,$5:null,$6:null}," + "_:{$8:null,$9:null,$10$31e:null}}"; } + virtual int expectedSeparator() const { return 4; } + }; + + template + class EvenRebalanceLeftCusp : public RebalancedSeparatorBase { + virtual string treeSpec() const { + return "{$6:{$1:null,$2$31f:null,$3:null,$4$31f:null,$5:null}," + "_:{$7:null,$8:null,$9$31e:null,$10:null}}"; + } + virtual int expectedSeparator() const { return 4; } + }; + + template + class EvenRebalanceRight : public RebalancedSeparatorBase { + virtual string treeSpec() const { return "{$3:{$1:null,$2$31f:null},_:{$4$31f:null,$5:null,$6:null,$7:null,$8$31e:null,$9:null,$10:null}}"; } + virtual int expectedSeparator() const { return 4; } + }; + + template + class EvenRebalanceRightCusp : public RebalancedSeparatorBase { + virtual string treeSpec() const { return "{$4$31f:{$1:null,$2$31f:null,$3:null},_:{$5:null,$6:null,$7$31e:null,$8:null,$9:null,$10:null}}"; } + virtual int expectedSeparator() const { return 4; } + }; + + template + class EvenRebalanceCenter : public RebalancedSeparatorBase { + virtual string treeSpec() const { return "{$5:{$1:null,$2$31f:null,$3:null,$4$31f:null},_:{$6:null,$7$31e:null,$8:null,$9:null,$10:null}}"; } + virtual int expectedSeparator() const { return 4; } + }; + + template + class OddRebalanceLeft : public RebalancedSeparatorBase { + virtual string treeSpec() const { return "{$6$31f:{$1:null,$2:null,$3:null,$4:null,$5:null},_:{$7:null,$8:null,$9:null,$10:null}}"; } + virtual int expectedSeparator() const { return 4; } + }; + + template + class OddRebalanceRight : public RebalancedSeparatorBase { + virtual string treeSpec() const { return "{$4:{$1:null,$2:null,$3:null},_:{$5:null,$6:null,$7:null,$8$31f:null,$9:null,$10:null}}"; } + virtual int expectedSeparator() const { return 4; } + }; + + template + class OddRebalanceCenter : public RebalancedSeparatorBase { + virtual string treeSpec() const { return "{$5:{$1:null,$2:null,$3:null,$4:null},_:{$6:null,$7:null,$8:null,$9:null,$10$31f:null}}"; } + virtual int expectedSeparator() const { return 4; } + }; + + template + class RebalanceEmptyRight : public RebalancedSeparatorBase { + virtual string treeSpec() const { return "{$a:{$1:null,$2:null,$3:null,$4:null,$5:null,$6:null,$7:null,$8:null,$9:null},_:{$b:null}}"; } + virtual void modTree() { + BSONObj k = BSON("" << bigNumString(0xb, 800)); + ASSERT(this->unindex(k)); + } + virtual int expectedSeparator() const { return 4; } + }; + + template + class RebalanceEmptyLeft : public RebalancedSeparatorBase { + virtual string treeSpec() const { return "{$a:{$1:null},_:{$11:null,$12:null,$13:null,$14:null,$15:null,$16:null,$17:null,$18:null,$19:null}}"; } + virtual void modTree() { + BSONObj k = BSON("" << bigNumString(0x1, 800)); + ASSERT(this->unindex(k)); + } + virtual int expectedSeparator() const { return 4; } + }; + + template + class NoMoveAtLowWaterMarkRight : public MergeSizeJustRightRight { + virtual int rightSize() const { return MergeSizeJustRightRight::rightSize() + 1; } + + virtual void initCheck() { + _oldTop = this->getKey(this->_helper.headManager.getHead(), 0).data.toBson(); + } + + virtual void validate() { + ASSERT_EQUALS(_oldTop, this->getKey(this->_helper.headManager.getHead(), 0).data.toBson()); + } + + virtual bool merge() const { return false; } + + protected: + BSONObj _oldTop; + }; + + template + class MoveBelowLowWaterMarkRight : public NoMoveAtLowWaterMarkRight { + virtual int rightSize() const { return MergeSizeJustRightRight::rightSize(); } + virtual int leftSize() const { return MergeSizeJustRightRight::leftSize() + 1; } + + virtual void validate() { + // Different top means we rebalanced + ASSERT_NOT_EQUALS(this->_oldTop, + this->getKey(this->_helper.headManager.getHead(), 0).data.toBson()); + } + }; + + template + class NoMoveAtLowWaterMarkLeft : public MergeSizeJustRightLeft { + virtual int leftSize() const { return MergeSizeJustRightLeft::leftSize() + 1; } + virtual void initCheck() { + this->_oldTop = this->getKey(this->_helper.headManager.getHead(), 0).data.toBson(); + } + + virtual void validate() { + ASSERT_EQUALS(this->_oldTop, + this->getKey(this->_helper.headManager.getHead(), 0).data.toBson()); + } + virtual bool merge() const { return false; } + + protected: + BSONObj _oldTop; + }; + + template + class MoveBelowLowWaterMarkLeft : public NoMoveAtLowWaterMarkLeft { + virtual int leftSize() const { return MergeSizeJustRightLeft::leftSize(); } + virtual int rightSize() const { return MergeSizeJustRightLeft::rightSize() + 1; } + + virtual void validate() { + // Different top means we rebalanced + ASSERT_NOT_EQUALS(this->_oldTop, + this->getKey(this->_helper.headManager.getHead(), 0).data.toBson()); + } + }; + + template + class PreferBalanceLeft : public BtreeLogicTestBase { + public: + void run() { + OperationContextNoop txn; + ArtificialTreeBuilder builder(&txn, &this->_helper); + + builder.makeTree("{$10:{$1:null,$2:null,$3:null,$4:null,$5:null,$6:null}," + "$20:{$11:null,$12:null,$13:null,$14:null}," + "_:{$30:null}}"); + + ASSERT_EQUALS(13, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 4 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(5, this->_helper.recordStore.numRecords()); + + const BSONObj k = BSON("" << bigNumString(0x12, 800)); + ASSERT(this->unindex(k)); + + ASSERT_EQUALS(12, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 4 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(5, this->_helper.recordStore.numRecords()); + + builder.checkStructure("{$5:{$1:null,$2:null,$3:null,$4:null}," + "$20:{$6:null,$10:null,$11:null,$13:null,$14:null}," + "_:{$30:null}}"); + } + }; + + template + class PreferBalanceRight : public BtreeLogicTestBase { + public: + void run() { + OperationContextNoop txn; + ArtificialTreeBuilder builder(&txn, &this->_helper); + + builder.makeTree("{$10:{$1:null}," + "$20:{$11:null,$12:null,$13:null,$14:null}," + "_:{$31:null,$32:null,$33:null,$34:null,$35:null,$36:null}}"); + + ASSERT_EQUALS(13, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 4 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(5, this->_helper.recordStore.numRecords()); + + const BSONObj k = BSON("" << bigNumString(0x12, 800)); + ASSERT(this->unindex(k)); + + ASSERT_EQUALS(12, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 4 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(5, this->_helper.recordStore.numRecords()); + + builder.checkStructure("{$10:{$1:null}," + "$31:{$11:null,$13:null,$14:null,$20:null}," + "_:{$32:null,$33:null,$34:null,$35:null,$36:null}}"); + } + }; + + template + class RecursiveMergeThenBalance : public BtreeLogicTestBase { + public: + void run() { + OperationContextNoop txn; + ArtificialTreeBuilder builder(&txn, &this->_helper); + + builder.makeTree("{$10:{$5:{$1:null,$2:null},$8:{$6:null,$7:null}}," + "_:{$20:null,$30:null,$40:null,$50:null," + "$60:null,$70:null,$80:null,$90:null}}"); + + ASSERT_EQUALS(15, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 5 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(6, this->_helper.recordStore.numRecords()); + + const BSONObj k = BSON("" << bigNumString(0x7, 800)); + ASSERT(this->unindex(k)); + + ASSERT_EQUALS(14, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 4 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(5, this->_helper.recordStore.numRecords()); + + builder.checkStructure( + "{$40:{$8:{$1:null,$2:null,$5:null,$6:null},$10:null,$20:null,$30:null}," + "_:{$50:null,$60:null,$70:null,$80:null,$90:null}}"); + } + }; + + template + class DelEmptyNoNeighbors : public BtreeLogicTestBase { + public: + void run() { + OperationContextNoop txn; + ArtificialTreeBuilder builder(&txn, &this->_helper); + + builder.makeTree("{b:{a:null}}"); + + ASSERT_EQUALS(2, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 2 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(3, this->_helper.recordStore.numRecords()); + + const BSONObj k = BSON("" << "a"); + ASSERT(this->unindex(k)); + + ASSERT_EQUALS(1, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 1 bucket + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(2, this->_helper.recordStore.numRecords()); + + builder.checkStructure("{b:null}"); + } + }; + + template + class DelEmptyEmptyNeighbors : public BtreeLogicTestBase { + public: + void run() { + OperationContextNoop txn; + ArtificialTreeBuilder builder(&txn, &this->_helper); + + builder.makeTree("{a:null,c:{b:null},d:null}"); + + ASSERT_EQUALS(4, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 2 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(3, this->_helper.recordStore.numRecords()); + + const BSONObj k = BSON("" << "b"); + ASSERT(this->unindex(k)); + + ASSERT_EQUALS(3, this->_helper.btree.fullValidate(NULL, true, true, 0)); + + // The tree has 1 bucket + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(2, this->_helper.recordStore.numRecords()); + + builder.checkStructure("{a:null,c:null,d:null}"); + } + }; + + template + class DelInternal : public BtreeLogicTestBase { + public: + void run() { + OperationContextNoop txn; + ArtificialTreeBuilder builder(&txn, &this->_helper); + + builder.makeTree("{a:null,c:{b:null},d:null}"); + + long long unused = 0; + ASSERT_EQUALS(4, this->_helper.btree.fullValidate(&unused, true, true, 0)); + + // The tree has 2 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(3, this->_helper.recordStore.numRecords()); + ASSERT_EQUALS(0, unused); + + const BSONObj k = BSON("" << "c"); + ASSERT(this->unindex(k)); + + ASSERT_EQUALS(3, this->_helper.btree.fullValidate(&unused, true, true, 0)); + + // The tree has 1 bucket + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(2, this->_helper.recordStore.numRecords()); + ASSERT_EQUALS(0, unused); + + builder.checkStructure("{a:null,b:null,d:null}"); + } + }; + + template + class DelInternalReplaceWithUnused : public BtreeLogicTestBase { + public: + void run() { + OperationContextNoop txn; + ArtificialTreeBuilder builder(&txn, &this->_helper); + + builder.makeTree("{a:null,c:{b:null},d:null}"); + + const DiskLoc prevChildBucket = + this->getKey(this->_helper.headManager.getHead(), 1).prevChildBucket; + this->markKeyUnused(prevChildBucket, 0); + + long long unused = 0; + ASSERT_EQUALS(3, this->_helper.btree.fullValidate(&unused, true, true, 0)); + + // The tree has 2 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(3, this->_helper.recordStore.numRecords()); + ASSERT_EQUALS(1, unused); + + const BSONObj k = BSON("" << "c"); + ASSERT(this->unindex(k)); + + unused = 0; + ASSERT_EQUALS(2, this->_helper.btree.fullValidate(&unused, true, true, 0)); + + // The tree has 1 bucket + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(2, this->_helper.recordStore.numRecords()); + ASSERT_EQUALS(1, unused); + + // doesn't discriminate between used and unused + builder.checkStructure("{a:null,b:null,d:null}"); + } + }; + + template + class DelInternalReplaceRight : public BtreeLogicTestBase { + public: + void run() { + OperationContextNoop txn; + ArtificialTreeBuilder builder(&txn, &this->_helper); + + builder.makeTree("{a:null,_:{b:null}}"); + + long long unused = 0; + ASSERT_EQUALS(2, this->_helper.btree.fullValidate(&unused, true, true, 0)); + + // The tree has 2 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(3, this->_helper.recordStore.numRecords()); + ASSERT_EQUALS(0, unused); + + const BSONObj k = BSON("" << "a"); + ASSERT(this->unindex(k)); + + unused = 0; + ASSERT_EQUALS(1, this->_helper.btree.fullValidate(&unused, true, true, 0)); + + // The tree has 1 bucket + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(2, this->_helper.recordStore.numRecords()); + ASSERT_EQUALS(0, unused); + + builder.checkStructure("{b:null}"); + } + }; + + template + class DelInternalPromoteKey : public BtreeLogicTestBase { + public: + void run() { + OperationContextNoop txn; + ArtificialTreeBuilder builder(&txn, &this->_helper); + + builder.makeTree("{a:null,y:{d:{c:{b:null}},_:{e:null}},z:null}"); + + long long unused = 0; + ASSERT_EQUALS(7, this->_helper.btree.fullValidate(&unused, true, true, 0)); + + // The tree has 5 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(6, this->_helper.recordStore.numRecords()); + ASSERT_EQUALS(0, unused); + + const BSONObj k = BSON("" << "y"); + ASSERT(this->unindex(k)); + + unused = 0; + ASSERT_EQUALS(6, this->_helper.btree.fullValidate(&unused, true, true, 0)); + + // The tree has 3 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(4, this->_helper.recordStore.numRecords()); + ASSERT_EQUALS(0, unused); + + builder.checkStructure("{a:null,e:{c:{b:null},d:null},z:null}"); + } + }; + + template + class DelInternalPromoteRightKey : public BtreeLogicTestBase { + public: + void run() { + OperationContextNoop txn; + ArtificialTreeBuilder builder(&txn, &this->_helper); + + builder.makeTree("{a:null,_:{e:{c:null},_:{f:null}}}"); + + long long unused = 0; + ASSERT_EQUALS(4, this->_helper.btree.fullValidate(&unused, true, true, 0)); + + // The tree has 4 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(5, this->_helper.recordStore.numRecords()); + ASSERT_EQUALS(0, unused); + + const BSONObj k = BSON("" << "a"); + ASSERT(this->unindex(k)); + + unused = 0; + ASSERT_EQUALS(3, this->_helper.btree.fullValidate(&unused, true, true, 0)); + + // The tree has 2 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(3, this->_helper.recordStore.numRecords()); + ASSERT_EQUALS(0, unused); + + builder.checkStructure("{c:null,_:{e:null,f:null}}"); + } + }; + + template + class DelInternalReplacementPrevNonNull : public BtreeLogicTestBase { + public: + void run() { + OperationContextNoop txn; + ArtificialTreeBuilder builder(&txn, &this->_helper); + + builder.makeTree("{a:null,d:{c:{b:null}},e:null}"); + + long long unused = 0; + ASSERT_EQUALS(5, this->_helper.btree.fullValidate(&unused, true, true, 0)); + + // The tree has 3 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(4, this->_helper.recordStore.numRecords()); + ASSERT_EQUALS(0, unused); + + const BSONObj k = BSON("" << "d"); + ASSERT(this->unindex(k)); + + ASSERT_EQUALS(4, this->_helper.btree.fullValidate(&unused, true, true, 0)); + + // The tree has 3 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(4, this->_helper.recordStore.numRecords()); + ASSERT_EQUALS(1, unused); + + builder.checkStructure("{a:null,d:{c:{b:null}},e:null}"); + + // Check 'unused' key + ASSERT(this->getKey(this->_helper.headManager.getHead(), 1).recordLoc.getOfs() & 1); + } + }; + + template + class DelInternalReplacementNextNonNull : public BtreeLogicTestBase { + public: + void run() { + OperationContextNoop txn; + ArtificialTreeBuilder builder(&txn, &this->_helper); + + builder.makeTree("{a:null,_:{c:null,_:{d:null}}}"); + + long long unused = 0; + ASSERT_EQUALS(3, this->_helper.btree.fullValidate(&unused, true, true, 0)); + + // The tree has 3 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(4, this->_helper.recordStore.numRecords()); + ASSERT_EQUALS(0, unused); + + const BSONObj k = BSON("" << "a"); + ASSERT(this->unindex(k)); + + ASSERT_EQUALS(2, this->_helper.btree.fullValidate(&unused, true, true, 0)); + + // The tree has 3 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(4, this->_helper.recordStore.numRecords()); + ASSERT_EQUALS(1, unused); + + builder.checkStructure("{a:null,_:{c:null,_:{d:null}}}"); + + // Check 'unused' key + ASSERT(this->getKey(this->_helper.headManager.getHead(), 0).recordLoc.getOfs() & 1); + } + }; + + template + class DelInternalSplitPromoteLeft : public BtreeLogicTestBase { + public: + void run() { + OperationContextNoop txn; + ArtificialTreeBuilder builder(&txn, &this->_helper); + + builder.makeTree("{$10:null,$20:null,$30$10:{$25:{$23:null},_:{$27:null}}," + "$40:null,$50:null,$60:null,$70:null,$80:null,$90:null,$100:null}"); + + long long unused = 0; + ASSERT_EQUALS(13, this->_helper.btree.fullValidate(&unused, true, true, 0)); + + // The tree has 4 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(5, this->_helper.recordStore.numRecords()); + ASSERT_EQUALS(0, unused); + + const BSONObj k = BSON("" << bigNumString(0x30, 0x10)); + ASSERT(this->unindex(k)); + + ASSERT_EQUALS(12, this->_helper.btree.fullValidate(&unused, true, true, 0)); + + // The tree has 4 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(5, this->_helper.recordStore.numRecords()); + ASSERT_EQUALS(0, unused); + + builder.checkStructure("{$60:{$10:null,$20:null," + "$27:{$23:null,$25:null},$40:null,$50:null}," + "_:{$70:null,$80:null,$90:null,$100:null}}"); + } + }; + + template + class DelInternalSplitPromoteRight : public BtreeLogicTestBase { + public: + void run() { + OperationContextNoop txn; + ArtificialTreeBuilder builder(&txn, &this->_helper); + + builder.makeTree("{$10:null,$20:null,$30:null,$40:null,$50:null,$60:null,$70:null," + "$80:null,$90:null,$100$10:{$95:{$93:null},_:{$97:null}}}"); + + long long unused = 0; + ASSERT_EQUALS(13, this->_helper.btree.fullValidate(&unused, true, true, 0)); + + // The tree has 4 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(5, this->_helper.recordStore.numRecords()); + ASSERT_EQUALS(0, unused); + + const BSONObj k = BSON("" << bigNumString(0x100, 0x10)); + ASSERT(this->unindex(k)); + + ASSERT_EQUALS(12, this->_helper.btree.fullValidate(&unused, true, true, 0)); + + // The tree has 4 buckets + 1 for the this->_helper.dummyDiskLoc + ASSERT_EQUALS(5, this->_helper.recordStore.numRecords()); + ASSERT_EQUALS(0, unused); + + builder.checkStructure( + "{$80:{$10:null,$20:null,$30:null,$40:null,$50:null,$60:null,$70:null}," + "_:{$90:null,$97:{$93:null,$95:null}}}"); + } + }; + + /* This test requires the entire server to be linked-in and it is better implemented using + the JS framework. Disabling here and will put in jsCore. + + template + class SignedZeroDuplication : public BtreeLogicTestBase { + public: + void run() { + ASSERT_EQUALS(0.0, -0.0); + DBDirectClient c; + + static const string ns("unittests.SignedZeroDuplication"); + + c.ensureIndex(ns, BSON("b" << 1), true); + c.insert(ns, BSON("b" << 0.0)); + c.insert(ns, BSON("b" << 1.0)); + c.update(ns, BSON("b" << 1.0), BSON("b" << -0.0)); + + ASSERT_EQUALS(1U, c.count(ns, BSON("b" << 0.0))); + } + }; + */ + +/* +// QUERY_MIGRATION: port later + class PackUnused : public Base { + public: + void run() { + for ( long long i = 0; i < 1000000; i += 1000 ) { + insert( i ); + } + string orig, after; + { + stringstream ss; + bt()->shape( ss ); + orig = ss.str(); + } + vector< string > toDel; + vector< string > other; + BSONObjBuilder start; + start.appendMinKey( "a" ); + BSONObjBuilder end; + end.appendMaxKey( "a" ); + auto_ptr< BtreeCursor > c( BtreeCursor::make( nsdetails( ns() ), + id(), + start.done(), + end.done(), + false, + 1 ) ); + while( c->ok() ) { + bool has_child = + c->getBucket().btree()->keyNode(c->getKeyOfs()).prevChildBucket.isNull(); + + if (has_child) { + toDel.push_back( c->currKey().firstElement().valuestr() ); + } + else { + other.push_back( c->currKey().firstElement().valuestr() ); + } + c->advance(); + } + ASSERT( toDel.size() > 0 ); + for( vector< string >::const_iterator i = toDel.begin(); i != toDel.end(); ++i ) { + BSONObj o = BSON( "a" << *i ); + this->unindex( o ); + } + ASSERT( other.size() > 0 ); + for( vector< string >::const_iterator i = other.begin(); i != other.end(); ++i ) { + BSONObj o = BSON( "a" << *i ); + this->unindex( o ); + } + + long long unused = 0; + ASSERT_EQUALS( 0, bt()->fullValidate( dl(), order(), &unused, true ) ); + + for ( long long i = 50000; i < 50100; ++i ) { + insert( i ); + } + + long long unused2 = 0; + ASSERT_EQUALS( 100, bt()->fullValidate( dl(), order(), &unused2, true ) ); + +// log() << "old unused: " << unused << ", new unused: " << unused2 << endl; +// + ASSERT( unused2 <= unused ); + } + protected: + void insert( long long n ) { + string val = bigNumString( n ); + BSONObj k = BSON( "a" << val ); + Base::insert( k ); + } + }; + + class DontDropReferenceKey : public PackUnused { + public: + void run() { + // with 80 root node is full + for ( long long i = 0; i < 80; i += 1 ) { + insert( i ); + } + + BSONObjBuilder start; + start.appendMinKey( "a" ); + BSONObjBuilder end; + end.appendMaxKey( "a" ); + BSONObj l = bt()->keyNode( 0 ).key.toBson(); + string toInsert; + auto_ptr< BtreeCursor > c( BtreeCursor::make( nsdetails( ns() ), + id(), + start.done(), + end.done(), + false, + 1 ) ); + while( c->ok() ) { + if ( c->currKey().woCompare( l ) > 0 ) { + toInsert = c->currKey().firstElement().valuestr(); + break; + } + c->advance(); + } + // too much work to try to make this happen through inserts and deletes + // we are intentionally manipulating the btree bucket directly here + BtreeBucket::Loc* L = const_cast< BtreeBucket::Loc* >( &bt()->keyNode( 1 ).prevChildBucket ); + getDur().writing(L)->Null(); + getDur().writingInt( const_cast< BtreeBucket::Loc& >( bt()->keyNode( 1 ).recordLoc ).GETOFS() ) |= 1; // make unused + BSONObj k = BSON( "a" << toInsert ); + Base::insert( k ); + } + }; + */ + + // + // TEST SUITE DEFINITION + // + + template + class BtreeLogicTestSuite : public unittest::Suite { + public: + BtreeLogicTestSuite(const std::string& name) : Suite(name) { + + } + + void setupTests() { + add< SimpleCreate >(); + add< SimpleInsertDelete >(); + add< SplitRightHeavyBucket >(); + add< SplitLeftHeavyBucket >(); + add< MissingLocate >(); + add< MissingLocateMultiBucket >(); + add< SERVER983 >(); + add< DontReuseUnused >(); + add< MergeBucketsLeft >(); + add< MergeBucketsRight >(); + add< MergeBucketsDontReplaceHead >(); + add< MergeBucketsDelInternal >(); + add< MergeBucketsRightNull >(); + add< DontMergeSingleBucket >(); + add< ParentMergeNonRightToLeft >(); + add< ParentMergeNonRightToRight >(); + add< CantMergeRightNoMerge >(); + add< CantMergeLeftNoMerge >(); + add< MergeOption >(); + add< ForceMergeLeft >(); + add< ForceMergeRight >(); + add< RecursiveMerge >(); + add< RecursiveMergeRightBucket >(); + add< RecursiveMergeDoubleRightBucket >(); + + add< MergeSizeJustRightRight >(); + add< MergeSizeJustRightLeft >(); + add< MergeSizeRight >(); + add< MergeSizeLeft >(); + add< NoMergeBelowMarkRight >(); + add< NoMergeBelowMarkLeft >(); + add< MergeSizeRightTooBig >(); + add< MergeSizeLeftTooBig >(); + add< MergeRightEmpty >(); + add< MergeMinRightEmpty >(); + add< MergeLeftEmpty >(); + add< MergeMinLeftEmpty >(); + add< BalanceRightEmpty >(); + add< BalanceLeftEmpty >(); + + add< BalanceOneLeftToRight >(); + add< BalanceOneRightToLeft >(); + add< BalanceThreeLeftToRight >(); + add< BalanceThreeRightToLeft >(); + add< BalanceSingleParentKey >(); + + add< PackEmptyBucket >(); + add< PackedDataSizeEmptyBucket >(); + + add< BalanceSingleParentKeyPackParent >(); + add< BalanceSplitParent >(); + add< EvenRebalanceLeft >(); + add< EvenRebalanceLeftCusp >(); + add< EvenRebalanceRight >(); + add< EvenRebalanceRightCusp >(); + add< EvenRebalanceCenter >(); + add< OddRebalanceLeft >(); + add< OddRebalanceRight >(); + add< OddRebalanceCenter >(); + add< RebalanceEmptyRight >(); + add< RebalanceEmptyLeft >(); + + add< NoMoveAtLowWaterMarkRight >(); + add< MoveBelowLowWaterMarkRight >(); + add< NoMoveAtLowWaterMarkLeft >(); + add< MoveBelowLowWaterMarkLeft >(); + + add< PreferBalanceLeft >(); + add< PreferBalanceRight >(); + add< RecursiveMergeThenBalance >(); + add< DelEmptyNoNeighbors >(); + add< DelEmptyEmptyNeighbors >(); + add< DelInternal >(); + add< DelInternalReplaceWithUnused >(); + add< DelInternalReplaceRight >(); + add< DelInternalPromoteKey >(); + add< DelInternalPromoteRightKey >(); + add< DelInternalReplacementPrevNonNull >(); + add< DelInternalReplacementNextNonNull >(); + add< DelInternalSplitPromoteLeft >(); + add< DelInternalSplitPromoteRight >(); + } + }; + + // Test suite for both V0 and V1 + static BtreeLogicTestSuite SUITE_V0("BTreeLogicTests_V0"); + static BtreeLogicTestSuite SUITE_V1("BTreeLogicTests_V1"); +} diff --git a/src/mongo/db/structure/btree/btree_ondisk.h b/src/mongo/db/structure/btree/btree_ondisk.h index 1c8e5ad3efe60..1045443c822bd 100644 --- a/src/mongo/db/structure/btree/btree_ondisk.h +++ b/src/mongo/db/structure/btree/btree_ondisk.h @@ -175,8 +175,16 @@ namespace mongo { /* Beginning of the bucket's body */ char data[4]; + + // Precalculated size constants + enum { HeaderSize = 40 }; }; + // BtreeBucketV0 is part of the on-disk format, so it should never be changed + BOOST_STATIC_ASSERT( + sizeof(BtreeBucketV0) - sizeof(reinterpret_cast(NULL)->data) + == BtreeBucketV0::HeaderSize); + /** * A variant of DiskLoc Used by the V1 bucket type. */ @@ -310,6 +318,18 @@ namespace mongo { /* Beginning of the bucket's body */ char data[4]; + + // Precalculated size constants + enum { HeaderSize = 22 }; + }; + + // BtreeBucketV1 is part of the on-disk format, so it should never be changed + BOOST_STATIC_ASSERT( + sizeof(BtreeBucketV1) - sizeof(reinterpret_cast(NULL)->data) + == BtreeBucketV1::HeaderSize); + + enum Flags { + Packed = 1 }; struct BtreeLayoutV0 { @@ -319,7 +339,9 @@ namespace mongo { typedef KeyBson KeyOwnedType; typedef BtreeBucketV0 BucketType; - enum { BucketSize = 8192 }; + enum { BucketSize = 8192, + BucketBodySize = BucketSize - BucketType::HeaderSize + }; // largest key size we allow. note we very much need to support bigger keys (somehow) in // the future. @@ -343,8 +365,9 @@ namespace mongo { typedef DiskLoc56Bit LocType; typedef BtreeBucketV1 BucketType; - // The -16 is to leave room for the Record header. - enum { BucketSize = 8192 - 16 }; + enum { BucketSize = 8192 - 16, // The -16 is to leave room for the Record header + BucketBodySize = BucketSize - BucketType::HeaderSize + }; static const int KeyMax = 1024; diff --git a/src/mongo/db/structure/btree/btree_test_help.cpp b/src/mongo/db/structure/btree/btree_test_help.cpp new file mode 100644 index 0000000000000..8890e607245c2 --- /dev/null +++ b/src/mongo/db/structure/btree/btree_test_help.cpp @@ -0,0 +1,246 @@ +// btree_test_help.cpp : Helper functions for Btree unit-testing +// + +/** + * Copyright (C) 2014 MongoDB + * + * 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. + */ + +#include "mongo/db/structure/btree/btree_test_help.h" + +#include "mongo/db/operation_context_noop.h" +#include "mongo/unittest/unittest.h" + + +namespace mongo { + + string bigNumString(long long n, int len) { + char sub[17]; + sprintf(sub, "%.16llx", n); + string val(len, ' '); + for (int i = 0; i < len; ++i) { + val[i] = sub[i % 16]; + } + return val; + } + + BSONObj simpleKey(char c, int n) { + BSONObjBuilder builder; + string val(n, c); + builder.append("a", val); + return builder.obj(); + } + + // + // BtreeLogicTestHelper + // + + static BucketDeletionNotification dummyBucketDeletionNotification; + + template + BtreeLogicTestHelper::BtreeLogicTestHelper(const BSONObj& order) + : recordStore("TestRecordStore"), + btree(&headManager, + &recordStore, + Ordering::make(order), + "TestIndex", + &dummyBucketDeletionNotification) { + + static const string randomData("RandomStuff"); + + // Generate a valid record location for a "fake" record, which we will repeatedly use + // thoughout the tests. + OperationContextNoop txn; + StatusWith s = + recordStore.insertRecord(&txn, randomData.c_str(), randomData.length(), 0); + + ASSERT_TRUE(s.isOK()); + ASSERT_EQUALS(1, recordStore.numRecords()); + + dummyDiskLoc = s.getValue(); + } + + + // + // ArtificialTreeBuilder + // + + template + void ArtificialTreeBuilder::makeTree(const string &spec) { + _helper->headManager.setHead(_txn, makeTree(fromjson(spec))); + } + + template + DiskLoc ArtificialTreeBuilder::makeTree(const BSONObj &spec) { + DiskLoc bucketLoc = _helper->btree._addBucket(_txn); + BucketType* bucket = _helper->btree.getBucket(bucketLoc); + + BSONObjIterator i(spec); + while (i.more()) { + BSONElement e = i.next(); + DiskLoc child; + if (e.type() == Object) { + child = makeTree(e.embeddedObject()); + } + + if (e.fieldName() == string("_")) { + bucket->nextChild = child; + } + else { + KeyDataOwnedType key(BSON("" << expectedKey(e.fieldName()))); + _helper->btree._pushBack(bucket, _helper->dummyDiskLoc, key, child); + } + } + + _helper->btree.fixParentPtrs(_txn, bucket, bucketLoc); + return bucketLoc; + } + + template + void ArtificialTreeBuilder::checkStructure(const string &spec) const { + checkStructure(fromjson(spec), _helper->headManager.getHead()); + } + + template + void ArtificialTreeBuilder::push( + const DiskLoc bucketLoc, const BSONObj& key, const DiskLoc child) { + KeyDataOwnedType k(key); + BucketType* bucket = _helper->btree.getBucket(bucketLoc); + + _helper->btree._pushBack(bucket, _helper->dummyDiskLoc, k, child); + _helper->btree.fixParentPtrs(_txn, bucket, bucketLoc); + } + + template + void ArtificialTreeBuilder::checkStructure( + const BSONObj &spec, const DiskLoc node) const { + BucketType* bucket = _helper->btree.getBucket(node); + + BSONObjIterator j(spec); + for (int i = 0; i < bucket->n; ++i) { + ASSERT(j.more()); + BSONElement e = j.next(); + KeyHeaderType kn = BtreeLogic::getKeyHeader(bucket, i); + string expected = expectedKey(e.fieldName()); + ASSERT(isPresent(BSON("" << expected), 1)); + ASSERT(isPresent(BSON("" << expected), -1)); + + // ASSERT_EQUALS(expected, kn.key.toBson().firstElement().valuestr()); + if (kn.prevChildBucket.isNull()) { + ASSERT(e.type() == jstNULL); + } + else { + ASSERT(e.type() == Object); + checkStructure(e.embeddedObject(), kn.prevChildBucket); + } + } + if (bucket->nextChild.isNull()) { + // maybe should allow '_' field with null value? + ASSERT(!j.more()); + } + else { + BSONElement e = j.next(); + ASSERT_EQUALS(string("_"), e.fieldName()); + ASSERT(e.type() == Object); + checkStructure(e.embeddedObject(), bucket->nextChild); + } + ASSERT(!j.more()); + } + + template + bool ArtificialTreeBuilder::isPresent(const BSONObj &key, int direction) const { + int pos; + DiskLoc loc; + return _helper->btree.locate(key, _helper->dummyDiskLoc, direction, &pos, &loc); + } + + // Static + template + string ArtificialTreeBuilder::expectedKey(const char *spec) { + if (spec[0] != '$') { + return spec; + } + char *endPtr; + + // parsing a long long is a pain, so just allow shorter keys for now + unsigned long long num = strtol(spec + 1, &endPtr, 16); + int len = 800; + if (*endPtr == '$') { + len = strtol(endPtr + 1, 0, 16); + } + + return bigNumString(num, len); + } + + template + int ArtificialTreeBuilder::fillBucketToExactSize( + const DiskLoc bucketLoc, int targetSize, char startKey) { + ASSERT_FALSE(bucketLoc.isNull()); + + BucketType* bucket = _helper->btree.getBucket(bucketLoc); + ASSERT_EQUALS(0, bucket->n); + + static const int bigSize = KeyDataOwnedType(simpleKey('a', 801)).dataSize(); + + int size = 0; + int keyCount = 0; + while (size < targetSize) { + int space = targetSize - size; + int nextSize = space - sizeof(FixedWidthKeyType); + verify(nextSize > 0); + + BSONObj newKey; + if (nextSize >= bigSize) { + newKey = simpleKey(startKey++, 801); + } + else { + newKey = simpleKey(startKey++, nextSize - (bigSize - 801)); + } + + push(bucketLoc, newKey, DiskLoc()); + + size += KeyDataOwnedType(newKey).dataSize() + + sizeof(FixedWidthKeyType); + keyCount += 1; + } + + ASSERT_EQUALS(_helper->btree._packedDataSize(bucket, 0), targetSize); + + return keyCount; + } + + // + // This causes actual code to be generated for the usages of the templates in this file. + // + + // V0 format. + template struct BtreeLogicTestHelper; + template class ArtificialTreeBuilder; + + // V1 format. + template struct BtreeLogicTestHelper; + template class ArtificialTreeBuilder; +} diff --git a/src/mongo/db/structure/btree/btree_test_help.h b/src/mongo/db/structure/btree/btree_test_help.h new file mode 100644 index 0000000000000..3dcc8c5986df3 --- /dev/null +++ b/src/mongo/db/structure/btree/btree_test_help.h @@ -0,0 +1,154 @@ +/** + * 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. + */ + +#pragma once + +#include + +#include "mongo/db/json.h" +#include "mongo/db/structure/btree/btree_logic.h" +#include "mongo/db/structure/record_store_heap.h" +#include "mongo/db/structure/record_store_v1_test_help.h" + + +namespace mongo { + + /** + * Generates a string of the specified length containing repeated concatenation of the + * hexadecimal representation of the input value. + */ + std::string bigNumString(long long n, int len); + + /** + * Generates key on a field 'a', with the specified number of repetitions of the character. + */ + BSONObj simpleKey(char c, int n = 1); + + /** + * Simple head manager, which performs no validity checking or persistence. + */ + class TestHeadManager : public HeadManager { + public: + virtual const DiskLoc getHead() const { + return _head; + } + + virtual void setHead(OperationContext* txn, const DiskLoc newHead) { + _head = newHead; + } + + private: + DiskLoc _head; + }; + + + /** + * This structure encapsulates a Btree and all the infrastructure needed by it (head manager, + * record store and a valid disk location to use by the tests). + */ + template + struct BtreeLogicTestHelper { + BtreeLogicTestHelper(const BSONObj& order); + + // Everything needed for a fully-functional Btree logic + TestHeadManager headManager; + HeapRecordStore recordStore; + BtreeLogic btree; + DiskLoc dummyDiskLoc; + }; + + + /** + * Tool to construct custom tree shapes for tests. + */ + template + class ArtificialTreeBuilder { + public: + + typedef typename BtreeLogic::BucketType BucketType; + typedef typename BtreeLogic::KeyDataOwnedType KeyDataOwnedType; + typedef typename BtreeLogic::KeyHeaderType KeyHeaderType; + + typedef typename OnDiskFormat::FixedWidthKeyType FixedWidthKeyType; + + /** + * The tree builder wraps around the passed-in helper and will invoke methods on it. It + * does not do any cleanup, so constructing multiple trees over the same helper will + * cause leaked records. + */ + ArtificialTreeBuilder(OperationContext* txn, + BtreeLogicTestHelper* helper) + : _txn(txn), _helper(helper) { + + } + + /** + * Causes the specified tree shape to be built on the associated helper and the tree's + * root installed as the head. Uses a custom JSON-based language with the following + * syntax: + * + * Btree := BTreeBucket + * BtreeBucket := { Child_1_Key: , + * Child_2_Key: , + * ..., + * _: } + * + * The _ key name specifies the content of the nextChild pointer. The value null means + * use a fixed disk loc. + */ + void makeTree(const std::string& spec); + + /** + * Validates that the structure of the Btree in the helper matches the specification. + */ + void checkStructure(const std::string& spec) const; + + /** + * Adds the following key to the bucket and fixes up the child pointers. + */ + void push(const DiskLoc bucketLoc, const BSONObj& key, const DiskLoc child); + + /** + * @return The number of keys inserted. + */ + int fillBucketToExactSize(const DiskLoc bucketLoc, int targetSize, char startKey); + + private: + DiskLoc makeTree(const BSONObj& spec); + + void checkStructure(const BSONObj& spec, const DiskLoc node) const; + + bool isPresent(const BSONObj& key, int direction) const; + + static string expectedKey(const char* spec); + + OperationContext* _txn; + BtreeLogicTestHelper* _helper; + }; + +} // namespace mongo diff --git a/src/mongo/db/structure/head_manager.h b/src/mongo/db/structure/head_manager.h index 9460b9302101c..bdb993ee3e368 100644 --- a/src/mongo/db/structure/head_manager.h +++ b/src/mongo/db/structure/head_manager.h @@ -42,9 +42,9 @@ namespace mongo { public: virtual ~HeadManager() { } - virtual const DiskLoc& getHead() const = 0; + virtual const DiskLoc getHead() const = 0; - virtual void setHead(OperationContext* txn, const DiskLoc& newHead) = 0; + virtual void setHead(OperationContext* txn, const DiskLoc newHead) = 0; }; } // namespace mongo diff --git a/src/mongo/dbtests/btreetests.cpp b/src/mongo/dbtests/btreetests.cpp deleted file mode 100644 index fa4fb53ba2fd3..0000000000000 --- a/src/mongo/dbtests/btreetests.cpp +++ /dev/null @@ -1,65 +0,0 @@ -// btreetests.cpp : Btree unit tests -// - -/** - * Copyright (C) 2008 10gen 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. - */ - - // QUERY_MIGRATION -#if 0 - -#include "mongo/pch.h" - -#include "mongo/db/structure/btree/btree.h" -#include "mongo/db/db.h" -#include "mongo/db/json.h" -#include "mongo/dbtests/dbtests.h" - -#define BtreeBucket BtreeBucket -#define btree btree -#define btreemod btreemod -#define testName "btree" -#define BTVERSION 0 -namespace BtreeTests0 { -#include "mongo/dbtests/btreetests.inl" -} - -#undef BtreeBucket -#undef btree -#undef btreemod -#define BtreeBucket BtreeBucket -#define btree btree -#define btreemod btreemod -#undef testName -#define testName "btree1" -#undef BTVERSION -#define BTVERSION 1 -namespace BtreeTests1 { -#include "mongo/dbtests/btreetests.inl" -} - -#endif diff --git a/src/mongo/dbtests/btreetests.inl b/src/mongo/dbtests/btreetests.inl deleted file mode 100644 index f80754895388b..0000000000000 --- a/src/mongo/dbtests/btreetests.inl +++ /dev/null @@ -1,1721 +0,0 @@ - typedef BtreeBucket::_KeyNode _KeyNode; - - const char* ns() { - return "unittests.btreetests"; - } - - // dummy, valid record loc - const DiskLoc recordLoc() { - return DiskLoc( 0, 2 ); - } - - class Ensure { - public: - Ensure() { - _c.ensureIndex( ns(), BSON( "a" << 1 ), false, "testIndex", - false, // given two versions not sure if cache true would mess us up... - false, BTVERSION); - } - ~Ensure() { - _c.dropCollection( ns() ); - //_c.dropIndexes( ns() ); - } - private: - DBDirectClient _c; - }; - - class Base : public Ensure { - public: - Base() : - _context( ns() ) { - { - bool f = false; - verify( f = true ); - massert( 10402 , "verify is misdefined", f); - } - } - virtual ~Base() {} - static string bigNumString( long long n, int len = 800 ) { - char sub[17]; - sprintf( sub, "%.16llx", n ); - string val( len, ' ' ); - for( int i = 0; i < len; ++i ) { - val[ i ] = sub[ i % 16 ]; - } - return val; - } - protected: - const BtreeBucket* bt() { - return id().head.btree(); - } - DiskLoc dl() { - return id().head; - } - IndexDetails& id() { - NamespaceDetails *nsd = nsdetails( ns() ); - verify( nsd ); - verify( nsd->getTotalIndexCount() >= 2 ); - return nsd->idx( 1 ); - } - void checkValid( int nKeys ) { - ASSERT( bt() ); - ASSERT( bt()->isHead() ); - bt()->assertValid( order(), true ); - ASSERT_EQUALS( nKeys, bt()->fullValidate( dl(), order(), 0, true ) ); - } - void dump() { - bt()->dumpTree( dl(), order() ); - } - void insert( BSONObj &key ) { - const BtreeBucket *b = bt(); - b->bt_insert( dl(), recordLoc(), key, Ordering::make(order()), true, id(), true ); - getDur().commitIfNeeded(); - } - bool unindex( BSONObj &key ) { - getDur().commitIfNeeded(); - return bt()->unindex( dl(), id(), key, recordLoc() ); - } - static BSONObj simpleKey( char c, int n = 1 ) { - BSONObjBuilder builder; - string val( n, c ); - builder.append( "a", val ); - return builder.obj(); - } - void locate( BSONObj &key, int expectedPos, - bool expectedFound, const DiskLoc &expectedLocation, - int direction = 1 ) { - int pos; - bool found; - DiskLoc location = - bt()->locate( id(), dl(), key, Ordering::make(order()), pos, found, recordLoc(), direction ); - ASSERT_EQUALS( expectedFound, found ); - ASSERT( location == expectedLocation ); - ASSERT_EQUALS( expectedPos, pos ); - } - bool present( BSONObj &key, int direction ) { - int pos; - bool found; - bt()->locate( id(), dl(), key, Ordering::make(order()), pos, found, recordLoc(), direction ); - return found; - } - BSONObj order() { - return id().keyPattern(); - } - const BtreeBucket *child( const BtreeBucket *b, int i ) { - verify( i <= b->nKeys() ); - DiskLoc d; - if ( i == b->nKeys() ) { - d = b->getNextChild(); - } - else { - d = b->keyNode( i ).prevChildBucket; - } - verify( !d.isNull() ); - return d.btree(); - } - void checkKey( char i ) { - stringstream ss; - ss << i; - checkKey( ss.str() ); - } - void checkKey( const string &k ) { - BSONObj key = BSON( "" << k ); -// log() << "key: " << key << endl; - ASSERT( present( key, 1 ) ); - ASSERT( present( key, -1 ) ); - } - private: - Lock::GlobalWrite lk_; - Client::Context _context; - }; - - class Create : public Base { - public: - Create() { } - void run() { - checkValid( 0 ); - } - }; - - class SimpleInsertDelete : public Base { - public: - void run() { - BSONObj key = simpleKey( 'z' ); - insert( key ); - - checkValid( 1 ); - locate( key, 0, true, dl() ); - - unindex( key ); - - checkValid( 0 ); - locate( key, 0, false, DiskLoc() ); - } - }; - - class SplitUnevenBucketBase : public Base { - public: - virtual ~SplitUnevenBucketBase() {} - void run() { - for ( int i = 0; i < 10; ++i ) { - BSONObj shortKey = simpleKey( shortToken( i ), 1 ); - insert( shortKey ); - BSONObj longKey = simpleKey( longToken( i ), 800 ); - insert( longKey ); - } - checkValid( 20 ); - ASSERT_EQUALS( 1, bt()->nKeys() ); - checkSplit(); - } - protected: - virtual char shortToken( int i ) const = 0; - virtual char longToken( int i ) const = 0; - static char leftToken( int i ) { - return 'a' + i; - } - static char rightToken( int i ) { - return 'z' - i; - } - virtual void checkSplit() = 0; - }; - - class SplitRightHeavyBucket : public SplitUnevenBucketBase { - private: - virtual char shortToken( int i ) const { - return leftToken( i ); - } - virtual char longToken( int i ) const { - return rightToken( i ); - } - virtual void checkSplit() { - ASSERT_EQUALS( 15, child( bt(), 0 )->nKeys() ); - ASSERT_EQUALS( 4, child( bt(), 1 )->nKeys() ); - } - }; - - class SplitLeftHeavyBucket : public SplitUnevenBucketBase { - private: - virtual char shortToken( int i ) const { - return rightToken( i ); - } - virtual char longToken( int i ) const { - return leftToken( i ); - } - virtual void checkSplit() { - ASSERT_EQUALS( 4, child( bt(), 0 )->nKeys() ); - ASSERT_EQUALS( 15, child( bt(), 1 )->nKeys() ); - } - }; - - class MissingLocate : public Base { - public: - void run() { - for ( int i = 0; i < 3; ++i ) { - BSONObj k = simpleKey( 'b' + 2 * i ); - insert( k ); - } - - locate( 1, 'a', 'b', dl() ); - locate( 1, 'c', 'd', dl() ); - locate( 1, 'e', 'f', dl() ); - locate( 1, 'g', 'g' + 1, DiskLoc() ); // of course, 'h' isn't in the index. - - // old behavior - // locate( -1, 'a', 'b', dl() ); - // locate( -1, 'c', 'd', dl() ); - // locate( -1, 'e', 'f', dl() ); - // locate( -1, 'g', 'f', dl() ); - - locate( -1, 'a', 'a' - 1, DiskLoc() ); // of course, 'a' - 1 isn't in the index - locate( -1, 'c', 'b', dl() ); - locate( -1, 'e', 'd', dl() ); - locate( -1, 'g', 'f', dl() ); - } - private: - void locate( int direction, char token, char expectedMatch, - DiskLoc expectedLocation ) { - BSONObj k = simpleKey( token ); - int expectedPos = ( expectedMatch - 'b' ) / 2; - Base::locate( k, expectedPos, false, expectedLocation, direction ); - } - }; - - class MissingLocateMultiBucket : public Base { - public: - void run() { - for ( int i = 0; i < 8; ++i ) { - insert( i ); - } - insert( 9 ); - insert( 8 ); -// dump(); - BSONObj straddle = key( 'i' ); - locate( straddle, 0, false, dl(), 1 ); - straddle = key( 'k' ); - locate( straddle, 0, false, dl(), -1 ); - } - private: - BSONObj key( char c ) { - return simpleKey( c, 800 ); - } - void insert( int i ) { - BSONObj k = key( 'b' + 2 * i ); - Base::insert( k ); - } - }; - - class SERVER983 : public Base { - public: - void run() { - for ( int i = 0; i < 10; ++i ) { - insert( i ); - } -// dump(); - BSONObj straddle = key( 'o' ); - locate( straddle, 0, false, dl(), 1 ); - straddle = key( 'q' ); - locate( straddle, 0, false, dl(), -1 ); - } - private: - BSONObj key( char c ) { - return simpleKey( c, 800 ); - } - void insert( int i ) { - BSONObj k = key( 'b' + 2 * i ); - Base::insert( k ); - } - }; - - class DontReuseUnused : public Base { - public: - void run() { - for ( int i = 0; i < 10; ++i ) { - insert( i ); - } -// dump(); - BSONObj root = key( 'p' ); - unindex( root ); - Base::insert( root ); - locate( root, 0, true, bt()->getNextChild(), 1 ); - } - private: - BSONObj key( char c ) { - return simpleKey( c, 800 ); - } - void insert( int i ) { - BSONObj k = key( 'b' + 2 * i ); - Base::insert( k ); - } - }; - -/* -// QUERY_MIGRATION: port later - class PackUnused : public Base { - public: - void run() { - for ( long long i = 0; i < 1000000; i += 1000 ) { - insert( i ); - } - string orig, after; - { - stringstream ss; - bt()->shape( ss ); - orig = ss.str(); - } - vector< string > toDel; - vector< string > other; - BSONObjBuilder start; - start.appendMinKey( "a" ); - BSONObjBuilder end; - end.appendMaxKey( "a" ); - auto_ptr< BtreeCursor > c( BtreeCursor::make( nsdetails( ns() ), - id(), - start.done(), - end.done(), - false, - 1 ) ); - while( c->ok() ) { - bool has_child = - c->getBucket().btree()->keyNode(c->getKeyOfs()).prevChildBucket.isNull(); - - if (has_child) { - toDel.push_back( c->currKey().firstElement().valuestr() ); - } - else { - other.push_back( c->currKey().firstElement().valuestr() ); - } - c->advance(); - } - ASSERT( toDel.size() > 0 ); - for( vector< string >::const_iterator i = toDel.begin(); i != toDel.end(); ++i ) { - BSONObj o = BSON( "a" << *i ); - unindex( o ); - } - ASSERT( other.size() > 0 ); - for( vector< string >::const_iterator i = other.begin(); i != other.end(); ++i ) { - BSONObj o = BSON( "a" << *i ); - unindex( o ); - } - - long long unused = 0; - ASSERT_EQUALS( 0, bt()->fullValidate( dl(), order(), &unused, true ) ); - - for ( long long i = 50000; i < 50100; ++i ) { - insert( i ); - } - - long long unused2 = 0; - ASSERT_EQUALS( 100, bt()->fullValidate( dl(), order(), &unused2, true ) ); - -// log() << "old unused: " << unused << ", new unused: " << unused2 << endl; -// - ASSERT( unused2 <= unused ); - } - protected: - void insert( long long n ) { - string val = bigNumString( n ); - BSONObj k = BSON( "a" << val ); - Base::insert( k ); - } - }; - - class DontDropReferenceKey : public PackUnused { - public: - void run() { - // with 80 root node is full - for ( long long i = 0; i < 80; i += 1 ) { - insert( i ); - } - - BSONObjBuilder start; - start.appendMinKey( "a" ); - BSONObjBuilder end; - end.appendMaxKey( "a" ); - BSONObj l = bt()->keyNode( 0 ).key.toBson(); - string toInsert; - auto_ptr< BtreeCursor > c( BtreeCursor::make( nsdetails( ns() ), - id(), - start.done(), - end.done(), - false, - 1 ) ); - while( c->ok() ) { - if ( c->currKey().woCompare( l ) > 0 ) { - toInsert = c->currKey().firstElement().valuestr(); - break; - } - c->advance(); - } - // too much work to try to make this happen through inserts and deletes - // we are intentionally manipulating the btree bucket directly here - BtreeBucket::Loc* L = const_cast< BtreeBucket::Loc* >( &bt()->keyNode( 1 ).prevChildBucket ); - getDur().writing(L)->Null(); - getDur().writingInt( const_cast< BtreeBucket::Loc& >( bt()->keyNode( 1 ).recordLoc ).GETOFS() ) |= 1; // make unused - BSONObj k = BSON( "a" << toInsert ); - Base::insert( k ); - } - }; - */ - - class MergeBuckets : public Base { - public: - virtual ~MergeBuckets() {} - void run() { - for ( int i = 0; i < 10; ++i ) { - insert( i ); - } -// dump(); - string ns = id().indexNamespace(); - ASSERT_EQUALS( 3, nsdetails( ns )->numRecords() ); - int expectedCount = 10 - unindexKeys(); -// dump(); - ASSERT_EQUALS( 1, nsdetails( ns )->numRecords() ); - long long unused = 0; - ASSERT_EQUALS( expectedCount, bt()->fullValidate( dl(), order(), &unused, true ) ); - ASSERT_EQUALS( 0, unused ); - } - protected: - BSONObj key( char c ) { - return simpleKey( c, 800 ); - } - void insert( int i ) { - BSONObj k = key( 'b' + 2 * i ); - Base::insert( k ); - } - virtual int unindexKeys() = 0; - }; - - class MergeBucketsLeft : public MergeBuckets { - virtual int unindexKeys() { - BSONObj k = key( 'b' ); - unindex( k ); - k = key( 'b' + 2 ); - unindex( k ); - k = key( 'b' + 4 ); - unindex( k ); - k = key( 'b' + 6 ); - unindex( k ); - return 4; - } - }; - - class MergeBucketsRight : public MergeBuckets { - virtual int unindexKeys() { - BSONObj k = key( 'b' + 2 * 9 ); - unindex( k ); - return 1; - } - }; - - // deleting from head won't coalesce yet -// class MergeBucketsHead : public MergeBuckets { -// virtual BSONObj unindexKey() { return key( 'p' ); } -// }; - - class MergeBucketsDontReplaceHead : public Base { - public: - void run() { - for ( int i = 0; i < 18; ++i ) { - insert( i ); - } - // dump(); - string ns = id().indexNamespace(); - ASSERT_EQUALS( 4, nsdetails( ns )->numRecords() ); - BSONObj k = key( 'a' + 17 ); - unindex( k ); - ASSERT_EQUALS( 3, nsdetails( ns )->numRecords() ); - long long unused = 0; - ASSERT_EQUALS( 17, bt()->fullValidate( dl(), order(), &unused, true ) ); - ASSERT_EQUALS( 0, unused ); - } - private: - BSONObj key( char c ) { - return simpleKey( c, 800 ); - } - void insert( int i ) { - BSONObj k = key( 'a' + i ); - Base::insert( k ); - } - }; - - // Tool to construct custom trees for tests. - class ArtificialTree : public BtreeBucket { - public: - void push( const BSONObj &key, const DiskLoc &child ) { - KeyOwned k(key); - pushBack( dummyDiskLoc(), k, Ordering::make( BSON( "a" << 1 ) ), child ); - } - void setNext( const DiskLoc &child ) { - nextChild = child; - } - static DiskLoc make( IndexDetails &id ) { - DiskLoc ret = addBucket( id ); - is( ret )->init(); - getDur().commitIfNeeded(); - return ret; - } - static ArtificialTree *is( const DiskLoc &l ) { - return static_cast< ArtificialTree * >( l.btreemod() ); - } - static DiskLoc makeTree( const string &spec, IndexDetails &id ) { - return makeTree( fromjson( spec ), id ); - } - static DiskLoc makeTree( const BSONObj &spec, IndexDetails &id ) { - DiskLoc node = make( id ); - ArtificialTree *n = ArtificialTree::is( node ); - BSONObjIterator i( spec ); - while( i.more() ) { - BSONElement e = i.next(); - DiskLoc child; - if ( e.type() == Object ) { - child = makeTree( e.embeddedObject(), id ); - } - if ( e.fieldName() == string( "_" ) ) { - n->setNext( child ); - } - else { - n->push( BSON( "" << expectedKey( e.fieldName() ) ), child ); - } - } - n->fixParentPtrs( node ); - return node; - } - static void setTree( const string &spec, IndexDetails &id ) { - set( makeTree( spec, id ), id ); - } - static void set( const DiskLoc &l, IndexDetails &id ) { - ArtificialTree::is( id.head )->deallocBucket( id.head, id ); - *getDur().writing(&id.head) = l; - } - static string expectedKey( const char *spec ) { - if ( spec[ 0 ] != '$' ) { - return spec; - } - char *endPtr; - // parsing a long long is a pain, so just allow shorter keys for now - unsigned long long num = strtol( spec + 1, &endPtr, 16 ); - int len = 800; - if( *endPtr == '$' ) { - len = strtol( endPtr + 1, 0, 16 ); - } - return Base::bigNumString( num, len ); - } - static void checkStructure( const BSONObj &spec, const IndexDetails &id, const DiskLoc node ) { - ArtificialTree *n = ArtificialTree::is( node ); - BSONObjIterator j( spec ); - for( int i = 0; i < n->n; ++i ) { - ASSERT( j.more() ); - BSONElement e = j.next(); - KeyNode kn = n->keyNode( i ); - string expected = expectedKey( e.fieldName() ); - ASSERT( present( id, BSON( "" << expected ), 1 ) ); - ASSERT( present( id, BSON( "" << expected ), -1 ) ); - ASSERT_EQUALS( expected, kn.key.toBson().firstElement().valuestr() ); - if ( kn.prevChildBucket.isNull() ) { - ASSERT( e.type() == jstNULL ); - } - else { - ASSERT( e.type() == Object ); - checkStructure( e.embeddedObject(), id, kn.prevChildBucket ); - } - } - if ( n->nextChild.isNull() ) { - // maybe should allow '_' field with null value? - ASSERT( !j.more() ); - } - else { - BSONElement e = j.next(); - ASSERT_EQUALS( string( "_" ), e.fieldName() ); - ASSERT( e.type() == Object ); - checkStructure( e.embeddedObject(), id, n->nextChild ); - } - ASSERT( !j.more() ); - } - static void checkStructure( const string &spec, const IndexDetails &id ) { - checkStructure( fromjson( spec ), id, id.head ); - } - static bool present( const IndexDetails &id, const BSONObj &key, int direction ) { - int pos; - bool found; - id.head.btree()->locate( id, id.head, key, Ordering::make(id.keyPattern()), pos, found, recordLoc(), direction ); - return found; - } - int headerSize() const { return BtreeBucket::headerSize(); } - int packedDataSize( int pos ) const { return BtreeBucket::packedDataSize( pos ); } - void fixParentPtrs( const DiskLoc &thisLoc ) { BtreeBucket::fixParentPtrs( thisLoc ); } - void forcePack() { - topSize += emptySize; - emptySize = 0; - setNotPacked(); - } - private: - DiskLoc dummyDiskLoc() const { return DiskLoc( 0, 2 ); } - }; - - /** - * We could probably refactor the following tests, but it's easier to debug - * them in the present state. - */ - - class MergeBucketsDelInternal : public Base { - public: - void run() { - ArtificialTree::setTree( "{d:{b:{a:null},bb:null,_:{c:null}},_:{f:{e:null},_:{g:null}}}", id() ); -// dump(); - string ns = id().indexNamespace(); - ASSERT_EQUALS( 8, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 7, nsdetails( ns )->numRecords() ); - - BSONObj k = BSON( "" << "bb" ); - verify( unindex( k ) ); -// dump(); - ASSERT_EQUALS( 7, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 5, nsdetails( ns )->numRecords() ); - ArtificialTree::checkStructure( "{b:{a:null},d:{c:null},f:{e:null},_:{g:null}}", id() ); - } - }; - - class MergeBucketsRightNull : public Base { - public: - void run() { - ArtificialTree::setTree( "{d:{b:{a:null},bb:null,cc:{c:null}},_:{f:{e:null},h:{g:null}}}", id() ); -// dump(); - string ns = id().indexNamespace(); - ASSERT_EQUALS( 10, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 7, nsdetails( ns )->numRecords() ); - - BSONObj k = BSON( "" << "bb" ); - verify( unindex( k ) ); -// dump(); - ASSERT_EQUALS( 9, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 5, nsdetails( ns )->numRecords() ); - ArtificialTree::checkStructure( "{b:{a:null},cc:{c:null},d:null,f:{e:null},h:{g:null}}", id() ); - } - }; - - // not yet handling this case - class DontMergeSingleBucket : public Base { - public: - void run() { - ArtificialTree::setTree( "{d:{b:{a:null},c:null}}", id() ); -// dump(); - string ns = id().indexNamespace(); - ASSERT_EQUALS( 4, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 3, nsdetails( ns )->numRecords() ); - BSONObj k = BSON( "" << "c" ); - verify( unindex( k ) ); -// dump(); - ASSERT_EQUALS( 3, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 3, nsdetails( ns )->numRecords() ); - ArtificialTree::checkStructure( "{d:{b:{a:null}}}", id() ); - } - }; - - class ParentMergeNonRightToLeft : public Base { - public: - void run() { - ArtificialTree::setTree( "{d:{b:{a:null},bb:null,cc:{c:null}},i:{f:{e:null},h:{g:null}}}", id() ); -// dump(); - string ns = id().indexNamespace(); - ASSERT_EQUALS( 11, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 7, nsdetails( ns )->numRecords() ); - - BSONObj k = BSON( "" << "bb" ); - verify( unindex( k ) ); -// dump(); - ASSERT_EQUALS( 10, bt()->fullValidate( dl(), order(), 0, true ) ); - // child does not currently replace parent in this case - ASSERT_EQUALS( 6, nsdetails( ns )->numRecords() ); - ArtificialTree::checkStructure( "{i:{b:{a:null},cc:{c:null},d:null,f:{e:null},h:{g:null}}}", id() ); - } - }; - - class ParentMergeNonRightToRight : public Base { - public: - void run() { - ArtificialTree::setTree( "{d:{b:{a:null},cc:{c:null}},i:{f:{e:null},ff:null,h:{g:null}}}", id() ); -// dump(); - string ns = id().indexNamespace(); - ASSERT_EQUALS( 11, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 7, nsdetails( ns )->numRecords() ); - - BSONObj k = BSON( "" << "ff" ); - verify( unindex( k ) ); -// dump(); - ASSERT_EQUALS( 10, bt()->fullValidate( dl(), order(), 0, true ) ); - // child does not currently replace parent in this case - ASSERT_EQUALS( 6, nsdetails( ns )->numRecords() ); - ArtificialTree::checkStructure( "{i:{b:{a:null},cc:{c:null},d:null,f:{e:null},h:{g:null}}}", id() ); - } - }; - - class CantMergeRightNoMerge : public Base { - public: - void run() { - ArtificialTree::setTree( "{d:{b:{a:null},bb:null,cc:{c:null}},dd:null,_:{f:{e:null},h:{g:null}}}", id() ); -// dump(); - string ns = id().indexNamespace(); - ASSERT_EQUALS( 11, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 7, nsdetails( ns )->numRecords() ); - - BSONObj k = BSON( "" << "bb" ); - verify( unindex( k ) ); -// dump(); - ASSERT_EQUALS( 10, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 7, nsdetails( ns )->numRecords() ); - ArtificialTree::checkStructure( "{d:{b:{a:null},cc:{c:null}},dd:null,_:{f:{e:null},h:{g:null}}}", id() ); - } - }; - - class CantMergeLeftNoMerge : public Base { - public: - void run() { - ArtificialTree::setTree( "{c:{b:{a:null}},d:null,_:{f:{e:null},g:null}}", id() ); -// dump(); - string ns = id().indexNamespace(); - ASSERT_EQUALS( 7, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 5, nsdetails( ns )->numRecords() ); - - BSONObj k = BSON( "" << "g" ); - verify( unindex( k ) ); -// dump(); - ASSERT_EQUALS( 6, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 5, nsdetails( ns )->numRecords() ); - ArtificialTree::checkStructure( "{c:{b:{a:null}},d:null,_:{f:{e:null}}}", id() ); - } - }; - - class MergeOption : public Base { - public: - void run() { - ArtificialTree::setTree( "{c:{b:{a:null}},f:{e:{d:null},ee:null},_:{h:{g:null}}}", id() ); -// dump(); - string ns = id().indexNamespace(); - ASSERT_EQUALS( 9, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 7, nsdetails( ns )->numRecords() ); - - BSONObj k = BSON( "" << "ee" ); - verify( unindex( k ) ); -// dump(); - ASSERT_EQUALS( 8, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 6, nsdetails( ns )->numRecords() ); - ArtificialTree::checkStructure( "{c:{b:{a:null}},_:{e:{d:null},f:null,h:{g:null}}}", id() ); - } - }; - - class ForceMergeLeft : public Base { - public: - void run() { - ArtificialTree::setTree( "{c:{b:{a:null}},f:{e:{d:null},ee:null},ff:null,_:{h:{g:null}}}", id() ); -// dump(); - string ns = id().indexNamespace(); - ASSERT_EQUALS( 10, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 7, nsdetails( ns )->numRecords() ); - - BSONObj k = BSON( "" << "ee" ); - verify( unindex( k ) ); -// dump(); - ASSERT_EQUALS( 9, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 6, nsdetails( ns )->numRecords() ); - ArtificialTree::checkStructure( "{f:{b:{a:null},c:null,e:{d:null}},ff:null,_:{h:{g:null}}}", id() ); - } - }; - - class ForceMergeRight : public Base { - public: - void run() { - ArtificialTree::setTree( "{c:{b:{a:null}},cc:null,f:{e:{d:null},ee:null},_:{h:{g:null}}}", id() ); -// dump(); - string ns = id().indexNamespace(); - ASSERT_EQUALS( 10, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 7, nsdetails( ns )->numRecords() ); - - BSONObj k = BSON( "" << "ee" ); - verify( unindex( k ) ); -// dump(); - ASSERT_EQUALS( 9, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 6, nsdetails( ns )->numRecords() ); - ArtificialTree::checkStructure( "{c:{b:{a:null}},cc:null,_:{e:{d:null},f:null,h:{g:null}}}", id() ); - } - }; - - class RecursiveMerge : public Base { - public: - void run() { - ArtificialTree::setTree( "{h:{e:{b:{a:null},c:null,d:null},g:{f:null}},j:{i:null}}", id() ); -// dump(); - string ns = id().indexNamespace(); - ASSERT_EQUALS( 10, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 6, nsdetails( ns )->numRecords() ); - - BSONObj k = BSON( "" << "c" ); - verify( unindex( k ) ); -// dump(); - ASSERT_EQUALS( 9, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 4, nsdetails( ns )->numRecords() ); - // height is not currently reduced in this case - ArtificialTree::checkStructure( "{j:{g:{b:{a:null},d:null,e:null,f:null},h:null,i:null}}", id() ); - } - }; - - class RecursiveMergeRightBucket : public Base { - public: - void run() { - ArtificialTree::setTree( "{h:{e:{b:{a:null},c:null,d:null},g:{f:null}},_:{i:null}}", id() ); -// dump(); - string ns = id().indexNamespace(); - ASSERT_EQUALS( 9, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 6, nsdetails( ns )->numRecords() ); - - BSONObj k = BSON( "" << "c" ); - verify( unindex( k ) ); -// dump(); - ASSERT_EQUALS( 8, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 3, nsdetails( ns )->numRecords() ); - ArtificialTree::checkStructure( "{g:{b:{a:null},d:null,e:null,f:null},h:null,i:null}", id() ); - } - }; - - class RecursiveMergeDoubleRightBucket : public Base { - public: - void run() { - ArtificialTree::setTree( "{h:{e:{b:{a:null},c:null,d:null},_:{f:null}},_:{i:null}}", id() ); - string ns = id().indexNamespace(); - ASSERT_EQUALS( 8, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 6, nsdetails( ns )->numRecords() ); - BSONObj k = BSON( "" << "c" ); - verify( unindex( k ) ); - long long keyCount = bt()->fullValidate( dl(), order(), 0, true ); - ASSERT_EQUALS( 7, keyCount ); - ASSERT_EQUALS( 4, nsdetails( ns )->numRecords() ); - // no recursion currently in this case - ArtificialTree::checkStructure( "{h:{b:{a:null},d:null,e:null,f:null},_:{i:null}}", id() ); - } - }; - - class MergeSizeBase : public Base { - public: - MergeSizeBase() : _count() {} - virtual ~MergeSizeBase() {} - void run() { - typedef ArtificialTree A; - A::set( A::make( id() ), id() ); - A* root = A::is( dl() ); - DiskLoc left = A::make( id() ); - root->push( biggestKey( 'm' ), left ); - _count = 1; - A* l = A::is( left ); - DiskLoc right = A::make( id() ); - root->setNext( right ); - A* r = A::is( right ); - root->fixParentPtrs( dl() ); - - //ASSERT_EQUALS( bigSize(), bigSize() / 2 * 2 ); - fillToExactSize( l, leftSize(), 'a' ); - fillToExactSize( r, rightSize(), 'n' ); - ASSERT( leftAdditional() <= 2 ); - if ( leftAdditional() >= 2 ) { - l->push( bigKey( 'k' ), DiskLoc() ); - } - if ( leftAdditional() >= 1 ) { - l->push( bigKey( 'l' ), DiskLoc() ); - } - ASSERT( rightAdditional() <= 2 ); - if ( rightAdditional() >= 2 ) { - r->push( bigKey( 'y' ), DiskLoc() ); - } - if ( rightAdditional() >= 1 ) { - r->push( bigKey( 'z' ), DiskLoc() ); - } - _count += leftAdditional() + rightAdditional(); - -// dump(); - - initCheck(); - string ns = id().indexNamespace(); - const char *keys = delKeys(); - for( const char *i = keys; *i; ++i ) { - long long unused = 0; - ASSERT_EQUALS( _count, bt()->fullValidate( dl(), order(), &unused, true ) ); - ASSERT_EQUALS( 0, unused ); - ASSERT_EQUALS( 3, nsdetails( ns )->numRecords() ); - BSONObj k = bigKey( *i ); - unindex( k ); -// dump(); - --_count; - } - -// dump(); - - long long unused = 0; - ASSERT_EQUALS( _count, bt()->fullValidate( dl(), order(), &unused, true ) ); - ASSERT_EQUALS( 0, unused ); - validate(); - if ( !merge() ) { - ASSERT_EQUALS( 3, nsdetails( ns )->numRecords() ); - } - else { - ASSERT_EQUALS( 1, nsdetails( ns )->numRecords() ); - } - } - protected: - virtual int leftAdditional() const { return 2; } - virtual int rightAdditional() const { return 2; } - virtual void initCheck() {} - virtual void validate() {} - virtual int leftSize() const = 0; - virtual int rightSize() const = 0; - virtual const char * delKeys() const { return "klyz"; } - virtual bool merge() const { return true; } - void fillToExactSize( ArtificialTree *t, int targetSize, char startKey ) { - int size = 0; - while( size < targetSize ) { - int space = targetSize - size; - int nextSize = space - sizeof( _KeyNode ); - verify( nextSize > 0 ); - BSONObj newKey = key( startKey++, nextSize ); - t->push( newKey, DiskLoc() ); - size += BtreeBucket::KeyOwned(newKey).dataSize() + sizeof( _KeyNode ); - _count += 1; - } - if( t->packedDataSize( 0 ) != targetSize ) { - ASSERT_EQUALS( t->packedDataSize( 0 ), targetSize ); - } - } - static BSONObj key( char a, int size ) { - if ( size >= bigSize() ) { - return bigKey( a ); - } - return simpleKey( a, size - ( bigSize() - 801 ) ); - } - static BSONObj bigKey( char a ) { - return simpleKey( a, 801 ); - } - static BSONObj biggestKey( char a ) { - int size = BtreeBucket::getKeyMax() - bigSize() + 801; - return simpleKey( a, size ); - } - static int bigSize() { - return BtreeBucket::KeyOwned(bigKey( 'a' )).dataSize(); - } - static int biggestSize() { - return BtreeBucket::KeyOwned(biggestKey( 'a' )).dataSize(); - } - int _count; - }; - - class MergeSizeJustRightRight : public MergeSizeBase { - protected: - virtual int rightSize() const { return BtreeBucket::lowWaterMark() - 1; } - virtual int leftSize() const { return BtreeBucket::bodySize() - biggestSize() - sizeof( _KeyNode ) - ( BtreeBucket::lowWaterMark() - 1 ); } - }; - - class MergeSizeJustRightLeft : public MergeSizeBase { - protected: - virtual int leftSize() const { return BtreeBucket::lowWaterMark() - 1; } - virtual int rightSize() const { return BtreeBucket::bodySize() - biggestSize() - sizeof( _KeyNode ) - ( BtreeBucket::lowWaterMark() - 1 ); } - virtual const char * delKeys() const { return "yzkl"; } - }; - - class MergeSizeRight : public MergeSizeJustRightRight { - virtual int rightSize() const { return MergeSizeJustRightRight::rightSize() - 1; } - virtual int leftSize() const { return MergeSizeJustRightRight::leftSize() + 1; } - }; - - class MergeSizeLeft : public MergeSizeJustRightLeft { - virtual int rightSize() const { return MergeSizeJustRightLeft::rightSize() + 1; } - virtual int leftSize() const { return MergeSizeJustRightLeft::leftSize() - 1; } - }; - - class NoMergeBelowMarkRight : public MergeSizeJustRightRight { - virtual int rightSize() const { return MergeSizeJustRightRight::rightSize() + 1; } - virtual int leftSize() const { return MergeSizeJustRightRight::leftSize() - 1; } - virtual bool merge() const { return false; } - }; - - class NoMergeBelowMarkLeft : public MergeSizeJustRightLeft { - virtual int rightSize() const { return MergeSizeJustRightLeft::rightSize() - 1; } - virtual int leftSize() const { return MergeSizeJustRightLeft::leftSize() + 1; } - virtual bool merge() const { return false; } - }; - - class MergeSizeRightTooBig : public MergeSizeJustRightLeft { - virtual int rightSize() const { return MergeSizeJustRightLeft::rightSize() + 1; } - virtual bool merge() const { return false; } - }; - - class MergeSizeLeftTooBig : public MergeSizeJustRightRight { - virtual int leftSize() const { return MergeSizeJustRightRight::leftSize() + 1; } - virtual bool merge() const { return false; } - }; - - class BalanceOneLeftToRight : public Base { - public: - void run() { - string ns = id().indexNamespace(); - ArtificialTree::setTree( "{$10:{$1:null,$2:null,$3:null,$4:null,$5:null,$6:null},b:{$20:null,$30:null,$40:null,$50:null,a:null},_:{c:null}}", id() ); - ASSERT_EQUALS( 14, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 4, nsdetails( ns )->numRecords() ); - BSONObj k = BSON( "" << bigNumString( 0x40 ) ); -// dump(); - ASSERT( unindex( k ) ); -// dump(); - ASSERT_EQUALS( 13, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 4, nsdetails( ns )->numRecords() ); - ArtificialTree::checkStructure( "{$6:{$1:null,$2:null,$3:null,$4:null,$5:null},b:{$10:null,$20:null,$30:null,$50:null,a:null},_:{c:null}}", id() ); - } - }; - - class BalanceOneRightToLeft : public Base { - public: - void run() { - string ns = id().indexNamespace(); - ArtificialTree::setTree( "{$10:{$1:null,$2:null,$3:null,$4:null},b:{$20:null,$30:null,$40:null,$50:null,$60:null,$70:null},_:{c:null}}", id() ); - ASSERT_EQUALS( 13, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 4, nsdetails( ns )->numRecords() ); - BSONObj k = BSON( "" << bigNumString( 0x3 ) ); -// dump(); - ASSERT( unindex( k ) ); -// dump(); - ASSERT_EQUALS( 12, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 4, nsdetails( ns )->numRecords() ); - ArtificialTree::checkStructure( "{$20:{$1:null,$2:null,$4:null,$10:null},b:{$30:null,$40:null,$50:null,$60:null,$70:null},_:{c:null}}", id() ); - } - }; - - class BalanceThreeLeftToRight : public Base { - public: - void run() { - string ns = id().indexNamespace(); - ArtificialTree::setTree( "{$20:{$1:{$0:null},$3:{$2:null},$5:{$4:null},$7:{$6:null},$9:{$8:null},$11:{$10:null},$13:{$12:null},_:{$14:null}},b:{$30:null,$40:{$35:null},$50:{$45:null}},_:{c:null}}", id() ); - ASSERT_EQUALS( 23, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 14, nsdetails( ns )->numRecords() ); - BSONObj k = BSON( "" << bigNumString( 0x30 ) ); - // dump(); - ASSERT( unindex( k ) ); - // dump(); - ASSERT_EQUALS( 22, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 14, nsdetails( ns )->numRecords() ); - ArtificialTree::checkStructure( "{$9:{$1:{$0:null},$3:{$2:null},$5:{$4:null},$7:{$6:null},_:{$8:null}},b:{$11:{$10:null},$13:{$12:null},$20:{$14:null},$40:{$35:null},$50:{$45:null}},_:{c:null}}", id() ); - } - }; - - class BalanceThreeRightToLeft : public Base { - public: - void run() { - string ns = id().indexNamespace(); - ArtificialTree::setTree( "{$20:{$1:{$0:null},$3:{$2:null},$5:null,_:{$14:null}},b:{$30:{$25:null},$40:{$35:null},$50:{$45:null},$60:{$55:null},$70:{$65:null},$80:{$75:null},$90:{$85:null},$100:{$95:null}},_:{c:null}}", id() ); - ASSERT_EQUALS( 25, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 15, nsdetails( ns )->numRecords() ); - BSONObj k = BSON( "" << bigNumString( 0x5 ) ); -// dump(); - ASSERT( unindex( k ) ); -// dump(); - ASSERT_EQUALS( 24, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 15, nsdetails( ns )->numRecords() ); - ArtificialTree::checkStructure( "{$50:{$1:{$0:null},$3:{$2:null},$20:{$14:null},$30:{$25:null},$40:{$35:null},_:{$45:null}},b:{$60:{$55:null},$70:{$65:null},$80:{$75:null},$90:{$85:null},$100:{$95:null}},_:{c:null}}", id() ); - } - }; - - class BalanceSingleParentKey : public Base { - public: - void run() { - string ns = id().indexNamespace(); - ArtificialTree::setTree( "{$10:{$1:null,$2:null,$3:null,$4:null,$5:null,$6:null},_:{$20:null,$30:null,$40:null,$50:null,a:null}}", id() ); - ASSERT_EQUALS( 12, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 3, nsdetails( ns )->numRecords() ); - BSONObj k = BSON( "" << bigNumString( 0x40 ) ); -// dump(); - ASSERT( unindex( k ) ); -// dump(); - ASSERT_EQUALS( 11, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 3, nsdetails( ns )->numRecords() ); - ArtificialTree::checkStructure( "{$6:{$1:null,$2:null,$3:null,$4:null,$5:null},_:{$10:null,$20:null,$30:null,$50:null,a:null}}", id() ); - } - }; - - class PackEmpty : public Base { - public: - void run() { - string ns = id().indexNamespace(); - ArtificialTree::setTree( "{a:null}", id() ); - BSONObj k = BSON( "" << "a" ); - ASSERT( unindex( k ) ); - ArtificialTree *t = ArtificialTree::is( dl() ); - t->forcePack(); - Tester::checkEmpty( t, id() ); - } - class Tester : public ArtificialTree { - public: - static void checkEmpty( ArtificialTree *a, const IndexDetails &id ) { - Tester *t = static_cast< Tester * >( a ); - ASSERT_EQUALS( 0, t->n ); - ASSERT( !( t->flags & Packed ) ); - Ordering o = Ordering::make( id.keyPattern() ); - int zero = 0; - t->_packReadyForMod( o, zero ); - ASSERT_EQUALS( 0, t->n ); - ASSERT_EQUALS( 0, t->topSize ); - ASSERT_EQUALS( BtreeBucket::bodySize(), t->emptySize ); - ASSERT( t->flags & Packed ); - } - }; - }; - - class PackedDataSizeEmpty : public Base { - public: - void run() { - string ns = id().indexNamespace(); - ArtificialTree::setTree( "{a:null}", id() ); - BSONObj k = BSON( "" << "a" ); - ASSERT( unindex( k ) ); - ArtificialTree *t = ArtificialTree::is( dl() ); - t->forcePack(); - Tester::checkEmpty( t, id() ); - } - class Tester : public ArtificialTree { - public: - static void checkEmpty( ArtificialTree *a, const IndexDetails &id ) { - Tester *t = static_cast< Tester * >( a ); - ASSERT_EQUALS( 0, t->n ); - ASSERT( !( t->flags & Packed ) ); - int zero = 0; - ASSERT_EQUALS( 0, t->packedDataSize( zero ) ); - ASSERT( !( t->flags & Packed ) ); - } - }; - }; - - class BalanceSingleParentKeyPackParent : public Base { - public: - void run() { - string ns = id().indexNamespace(); - ArtificialTree::setTree( "{$10:{$1:null,$2:null,$3:null,$4:null,$5:null,$6:null},_:{$20:null,$30:null,$40:null,$50:null,a:null}}", id() ); - ASSERT_EQUALS( 12, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 3, nsdetails( ns )->numRecords() ); - // force parent pack - ArtificialTree::is( dl() )->forcePack(); - BSONObj k = BSON( "" << bigNumString( 0x40 ) ); -// dump(); - ASSERT( unindex( k ) ); -// dump(); - ASSERT_EQUALS( 11, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 3, nsdetails( ns )->numRecords() ); - ArtificialTree::checkStructure( "{$6:{$1:null,$2:null,$3:null,$4:null,$5:null},_:{$10:null,$20:null,$30:null,$50:null,a:null}}", id() ); - } - }; - - class BalanceSplitParent : public Base { - public: - void run() { - string ns = id().indexNamespace(); - ArtificialTree::setTree( "{$10$10:{$1:null,$2:null,$3:null,$4:null},$100:{$20:null,$30:null,$40:null,$50:null,$60:null,$70:null,$80:null},$200:null,$300:null,$400:null,$500:null,$600:null,$700:null,$800:null,$900:null,_:{c:null}}", id() ); - ASSERT_EQUALS( 22, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 4, nsdetails( ns )->numRecords() ); - BSONObj k = BSON( "" << bigNumString( 0x3 ) ); -// dump(); - ASSERT( unindex( k ) ); -// dump(); - ASSERT_EQUALS( 21, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 6, nsdetails( ns )->numRecords() ); - ArtificialTree::checkStructure( "{$500:{$30:{$1:null,$2:null,$4:null,$10$10:null,$20:null},$100:{$40:null,$50:null,$60:null,$70:null,$80:null},$200:null,$300:null,$400:null},_:{$600:null,$700:null,$800:null,$900:null,_:{c:null}}}", id() ); - } - }; - - class RebalancedSeparatorBase : public Base { - public: - void run() { - ArtificialTree::setTree( treeSpec(), id() ); - modTree(); - Tester::checkSeparator( id(), expectedSeparator() ); - } - virtual string treeSpec() const = 0; - virtual int expectedSeparator() const = 0; - virtual void modTree() {} - struct Tester : public ArtificialTree { - static void checkSeparator( const IndexDetails& id, int expected ) { - ASSERT_EQUALS( expected, static_cast< Tester * >( id.head.btreemod() )->rebalancedSeparatorPos( id.head, 0 ) ); - } - }; - }; - - class EvenRebalanceLeft : public RebalancedSeparatorBase { - virtual string treeSpec() const { return "{$7:{$1:null,$2$31f:null,$3:null,$4$31f:null,$5:null,$6:null},_:{$8:null,$9:null,$10$31e:null}}"; } - virtual int expectedSeparator() const { return 4; } - }; - - class EvenRebalanceLeftCusp : public RebalancedSeparatorBase { - virtual string treeSpec() const { return "{$6:{$1:null,$2$31f:null,$3:null,$4$31f:null,$5:null},_:{$7:null,$8:null,$9$31e:null,$10:null}}"; } - virtual int expectedSeparator() const { return 4; } - }; - - class EvenRebalanceRight : public RebalancedSeparatorBase { - virtual string treeSpec() const { return "{$3:{$1:null,$2$31f:null},_:{$4$31f:null,$5:null,$6:null,$7:null,$8$31e:null,$9:null,$10:null}}"; } - virtual int expectedSeparator() const { return 4; } - }; - - class EvenRebalanceRightCusp : public RebalancedSeparatorBase { - virtual string treeSpec() const { return "{$4$31f:{$1:null,$2$31f:null,$3:null},_:{$5:null,$6:null,$7$31e:null,$8:null,$9:null,$10:null}}"; } - virtual int expectedSeparator() const { return 4; } - }; - - class EvenRebalanceCenter : public RebalancedSeparatorBase { - virtual string treeSpec() const { return "{$5:{$1:null,$2$31f:null,$3:null,$4$31f:null},_:{$6:null,$7$31e:null,$8:null,$9:null,$10:null}}"; } - virtual int expectedSeparator() const { return 4; } - }; - - class OddRebalanceLeft : public RebalancedSeparatorBase { - virtual string treeSpec() const { return "{$6$31f:{$1:null,$2:null,$3:null,$4:null,$5:null},_:{$7:null,$8:null,$9:null,$10:null}}"; } - virtual int expectedSeparator() const { return 4; } - }; - - class OddRebalanceRight : public RebalancedSeparatorBase { - virtual string treeSpec() const { return "{$4:{$1:null,$2:null,$3:null},_:{$5:null,$6:null,$7:null,$8$31f:null,$9:null,$10:null}}"; } - virtual int expectedSeparator() const { return 4; } - }; - - class OddRebalanceCenter : public RebalancedSeparatorBase { - virtual string treeSpec() const { return "{$5:{$1:null,$2:null,$3:null,$4:null},_:{$6:null,$7:null,$8:null,$9:null,$10$31f:null}}"; } - virtual int expectedSeparator() const { return 4; } - }; - - class RebalanceEmptyRight : public RebalancedSeparatorBase { - virtual string treeSpec() const { return "{$a:{$1:null,$2:null,$3:null,$4:null,$5:null,$6:null,$7:null,$8:null,$9:null},_:{$b:null}}"; } - virtual void modTree() { - BSONObj k = BSON( "" << bigNumString( 0xb ) ); - ASSERT( unindex( k ) ); - } - virtual int expectedSeparator() const { return 4; } - }; - - class RebalanceEmptyLeft : public RebalancedSeparatorBase { - virtual string treeSpec() const { return "{$a:{$1:null},_:{$11:null,$12:null,$13:null,$14:null,$15:null,$16:null,$17:null,$18:null,$19:null}}"; } - virtual void modTree() { - BSONObj k = BSON( "" << bigNumString( 0x1 ) ); - ASSERT( unindex( k ) ); - } - virtual int expectedSeparator() const { return 4; } - }; - - class NoMoveAtLowWaterMarkRight : public MergeSizeJustRightRight { - virtual int rightSize() const { return MergeSizeJustRightRight::rightSize() + 1; } - virtual void initCheck() { _oldTop = bt()->keyNode( 0 ).key.toBson(); } - virtual void validate() { ASSERT_EQUALS( _oldTop, bt()->keyNode( 0 ).key.toBson() ); } - virtual bool merge() const { return false; } - protected: - BSONObj _oldTop; - }; - - class MoveBelowLowWaterMarkRight : public NoMoveAtLowWaterMarkRight { - virtual int rightSize() const { return MergeSizeJustRightRight::rightSize(); } - virtual int leftSize() const { return MergeSizeJustRightRight::leftSize() + 1; } - // different top means we rebalanced - virtual void validate() { ASSERT( !( _oldTop == bt()->keyNode( 0 ).key.toBson() ) ); } - }; - - class NoMoveAtLowWaterMarkLeft : public MergeSizeJustRightLeft { - virtual int leftSize() const { return MergeSizeJustRightLeft::leftSize() + 1; } - virtual void initCheck() { _oldTop = bt()->keyNode( 0 ).key.toBson(); } - virtual void validate() { ASSERT_EQUALS( _oldTop, bt()->keyNode( 0 ).key.toBson() ); } - virtual bool merge() const { return false; } - protected: - BSONObj _oldTop; - }; - - class MoveBelowLowWaterMarkLeft : public NoMoveAtLowWaterMarkLeft { - virtual int leftSize() const { return MergeSizeJustRightLeft::leftSize(); } - virtual int rightSize() const { return MergeSizeJustRightLeft::rightSize() + 1; } - // different top means we rebalanced - virtual void validate() { ASSERT( !( _oldTop == bt()->keyNode( 0 ).key.toBson() ) ); } - }; - - class PreferBalanceLeft : public Base { - public: - void run() { - string ns = id().indexNamespace(); - ArtificialTree::setTree( "{$10:{$1:null,$2:null,$3:null,$4:null,$5:null,$6:null},$20:{$11:null,$12:null,$13:null,$14:null},_:{$30:null}}", id() ); - ASSERT_EQUALS( 13, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 4, nsdetails( ns )->numRecords() ); - BSONObj k = BSON( "" << bigNumString( 0x12 ) ); -// dump(); - ASSERT( unindex( k ) ); -// dump(); - ASSERT_EQUALS( 12, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 4, nsdetails( ns )->numRecords() ); - ArtificialTree::checkStructure( "{$5:{$1:null,$2:null,$3:null,$4:null},$20:{$6:null,$10:null,$11:null,$13:null,$14:null},_:{$30:null}}", id() ); - } - }; - - class PreferBalanceRight : public Base { - public: - void run() { - string ns = id().indexNamespace(); - ArtificialTree::setTree( "{$10:{$1:null},$20:{$11:null,$12:null,$13:null,$14:null},_:{$31:null,$32:null,$33:null,$34:null,$35:null,$36:null}}", id() ); - ASSERT_EQUALS( 13, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 4, nsdetails( ns )->numRecords() ); - BSONObj k = BSON( "" << bigNumString( 0x12 ) ); - // dump(); - ASSERT( unindex( k ) ); - // dump(); - ASSERT_EQUALS( 12, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 4, nsdetails( ns )->numRecords() ); - ArtificialTree::checkStructure( "{$10:{$1:null},$31:{$11:null,$13:null,$14:null,$20:null},_:{$32:null,$33:null,$34:null,$35:null,$36:null}}", id() ); - } - }; - - class RecursiveMergeThenBalance : public Base { - public: - void run() { - string ns = id().indexNamespace(); - ArtificialTree::setTree( "{$10:{$5:{$1:null,$2:null},$8:{$6:null,$7:null}},_:{$20:null,$30:null,$40:null,$50:null,$60:null,$70:null,$80:null,$90:null}}", id() ); - ASSERT_EQUALS( 15, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 5, nsdetails( ns )->numRecords() ); - BSONObj k = BSON( "" << bigNumString( 0x7 ) ); - // dump(); - ASSERT( unindex( k ) ); - // dump(); - ASSERT_EQUALS( 14, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 4, nsdetails( ns )->numRecords() ); - ArtificialTree::checkStructure( "{$40:{$8:{$1:null,$2:null,$5:null,$6:null},$10:null,$20:null,$30:null},_:{$50:null,$60:null,$70:null,$80:null,$90:null}}", id() ); - } - }; - - class MergeRightEmpty : public MergeSizeBase { - protected: - virtual int rightAdditional() const { return 1; } - virtual int leftAdditional() const { return 1; } - virtual const char * delKeys() const { return "lz"; } - virtual int rightSize() const { return 0; } - virtual int leftSize() const { return BtreeBucket::bodySize() - biggestSize() - sizeof( _KeyNode ); } - }; - - class MergeMinRightEmpty : public MergeSizeBase { - protected: - virtual int rightAdditional() const { return 1; } - virtual int leftAdditional() const { return 0; } - virtual const char * delKeys() const { return "z"; } - virtual int rightSize() const { return 0; } - virtual int leftSize() const { return bigSize() + sizeof( _KeyNode ); } - }; - - class MergeLeftEmpty : public MergeSizeBase { - protected: - virtual int rightAdditional() const { return 1; } - virtual int leftAdditional() const { return 1; } - virtual const char * delKeys() const { return "zl"; } - virtual int leftSize() const { return 0; } - virtual int rightSize() const { return BtreeBucket::bodySize() - biggestSize() - sizeof( _KeyNode ); } - }; - - class MergeMinLeftEmpty : public MergeSizeBase { - protected: - virtual int leftAdditional() const { return 1; } - virtual int rightAdditional() const { return 0; } - virtual const char * delKeys() const { return "l"; } - virtual int leftSize() const { return 0; } - virtual int rightSize() const { return bigSize() + sizeof( _KeyNode ); } - }; - - class BalanceRightEmpty : public MergeRightEmpty { - protected: - virtual int leftSize() const { return BtreeBucket::bodySize() - biggestSize() - sizeof( _KeyNode ) + 1; } - virtual bool merge() const { return false; } - virtual void initCheck() { _oldTop = bt()->keyNode( 0 ).key.toBson(); } - virtual void validate() { ASSERT( !( _oldTop == bt()->keyNode( 0 ).key.toBson() ) ); } - private: - BSONObj _oldTop; - }; - - class BalanceLeftEmpty : public MergeLeftEmpty { - protected: - virtual int rightSize() const { return BtreeBucket::bodySize() - biggestSize() - sizeof( _KeyNode ) + 1; } - virtual bool merge() const { return false; } - virtual void initCheck() { _oldTop = bt()->keyNode( 0 ).key.toBson(); } - virtual void validate() { ASSERT( !( _oldTop == bt()->keyNode( 0 ).key.toBson() ) ); } - private: - BSONObj _oldTop; - }; - - class DelEmptyNoNeighbors : public Base { - public: - void run() { - string ns = id().indexNamespace(); - ArtificialTree::setTree( "{b:{a:null}}", id() ); - ASSERT_EQUALS( 2, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 2, nsdetails( ns )->numRecords() ); - BSONObj k = BSON( "" << "a" ); - // dump(); - ASSERT( unindex( k ) ); - // dump(); - ASSERT_EQUALS( 1, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 1, nsdetails( ns )->numRecords() ); - ArtificialTree::checkStructure( "{b:null}", id() ); - } - }; - - class DelEmptyEmptyNeighbors : public Base { - public: - void run() { - string ns = id().indexNamespace(); - ArtificialTree::setTree( "{a:null,c:{b:null},d:null}", id() ); - ASSERT_EQUALS( 4, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 2, nsdetails( ns )->numRecords() ); - BSONObj k = BSON( "" << "b" ); - // dump(); - ASSERT( unindex( k ) ); - // dump(); - ASSERT_EQUALS( 3, bt()->fullValidate( dl(), order(), 0, true ) ); - ASSERT_EQUALS( 1, nsdetails( ns )->numRecords() ); - ArtificialTree::checkStructure( "{a:null,c:null,d:null}", id() ); - } - }; - - class DelInternal : public Base { - public: - void run() { - string ns = id().indexNamespace(); - ArtificialTree::setTree( "{a:null,c:{b:null},d:null}", id() ); - long long unused = 0; - ASSERT_EQUALS( 4, bt()->fullValidate( dl(), order(), &unused, true ) ); - ASSERT_EQUALS( 0, unused ); - ASSERT_EQUALS( 2, nsdetails( ns )->numRecords() ); - BSONObj k = BSON( "" << "c" ); -// dump(); - ASSERT( unindex( k ) ); -// dump(); - ASSERT_EQUALS( 3, bt()->fullValidate( dl(), order(), &unused, true ) ); - ASSERT_EQUALS( 0, unused ); - ASSERT_EQUALS( 1, nsdetails( ns )->numRecords() ); - ArtificialTree::checkStructure( "{a:null,b:null,d:null}", id() ); - } - }; - - class DelInternalReplaceWithUnused : public Base { - public: - void run() { - string ns = id().indexNamespace(); - ArtificialTree::setTree( "{a:null,c:{b:null},d:null}", id() ); - getDur().writingInt( const_cast< BtreeBucket::Loc& >( bt()->keyNode( 1 ).prevChildBucket.btree()->keyNode( 0 ).recordLoc ).GETOFS() ) |= 1; // make unused - long long unused = 0; - ASSERT_EQUALS( 3, bt()->fullValidate( dl(), order(), &unused, true ) ); - ASSERT_EQUALS( 1, unused ); - ASSERT_EQUALS( 2, nsdetails( ns )->numRecords() ); - BSONObj k = BSON( "" << "c" ); -// dump(); - ASSERT( unindex( k ) ); -// dump(); - unused = 0; - ASSERT_EQUALS( 2, bt()->fullValidate( dl(), order(), &unused, true ) ); - ASSERT_EQUALS( 1, unused ); - ASSERT_EQUALS( 1, nsdetails( ns )->numRecords() ); - // doesn't discriminate between used and unused - ArtificialTree::checkStructure( "{a:null,b:null,d:null}", id() ); - } - }; - - class DelInternalReplaceRight : public Base { - public: - void run() { - string ns = id().indexNamespace(); - ArtificialTree::setTree( "{a:null,_:{b:null}}", id() ); - long long unused = 0; - ASSERT_EQUALS( 2, bt()->fullValidate( dl(), order(), &unused, true ) ); - ASSERT_EQUALS( 0, unused ); - ASSERT_EQUALS( 2, nsdetails( ns )->numRecords() ); - BSONObj k = BSON( "" << "a" ); -// dump(); - ASSERT( unindex( k ) ); -// dump(); - unused = 0; - ASSERT_EQUALS( 1, bt()->fullValidate( dl(), order(), &unused, true ) ); - ASSERT_EQUALS( 0, unused ); - ASSERT_EQUALS( 1, nsdetails( ns )->numRecords() ); - ArtificialTree::checkStructure( "{b:null}", id() ); - } - }; - - class DelInternalPromoteKey : public Base { - public: - void run() { - string ns = id().indexNamespace(); - ArtificialTree::setTree( "{a:null,y:{d:{c:{b:null}},_:{e:null}},z:null}", id() ); - long long unused = 0; - ASSERT_EQUALS( 7, bt()->fullValidate( dl(), order(), &unused, true ) ); - ASSERT_EQUALS( 0, unused ); - ASSERT_EQUALS( 5, nsdetails( ns )->numRecords() ); - BSONObj k = BSON( "" << "y" ); -// dump(); - ASSERT( unindex( k ) ); -// dump(); - unused = 0; - ASSERT_EQUALS( 6, bt()->fullValidate( dl(), order(), &unused, true ) ); - ASSERT_EQUALS( 0, unused ); - ASSERT_EQUALS( 3, nsdetails( ns )->numRecords() ); - ArtificialTree::checkStructure( "{a:null,e:{c:{b:null},d:null},z:null}", id() ); - } - }; - - class DelInternalPromoteRightKey : public Base { - public: - void run() { - string ns = id().indexNamespace(); - ArtificialTree::setTree( "{a:null,_:{e:{c:null},_:{f:null}}}", id() ); - long long unused = 0; - ASSERT_EQUALS( 4, bt()->fullValidate( dl(), order(), &unused, true ) ); - ASSERT_EQUALS( 0, unused ); - ASSERT_EQUALS( 4, nsdetails( ns )->numRecords() ); - BSONObj k = BSON( "" << "a" ); -// dump(); - ASSERT( unindex( k ) ); -// dump(); - unused = 0; - ASSERT_EQUALS( 3, bt()->fullValidate( dl(), order(), &unused, true ) ); - ASSERT_EQUALS( 0, unused ); - ASSERT_EQUALS( 2, nsdetails( ns )->numRecords() ); - ArtificialTree::checkStructure( "{c:null,_:{e:null,f:null}}", id() ); - } - }; - - class DelInternalReplacementPrevNonNull : public Base { - public: - void run() { - string ns = id().indexNamespace(); - ArtificialTree::setTree( "{a:null,d:{c:{b:null}},e:null}", id() ); - long long unused = 0; - ASSERT_EQUALS( 5, bt()->fullValidate( dl(), order(), &unused, true ) ); - ASSERT_EQUALS( 0, unused ); - ASSERT_EQUALS( 3, nsdetails( ns )->numRecords() ); - BSONObj k = BSON( "" << "d" ); - // dump(); - ASSERT( unindex( k ) ); - // dump(); - ASSERT_EQUALS( 4, bt()->fullValidate( dl(), order(), &unused, true ) ); - ASSERT_EQUALS( 1, unused ); - ASSERT_EQUALS( 3, nsdetails( ns )->numRecords() ); - ArtificialTree::checkStructure( "{a:null,d:{c:{b:null}},e:null}", id() ); - ASSERT( bt()->keyNode( 1 ).recordLoc.getOfs() & 1 ); // check 'unused' key - } - }; - - class DelInternalReplacementNextNonNull : public Base { - public: - void run() { - string ns = id().indexNamespace(); - ArtificialTree::setTree( "{a:null,_:{c:null,_:{d:null}}}", id() ); - long long unused = 0; - ASSERT_EQUALS( 3, bt()->fullValidate( dl(), order(), &unused, true ) ); - ASSERT_EQUALS( 0, unused ); - ASSERT_EQUALS( 3, nsdetails( ns )->numRecords() ); - BSONObj k = BSON( "" << "a" ); - // dump(); - ASSERT( unindex( k ) ); - // dump(); - ASSERT_EQUALS( 2, bt()->fullValidate( dl(), order(), &unused, true ) ); - ASSERT_EQUALS( 1, unused ); - ASSERT_EQUALS( 3, nsdetails( ns )->numRecords() ); - ArtificialTree::checkStructure( "{a:null,_:{c:null,_:{d:null}}}", id() ); - ASSERT( bt()->keyNode( 0 ).recordLoc.getOfs() & 1 ); // check 'unused' key - } - }; - - class DelInternalSplitPromoteLeft : public Base { - public: - void run() { - string ns = id().indexNamespace(); - ArtificialTree::setTree( "{$10:null,$20:null,$30$10:{$25:{$23:null},_:{$27:null}},$40:null,$50:null,$60:null,$70:null,$80:null,$90:null,$100:null}", id() ); - long long unused = 0; - ASSERT_EQUALS( 13, bt()->fullValidate( dl(), order(), &unused, true ) ); - ASSERT_EQUALS( 0, unused ); - ASSERT_EQUALS( 4, nsdetails( ns )->numRecords() ); - BSONObj k = BSON( "" << bigNumString( 0x30, 0x10 ) ); -// dump(); - ASSERT( unindex( k ) ); -// dump(); - ASSERT_EQUALS( 12, bt()->fullValidate( dl(), order(), &unused, true ) ); - ASSERT_EQUALS( 0, unused ); - ASSERT_EQUALS( 4, nsdetails( ns )->numRecords() ); - ArtificialTree::checkStructure( "{$60:{$10:null,$20:null,$27:{$23:null,$25:null},$40:null,$50:null},_:{$70:null,$80:null,$90:null,$100:null}}", id() ); - } - }; - - class DelInternalSplitPromoteRight : public Base { - public: - void run() { - string ns = id().indexNamespace(); - ArtificialTree::setTree( "{$10:null,$20:null,$30:null,$40:null,$50:null,$60:null,$70:null,$80:null,$90:null,$100$10:{$95:{$93:null},_:{$97:null}}}", id() ); - long long unused = 0; - ASSERT_EQUALS( 13, bt()->fullValidate( dl(), order(), &unused, true ) ); - ASSERT_EQUALS( 0, unused ); - ASSERT_EQUALS( 4, nsdetails( ns )->numRecords() ); - BSONObj k = BSON( "" << bigNumString( 0x100, 0x10 ) ); -// dump(); - ASSERT( unindex( k ) ); -// dump(); - ASSERT_EQUALS( 12, bt()->fullValidate( dl(), order(), &unused, true ) ); - ASSERT_EQUALS( 0, unused ); - ASSERT_EQUALS( 4, nsdetails( ns )->numRecords() ); - ArtificialTree::checkStructure( "{$80:{$10:null,$20:null,$30:null,$40:null,$50:null,$60:null,$70:null},_:{$90:null,$97:{$93:null,$95:null}}}", id() ); - } - }; - - class SignedZeroDuplication : public Base { - public: - void run() { - ASSERT_EQUALS( 0.0, -0.0 ); - DBDirectClient c; - c.ensureIndex( ns(), BSON( "b" << 1 ), true ); - c.insert( ns(), BSON( "b" << 0.0 ) ); - c.insert( ns(), BSON( "b" << 1.0 ) ); - c.update( ns(), BSON( "b" << 1.0 ), BSON( "b" << -0.0 ) ); - ASSERT_EQUALS( 1U, c.count( ns(), BSON( "b" << 0.0 ) ) ); - } - }; - - class All : public Suite { - public: - All() : Suite( testName ) { - } - - void setupTests() { - add< Create >(); - add< SimpleInsertDelete >(); - add< SplitRightHeavyBucket >(); - add< SplitLeftHeavyBucket >(); - add< MissingLocate >(); - add< MissingLocateMultiBucket >(); - add< SERVER983 >(); - add< DontReuseUnused >(); - // QUERY_MIGRATION - // add< PackUnused >(); - // add< DontDropReferenceKey >(); - add< MergeBucketsLeft >(); - add< MergeBucketsRight >(); -// add< MergeBucketsHead >(); - add< MergeBucketsDontReplaceHead >(); - add< MergeBucketsDelInternal >(); - add< MergeBucketsRightNull >(); - add< DontMergeSingleBucket >(); - add< ParentMergeNonRightToLeft >(); - add< ParentMergeNonRightToRight >(); - add< CantMergeRightNoMerge >(); - add< CantMergeLeftNoMerge >(); - add< MergeOption >(); - add< ForceMergeLeft >(); - add< ForceMergeRight >(); - add< RecursiveMerge >(); - add< RecursiveMergeRightBucket >(); - add< RecursiveMergeDoubleRightBucket >(); - add< MergeSizeJustRightRight >(); - add< MergeSizeJustRightLeft >(); - add< MergeSizeRight >(); - add< MergeSizeLeft >(); - add< NoMergeBelowMarkRight >(); - add< NoMergeBelowMarkLeft >(); - add< MergeSizeRightTooBig >(); - add< MergeSizeLeftTooBig >(); - add< BalanceOneLeftToRight >(); - add< BalanceOneRightToLeft >(); - add< BalanceThreeLeftToRight >(); - add< BalanceThreeRightToLeft >(); - add< BalanceSingleParentKey >(); - add< PackEmpty >(); - add< PackedDataSizeEmpty >(); - add< BalanceSingleParentKeyPackParent >(); - add< BalanceSplitParent >(); - add< EvenRebalanceLeft >(); - add< EvenRebalanceLeftCusp >(); - add< EvenRebalanceRight >(); - add< EvenRebalanceRightCusp >(); - add< EvenRebalanceCenter >(); - add< OddRebalanceLeft >(); - add< OddRebalanceRight >(); - add< OddRebalanceCenter >(); - add< RebalanceEmptyRight >(); - add< RebalanceEmptyLeft >(); - add< NoMoveAtLowWaterMarkRight >(); - add< MoveBelowLowWaterMarkRight >(); - add< NoMoveAtLowWaterMarkLeft >(); - add< MoveBelowLowWaterMarkLeft >(); - add< PreferBalanceLeft >(); - add< PreferBalanceRight >(); - add< RecursiveMergeThenBalance >(); - add< MergeRightEmpty >(); - add< MergeMinRightEmpty >(); - add< MergeLeftEmpty >(); - add< MergeMinLeftEmpty >(); - add< BalanceRightEmpty >(); - add< BalanceLeftEmpty >(); - add< DelEmptyNoNeighbors >(); - add< DelEmptyEmptyNeighbors >(); - add< DelInternal >(); - add< DelInternalReplaceWithUnused >(); - add< DelInternalReplaceRight >(); - add< DelInternalPromoteKey >(); - add< DelInternalPromoteRightKey >(); - add< DelInternalReplacementPrevNonNull >(); - add< DelInternalReplacementNextNonNull >(); - add< DelInternalSplitPromoteLeft >(); - add< DelInternalSplitPromoteRight >(); - add< SignedZeroDuplication >(); - } - } myall;