diff options
Diffstat (limited to 'src/mongo/dbtests/intervalbtreecursortests.cpp')
-rw-r--r-- | src/mongo/dbtests/intervalbtreecursortests.cpp | 660 |
1 files changed, 660 insertions, 0 deletions
diff --git a/src/mongo/dbtests/intervalbtreecursortests.cpp b/src/mongo/dbtests/intervalbtreecursortests.cpp new file mode 100644 index 00000000000..4bb5a02ad94 --- /dev/null +++ b/src/mongo/dbtests/intervalbtreecursortests.cpp @@ -0,0 +1,660 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + */ + +#include "mongo/db/intervalbtreecursor.h" + +#include "mongo/db/btree.h" +#include "mongo/db/pdfile.h" +#include "mongo/dbtests/dbtests.h" +#include "mongo/platform/cstdint.h" + +namespace IntervalBtreeCursorTests { + + DBDirectClient _client; + const char* _ns = "unittests.intervalbtreecursor"; + + /** An IntervalBtreeCursor can only be created for a version 1 index. */ + class WrongIndexVersion { + public: + void run() { + Client::WriteContext ctx( _ns ); + _client.dropCollection( _ns ); + + // Create a version 0 index. + _client.ensureIndex( _ns, BSON( "a" << 1 ), false, "", true, false, /* version */ 0 ); + + // Attempt to create a cursor on the a:1 index. + scoped_ptr<Cursor> cursor( IntervalBtreeCursor::make( nsdetails( _ns ), + nsdetails( _ns )->idx( 1 ), + BSON( "" << 5 ), + true, + BSON( "" << 6 ), + true ) ); + + // No cursor was created because the index was not of version 1. + ASSERT( !cursor ); + } + }; + + class BasicAccessors { + public: + void run() { + Client::WriteContext ctx( _ns ); + _client.dropCollection( _ns ); + _client.insert( _ns, BSON( "a" << 5 ) ); + _client.ensureIndex( _ns, BSON( "a" << 1 ) ); + scoped_ptr<Cursor> cursor( IntervalBtreeCursor::make( nsdetails( _ns ), + nsdetails( _ns )->idx( 1 ), + BSON( "" << 5 ), + true, + BSON( "" << 6 ), + true ) ); + + // Create a reference BasicCursor, which will return identical values from certain + // accessors. + boost::shared_ptr<Cursor> reference = theDataFileMgr.findAll( _ns ); + + ASSERT( cursor->ok() ); + ASSERT( reference->_current() == cursor->_current() ); + ASSERT_EQUALS( reference->current(), cursor->current() ); + ASSERT_EQUALS( reference->currLoc(), cursor->currLoc() ); + ASSERT_EQUALS( BSON( "" << 5 ), cursor->currKey() ); + ASSERT_EQUALS( cursor->currLoc(), cursor->refLoc() ); + ASSERT_EQUALS( BSON( "a" << 1 ), cursor->indexKeyPattern() ); + ASSERT( !cursor->supportGetMore() ); + ASSERT( cursor->supportYields() ); + ASSERT_EQUALS( "IntervalBtreeCursor", cursor->toString() ); + ASSERT( !cursor->isMultiKey() ); + ASSERT( !cursor->modifiedKeys() ); + ASSERT_EQUALS( BSON( "lower" << BSON( "a" << 5 ) << "upper" << BSON( "a" << 6 ) ), + cursor->prettyIndexBounds() ); + + // Advance the cursor to the end. + ASSERT( !cursor->advance() ); + ASSERT( !cursor->ok() ); + ASSERT( cursor->currLoc().isNull() ); + ASSERT( cursor->currKey().isEmpty() ); + ASSERT( cursor->refLoc().isNull() ); + } + }; + + /** + * Check nscanned counting semantics. The expectation is to match the behavior of BtreeCursor, + * as described in the test comments. + */ + class Nscanned { + public: + void run() { + Client::WriteContext ctx( _ns ); + _client.dropCollection( _ns ); + for( int32_t i = 0; i < 10; ++i ) { + _client.insert( _ns, BSON( "a" << 5 ) ); + } + _client.insert( _ns, BSON( "a" << 7 ) ); + _client.ensureIndex( _ns, BSON( "a" << 1 ) ); + scoped_ptr<Cursor> cursor( IntervalBtreeCursor::make( nsdetails( _ns ), + nsdetails( _ns )->idx( 1 ), + BSON( "" << 5 ), + true, + BSON( "" << 6 ), + true ) ); + // nscanned is 1 for the first match. + ASSERT_EQUALS( 1, cursor->nscanned() ); + for( int32_t i = 1; i < 10; ++i ) { + ASSERT( cursor->ok() ); + + // nscanned is incremented by 1 for intermediate matches. + ASSERT_EQUALS( i, cursor->nscanned() ); + ASSERT( cursor->advance() ); + } + ASSERT( cursor->ok() ); + ASSERT_EQUALS( 10, cursor->nscanned() ); + ASSERT( !cursor->advance() ); + ASSERT( !cursor->ok() ); + + // nscanned is not incremented when reaching the end of the cursor. + ASSERT_EQUALS( 10, cursor->nscanned() ); + } + }; + + /** Check that a CoveredIndexMatcher can be set and used properly by the cursor. */ + class Matcher { + public: + void run() { + Client::WriteContext ctx( _ns ); + _client.dropCollection( _ns ); + + // Insert a document that will match. + _client.insert( _ns, BSON( "a" << 5 << "b" << 1 ) ); + + // Insert a document that will not match. + _client.insert( _ns, BSON( "a" << 5 << "b" << 2 ) ); + _client.ensureIndex( _ns, BSON( "a" << 1 ) ); + scoped_ptr<Cursor> cursor( IntervalBtreeCursor::make( nsdetails( _ns ), + nsdetails( _ns )->idx( 1 ), + BSON( "" << 5 ), + true, + BSON( "" << 5 ), + true ) ); + + // No matcher is set yet. + ASSERT( !cursor->matcher() ); + ASSERT( cursor->currentMatches() ); + + // Create a matcher and set it on the cursor. + boost::shared_ptr<CoveredIndexMatcher> matcher + ( new CoveredIndexMatcher( BSON( "a" << 5 << "b" << 1 ), BSON( "a" << 1 ) ) ); + cursor->setMatcher( matcher ); + + // The document with b:1 matches. + ASSERT_EQUALS( 1, cursor->current()[ "b" ].Int() ); + ASSERT( cursor->matcher()->matchesCurrent( cursor.get() ) ); + ASSERT( cursor->currentMatches() ); + cursor->advance(); + + // The document with b:2 does not match. + ASSERT_EQUALS( 2, cursor->current()[ "b" ].Int() ); + ASSERT( !cursor->matcher()->matchesCurrent( cursor.get() ) ); + ASSERT( !cursor->currentMatches() ); + } + }; + + /** Check that dups are properly identified by the cursor. */ + class Dups { + public: + void run() { + Client::WriteContext ctx( _ns ); + _client.dropCollection( _ns ); + _client.insert( _ns, BSON( "a" << BSON_ARRAY( 5 << 7 ) ) ); + _client.insert( _ns, BSON( "a" << BSON_ARRAY( 6 << 8 ) ) ); + _client.ensureIndex( _ns, BSON( "a" << 1 ) ); + scoped_ptr<Cursor> cursor( IntervalBtreeCursor::make( nsdetails( _ns ), + nsdetails( _ns )->idx( 1 ), + BSON( "" << 5 ), + true, + BSON( "" << 10 ), + true ) ); + ASSERT( cursor->isMultiKey() ); + ASSERT( cursor->modifiedKeys() ); + + // This is the 5,7 document, first time seen. Not a dup. + DiskLoc firstLoc = cursor->currLoc(); + ASSERT( !cursor->getsetdup( cursor->currLoc() ) ); + cursor->advance(); + + // This is the 6,8 document, first time seen. Not a dup. + DiskLoc secondLoc = cursor->currLoc(); + ASSERT( !cursor->getsetdup( cursor->currLoc() ) ); + cursor->advance(); + + // This is the 5,7 document, second time seen. A dup. + ASSERT_EQUALS( firstLoc, cursor->currLoc() ); + ASSERT( cursor->getsetdup( cursor->currLoc() ) ); + cursor->advance(); + + // This is the 6,8 document, second time seen. A dup. + ASSERT_EQUALS( secondLoc, cursor->currLoc() ); + ASSERT( cursor->getsetdup( cursor->currLoc() ) ); + } + }; + + /** Check that expected results are returned with inclusive bounds. */ + class InclusiveBounds { + public: + void run() { + Client::WriteContext ctx( _ns ); + _client.dropCollection( _ns ); + + // Save 'a' values 1-10. + for( int32_t i = 0; i < 10; ++i ) { + _client.insert( _ns, BSON( "a" << i ) ); + } + _client.ensureIndex( _ns, BSON( "a" << 1 ) ); + + // Iterate over 'a' values 3-7 inclusive. + scoped_ptr<Cursor> cursor( IntervalBtreeCursor::make( nsdetails( _ns ), + nsdetails( _ns )->idx( 1 ), + BSON( "" << 3 ), + true, + BSON( "" << 7 ), + true ) ); + + // Check that the expected 'a' values are returned. + for( int32_t i = 3; i < 8; ++i, cursor->advance() ) { + ASSERT_EQUALS( i, cursor->current()[ "a" ].Int() ); + } + ASSERT( !cursor->ok() ); + } + }; + + /** Check that expected results are returned with exclusive bounds. */ + class ExclusiveBounds { + public: + void run() { + Client::WriteContext ctx( _ns ); + _client.dropCollection( _ns ); + + // Save 'a' values 1-10. + for( int32_t i = 0; i < 10; ++i ) { + _client.insert( _ns, BSON( "a" << i ) ); + } + _client.ensureIndex( _ns, BSON( "a" << 1 ) ); + + // Iterate over 'a' values 3-7 exclusive. + scoped_ptr<Cursor> cursor( IntervalBtreeCursor::make( nsdetails( _ns ), + nsdetails( _ns )->idx( 1 ), + BSON( "" << 3 ), + false, + BSON( "" << 7 ), + false ) ); + + // Check that the expected 'a' values are returned. + for( int32_t i = 4; i < 7; ++i, cursor->advance() ) { + ASSERT_EQUALS( i, cursor->current()[ "a" ].Int() ); + } + ASSERT( !cursor->ok() ); + } + }; + + /** Check that killOp will interrupt cursor iteration. */ + class Interrupt { + public: + ~Interrupt() { + // Reset curop's kill flag. + cc().curop()->reset(); + } + void run() { + _client.dropCollection( _ns ); + for( int32_t i = 0; i < 150; ++i ) { + _client.insert( _ns, BSON( "a" << 5 ) ); + } + _client.ensureIndex( _ns, BSON( "a" << 1 ) ); + Client::ReadContext ctx( _ns ); + scoped_ptr<Cursor> cursor( IntervalBtreeCursor::make( nsdetails( _ns ), + nsdetails( _ns )->idx( 1 ), + BSON( "" << 5 ), + true, + BSON( "" << 5 ), + true ) ); + + // Register a request to kill the current operation. + cc().curop()->kill(); + + // Check that an exception is thrown when iterating the cursor. + ASSERT_THROWS( exhaustCursor( cursor.get() ), UserException ); + } + private: + void exhaustCursor( Cursor* cursor ) { + while( cursor->advance() ); + } + }; + + /** Check that a cursor returns no results if all documents are below the lower bound. */ + class NothingAboveLowerBound { + public: + void run() { + Client::WriteContext ctx( _ns ); + _client.dropCollection( _ns ); + _client.insert( _ns, BSON( "a" << 2 ) ); + _client.ensureIndex( _ns, BSON( "a" << 1 ) ); + scoped_ptr<Cursor> cursor( IntervalBtreeCursor::make( nsdetails( _ns ), + nsdetails( _ns )->idx( 1 ), + BSON( "" << 2 ), + false, + BSON( "" << 3 ), + false ) ); + + // The cursor returns no results. + ASSERT( !cursor->ok() ); + } + }; + + /** Check that a cursor returns no results if there are no documents within the interval. */ + class NothingInInterval { + public: + void run() { + Client::WriteContext ctx( _ns ); + _client.dropCollection( _ns ); + _client.insert( _ns, BSON( "a" << 2 ) ); + _client.insert( _ns, BSON( "a" << 3 ) ); + _client.ensureIndex( _ns, BSON( "a" << 1 ) ); + scoped_ptr<Cursor> cursor( IntervalBtreeCursor::make( nsdetails( _ns ), + nsdetails( _ns )->idx( 1 ), + BSON( "" << 2 ), + false, + BSON( "" << 3 ), + false ) ); + + // The cursor returns no results. + ASSERT( !cursor->ok() ); + } + }; + + /** + * Check that a cursor returns no results if there are no documents within the interval and + * the first key located during initialization is above the upper bound. + */ + class NothingInIntervalFirstMatchBeyondUpperBound { + public: + void run() { + Client::WriteContext ctx( _ns ); + _client.dropCollection( _ns ); + _client.insert( _ns, BSON( "a" << 2 ) ); + _client.insert( _ns, BSON( "a" << 4 ) ); + _client.ensureIndex( _ns, BSON( "a" << 1 ) ); + + // Iterate over 'a' values ( 2, 3 ]. + scoped_ptr<Cursor> cursor( IntervalBtreeCursor::make( nsdetails( _ns ), + nsdetails( _ns )->idx( 1 ), + BSON( "" << 2 ), + false, + BSON( "" << 3 ), + true ) ); + ASSERT( !cursor->ok() ); + } + }; + + /** Check that a cursor recovers its position properly if there is no change during a yield. */ + class NoChangeDuringYield { + public: + void run() { + Client::WriteContext ctx( _ns ); + _client.dropCollection( _ns ); + for( int32_t i = 0; i < 10; ++i ) { + _client.insert( _ns, BSON( "a" << i ) ); + } + _client.ensureIndex( _ns, BSON( "a" << 1 ) ); + scoped_ptr<Cursor> cursor( IntervalBtreeCursor::make( nsdetails( _ns ), + nsdetails( _ns )->idx( 1 ), + BSON( "" << 2 ), + false, + BSON( "" << 6 ), + true ) ); + while( cursor->current()[ "a" ].Int() != 5 ) { + cursor->advance(); + } + + // Prepare the cursor to yield. + cursor->prepareToYield(); + + // Recover the cursor. + cursor->recoverFromYield(); + + // The cursor is still at its original position. + ASSERT_EQUALS( 5, cursor->current()[ "a" ].Int() ); + + // The cursor can be advanced from this position. + ASSERT( cursor->advance() ); + ASSERT_EQUALS( 6, cursor->current()[ "a" ].Int() ); + } + }; + + /** + * Check that a cursor recovers its position properly if its current location is deleted + * during a yield. + */ + class DeleteDuringYield { + public: + void run() { + Client::WriteContext ctx( _ns ); + _client.dropCollection( _ns ); + for( int32_t i = 0; i < 10; ++i ) { + _client.insert( _ns, BSON( "a" << i ) ); + } + _client.ensureIndex( _ns, BSON( "a" << 1 ) ); + scoped_ptr<Cursor> cursor( IntervalBtreeCursor::make( nsdetails( _ns ), + nsdetails( _ns )->idx( 1 ), + BSON( "" << 2 ), + false, + BSON( "" << 6 ), + true ) ); + while( cursor->current()[ "a" ].Int() != 5 ) { + cursor->advance(); + } + + // Prepare the cursor to yield. + cursor->prepareToYield(); + + // Remove the current iterate and all remaining iterates. + _client.remove( _ns, BSON( "a" << GTE << 5 ) ); + + // Recover the cursor. + cursor->recoverFromYield(); + + // The cursor is exhausted. + ASSERT( !cursor->ok() ); + } + }; + + /** + * Check that a cursor relocates its end location properly if the end location changes during a + * yield. + */ + class InsertNewDocsDuringYield { + public: + void run() { + Client::WriteContext ctx( _ns ); + _client.dropCollection( _ns ); + for( int32_t i = 0; i < 10; ++i ) { + _client.insert( _ns, BSON( "a" << i ) ); + } + _client.ensureIndex( _ns, BSON( "a" << 1 ) ); + scoped_ptr<Cursor> cursor( IntervalBtreeCursor::make( nsdetails( _ns ), + nsdetails( _ns )->idx( 1 ), + BSON( "" << 2 ), + false, + BSON( "" << 6 ), + true ) ); + while( cursor->current()[ "a" ].Int() != 4 ) { + cursor->advance(); + } + + // Prepare the cursor to yield. + cursor->prepareToYield(); + + // Insert one doc before the end. + _client.insert( _ns, BSON( "a" << 5.5 ) ); + + // Insert one doc after the end. + _client.insert( _ns, BSON( "a" << 6.5 ) ); + + // Recover the cursor. + cursor->recoverFromYield(); + + // Check that the cursor returns the expected remaining documents. + ASSERT_EQUALS( 4, cursor->current()[ "a" ].Int() ); + ASSERT( cursor->advance() ); + ASSERT_EQUALS( 5, cursor->current()[ "a" ].Int() ); + ASSERT( cursor->advance() ); + ASSERT_EQUALS( 5.5, cursor->current()[ "a" ].number() ); + ASSERT( cursor->advance() ); + ASSERT_EQUALS( 6, cursor->current()[ "a" ].Int() ); + ASSERT( !cursor->advance() ); + } + }; + + /** Check that isMultiKey() is updated correctly if an index becomes multikey during a yield. */ + class BecomesMultikeyDuringYield { + public: + void run() { + Client::WriteContext ctx( _ns ); + _client.dropCollection( _ns ); + for( int32_t i = 0; i < 10; ++i ) { + _client.insert( _ns, BSON( "a" << i ) ); + } + _client.ensureIndex( _ns, BSON( "a" << 1 ) ); + scoped_ptr<Cursor> cursor( IntervalBtreeCursor::make( nsdetails( _ns ), + nsdetails( _ns )->idx( 1 ), + BSON( "" << 2 ), + false, + BSON( "" << 50 ), + true ) ); + while( cursor->current()[ "a" ].Int() != 4 ) { + cursor->advance(); + } + + // Check that the cursor is not multikey. + ASSERT( !cursor->isMultiKey() ); + + // Prepare the cursor to yield. + cursor->prepareToYield(); + + // Insert a document with two values of 'a'. + _client.insert( _ns, BSON( "a" << BSON_ARRAY( 10 << 11 ) ) ); + + // Recover the cursor. + cursor->recoverFromYield(); + + // Check that the cursor becomes multikey. + ASSERT( cursor->isMultiKey() ); + + // Check that keys 10 and 11 are detected as duplicates. + while( cursor->currKey().firstElement().Int() != 10 ) { + ASSERT( cursor->advance() ); + } + ASSERT( !cursor->getsetdup( cursor->currLoc() ) ); + ASSERT( cursor->advance() ); + ASSERT_EQUALS( 11, cursor->currKey().firstElement().Int() ); + ASSERT( cursor->getsetdup( cursor->currLoc() ) ); + } + }; + + /** Unused keys are not returned during iteration. */ + class UnusedKeys { + public: + void run() { + Client::WriteContext ctx( _ns ); + _client.dropCollection( _ns ); + for( int32_t i = 0; i < 10; ++i ) { + _client.insert( _ns, BSON( "a" << i ) ); + } + _client.ensureIndex( _ns, BSON( "a" << 1 ) ); + + // Mark keys at position 0, 3, and 4 as unused. + nsdetails( _ns )->idx( 1 ).head.btreemod<V1>()->_k( 0 ).setUnused(); + nsdetails( _ns )->idx( 1 ).head.btreemod<V1>()->_k( 3 ).setUnused(); + nsdetails( _ns )->idx( 1 ).head.btreemod<V1>()->_k( 4 ).setUnused(); + + scoped_ptr<Cursor> cursor( IntervalBtreeCursor::make( nsdetails( _ns ), + nsdetails( _ns )->idx( 1 ), + BSON( "" << 0 ), + true, + BSON( "" << 6 ), + true ) ); + + // Check that the unused keys are not returned but the other keys are returned. + ASSERT_EQUALS( 1, cursor->current()[ "a" ].Int() ); + ASSERT( cursor->advance() ); + ASSERT_EQUALS( 2, cursor->current()[ "a" ].Int() ); + ASSERT( cursor->advance() ); + ASSERT_EQUALS( 5, cursor->current()[ "a" ].Int() ); + ASSERT( cursor->advance() ); + ASSERT_EQUALS( 6, cursor->current()[ "a" ].Int() ); + ASSERT( !cursor->advance() ); + } + }; + + /** Iteration is properly terminated when the end location is an unused key. */ + class UnusedEndKey { + public: + void run() { + Client::WriteContext ctx( _ns ); + _client.dropCollection( _ns ); + for( int32_t i = 0; i < 10; ++i ) { + _client.insert( _ns, BSON( "a" << i ) ); + } + _client.ensureIndex( _ns, BSON( "a" << 1 ) ); + + // Mark the key at position 5 as unused. + nsdetails( _ns )->idx( 1 ).head.btreemod<V1>()->_k( 5 ).setUnused(); + + scoped_ptr<Cursor> cursor( IntervalBtreeCursor::make( nsdetails( _ns ), + nsdetails( _ns )->idx( 1 ), + BSON( "" << 4 ), + true, + BSON( "" << 5 ), + false ) ); + + // Check the documents produced by the cursor. + ASSERT_EQUALS( 4, cursor->current()[ "a" ].Int() ); + ASSERT( !cursor->advance() ); + } + }; + + /** Advances past a key that becomes unused during a yield. */ + class KeyBecomesUnusedDuringYield { + public: + void run() { + Client::WriteContext ctx( _ns ); + _client.dropCollection( _ns ); + for( int32_t i = 0; i < 10; ++i ) { + _client.insert( _ns, BSON( "a" << i ) ); + } + _client.ensureIndex( _ns, BSON( "a" << 1 ) ); + + scoped_ptr<Cursor> cursor( IntervalBtreeCursor::make( nsdetails( _ns ), + nsdetails( _ns )->idx( 1 ), + BSON( "" << 3 ), + true, + BSON( "" << 9 ), + true ) ); + + // Advance the cursor to key a:5. + while( cursor->current()[ "a" ].Int() != 5 ) { + cursor->advance(); + } + + // Backup the cursor position. + cursor->prepareToYield(); + + // Mark the key at position 5 as unused. + nsdetails( _ns )->idx( 1 ).head.btreemod<V1>()->_k( 5 ).setUnused(); + + // Restore the cursor position. + cursor->recoverFromYield(); + + // The cursor advanced from 5, now unused, to 6. + ASSERT_EQUALS( 6, cursor->current()[ "a" ].Int() ); + } + }; + + class All : public Suite { + public: + All() : Suite( "intervalbtreecursor" ) { + } + void setupTests() { + add<WrongIndexVersion>(); + add<BasicAccessors>(); + add<Nscanned>(); + add<Matcher>(); + add<Dups>(); + add<InclusiveBounds>(); + add<ExclusiveBounds>(); + add<Interrupt>(); + add<NothingAboveLowerBound>(); + add<NothingInInterval>(); + add<NothingInIntervalFirstMatchBeyondUpperBound>(); + add<NoChangeDuringYield>(); + add<DeleteDuringYield>(); + add<InsertNewDocsDuringYield>(); + add<BecomesMultikeyDuringYield>(); + add<UnusedKeys>(); + add<UnusedEndKey>(); + add<KeyBecomesUnusedDuringYield>(); + } + } myall; + +} // namespace IntervalBtreeCursorTests |