diff options
-rw-r--r-- | db/oplog.cpp | 30 | ||||
-rw-r--r-- | db/oplog.h | 1 | ||||
-rw-r--r-- | dbtests/querytests.cpp | 29 | ||||
-rw-r--r-- | dbtests/repltests.cpp | 28 |
4 files changed, 78 insertions, 10 deletions
diff --git a/db/oplog.cpp b/db/oplog.cpp index 90a5544788b..d58cdcff9ba 100644 --- a/db/oplog.cpp +++ b/db/oplog.cpp @@ -473,9 +473,9 @@ namespace mongo { return _qp.nsd()->capFirstNewRecord; } - void assertExtentNonempty( const Extent *e ) { + void wassertExtentNonempty( const Extent *e ) { // TODO ensure this requirement is clearly enforced, or fix. - massert( 14834, "empty extent found during finding start scan", !e->firstRecord.isNull() ); + wassert( !e->firstRecord.isNull() ); } DiskLoc FindingStartCursor::prevExtentFirstLoc( const DiskLoc &rec ) { @@ -488,14 +488,14 @@ namespace mongo { e = e->xprev.ext(); } if ( e->myLoc != _qp.nsd()->capExtent ) { - assertExtentNonempty( e ); + wassertExtentNonempty( e ); return e->firstRecord; } } else { if ( !e->xprev.isNull() ) { e = e->xprev.ext(); - assertExtentNonempty( e ); + wassertExtentNonempty( e ); return e->firstRecord; } } @@ -506,20 +506,30 @@ namespace mongo { shared_ptr<Cursor> c = _qp.newCursor( startLoc ); _findingStartCursor.reset( new ClientCursor(QueryOption_NoCursorTimeout, c, _qp.ns()) ); } + + bool FindingStartCursor::firstDocMatches() const { + shared_ptr<Cursor> c = _qp.newCursor(); + return _matcher->matchesCurrent( c.get() ); + } void FindingStartCursor::init() { - // Use a ClientCursor here so we can release db mutex while scanning - // oplog (can take quite a while with large oplogs). - shared_ptr<Cursor> c = _qp.newReverseCursor(); - _findingStartCursor.reset( new ClientCursor(QueryOption_NoCursorTimeout, c, _qp.ns(), BSONObj()) ); - _findingStartTimer.reset(); - _findingStartMode = Initial; BSONElement tsElt = _qp.originalQuery()[ "ts" ]; massert( 13044, "no ts field in query", !tsElt.eoo() ); BSONObjBuilder b; b.append( tsElt ); BSONObj tsQuery = b.obj(); _matcher.reset(new CoveredIndexMatcher(tsQuery, _qp.indexKey())); + if ( firstDocMatches() ) { + _c = _qp.newCursor(); + _findingStart = false; + return; + } + // Use a ClientCursor here so we can release db mutex while scanning + // oplog (can take quite a while with large oplogs). + shared_ptr<Cursor> c = _qp.newReverseCursor(); + _findingStartCursor.reset( new ClientCursor(QueryOption_NoCursorTimeout, c, _qp.ns(), BSONObj()) ); + _findingStartTimer.reset(); + _findingStartMode = Initial; } // ------------------------------------- diff --git a/db/oplog.h b/db/oplog.h index f87a1c85e04..223e8fbe9bb 100644 --- a/db/oplog.h +++ b/db/oplog.h @@ -118,6 +118,7 @@ namespace mongo { _findingStartCursor.reset( 0 ); } void init(); + bool firstDocMatches() const; }; void pretouchOperation(const BSONObj& op); diff --git a/dbtests/querytests.cpp b/dbtests/querytests.cpp index a50eadfcd31..b2ba9d02587 100644 --- a/dbtests/querytests.cpp +++ b/dbtests/querytests.cpp @@ -1146,7 +1146,35 @@ namespace QueryTests { private: int _old; }; + + /** + * Check OplogReplay mode where query timestamp is earlier than the earliest + * entry in the collection. + */ + class FindingStartStale : public CollectionBase { + public: + FindingStartStale() : CollectionBase( "findingstart" ) {} + void run() { + unsigned startNumCursors = ClientCursor::numCursors(); + + BSONObj info; + ASSERT( client().runCommand( "unittests", BSON( "create" << "querytests.findingstart" << "capped" << true << "$nExtents" << 5 << "autoIndexId" << false ), info ) ); + + // Check OplogReplay mode with empty collection. + auto_ptr< DBClientCursor > c = client().query( ns(), QUERY( "ts" << GTE << 50 ), 0, 0, 0, QueryOption_OplogReplay ); + ASSERT( !c->more() ); + + // Check with some docs in the collection. + for( int i = 100; i < 150; client().insert( ns(), BSON( "ts" << i++ ) ) ); + c = client().query( ns(), QUERY( "ts" << GTE << 50 ), 0, 0, 0, QueryOption_OplogReplay ); + ASSERT( c->more() ); + ASSERT_EQUALS( 100, c->next()[ "ts" ].numberInt() ); + + // Check that no persistent cursors outlast our queries above. + ASSERT_EQUALS( startNumCursors, ClientCursor::numCursors() ); + } + }; class WhatsMyUri : public CollectionBase { public: @@ -1362,6 +1390,7 @@ namespace QueryTests { add< HelperTest >(); add< HelperByIdTest >(); add< FindingStartPartiallyFull >(); + add< FindingStartStale >(); add< WhatsMyUri >(); add< parsedtests::basic1 >(); diff --git a/dbtests/repltests.cpp b/dbtests/repltests.cpp index ecaacf74874..7009b556899 100644 --- a/dbtests/repltests.cpp +++ b/dbtests/repltests.cpp @@ -25,6 +25,8 @@ #include "../db/json.h" #include "dbtests.h" +#include "../db/oplog.h" +#include "../db/queryoptimizer.h" namespace mongo { void createOplog(); @@ -1049,6 +1051,31 @@ namespace ReplTests { } }; + /** + * Check against oldest document in the oplog before scanning backward + * from the newest document. + */ + class FindingStartCursorStale : public Base { + public: + void run() { + for( int i = 0; i < 10; ++i ) { + client()->insert( ns(), BSON( "_id" << i ) ); + } + dblock lk; + Client::Context ctx( cllNS() ); + NamespaceDetails *nsd = nsdetails( cllNS() ); + BSONObjBuilder b; + b.appendTimestamp( "$gte" ); + BSONObj query = BSON( "ts" << b.obj() ); + FieldRangeSetPair frsp( cllNS(), query ); + BSONObj order = BSON( "$natural" << 1 ); + QueryPlan qp( nsd, -1, frsp, frsp, query, order ); + FindingStartCursor fsc( qp ); + ASSERT( fsc.done() ); + ASSERT_EQUALS( 0, fsc.cursor()->current()[ "o" ].Obj()[ "_id" ].Int() ); + } + }; + class All : public Suite { public: All() : Suite( "repl" ) { @@ -1103,6 +1130,7 @@ namespace ReplTests { add< DeleteOpIsIdBased >(); add< DatabaseIgnorerBasic >(); add< DatabaseIgnorerUpdate >(); + add< FindingStartCursorStale >(); } } myall; |