diff --git a/jstests/explain4.js b/jstests/explain4.js index fe5cf73d7b71c..1c81aefdd5365 100644 --- a/jstests/explain4.js +++ b/jstests/explain4.js @@ -1,9 +1,9 @@ -// Basic validation of explain output fields +// Basic validation of explain output fields. t = db.jstests_explain4; t.drop(); -function checkField( name, value ) { +function checkField( explain, name, value ) { assert( explain.hasOwnProperty( name ) ); if ( value != null ) { assert.eq( value, explain[ name ], name ); @@ -11,33 +11,33 @@ function checkField( name, value ) { } function checkPlanFields( explain, matches, n ) { - checkField( "cursor", "BasicCursor" ); - checkField( "n", n ); - checkField( "nscannedObjects", matches ); - checkField( "nscanned", matches ); - checkField( "indexBounds", {} ); + checkField( explain, "cursor", "BasicCursor" ); + checkField( explain, "n", n ); + checkField( explain, "nscannedObjects", matches ); + checkField( explain, "nscanned", matches ); + checkField( explain, "indexBounds", {} ); } function checkFields( matches, sort, limit ) { - it = t.find(); + cursor = t.find(); if ( sort ) { - it.sort({a:1}); + cursor.sort({a:1}); } if ( limit ) { - it.limit( limit ); + cursor.limit( limit ); } - explain = it.explain( true ); + explain = cursor.explain( true ); // printjson( explain ); checkPlanFields( explain, matches, matches > 0 ? 1 : 0 ); - checkField( "scanAndOrder", sort ); - checkField( "millis" ); - checkField( "nYields" ); - checkField( "nChunkSkips", 0 ); - checkField( "isMultiKey", false ); - checkField( "indexOnly", false ); - checkField( "server" ); - checkField( "allPlans" ); - explain.allPlans.forEach( function( x ) { checkPlanFields( x, matches ); } ); + checkField( explain, "scanAndOrder", sort ); + checkField( explain, "millis" ); + checkField( explain, "nYields" ); + checkField( explain, "nChunkSkips", 0 ); + checkField( explain, "isMultiKey", false ); + checkField( explain, "indexOnly", false ); + checkField( explain, "server" ); + checkField( explain, "allPlans" ); + explain.allPlans.forEach( function( x ) { checkPlanFields( x, matches, matches ); } ); } checkFields( 0, false ); diff --git a/jstests/explain6.js b/jstests/explain6.js index 7a99496844292..cf9668c9dbb4a 100644 --- a/jstests/explain6.js +++ b/jstests/explain6.js @@ -20,4 +20,4 @@ t.dropIndexes(); explain = t.find().skip( 1 ).sort({a:1}).explain( true ); // Skip is applied for an in memory sort. assert.eq( 0, explain.n ); -assert.eq( 0, explain.allPlans[ 0 ].n ); +assert.eq( 1, explain.allPlans[ 0 ].n ); diff --git a/src/mongo/db/ops/query.cpp b/src/mongo/db/ops/query.cpp index 1dd646b748754..7588324bbfedc 100644 --- a/src/mongo/db/ops/query.cpp +++ b/src/mongo/db/ops/query.cpp @@ -234,13 +234,14 @@ namespace mongo { MatchCountingExplainStrategy::MatchCountingExplainStrategy ( const ExplainQueryInfo::AncillaryInfo &ancillaryInfo ) : ExplainRecordingStrategy( ancillaryInfo ), - _matches() { + _orderedMatches() { } - void MatchCountingExplainStrategy::noteIterate( bool match, bool loadedObject, bool chunkSkip ) { - _noteIterate( match, loadedObject, chunkSkip ); - if ( match ) { - ++_matches; + void MatchCountingExplainStrategy::noteIterate( bool match, bool orderedMatch, + bool loadedObject, bool chunkSkip ) { + _noteIterate( match, orderedMatch, loadedObject, chunkSkip ); + if ( orderedMatch ) { + ++_orderedMatches; } } @@ -256,7 +257,8 @@ namespace mongo { _explainInfo->notePlan( *_cursor, scanAndOrder, indexOnly ); } - void SimpleCursorExplainStrategy::_noteIterate( bool match, bool loadedObject, bool chunkSkip ) { + void SimpleCursorExplainStrategy::_noteIterate( bool match, bool orderedMatch, + bool loadedObject, bool chunkSkip ) { _explainInfo->noteIterate( match, loadedObject, chunkSkip, *_cursor ); } @@ -276,9 +278,11 @@ namespace mongo { _cursor( cursor ) { } - void QueryOptimizerCursorExplainStrategy::_noteIterate( bool match, bool loadedObject, - bool chunkSkip ) { - _cursor->noteIterate( match, loadedObject, chunkSkip ); + void QueryOptimizerCursorExplainStrategy::_noteIterate( bool match, bool orderedMatch, + bool loadedObject, bool chunkSkip ) { + // Note ordered matches only; if an unordered plan is selected, the explain result will + // be updated with reviseN(). + _cursor->noteIterate( orderedMatch, loadedObject, chunkSkip ); } shared_ptr QueryOptimizerCursorExplainStrategy::_doneQueryInfo() { @@ -336,14 +340,14 @@ namespace mongo { _bufferedMatches() { } - bool OrderedBuildStrategy::handleMatch() { + bool OrderedBuildStrategy::handleMatch( bool &orderedMatch ) { DiskLoc loc = _cursor->currLoc(); if ( _cursor->getsetdup( loc ) ) { - return false; + return orderedMatch = false; } if ( _skip > 0 ) { --_skip; - return false; + return orderedMatch = false; } // Explain does not obey soft limits, so matches should not be buffered. if ( !_parsedQuery.isExplain() ) { @@ -351,7 +355,7 @@ namespace mongo { ( _parsedQuery.showDiskLoc() ? &loc : 0 ) ); ++_bufferedMatches; } - return true; + return orderedMatch = true; } ReorderBuildStrategy::ReorderBuildStrategy( const ParsedQuery &parsedQuery, @@ -363,17 +367,18 @@ namespace mongo { _bufferedMatches() { } - bool ReorderBuildStrategy::handleMatch() { + bool ReorderBuildStrategy::handleMatch( bool &orderedMatch ) { + orderedMatch = false; if ( _cursor->getsetdup( _cursor->currLoc() ) ) { return false; } - return _handleMatchNoDedup(); + _handleMatchNoDedup(); + return true; } - bool ReorderBuildStrategy::_handleMatchNoDedup() { + void ReorderBuildStrategy::_handleMatchNoDedup() { DiskLoc loc = _cursor->currLoc(); _scanAndOrder->add( current( false ), _parsedQuery.showDiskLoc() ? &loc : 0 ); - return false; } int ReorderBuildStrategy::rewriteMatches() { @@ -412,18 +417,18 @@ namespace mongo { _reorderedMatches() { } - bool HybridBuildStrategy::handleMatch() { + bool HybridBuildStrategy::handleMatch( bool &orderedMatch ) { if ( !_queryOptimizerCursor->currentPlanScanAndOrderRequired() ) { - return _orderedBuild.handleMatch(); + return _orderedBuild.handleMatch( orderedMatch ); } - handleReorderMatch(); - return false; + orderedMatch = false; + return handleReorderMatch(); } - void HybridBuildStrategy::handleReorderMatch() { + bool HybridBuildStrategy::handleReorderMatch() { DiskLoc loc = _cursor->currLoc(); if ( _scanAndOrderDups.getsetdup( loc ) ) { - return; + return false; } try { _reorderBuild._handleMatchNoDedup(); @@ -435,11 +440,12 @@ namespace mongo { } else if ( _queryOptimizerCursor->runningInitialInOrderPlan() ) { _queryOptimizerCursor->abortOutOfOrderPlans(); - return; + return true; } } throw; - } + } + return true; } int HybridBuildStrategy::rewriteMatches() { @@ -482,9 +488,10 @@ namespace mongo { if ( !chunkMatches() ) { return false; } - bool orderedMatch = _builder->handleMatch(); - _explain->noteIterate( orderedMatch, true, false ); - return true; + bool orderedMatch = false; + bool match = _builder->handleMatch( orderedMatch ); + _explain->noteIterate( match, orderedMatch, true, false ); + return match; } void QueryResponseBuilder::noteYield() { @@ -497,7 +504,7 @@ namespace mongo { bool QueryResponseBuilder::enoughTotalResults() const { if ( _parsedQuery.isExplain() ) { - return _parsedQuery.enoughForExplain( _explain->matches() ); + return _parsedQuery.enoughForExplain( _explain->orderedMatches() ); } return ( _parsedQuery.enough( _builder->bufferedMatches() ) || _buf.len() >= MaxBytesToReturnToClientAtOnce ); @@ -584,7 +591,7 @@ namespace mongo { if ( _cursor->currentMatches( &details ) ) { return true; } - _explain->noteIterate( false, details._loadedObject, false ); + _explain->noteIterate( false, false, details._loadedObject, false ); return false; } @@ -596,7 +603,7 @@ namespace mongo { if ( _chunkManager->belongsToMe( _cursor->current() ) ) { return true; } - _explain->noteIterate( false, true, true ); + _explain->noteIterate( false, false, true, true ); return false; } diff --git a/src/mongo/db/ops/query.h b/src/mongo/db/ops/query.h index 947f2886cae82..a9f5bd33e4b09 100644 --- a/src/mongo/db/ops/query.h +++ b/src/mongo/db/ops/query.h @@ -54,11 +54,12 @@ namespace mongo { /** Note information about a single query plan. */ virtual void notePlan( bool scanAndOrder, bool indexOnly ) {} /** Note an iteration of the query. */ - virtual void noteIterate( bool match, bool loadedObject, bool chunkSkip ) {} + virtual void noteIterate( bool match, bool orderedMatch, bool loadedObject, + bool chunkSkip ) {} /** Note that the query yielded. */ virtual void noteYield() {} - /** @return number of matches noted. */ - virtual long long matches() const { return 0; } + /** @return number of ordered matches noted. */ + virtual long long orderedMatches() const { return 0; } /** @return ExplainQueryInfo for a complete query. */ shared_ptr doneQueryInfo(); protected: @@ -81,11 +82,13 @@ namespace mongo { public: MatchCountingExplainStrategy( const ExplainQueryInfo::AncillaryInfo &ancillaryInfo ); protected: - virtual void _noteIterate( bool match, bool loadedObject, bool chunkSkip ) = 0; + virtual void _noteIterate( bool match, bool orderedMatch, bool loadedObject, + bool chunkSkip ) = 0; private: - virtual void noteIterate( bool match, bool loadedObject, bool chunkSkip ); - virtual long long matches() const { return _matches; } - long long _matches; + virtual void noteIterate( bool match, bool orderedMatch, bool loadedObject, + bool chunkSkip ); + virtual long long orderedMatches() const { return _orderedMatches; } + long long _orderedMatches; }; /** Record explain events for a simple cursor representing a single clause and plan. */ @@ -95,7 +98,8 @@ namespace mongo { const shared_ptr &cursor ); private: virtual void notePlan( bool scanAndOrder, bool indexOnly ); - virtual void _noteIterate( bool match, bool loadedObject, bool chunkSkip ); + virtual void _noteIterate( bool match, bool orderedMatch, bool loadedObject, + bool chunkSkip ); virtual void noteYield(); virtual shared_ptr _doneQueryInfo(); shared_ptr _cursor; @@ -111,7 +115,8 @@ namespace mongo { QueryOptimizerCursorExplainStrategy( const ExplainQueryInfo::AncillaryInfo &ancillaryInfo, const shared_ptr &cursor ); private: - virtual void _noteIterate( bool match, bool loadedObject, bool chunkSkip ); + virtual void _noteIterate( bool match, bool orderedMatch, bool loadedObject, + bool chunkSkip ); virtual shared_ptr _doneQueryInfo(); shared_ptr _cursor; }; @@ -128,9 +133,10 @@ namespace mongo { virtual ~ResponseBuildStrategy() {} /** * Handle the current iterate of the supplied cursor as a (possibly duplicate) match. - * @return true if an ordered match is found. + * @return true if a match is found. + * @param orderedMatch set if it is an ordered match. */ - virtual bool handleMatch() = 0; + virtual bool handleMatch( bool &orderedMatch ) = 0; /** * Write all matches into the buffer, overwriting existing data. * @return number of matches written, or -1 if no op. @@ -165,7 +171,7 @@ namespace mongo { public: OrderedBuildStrategy( const ParsedQuery &parsedQuery, const shared_ptr &cursor, BufBuilder &buf, const QueryPlan::Summary &queryPlan ); - virtual bool handleMatch(); + virtual bool handleMatch( bool &orderedMatch ); virtual int bufferedMatches() const { return _bufferedMatches; } private: int _skip; @@ -181,9 +187,9 @@ namespace mongo { const shared_ptr &cursor, BufBuilder &buf, const QueryPlan::Summary &queryPlan ); - virtual bool handleMatch(); + virtual bool handleMatch( bool &orderedMatch ); /** Handle a match without performing deduping. */ - bool _handleMatchNoDedup(); + void _handleMatchNoDedup(); virtual int rewriteMatches(); virtual int bufferedMatches() const { return _bufferedMatches; } private: @@ -202,12 +208,11 @@ namespace mongo { const shared_ptr &cursor, BufBuilder &buf ); private: - virtual bool handleMatch(); + virtual bool handleMatch( bool &orderedMatch ); virtual int rewriteMatches(); virtual int bufferedMatches() const; virtual void finishedFirstBatch(); - void handleReorderMatch(); - bool handleOrderedMatch(); + bool handleReorderMatch(); DiskLocDupSet _scanAndOrderDups; OrderedBuildStrategy _orderedBuild; ReorderBuildStrategy _reorderBuild;