From 714de2af682431ffb6a1c930e03699fc3f065f69 Mon Sep 17 00:00:00 2001 From: Aaron Staple Date: Tue, 11 Aug 2009 16:42:27 -0400 Subject: [PATCH] SERVER-100 checkpoint btree cursor can take multiple ranges --- db/btree.h | 35 +++++++++++---- db/btreecursor.cpp | 39 +++++++++++++++-- dbtests/cursortests.cpp | 95 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 156 insertions(+), 13 deletions(-) diff --git a/db/btree.h b/db/btree.h index e3587f97543..1d58617c7be 100644 --- a/db/btree.h +++ b/db/btree.h @@ -215,15 +215,17 @@ namespace mongo { }; class BtreeCursor : public Cursor { - friend class BtreeBucket; - NamespaceDetails *d; - int idxNo; - BSONObj startKey; - BSONObj endKey; - bool endKeyInclusive_; - bool multikey; // note this must be updated every getmore batch in case someone added a multikey... public: BtreeCursor( NamespaceDetails *_d, int _idxNo, const IndexDetails&, const BSONObj &startKey, const BSONObj &endKey, bool endKeyInclusive, int direction ); + + // a BoundList contains intervals specified by inclusive start + // and end bounds. The intervals should be nonoverlapping and occur in + // the specified direction of traversal. For example, given a simple index {i:1} + // and direction +1, one valid BoundList is: (1, 2); (4, 6). The same BoundList + // would be valid for index {i:-1} with direction -1. + typedef vector< pair< BSONObj, BSONObj > > BoundList; + BtreeCursor( NamespaceDetails *_d, int _idxNo, const IndexDetails& _id, const vector< pair< BSONObj, BSONObj > > &_bounds, int _direction ); + virtual bool ok() { return !bucket.isNull(); } @@ -290,13 +292,14 @@ namespace mongo { virtual string toString() { string s = string("BtreeCursor ") + indexDetails.indexName(); if ( direction < 0 ) s += " reverse"; + if ( bounds_.size() > 1 ) s += " multi"; return s; } BSONObj prettyKey( const BSONObj &key ) const { return key.replaceFieldNames( indexDetails.keyPattern() ).clientReadable(); } - + virtual BSONObj prettyStartKey() const { return prettyKey( startKey ); } @@ -315,6 +318,20 @@ namespace mongo { /* Check if the current key is beyond endKey. */ void checkEnd(); + // selective audits on construction + void audit(); + + // init start / end keys with a new range + void init(); + + friend class BtreeBucket; + NamespaceDetails *d; + int idxNo; + BSONObj startKey; + BSONObj endKey; + bool endKeyInclusive_; + bool multikey; // note this must be updated every getmore batch in case someone added a multikey... + const IndexDetails& indexDetails; BSONObj order; DiskLoc bucket; @@ -322,6 +339,8 @@ namespace mongo { int direction; // 1=fwd,-1=reverse BSONObj keyAtKeyOfs; // so we can tell if things moved around on us between the query and the getMore call DiskLoc locAtKeyOfs; + BoundList bounds_; + unsigned boundIndex_; }; #pragma pack() diff --git a/db/btreecursor.cpp b/db/btreecursor.cpp index 55028036036..e3e942dfe60 100644 --- a/db/btreecursor.cpp +++ b/db/btreecursor.cpp @@ -34,14 +34,37 @@ namespace mongo { startKey( _startKey ), endKey( _endKey ), endKeyInclusive_( endKeyInclusive ), + multikey( d->isMultikey( idxNo ) ), indexDetails( _id ), order( _id.keyPattern() ), - direction( _direction ) + direction( _direction ), + boundIndex_() { - dassert( d->idxNo((IndexDetails&) indexDetails) == idxNo ); - multikey = d->isMultikey(idxNo); + audit(); + init(); + } + + BtreeCursor::BtreeCursor( NamespaceDetails *_d, int _idxNo, const IndexDetails& _id, const vector< pair< BSONObj, BSONObj > > &_bounds, int _direction ) + : + d(_d), idxNo(_idxNo), + endKeyInclusive_( true ), + multikey( d->isMultikey( idxNo ) ), + indexDetails( _id ), + order( _id.keyPattern() ), + direction( _direction ), + bounds_( _bounds ), + boundIndex_() + { + assert( !bounds_.empty() ); + startKey = bounds_[ 0 ].first; + endKey = bounds_[ 0 ].second; + audit(); + init(); + } + + void BtreeCursor::audit() { + dassert( d->idxNo((IndexDetails&) indexDetails) == idxNo ); - bool found; if ( otherTraceLevel >= 12 ) { if ( otherTraceLevel >= 200 ) { out() << "::BtreeCursor() qtl>200. validating entire index." << endl; @@ -52,7 +75,10 @@ namespace mongo { indexDetails.head.btree()->dump(); } } + } + void BtreeCursor::init() { + bool found; bucket = indexDetails.head.btree()-> locate(indexDetails, indexDetails.head, startKey, order, keyOfs, found, direction > 0 ? minDiskLoc : maxDiskLoc, direction); @@ -104,6 +130,11 @@ namespace mongo { bucket = bucket.btree()->advance(bucket, keyOfs, direction, "BtreeCursor::advance"); skipUnusedKeys(); checkEnd(); + while( !ok() && ++boundIndex_ < bounds_.size() ) { + startKey = bounds_[ boundIndex_ ].first; + endKey = bounds_[ boundIndex_ ].second; + init(); + } return !bucket.isNull(); } diff --git a/dbtests/cursortests.cpp b/dbtests/cursortests.cpp index fafc48c26a2..18c4a828855 100644 --- a/dbtests/cursortests.cpp +++ b/dbtests/cursortests.cpp @@ -18,6 +18,8 @@ */ #include "../db/clientcursor.h" +#include "../db/instance.h" +#include "../db/btree.h" #include "dbtests.h" @@ -103,12 +105,103 @@ namespace CursorTests { }; } // namespace IdSetTests + + namespace BtreeCursorTests { + + class MultiRange { + public: + void run() { + dblock lk; + const char *ns = "unittests.cursortests.BtreeCursorTests.MultiRange"; + { + DBDirectClient c; + for( int i = 0; i < 10; ++i ) + c.insert( ns, BSON( "a" << i ) ); + ASSERT( c.ensureIndex( ns, BSON( "a" << 1 ) ) ); + } + BtreeCursor::BoundList b; + b.push_back( pair< BSONObj, BSONObj >( BSON( "" << 1 ), BSON( "" << 2 ) ) ); + b.push_back( pair< BSONObj, BSONObj >( BSON( "" << 4 ), BSON( "" << 6 ) ) ); + setClient( ns ); + BtreeCursor c( nsdetails( ns ), 1, nsdetails( ns )->indexes[ 1 ], b, 1 ); + ASSERT_EQUALS( "BtreeCursor a_1 multi", c.toString() ); + double expected[] = { 1, 2, 4, 5, 6 }; + for( int i = 0; i < 5; ++i ) { + ASSERT( c.ok() ); + ASSERT_EQUALS( expected[ i ], c.currKey().firstElement().number() ); + c.advance(); + } + ASSERT( !c.ok() ); + } + }; + + class MultiRangeGap { + public: + void run() { + dblock lk; + const char *ns = "unittests.cursortests.BtreeCursorTests.MultiRangeGap"; + { + DBDirectClient c; + for( int i = 0; i < 10; ++i ) + c.insert( ns, BSON( "a" << i ) ); + for( int i = 100; i < 110; ++i ) + c.insert( ns, BSON( "a" << i ) ); + ASSERT( c.ensureIndex( ns, BSON( "a" << 1 ) ) ); + } + BtreeCursor::BoundList b; + b.push_back( pair< BSONObj, BSONObj >( BSON( "" << -50 ), BSON( "" << 2 ) ) ); + b.push_back( pair< BSONObj, BSONObj >( BSON( "" << 40 ), BSON( "" << 60 ) ) ); + b.push_back( pair< BSONObj, BSONObj >( BSON( "" << 109 ), BSON( "" << 200 ) ) ); + setClient( ns ); + BtreeCursor c( nsdetails( ns ), 1, nsdetails( ns )->indexes[ 1 ], b, 1 ); + ASSERT_EQUALS( "BtreeCursor a_1 multi", c.toString() ); + double expected[] = { 0, 1, 2, 109 }; + for( int i = 0; i < 4; ++i ) { + ASSERT( c.ok() ); + ASSERT_EQUALS( expected[ i ], c.currKey().firstElement().number() ); + c.advance(); + } + ASSERT( !c.ok() ); + } + }; + + class MultiRangeReverse { + public: + void run() { + dblock lk; + const char *ns = "unittests.cursortests.BtreeCursorTests.MultiRangeReverse"; + { + DBDirectClient c; + for( int i = 0; i < 10; ++i ) + c.insert( ns, BSON( "a" << i ) ); + ASSERT( c.ensureIndex( ns, BSON( "a" << 1 ) ) ); + } + BtreeCursor::BoundList b; + b.push_back( pair< BSONObj, BSONObj >( BSON( "" << 6 ), BSON( "" << 4 ) ) ); + b.push_back( pair< BSONObj, BSONObj >( BSON( "" << 2 ), BSON( "" << 1 ) ) ); + setClient( ns ); + BtreeCursor c( nsdetails( ns ), 1, nsdetails( ns )->indexes[ 1 ], b, -1 ); + ASSERT_EQUALS( "BtreeCursor a_1 reverse multi", c.toString() ); + double expected[] = { 6, 5, 4, 2, 1 }; + for( int i = 0; i < 5; ++i ) { + ASSERT( c.ok() ); + ASSERT_EQUALS( expected[ i ], c.currKey().firstElement().number() ); + c.advance(); + } + ASSERT( !c.ok() ); + } + }; + + } // namespace MultiBtreeCursorTests - class All : public Suite { + class All : public ::Suite { public: All() { add< IdSetTests::BasicSize >(); add< IdSetTests::Upgrade >(); + add< BtreeCursorTests::MultiRange >(); + add< BtreeCursorTests::MultiRangeGap >(); + add< BtreeCursorTests::MultiRangeReverse >(); } }; } // namespace CursorTests