/**
* Copyright (C) 2012 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 .
*/
#include "mongo/db/btreeposition.h"
#include "mongo/db/btree.h"
#include "mongo/db/btreecursor.h"
#include "mongo/db/pdfile.h"
#include "mongo/dbtests/dbtests.h"
#include "mongo/platform/cstdint.h"
namespace BtreePositionTests {
DBDirectClient _client;
const char* _ns = "unittests.btreeposition";
namespace BtreeKeyLocation {
using mongo::BtreeKeyLocation;
/** Check equality comparison performed by BtreeKeyLocation::operator==. */
class Equality {
public:
void run() {
// Equal initially.
BtreeKeyLocation one, two;
ASSERT_EQUALS( one, two );
// Unequal with equal indexes but unequal buckets.
one.bucket = DiskLoc( 1, 2 );
ASSERT( !( one == two ) );
// Unequal with equal buckets but unequal indexes.
one.pos = 1;
two.bucket = DiskLoc( 1, 2 );
ASSERT( !( one == two ) );
// Equal with both bucket and index equal.
two.pos = 1;
ASSERT_EQUALS( one, two );
}
};
} // namespace BtreeKeyLocation
namespace LogicalBtreePosition {
using mongo::LogicalBtreePosition;
using mongo::BtreeKeyLocation;
/** Helper to construct custom btrees for tests. */
class TestableBtree : public BtreeBucket {
public:
/**
* Create a btree structure based on the json structure in @param 'spec', and set
* @param 'id' to this btree.
* @return the btree.
*
* For example the spec { b:{ a:null }, d:{ c:null }, _:{ e:null } } would create the
* btree
*
* [ b, d ]
* / | \
* [ a ] [ c ] [ e ]
*
* Dummy record locations are populated based on the string values. The first character
* of each string value must be a hex digit. See dummyRecordForKey().
*/
static TestableBtree* set( const string& spec, IndexDetails& id ) {
DiskLoc btree = make( spec, id );
id.head = btree;
return cast( btree );
}
/** Cast a disk location to a TestableBtree. */
static TestableBtree* cast( const DiskLoc& l ) {
return static_cast( l.btreemod() );
}
/** Push a new key to this bucket. */
void push( const BSONObj& key, DiskLoc child ) {
KeyOwned k(key);
pushBack( dummyRecordForKey( key ),
k,
Ordering::make( BSON( "a" << 1 ) ),
child );
}
/** Delete a key from this bucket. */
void delKey( int index ) { _delKeyAtPos( index ); }
/** Reset the number of keys for this bucket. */
void setN( int newN ) { n = newN; }
/** Set the right child for this bucket. */
void setNext( const DiskLoc &child ) { nextChild = child; }
/** A dummy record DiskLoc generated from a key's string value. */
static DiskLoc dummyRecordForKey( const BSONObj& key ) {
return DiskLoc( 0, fromHex( key.firstElement().String()[ 0 ] ) );
}
private:
static DiskLoc make( const string& specString, IndexDetails& id ) {
BSONObj spec = fromjson( specString );
DiskLoc bucket = addBucket( id );
cast( bucket )->init();
TestableBtree* btree = TestableBtree::cast( bucket );
BSONObjIterator i( spec );
while( i.more() ) {
BSONElement e = i.next();
DiskLoc child;
if ( e.type() == Object ) {
child = make( e.embeddedObject().jsonString(), id );
}
if ( e.fieldName() == string( "_" ) ) {
btree->setNext( child );
}
else {
btree->push( BSON( "" << e.fieldName() ), child );
}
}
btree->fixParentPtrs( bucket );
return bucket;
}
};
/**
* Helper to check that the expected key and its corresponding dummy record are located at
* the supplied key location.
*/
void assertKeyForPosition( const string& expectedKey,
const BtreeKeyLocation& location ) {
BucketBasics::KeyNode keyNode =
location.bucket.btree()->keyNode( location.pos );
BSONObj expectedKeyObj = BSON( "" << expectedKey );
ASSERT_EQUALS( expectedKeyObj, keyNode.key.toBson() );
ASSERT_EQUALS( TestableBtree::dummyRecordForKey( expectedKeyObj ),
keyNode.recordLoc );
}
/** A btree position is recovered when the btree bucket is unmodified. */
class RecoverPositionBucketUnchanged {
public:
void run() {
Client::WriteContext ctx( _ns );
_client.dropCollection( _ns );
// Add an index and populate it with dummy keys.
_client.ensureIndex( _ns, BSON( "a" << 1 ) );
IndexDetails& idx = nsdetails( _ns )->idx( 1 );
TestableBtree* btree = TestableBtree::set( "{b:{a:null},_:{c:null}}", idx );
// Locate the 'a' key.
BtreeKeyLocation aLocation( btree->keyNode( 0 ).prevChildBucket, 0 );
// Try to recover the key location.
Ordering ordering = Ordering::make( nsdetails( _ns )->idx( 1 ).keyPattern() );
LogicalBtreePosition logical( idx, ordering, aLocation );
logical.init();
// Check that the original location is recovered.
ASSERT_EQUALS( aLocation, logical.currentLocation() );
// Invalidate the original location.
logical.invalidateInitialLocation();
// Check that the original location is still recovered.
ASSERT_EQUALS( aLocation, logical.currentLocation() );
assertKeyForPosition( "a", logical.currentLocation() );
}
};
/** A btree position is recovered after its initial bucket is deallocated. */
class RecoverPositionBucketDeallocated {
public:
void run() {
Client::WriteContext ctx( _ns );
_client.dropCollection( _ns );
// Add an index and populate it with dummy keys.
_client.ensureIndex( _ns, BSON( "a" << 1 ) );
IndexDetails& idx = nsdetails( _ns )->idx( 1 );
TestableBtree* btree = TestableBtree::set( "{b:{a:null},_:{c:null}}", idx );
// Locate the 'c' key.
BtreeKeyLocation cLocation( btree->getNextChild(), 0 );
// Identify the key position.
Ordering ordering = Ordering::make( nsdetails( _ns )->idx( 1 ).keyPattern() );
LogicalBtreePosition logical( idx, ordering, cLocation );
logical.init();
// Invalidate the 'c' key's btree bucket.
TestableBtree::cast( cLocation.bucket )->deallocBucket( cLocation.bucket, idx );
// Add the 'c' key back to the tree, in the root bucket.
btree->push( BSON( "" << "c" ),
TestableBtree::dummyRecordForKey( BSON( "" << "c" ) ) );
// Check that the new location of 'c' is recovered.
ASSERT_EQUALS( BtreeKeyLocation( idx.head, 1 ), logical.currentLocation() );
assertKeyForPosition( "c", logical.currentLocation() );
}
};
/** A btree position is recovered after its initial bucket shrinks. */
class RecoverPositionKeyIndexInvalid {
public:
void run() {
Client::WriteContext ctx( _ns );
_client.dropCollection( _ns );
// Add an index and populate it with dummy keys.
_client.ensureIndex( _ns, BSON( "a" << 1 ) );
IndexDetails& idx = nsdetails( _ns )->idx( 1 );
TestableBtree::set( "{b:{a:null},c:null,_:{d:null}}", idx );
// Locate the 'c' key.
BtreeKeyLocation cLocation( idx.head, 1 );
// Identify the key position.
Ordering ordering = Ordering::make( nsdetails( _ns )->idx( 1 ).keyPattern() );
LogicalBtreePosition logical( idx, ordering, cLocation );
logical.init();
// Remove the 'c' key by resizing the root bucket.
TestableBtree::cast( cLocation.bucket )->setN( 1 );
// Check that the location of 'd' is recovered.
assertKeyForPosition( "d", logical.currentLocation() );
}
};
/** A btree position is recovered after the key it refers to is removed. */
class RecoverPositionKeyRemoved {
public:
void run() {
Client::WriteContext ctx( _ns );
_client.dropCollection( _ns );
// Add an index and populate it with dummy keys.
_client.ensureIndex( _ns, BSON( "a" << 1 ) );
IndexDetails& idx = nsdetails( _ns )->idx( 1 );
TestableBtree::set( "{b:{a:null},c:null,e:{d:null}}", idx );
// Locate the 'c' key.
BtreeKeyLocation cLocation( idx.head, 1 );
// Identify the key position.
Ordering ordering = Ordering::make( nsdetails( _ns )->idx( 1 ).keyPattern() );
LogicalBtreePosition logical( idx, ordering, cLocation );
logical.init();
// Remove the 'c' key.
TestableBtree::cast( cLocation.bucket )->delKey( 1 );
// Check that the location of 'd' is recovered.
assertKeyForPosition( "d", logical.currentLocation() );
}
};
/**
* A btree position is recovered after the key it refers to is removed, and a subsequent key
* has the same record location.
*/
class RecoverPositionKeyRemovedWithMatchingRecord {
public:
void run() {
Client::WriteContext ctx( _ns );
_client.dropCollection( _ns );
// Add an index and populate it with dummy keys.
_client.ensureIndex( _ns, BSON( "a" << 1 ) );
IndexDetails& idx = nsdetails( _ns )->idx( 1 );
TestableBtree* btree =
TestableBtree::set( "{b:{a:null},c:null,ccc:{cc:null}}", idx );
// Verify that the 'c' key has the the same record location as the 'ccc' key, which
// is a requirement of this test.
ASSERT_EQUALS( btree->keyNode( 1 ).recordLoc, btree->keyNode( 2 ).recordLoc );
// Locate the 'c' key.
BtreeKeyLocation cLocation( idx.head, 1 );
// Identify the key position.
Ordering ordering = Ordering::make( nsdetails( _ns )->idx( 1 ).keyPattern() );
LogicalBtreePosition logical( idx, ordering, cLocation );
logical.init();
// Remove the 'c' key.
TestableBtree::cast( cLocation.bucket )->delKey( 1 );
// Check that the location of 'cc' is recovered.
assertKeyForPosition( "cc", logical.currentLocation() );
}
};
} // namespace LogicalBtreePosition
class All : public Suite {
public:
All() : Suite( "btreeposition" ) {
}
void setupTests() {
add();
add();
add();
add();
add();
add();
}
} myall;
} // BtreePositionTests