diff options
Diffstat (limited to 'src/mongo')
72 files changed, 69 insertions, 16494 deletions
diff --git a/src/mongo/SConscript b/src/mongo/SConscript index 53cadc7417f..1157e56ac21 100644 --- a/src/mongo/SConscript +++ b/src/mongo/SConscript @@ -426,7 +426,6 @@ env.Library("coredb", [ "db/pipeline/field_path.cpp", "db/pipeline/value.cpp", "db/projection.cpp", - "db/querypattern.cpp", "db/queryutil.cpp", "db/stats/timer_stats.cpp", "s/shardconnection.cpp", @@ -555,7 +554,6 @@ serverOnlyFiles = [ "db/curop.cpp", "db/repl/oplog.cpp", "db/prefetch.cpp", "db/repl/write_concern.cpp", - "db/btreecursor.cpp", "db/index_legacy.cpp", "db/index_selection.cpp", "db/index/2d_access_method.cpp", @@ -567,13 +565,10 @@ serverOnlyFiles = [ "db/curop.cpp", "db/index/hash_access_method.cpp", "db/index/haystack_access_method.cpp", "db/index/s2_access_method.cpp", - "db/intervalbtreecursor.cpp", - "db/btreeposition.cpp", "db/cloner.cpp", "db/namespace_details.cpp", "db/catalog/ondisk/namespace_index.cpp", "db/cap.cpp", - "db/matcher_covered.cpp", "db/dbeval.cpp", "db/dbhelpers.cpp", "db/instance.cpp", @@ -592,18 +587,10 @@ serverOnlyFiles = [ "db/curop.cpp", "db/storage/extent_manager.cpp", "db/storage/index_details.cpp", "db/structure/record_store.cpp", - "db/cursor.cpp", - "db/query_optimizer.cpp", - "db/query_optimizer_internal.cpp", - "db/queryoptimizercursorimpl.cpp", - "db/query_plan.cpp", - "db/query_plan_selection_policy.cpp", "db/extsort.cpp", "db/index_builder.cpp", "db/index_rebuilder.cpp", "db/storage/record.cpp", - "db/scanandorder.cpp", - "db/explain.cpp", "db/commands/geonear.cpp", "db/geo/haystack.cpp", "db/geo/s2common.cpp", diff --git a/src/mongo/db/btree.cpp b/src/mongo/db/btree.cpp index 24151dbfbec..5b59d292625 100644 --- a/src/mongo/db/btree.cpp +++ b/src/mongo/db/btree.cpp @@ -41,7 +41,6 @@ #include "mongo/db/dbhelpers.h" #include "mongo/db/dur_commitjob.h" #include "mongo/db/index/btree_index_cursor.h" // for aboutToDeleteBucket -#include "mongo/db/intervalbtreecursor.h" // also for aboutToDeleteBucket #include "mongo/db/json.h" #include "mongo/db/kill_current_op.h" #include "mongo/db/pdfile.h" @@ -834,7 +833,6 @@ namespace mongo { template< class V > void BtreeBucket<V>::delBucket(const DiskLoc thisLoc, const IndexDetails& id) { BtreeIndexCursor::aboutToDeleteBucket(thisLoc); - IntervalBtreeCursor::aboutToDeleteBucket(thisLoc); verify( !isHead() ); DiskLoc ll = this->parent; @@ -967,7 +965,6 @@ namespace mongo { } BTREE(this->nextChild)->parent.writing() = this->parent; BtreeIndexCursor::aboutToDeleteBucket(thisLoc); - IntervalBtreeCursor::aboutToDeleteBucket(thisLoc); deallocBucket( thisLoc, id ); } diff --git a/src/mongo/db/btreecursor.cpp b/src/mongo/db/btreecursor.cpp deleted file mode 100644 index 8e9ea001994..00000000000 --- a/src/mongo/db/btreecursor.cpp +++ /dev/null @@ -1,310 +0,0 @@ -// DEPRECATED -/** -* Copyright (C) 2008 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/>. -* -* As a special exception, the copyright holders give permission to link the -* code of portions of this program with the OpenSSL library under certain -* conditions as described in each individual source file and distribute -* linked combinations including the program with the OpenSSL library. You -* must comply with the GNU Affero General Public License in all respects for -* all of the code used other than as permitted herein. If you modify file(s) -* with this exception, you may extend this exception to your version of the -* file(s), but you are not obligated to do so. If you do not wish to do so, -* delete this exception statement from your version. If you delete this -* exception statement from all source files in the program, then also delete -* it in the license file. -*/ - -#include "mongo/db/btreecursor.h" - -#include "mongo/db/kill_current_op.h" -#include "mongo/db/pdfile.h" -#include "mongo/db/queryutil.h" -#include "mongo/db/index/index_access_method.h" -#include "mongo/db/index/btree_access_method.h" -#include "mongo/db/index/catalog_hack.h" - -namespace mongo { - - BtreeCursor* BtreeCursor::make( NamespaceDetails * nsd , int idxNo , const IndexDetails& indexDetails ) { - return new BtreeCursor( nsd , idxNo , indexDetails ); - } - - BtreeCursor* BtreeCursor::make( NamespaceDetails* namespaceDetails, - const IndexDetails& id, - const BSONObj& startKey, - const BSONObj& endKey, - bool endKeyInclusive, - int direction ) { - auto_ptr<BtreeCursor> c( make( namespaceDetails, namespaceDetails->idxNo( id ), id ) ); - c->init(startKey,endKey,endKeyInclusive,direction); - c->initWithoutIndependentFieldRanges(); - dassert( c->_dups.size() == 0 ); - return c.release(); - } - - BtreeCursor* BtreeCursor::make( NamespaceDetails* namespaceDetails, - const IndexDetails& id, - const shared_ptr<FieldRangeVector>& bounds, - int singleIntervalLimit, - int direction ) - { - auto_ptr<BtreeCursor> c( make( namespaceDetails, namespaceDetails->idxNo( id ), id ) ); - c->init(bounds,singleIntervalLimit,direction); - return c.release(); - } - - BtreeCursor::BtreeCursor( NamespaceDetails* nsd, int theIndexNo, const IndexDetails& id ) : - d( nsd ), - idxNo( theIndexNo ), - indexDetails( id ), - _boundsMustMatch( true ), - _nscanned() { - } - - void BtreeCursor::_finishConstructorInit() { - _multikey = d->isMultikey( idxNo ); - _order = indexDetails.keyPattern(); - } - - void BtreeCursor::init(const BSONObj& sk, const BSONObj& ek, bool endKeyInclusive, - int direction ) { - _finishConstructorInit(); - startKey = sk; - endKey = ek; - _endKeyInclusive = endKeyInclusive; - _direction = direction; - _independentFieldRanges = false; - dassert( d->idxNo((IndexDetails&) indexDetails) == idxNo ); - - _indexDescriptor.reset(CatalogHack::getDescriptor(d, idxNo)); - _indexAM.reset(CatalogHack::getBtreeIndex(_indexDescriptor.get())); - - IndexCursor *cursor; - _indexAM->newCursor(&cursor); - _indexCursor.reset(static_cast<BtreeIndexCursor*>(cursor)); - - CursorOptions opts; - opts.direction = _direction == 1 ? CursorOptions::INCREASING : CursorOptions::DECREASING; - cursor->setOptions(opts); - - _hitEnd = false; - } - - void BtreeCursor::init( const shared_ptr< FieldRangeVector > &bounds, int singleIntervalLimit, int direction ) { - _finishConstructorInit(); - _bounds = bounds; - verify( _bounds ); - _direction = direction; - _endKeyInclusive = true; - _boundsIterator.reset( new FieldRangeVectorIterator( *_bounds , singleIntervalLimit ) ); - _independentFieldRanges = true; - dassert( d->idxNo((IndexDetails&) indexDetails) == idxNo ); - startKey = _bounds->startKey(); - _boundsIterator->advance( startKey ); // handles initialization - _boundsIterator->prepDive(); - - _indexDescriptor.reset(CatalogHack::getDescriptor(d, idxNo)); - _indexAM.reset(CatalogHack::getBtreeIndex(_indexDescriptor.get())); - - IndexCursor *cursor; - _indexAM->newCursor(&cursor); - _indexCursor.reset(static_cast<BtreeIndexCursor*>(cursor)); - - CursorOptions opts; - opts.direction = _direction == 1 ? CursorOptions::INCREASING : CursorOptions::DECREASING; - _indexCursor->setOptions(opts); - - _indexCursor->seek(_boundsIterator->cmp(), _boundsIterator->inc()); - _hitEnd = false; - skipAndCheck(); - dassert( _dups.size() == 0 ); - } - - /** Properly destroy forward declared class members. */ - BtreeCursor::~BtreeCursor() {} - - DiskLoc BtreeCursor::currLoc() { - if (!ok()) { - return DiskLoc(); - } else { - return _indexCursor->getValue(); - } - } - - const DiskLoc BtreeCursor::getBucket() const { - return _indexCursor->getBucket(); - } - - int BtreeCursor::getKeyOfs() const { - return _indexCursor->getKeyOfs(); - } - - BSONObj BtreeCursor::currKey() const { - return _indexCursor->getKey(); - } - - /* Since the last noteLocation(), our key may have moved around, and that old cached - information may thus be stale and wrong (although often it is right). We check - that here; if we have moved, we have to search back for where we were at. - - i.e., after operations on the index, the BtreeCursor's cached location info may - be invalid. This function ensures validity, so you should call it before using - the cursor if other writers have used the database since the last noteLocation - call. - */ - void BtreeCursor::checkLocation() { - if (eof()) return; - _indexCursor->restorePosition(); - _multikey = d->isMultikey(idxNo); - } - - void BtreeCursor::initWithoutIndependentFieldRanges() { - _indexCursor->seek(startKey); - if (ok()) { _nscanned = 1; } - checkEnd(); - } - - void BtreeCursor::skipAndCheck() { - long long startNscanned = _nscanned; - if ( !skipOutOfRangeKeysAndCheckEnd() ) { - return; - } - do { - // If nscanned is increased by more than 20 before a matching key is found, abort - // skipping through the btree to find a matching key. This iteration cutoff - // prevents unbounded internal iteration within BtreeCursor::init() and - // BtreeCursor::advance() (the callers of skipAndCheck()). See SERVER-3448. - if ( _nscanned > startNscanned + 20 ) { - //skipUnusedKeys(); - // If iteration is aborted before a key matching _bounds is identified, the - // cursor may be left pointing at a key that is not within bounds - // (_bounds->matchesKey( currKey() ) may be false). Set _boundsMustMatch to - // false accordingly. - _boundsMustMatch = false; - return; - } - } while( skipOutOfRangeKeysAndCheckEnd() ); - } - - bool BtreeCursor::skipOutOfRangeKeysAndCheckEnd() { - if ( !ok() ) { - return false; - } - int ret = _boundsIterator->advance( currKey() ); - if ( ret == -2 ) { - _hitEnd = true; - return false; - } - else if ( ret == -1 ) { - ++_nscanned; - return false; - } - ++_nscanned; - advanceTo( currKey(), ret, _boundsIterator->after(), _boundsIterator->cmp(), _boundsIterator->inc() ); - return true; - } - - // Return a value in the set {-1, 0, 1} to represent the sign of parameter i. - int sgn( int i ) { - if ( i == 0 ) - return 0; - return i > 0 ? 1 : -1; - } - - // Check if the current key is beyond endKey. - void BtreeCursor::checkEnd() { - if (!ok()) { return; } - - if ( !endKey.isEmpty() ) { - int cmp = sgn( endKey.woCompare( currKey(), _order ) ); - if ( ( cmp != 0 && cmp != _direction ) || ( cmp == 0 && !_endKeyInclusive ) ) { - _hitEnd = true; - } - } - } - - bool BtreeCursor::ok() { - return !_indexCursor->isEOF() && !_hitEnd; - } - - void BtreeCursor::advanceTo( const BSONObj &keyBegin, int keyBeginLen, bool afterKey, const vector< const BSONElement * > &keyEnd, const vector< bool > &keyEndInclusive) { - _indexCursor->skip(keyBegin, keyBeginLen, afterKey, keyEnd, keyEndInclusive); - } - - bool BtreeCursor::advance() { - // Reset this flag at the start of a new iteration. - _boundsMustMatch = true; - - killCurrentOp.checkForInterrupt(); - if (!ok()) { - return false; - } - - _indexCursor->next(); - - if ( !_independentFieldRanges ) { - checkEnd(); - if ( ok() ) { - ++_nscanned; - } - } - else { - skipAndCheck(); - } - - return ok(); - } - - void BtreeCursor::noteLocation() { - if (!eof()) { _indexCursor->savePosition(); } - } - - string BtreeCursor::toString() { - string s = string("BtreeCursor ") + indexDetails.indexName(); - if ( _direction < 0 ) s += " reverse"; - if ( _bounds.get() && _bounds->size() > 1 ) s += " multi"; - return s; - } - - BSONObj BtreeCursor::prettyIndexBounds() const { - if ( !_independentFieldRanges ) { - return BSON( "start" << prettyKey( startKey ) << "end" << prettyKey( endKey ) ); - } - else { - return _bounds->obj(); - } - } - - bool BtreeCursor::currentMatches( MatchDetails* details ) { - // If currKey() might not match the specified _bounds, check whether or not it does. - if ( !_boundsMustMatch && _bounds && !_bounds->matchesKey( currKey() ) ) { - // If the key does not match _bounds, it does not match the query. - return false; - } - // Forward to the base class implementation, which may utilize a Matcher. - return Cursor::currentMatches( details ); - } - - /* -------------------------- tests below -------------------------------------- */ - /* ----------------------------------------------------------------------------- */ - - struct BtreeCursorUnitTest { - BtreeCursorUnitTest() { - verify( minDiskLoc.compare(maxDiskLoc) < 0 ); - } - } btut; - -} // namespace mongo diff --git a/src/mongo/db/btreecursor.h b/src/mongo/db/btreecursor.h deleted file mode 100644 index 783a00d4a2d..00000000000 --- a/src/mongo/db/btreecursor.h +++ /dev/null @@ -1,226 +0,0 @@ -// DEPRECATED -/** - * Copyright (C) 2008 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/>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the GNU Affero General Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#pragma once - -#include <set> -#include <vector> - -#include "mongo/db/cursor.h" -#include "mongo/db/diskloc.h" -#include "mongo/db/jsobj.h" -#include "mongo/db/namespace_details.h" -#include "mongo/db/index/btree_index_cursor.h" -#include "mongo/db/index/index_access_method.h" - -namespace mongo { - - class FieldRangeVector; - class FieldRangeVectorIterator; - - /** - * A Cursor class for btree iteration. - * - * A BtreeCursor iterates over all keys of a specified btree that are within the provided - * btree bounds, using the specified direction of traversal. - * - * Notes on specifying btree bounds: - * - * A BtreeCursor may be initialized with the 'startKey' and 'endKey' bounds of an interval of - * keys within a btree. A BtreeCursor may alternatively be initialized with a FieldRangeVector - * describing a list of intervals for every field of the btree index. - * - * Notes on the yielding implementation: - * - * When an operation using a BtreeCursor yields the database mutex that locks the btree data - * structure, the btree may be modified. When the operation regains the database mutex, the - * BtreeCursor can relocate its position in the modified btree and continue iteration from that - * point. - * - * Before the database mutex is yielded, a BtreeCursor records its position (noteLoc()). A - * recorded btree position consists of a btree bucket, bucket key offset, and unique btree key. - * - * After the database mutex is regained, a BtreeCursor relocates its position (checkLoc()). To - * relocate a unique btree key, a BtreeCursor first checks the btree key at its recorded btree - * bucket and bucket key offset. If the key at that location does not match the recorded btree - * key, and a preceding offset also fails to match, the recorded key (or the next existing key - * following it) is located in the btree using binary search. If the recorded btree bucket is - * invalidated, the initial recorded bucket check is not attempted (see SERVER-4575). - */ - class BtreeCursor : public Cursor { - public: - virtual ~BtreeCursor(); - - /** Makes an appropriate subclass depending on the index version. */ - - static BtreeCursor* make( NamespaceDetails* namespaceDetails, - const IndexDetails& id, - const BSONObj& startKey, - const BSONObj& endKey, - bool endKeyInclusive, - int direction ); - - static BtreeCursor* make( NamespaceDetails* namespaceDetails, - const IndexDetails& id, - const shared_ptr<FieldRangeVector>& bounds, - int singleIntervalLimit, - int direction ); - - virtual bool ok(); - virtual bool advance(); - virtual void noteLocation(); - virtual void checkLocation(); - virtual bool supportGetMore() { return true; } - virtual bool supportYields() { return true; } - - /** - * used for multikey index traversal to avoid sending back dups. see Matcher::matches(). - * if a multikey index traversal: - * if loc has already been sent, returns true. - * otherwise, marks loc as sent. - * @return false if the loc has not been seen - */ - virtual bool getsetdup(DiskLoc loc) { - if( _multikey ) { - pair<set<DiskLoc>::iterator, bool> p = _dups.insert(loc); - return !p.second; - } - return false; - } - - virtual bool modifiedKeys() const { return _multikey; } - virtual bool isMultiKey() const { return _multikey; } - - virtual BSONObj currKey() const; - virtual BSONObj indexKeyPattern() { return _order; } - - virtual DiskLoc currLoc(); - virtual DiskLoc refLoc() { return currLoc(); } - virtual Record* _current() { return currLoc().rec(); } - virtual BSONObj current() { return BSONObj::make(_current()); } - virtual string toString(); - - BSONObj prettyKey( const BSONObj& key ) const { - return key.replaceFieldNames( indexDetails.keyPattern() ).clientReadable(); - } - - virtual BSONObj prettyIndexBounds() const; - - virtual CoveredIndexMatcher* matcher() const { return _matcher.get(); } - - virtual bool currentMatches( MatchDetails* details = 0 ); - - virtual void setMatcher( shared_ptr<CoveredIndexMatcher> matcher ) { _matcher = matcher; } - - virtual const Projection::KeyOnly* keyFieldsOnly() const { return _keyFieldsOnly.get(); } - - virtual void setKeyFieldsOnly( const shared_ptr<Projection::KeyOnly>& keyFieldsOnly ) { - _keyFieldsOnly = keyFieldsOnly; - } - - virtual long long nscanned() { return _nscanned; } - - // XXX(hk): geo uses this for restoring state...for now. - virtual const DiskLoc getBucket() const; - // XXX(hk): geo uses this too. :( - virtual int getKeyOfs() const; - - private: - BtreeCursor( NamespaceDetails* nsd, int theIndexNo, const IndexDetails& idxDetails ); - - virtual void init( const BSONObj& startKey, - const BSONObj& endKey, - bool endKeyInclusive, - int direction ); - - virtual void init( const shared_ptr<FieldRangeVector>& bounds, - int singleIntervalLimit, - int direction ); - - bool skipOutOfRangeKeysAndCheckEnd(); - - /** - * Attempt to locate the next btree key matching _bounds. This may mean advancing to the - * next successive key in the btree, or skipping to a new position in the btree. If an - * internal iteration cutoff is reached before a matching key is found, then the search for - * a matching key will be aborted, leaving the cursor pointing at a key that is not within - * bounds. - */ - void skipAndCheck(); - - void checkEnd(); - - /** set initial bucket */ - void initWithoutIndependentFieldRanges(); - - /** if afterKey is true, we want the first key with values of the keyBegin fields greater than keyBegin */ - void advanceTo( const BSONObj& keyBegin, - int keyBeginLen, - bool afterKey, - const vector<const BSONElement*>& keyEnd, - const vector<bool>& keyEndInclusive ); - - void _finishConstructorInit(); - static BtreeCursor* make( NamespaceDetails* nsd, - int idxNo, - const IndexDetails& indexDetails ); - - // these are set in the construtor - NamespaceDetails* const d; - const int idxNo; - const IndexDetails& indexDetails; - - // These variables are for query-level index scanning. - // these are all set in init() - set<DiskLoc> _dups; - BSONObj startKey; - BSONObj endKey; - bool _endKeyInclusive; - bool _multikey; // this must be updated every getmore batch in case someone added a multikey - BSONObj _order; // this is the same as indexDetails.keyPattern() - int _direction; - shared_ptr<FieldRangeVector> _bounds; - auto_ptr<FieldRangeVectorIterator> _boundsIterator; - bool _boundsMustMatch; // If iteration is aborted before a key matching _bounds is - // identified, the cursor may be left pointing at a key that is not - // within bounds (_bounds->matchesKey( currKey() ) may be false). - // _boundsMustMatch will be set to false accordingly. - shared_ptr<CoveredIndexMatcher> _matcher; - shared_ptr<Projection::KeyOnly> _keyFieldsOnly; - bool _independentFieldRanges; - long long _nscanned; - - // These variables are for index traversal. - scoped_ptr<BtreeIndexCursor> _indexCursor; - scoped_ptr<IndexAccessMethod> _indexAM; - scoped_ptr<IndexDescriptor> _indexDescriptor; - bool _hitEnd; - }; - -} // namespace mongo diff --git a/src/mongo/db/btreeposition.cpp b/src/mongo/db/btreeposition.cpp deleted file mode 100644 index 290396f9bab..00000000000 --- a/src/mongo/db/btreeposition.cpp +++ /dev/null @@ -1,109 +0,0 @@ -/** - * 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/>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the GNU Affero General Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#include "mongo/db/btreeposition.h" - -#include "mongo/db/btree.h" -#include "mongo/db/storage/index_details.h" -#include "mongo/db/pdfile.h" - -namespace mongo { - - std::ostream& operator<<( std::ostream& stream, const BtreeKeyLocation& loc ) { - return stream << BSON( "bucket" << loc.bucket.toString() << - "index" << loc.pos ).jsonString(); - } - - LogicalBtreePosition::LogicalBtreePosition( const IndexDetails& indexDetails, - Ordering ordering, - const BtreeKeyLocation& initialLocation ) : - _indexDetails( &indexDetails ), - _ordering( ordering ), - _initialLocation( initialLocation ), - _initialLocationValid() { - fassert( 16494, _indexDetails->version() == 1 ); - } - - void LogicalBtreePosition::init() { - if ( _initialLocation.bucket.isNull() ) { - // Abort if the initial location is not a valid bucket. - return; - } - - // Store the key and record referenced at the supplied initial location. - BucketBasics<V1>::KeyNode keyNode = - _initialLocation.bucket.btree<V1>()->keyNode( _initialLocation.pos ); - _key = keyNode.key.toBson().getOwned(); - _record = keyNode.recordLoc; - _initialLocationValid = true; - } - - BtreeKeyLocation LogicalBtreePosition::currentLocation() const { - if ( _initialLocation.bucket.isNull() ) { - // Abort if the initial location is not a valid bucket. - return BtreeKeyLocation(); - } - - // If the initial location has not been invalidated ... - if ( _initialLocationValid ) { - - const BtreeBucket<V1>* bucket = _initialLocation.bucket.btree<V1>(); - if ( // ... and the bucket was not marked as invalid ... - bucket->getN() != bucket->INVALID_N_SENTINEL && - // ... and the initial location index is valid for the bucket ... - _initialLocation.pos < bucket->getN() ) { - - BucketBasics<V1>::KeyNode keyNode = bucket->keyNode( _initialLocation.pos ); - if ( // ... and the record location equals the initial record location ... - keyNode.recordLoc == _record && - // ... and the key equals the initial key ... - keyNode.key.toBson().binaryEqual( _key ) ) { - // ... then the initial location is the current location, so return it. - return _initialLocation; - } - } - } - - // Relocate the key and record location retrieved from _initialLocation. - BtreeKeyLocation ret; - bool found; - ret.bucket = _indexDetails->head.btree<V1>()->locate( *_indexDetails, - _indexDetails->head, - _key, - _ordering, - ret.pos, - found, - _record, - 1 // Forward direction means the next - // ordered key will be returned if - // the requested key is missing. - ); - return ret; - } - -} // namespace mongo diff --git a/src/mongo/db/btreeposition.h b/src/mongo/db/btreeposition.h deleted file mode 100644 index 21e947eaa38..00000000000 --- a/src/mongo/db/btreeposition.h +++ /dev/null @@ -1,117 +0,0 @@ -/** - * 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/>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the GNU Affero General Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#pragma once - -#include "mongo/db/diskloc.h" -#include "mongo/db/jsobj.h" -#include "mongo/platform/cstdint.h" - -namespace mongo { - - class IndexDetails; - - /** - * Physical location of a key within a btree. Comprised of the DiskLoc address of a btree - * bucket and the index of a key within that bucket. - */ - struct BtreeKeyLocation { - - BtreeKeyLocation() : - pos() { - } - - BtreeKeyLocation( DiskLoc initialBucket, int32_t initialPos ) : - bucket( initialBucket ), - pos( initialPos ) { - } - - bool operator==( const BtreeKeyLocation& other ) const { - return bucket == other.bucket && pos == other.pos; - } - - DiskLoc bucket; // Bucket within btree. - int32_t pos; // Index within bucket. - }; - - std::ostream& operator<<( std::ostream& stream, const BtreeKeyLocation& loc ); - - /** - * Logical btree position independent of the physical structure of a btree. This is used to - * track a position within a btree while the structure of the btree is changing. - * - * For example, a btree containing keys 'a', 'b', and 'c' might have all three keys in one - * bucket or alternatively 'b' within a left child of 'c'. The same LogicalBtreePosition can - * represent the position of 'b' in both cases and can retrieve the physical BtreeKeyLocation of - * 'b' in each case. If the btree is changed so that it lacks a 'b' key, the position will - * reference the lowest key greater than 'b'. This is desirable behavior when the logical btree - * position is used to implement a forward direction iterator. - * - * The class is seeded with a BtreeKeyLocation identifying a btree key. This initial physical - * location is cached in order to quickly check if the physical location corresponding to the - * logical position is unchanged and can be returned as is. - * - * NOTE Only supports V1 indexes. - */ - class LogicalBtreePosition { - public: - - /** - * Create a position with the @param 'indexDetails', @param 'ordering', and initial key - * location @param 'initialLocation' specified. - * @fasserts if 'indexDetails' is not a V1 index. - */ - LogicalBtreePosition( const IndexDetails& indexDetails, - Ordering ordering, - const BtreeKeyLocation& initialLocation ); - - /** Initialize the position by reading the key at the supplied initial location. */ - void init(); - - /** - * Invalidate the supplied initial location. This may be called when bucket containing the - * supplied location is deleted. - */ - void invalidateInitialLocation() { _initialLocationValid = false; } - - /** - * Retrieve the current physical location in the btree corresponding to this logical - * position. - */ - BtreeKeyLocation currentLocation() const; - - private: - const IndexDetails* _indexDetails; - Ordering _ordering; - BtreeKeyLocation _initialLocation; - bool _initialLocationValid; - BSONObj _key; - DiskLoc _record; - }; - -} // namespace mongo diff --git a/src/mongo/db/clientcursor.cpp b/src/mongo/db/clientcursor.cpp index 79af41825bf..e3d2c2cdfc9 100644 --- a/src/mongo/db/clientcursor.cpp +++ b/src/mongo/db/clientcursor.cpp @@ -50,7 +50,6 @@ #include "mongo/db/parsed_query.h" #include "mongo/db/repl/rs.h" #include "mongo/db/repl/write_concern.h" -#include "mongo/db/scanandorder.h" #include "mongo/platform/random.h" #include "mongo/util/processinfo.h" #include "mongo/util/timer.h" @@ -67,18 +66,7 @@ namespace mongo { const NamespaceDetails* nsd, const DiskLoc& dl ); // from s/d_logic.h - ClientCursor::ClientCursor(int qopts, const shared_ptr<Cursor>& c, const StringData& ns, - BSONObj query) - : _ns(ns.toString()), _query(query), _runner(NULL), _c(c), _yieldSometimesTracker(128, 10) { - - _queryOptions = qopts; - _doingDeletes = false; - init(); - } - - ClientCursor::ClientCursor(Runner* runner, int qopts, const BSONObj query) - : _yieldSometimesTracker(128, 10) { - + ClientCursor::ClientCursor(Runner* runner, int qopts, const BSONObj query) { _runner.reset(runner); _ns = runner->ns(); _query = query; @@ -88,8 +76,7 @@ namespace mongo { ClientCursor::ClientCursor(const string& ns) : _ns(ns), - _queryOptions(QueryOption_NoCursorTimeout), - _yieldSometimesTracker(128, 10) { + _queryOptions(QueryOption_NoCursorTimeout) { init(); } @@ -128,11 +115,6 @@ namespace mongo { { recursive_scoped_lock lock(ccmutex); - if (NULL != _c.get()) { - // Removes 'this' from bylocation map - setLastLoc_inlock( DiskLoc() ); - } - clientCursorsById.erase(_cursorid); // defensive: @@ -202,7 +184,7 @@ namespace mongo { // Note that a valid ClientCursor state is "no cursor no runner." This is because // the set of active cursor IDs in ClientCursor is used as representation of query // state. See sharding_block.h. TODO(greg,hk): Move this out. - if (NULL == cc->c() && NULL == cc->_runner.get()) { + if (NULL == cc->_runner.get()) { ++it; continue; } @@ -212,8 +194,6 @@ namespace mongo { // We will only delete CCs with runners that are not actively in use. The runners that // are actively in use are instead kill()-ed. if (NULL != cc->_runner.get()) { - verify(NULL == cc->c()); - if (isDB || cc->_runner->ns() == ns) { // If there is a pinValue >= 100, somebody is actively using the CC and we do // not delete it. Instead we notify the holder that we killed it. The holder @@ -228,22 +208,6 @@ namespace mongo { } } } - // Begin cursor-only DEPRECATED - else if (cc->c()->shouldDestroyOnNSDeletion()) { - verify(NULL == cc->_runner.get()); - - if (isDB) { - // already checked that db matched above - dassert( StringData(cc->_ns).startsWith( ns ) ); - shouldDelete = true; - } - else { - if ( ns == cc->_ns ) { - shouldDelete = true; - } - } - } - // End cursor-only DEPRECATED if (shouldDelete) { ClientCursor* toDelete = it->second; @@ -301,80 +265,6 @@ namespace mongo { if (NULL == cc->_runner.get()) { continue; } cc->_runner->invalidate(dl); } - - // Begin cursor-only. Only cursors that are in ccByLoc are processed here. - CCByLoc& bl = db->ccByLoc(); - CCByLoc::iterator j = bl.lower_bound(ByLocKey::min(dl)); - CCByLoc::iterator stop = bl.upper_bound(ByLocKey::max(dl)); - if ( j == stop ) - return; - - vector<ClientCursor*> toAdvance; - - while ( 1 ) { - toAdvance.push_back(j->second); - DEV verify( j->first.loc == dl ); - ++j; - if ( j == stop ) - break; - } - - if( toAdvance.size() >= 3000 ) { - log() << "perf warning MPW101: " << toAdvance.size() << " cursors for one diskloc " - << dl.toString() - << ' ' << toAdvance[1000]->_ns - << ' ' << toAdvance[2000]->_ns - << ' ' << toAdvance[1000]->_pinValue - << ' ' << toAdvance[2000]->_pinValue - << ' ' << toAdvance[1000]->_pos - << ' ' << toAdvance[2000]->_pos - << ' ' << toAdvance[1000]->_idleAgeMillis - << ' ' << toAdvance[2000]->_idleAgeMillis - << ' ' << toAdvance[1000]->_doingDeletes - << ' ' << toAdvance[2000]->_doingDeletes - << endl; - //wassert( toAdvance.size() < 5000 ); - } - - for ( vector<ClientCursor*>::iterator i = toAdvance.begin(); i != toAdvance.end(); ++i ) { - ClientCursor* cc = *i; - wassert(cc->_db == db); - - if ( cc->_doingDeletes ) continue; - - Cursor *c = cc->_c.get(); - if ( c->capped() ) { - /* note we cannot advance here. if this condition occurs, writes to the oplog - have "caught" the reader. skipping ahead, the reader would miss postentially - important data. - */ - delete cc; - continue; - } - - c->recoverFromYield(); - DiskLoc tmp1 = c->refLoc(); - if ( tmp1 != dl ) { - // This might indicate a failure to call ClientCursor::prepareToYield() but it can - // also happen during correct operation, see SERVER-2009. - problem() << "warning: cursor loc " << tmp1 << " does not match byLoc position " << dl << " !" << endl; - } - else { - c->advance(); - } - while (!c->eof() && c->refLoc() == dl) { - /* We don't delete at EOF because we want to return "no more results" rather than "no such cursor". - * The loop is to handle MultiKey indexes where the deleted record is pointed to by multiple adjacent keys. - * In that case we need to advance until we get to the next distinct record or EOF. - * SERVER-4154 - * SERVER-5198 - * But see SERVER-5725. - */ - c->advance(); - } - cc->updateLocation(); - } - // End cursor-only } void ClientCursor::registerRunner(Runner* runner) { @@ -519,20 +409,6 @@ namespace mongo { } } - // DEPRECATED only used by Cursor. - void ClientCursor::storeOpForSlave( DiskLoc last ) { - verify(NULL == _runner.get()); - if ( ! ( _queryOptions & QueryOption_OplogReplay )) - return; - - if ( last.isNull() ) - return; - - BSONElement e = last.obj()["ts"]; - if ( e.type() == Date || e.type() == Timestamp ) - _slaveReadTill = e._opTime(); - } - void ClientCursor::updateSlaveLocation( CurOp& curop ) { if ( _slaveReadTill.isNull() ) return; @@ -715,139 +591,6 @@ namespace mongo { return found; } - // - // Yielding that is DEPRECATED. Will be removed when we use runners and they yield internally. - // - - Record* ClientCursor::_recordForYield( ClientCursor::RecordNeeds need ) { - if ( ! ok() ) - return 0; - - if ( need == DontNeed ) { - return 0; - } - else if ( need == MaybeCovered ) { - // TODO - return 0; - } - else if ( need == WillNeed ) { - // no-op - } - else { - warning() << "don't understand RecordNeeds: " << (int)need << endl; - return 0; - } - - DiskLoc l = currLoc(); - if ( l.isNull() ) - return 0; - - Record * rec = l.rec(); - if ( rec->likelyInPhysicalMemory() ) - return 0; - - return rec; - } - - void ClientCursor::updateLocation() { - verify( _cursorid ); - _idleAgeMillis = 0; - // Cursor-specific - _c->prepareToYield(); - DiskLoc cl = _c->refLoc(); - if ( lastLoc() == cl ) { - //log() << "info: lastloc==curloc " << ns << endl; - } - else { - recursive_scoped_lock lock(ccmutex); - setLastLoc_inlock(cl); - } - } - - void ClientCursor::setLastLoc_inlock(DiskLoc L) { - verify(NULL == _runner.get()); - verify( _pos != -2 ); // defensive - see ~ClientCursor - - if (L == _lastLoc) { return; } - CCByLoc& bl = _db->ccByLoc(); - - if (!_lastLoc.isNull()) { - bl.erase(ByLocKey(_lastLoc, _cursorid)); - } - - if (!L.isNull()) { - bl[ByLocKey(L,_cursorid)] = this; - } - - _lastLoc = L; - } - - bool ClientCursor::yield( int micros , Record * recordToLoad ) { - // some cursors (geo@oct2011) don't support yielding - if (!_c->supportYields()) { return true; } - - YieldData data; - prepareToYield( data ); - staticYield( micros , _ns , recordToLoad ); - return ClientCursor::recoverFromYield( data ); - } - - bool ClientCursor::yieldSometimes(RecordNeeds need, bool* yielded) { - if (yielded) { *yielded = false; } - - if ( ! _yieldSometimesTracker.intervalHasElapsed() ) { - Record* rec = _recordForYield( need ); - if ( rec ) { - // yield for page fault - if ( yielded ) { - *yielded = true; - } - bool res = yield( suggestYieldMicros() , rec ); - if ( res ) - _yieldSometimesTracker.resetLastTime(); - return res; - } - return true; - } - - int micros = suggestYieldMicros(); - if ( micros > 0 ) { - if ( yielded ) { - *yielded = true; - } - bool res = yield( micros , _recordForYield( need ) ); - if ( res ) - _yieldSometimesTracker.resetLastTime(); - return res; - } - return true; - } - - bool ClientCursor::prepareToYield( YieldData &data ) { - if (!_c->supportYields()) { return false; } - - // need to store in case 'this' gets deleted - data._id = _cursorid; - data._doingDeletes = _doingDeletes; - _doingDeletes = false; - - updateLocation(); - - return true; - } - - bool ClientCursor::recoverFromYield( const YieldData &data ) { - ClientCursor *cc = ClientCursor::find( data._id , false ); - if ( cc == 0 ) { - // id was deleted - return false; - } - - cc->_doingDeletes = data._doingDeletes; - cc->_c->recoverFromYield(); - return true; - } - int ClientCursor::suggestYieldMicros() { int writers = 0; int readers = 0; @@ -909,41 +652,6 @@ namespace mongo { } // - // Holder methods DEPRECATED - // - ClientCursorHolder::ClientCursorHolder(ClientCursor *c) : _c(0), _id(INVALID_CURSOR_ID) { - reset(c); - } - - ClientCursorHolder::~ClientCursorHolder() { - DESTRUCTOR_GUARD(reset();); - } - - void ClientCursorHolder::reset(ClientCursor *c) { - if ( c == _c ) - return; - if ( _c ) { - // be careful in case cursor was deleted by someone else - ClientCursor::erase( _id ); - } - if ( c ) { - _c = c; - _id = c->_cursorid; - } - else { - _c = 0; - _id = INVALID_CURSOR_ID; - } - } - ClientCursor* ClientCursorHolder::get() { return _c; } - ClientCursor * ClientCursorHolder::operator-> () { return _c; } - const ClientCursor * ClientCursorHolder::operator-> () const { return _c; } - void ClientCursorHolder::release() { - _c = 0; - _id = INVALID_CURSOR_ID; - } - - // // ClientCursorMonitor // @@ -1062,37 +770,4 @@ namespace mongo { } } cursorServerStats; - // - // YieldLock - // - - ClientCursorYieldLock::ClientCursorYieldLock( ptr<ClientCursor> cc ) - : _canYield(cc->_c->supportYields()) { - - if ( _canYield ) { - cc->prepareToYield( _data ); - _unlock.reset(new dbtempreleasecond()); - } - - } - - ClientCursorYieldLock::~ClientCursorYieldLock() { - if ( _unlock ) { - warning() << "ClientCursorYieldLock not closed properly" << endl; - relock(); - } - } - - bool ClientCursorYieldLock::stillOk() { - if ( ! _canYield ) - return true; - relock(); - return ClientCursor::recoverFromYield( _data ); - } - - void ClientCursorYieldLock::relock() { - _unlock.reset(); - } - - } // namespace mongo diff --git a/src/mongo/db/clientcursor.h b/src/mongo/db/clientcursor.h index 7d108c521e7..41414978d12 100644 --- a/src/mongo/db/clientcursor.h +++ b/src/mongo/db/clientcursor.h @@ -33,7 +33,6 @@ #include <boost/thread/recursive_mutex.hpp> #include "mongo/db/cc_by_loc.h" -#include "mongo/db/cursor.h" #include "mongo/db/diskloc.h" #include "mongo/db/dbhelpers.h" #include "mongo/db/jsobj.h" @@ -58,9 +57,6 @@ namespace mongo { */ class ClientCursor : private boost::noncopyable { public: - ClientCursor(int qopts, const shared_ptr<Cursor>& c, const StringData& ns, - BSONObj query = BSONObj()); - ClientCursor(Runner* runner, int qopts = 0, const BSONObj query = BSONObj()); ClientCursor(const string& ns); @@ -248,36 +244,6 @@ namespace mongo { */ bool isAggCursor; - // - // Cursor-only DEPRECATED methods. - // - - void storeOpForSlave( DiskLoc last ); - - // Only used by ops/query.cpp, which will stop using them when queries are answered only by - // a runner. - const BSONObj& query() const { return _query; } - shared_ptr<ParsedQuery> pq; - // This one is used also by pipeline/document_source_cursor.cpp - shared_ptr<Projection> fields; // which fields query wants returned - - DiskLoc lastLoc() const { return _lastLoc; } - Cursor* c() const { return _c.get(); } - bool ok() { return _c->ok(); } - bool advance() { return _c->advance(); } - BSONObj current() { return _c->current(); } - DiskLoc currLoc() { return _c->currLoc(); } - BSONObj currKey() const { return _c->currKey(); } - - bool currentIsDup() { return _c->getsetdup( _c->currLoc() ); } - bool currentMatches() { - if ( ! _c->matcher() ) - return true; - return _c->matcher()->matchesCurrent( _c.get() ); - } - - void setDoingDeletes( bool doingDeletes ) {_doingDeletes = doingDeletes; } - private: friend class ClientCursorHolder; friend class ClientCursorPin; @@ -376,28 +342,6 @@ namespace mongo { // The new world: a runner. scoped_ptr<Runner> _runner; - - // - // Cursor-only private data and methods. DEPRECATED. - // - - // The old world: a cursor. DEPRECATED. - const shared_ptr<Cursor> _c; - - /** - * call when cursor's location changes so that we can update the cursorsbylocation map. if - * you are locked and internally iterating, only need to call when you are ready to - * "unlock". - */ - void updateLocation(); - void setLastLoc_inlock(DiskLoc); - Record* _recordForYield( RecordNeeds need ); - - DiskLoc _lastLoc; // use getter and setter not this (important) - bool _doingDeletes; // when true we are the delete and aboutToDelete shouldn't manipulate us - - // TODO: This will be moved into the runner. - ElapsedTracker _yieldSometimesTracker; }; /** @@ -420,56 +364,11 @@ namespace mongo { CursorId _cursorid; }; - /** Assures safe and reliable cleanup of a ClientCursor. */ - class ClientCursorHolder : boost::noncopyable { - public: - ClientCursorHolder( ClientCursor *c = 0 ); - ~ClientCursorHolder(); - void reset( ClientCursor *c = 0 ); - ClientCursor* get(); - operator bool() { return _c; } - ClientCursor * operator-> (); - const ClientCursor * operator-> () const; - /** Release ownership of the ClientCursor. */ - void release(); - private: - ClientCursor *_c; - CursorId _id; - }; - /** thread for timing out old cursors */ class ClientCursorMonitor : public BackgroundJob { public: string name() const { return "ClientCursorMonitor"; } void run(); }; - - struct ClientCursorYieldLock : boost::noncopyable { - explicit ClientCursorYieldLock( ptr<ClientCursor> cc ); - ~ClientCursorYieldLock(); - - /** - * @return if the cursor is still ok - * if it is, we also relock - */ - bool stillOk(); - void relock(); - - private: - const bool _canYield; - ClientCursor::YieldData _data; - scoped_ptr<dbtempreleasecond> _unlock; - }; } // namespace mongo - -// ClientCursor should only be used with auto_ptr because it needs to be -// release()ed after a yield if stillOk() returns false and these pointer types -// do not support releasing. This will prevent them from being used accidentally -// Instead of auto_ptr<>, which still requires some degree of manual management -// of this, consider using ClientCursor::Holder which handles ClientCursor's -// unusual self-deletion mechanics. -namespace boost{ - template<> class scoped_ptr<mongo::ClientCursor> {}; - template<> class shared_ptr<mongo::ClientCursor> {}; -} diff --git a/src/mongo/db/commands/mr.cpp b/src/mongo/db/commands/mr.cpp index e24dc93515f..bd0328ffd51 100644 --- a/src/mongo/db/commands/mr.cpp +++ b/src/mongo/db/commands/mr.cpp @@ -41,7 +41,6 @@ #include "mongo/db/instance.h" #include "mongo/db/kill_current_op.h" #include "mongo/db/matcher.h" -#include "mongo/db/query_optimizer.h" #include "mongo/db/query/get_runner.h" #include "mongo/db/query/query_planner.h" #include "mongo/db/repl/is_master.h" diff --git a/src/mongo/db/commands/test_commands.cpp b/src/mongo/db/commands/test_commands.cpp index 595dcf63fb0..3b3924561bd 100644 --- a/src/mongo/db/commands/test_commands.cpp +++ b/src/mongo/db/commands/test_commands.cpp @@ -35,6 +35,7 @@ #include "mongo/db/index_builder.h" #include "mongo/db/kill_current_op.h" #include "mongo/db/pdfile.h" +#include "mongo/db/query/internal_plans.h" #include "mongo/db/structure/collection.h" namespace mongo { @@ -142,12 +143,15 @@ namespace mongo { // inclusive range? bool inc = cmdObj.getBoolField( "inc" ); NamespaceDetails *nsd = nsdetails( ns ); - ReverseCappedCursor c( nsd ); - massert( 13417, "captrunc collection not found or empty", c.ok() ); - for( int i = 0; i < n; ++i ) { - massert( 13418, "captrunc invalid n", c.advance() ); + massert( 13417, "captrunc collection not found or empty", nsd); + + boost::scoped_ptr<Runner> runner(InternalPlanner::collectionScan(ns, InternalPlanner::BACKWARD)); + DiskLoc end; + // We remove 'n' elements so the start is one past that + for( int i = 0; i < n + 1; ++i ) { + Runner::RunnerState state = runner->getNext(NULL, &end); + massert( 13418, "captrunc invalid n", Runner::RUNNER_ADVANCED == state); } - DiskLoc end = c.currLoc(); nsd->cappedTruncateAfter( ns.c_str(), end, inc ); return true; } diff --git a/src/mongo/db/curop.cpp b/src/mongo/db/curop.cpp index 01e33330111..b7581d3c33a 100644 --- a/src/mongo/db/curop.cpp +++ b/src/mongo/db/curop.cpp @@ -33,6 +33,7 @@ #include "mongo/db/curop.h" #include "mongo/db/database.h" #include "mongo/db/kill_current_op.h" +#include "mongo/db/matcher.h" #include "mongo/util/fail_point_service.h" namespace mongo { diff --git a/src/mongo/db/cursor.cpp b/src/mongo/db/cursor.cpp deleted file mode 100644 index 5f4131924a8..00000000000 --- a/src/mongo/db/cursor.cpp +++ /dev/null @@ -1,190 +0,0 @@ -/** - * Copyright (C) 2008 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/>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the GNU Affero General Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#include "mongo/pch.h" - -#include "mongo/db/curop-inl.h" -#include "mongo/db/kill_current_op.h" -#include "mongo/db/pdfile.h" - -namespace mongo { - - bool BasicCursor::advance() { - killCurrentOp.checkForInterrupt(); - if ( eof() ) { - if ( tailable_ && !last.isNull() ) { - curr = s->next( last ); - } - else { - return false; - } - } - else { - last = curr; - curr = s->next( curr ); - } - incNscanned(); - return ok(); - } - - /* these will be used outside of mutexes - really functors - thus the const */ - class Forward : public AdvanceStrategy { - virtual DiskLoc next( const DiskLoc &prev ) const { - return prev.rec()->getNext( prev ); - } - } _forward; - - class Reverse : public AdvanceStrategy { - virtual DiskLoc next( const DiskLoc &prev ) const { - return prev.rec()->getPrev( prev ); - } - } _reverse; - - const AdvanceStrategy *forward() { - return &_forward; - } - const AdvanceStrategy *reverse() { - return &_reverse; - } - - DiskLoc nextLoop( NamespaceDetails *nsd, const DiskLoc &prev ) { - verify( nsd->capLooped() ); - DiskLoc next = forward()->next( prev ); - if ( !next.isNull() ) - return next; - return nsd->firstRecord(); - } - - DiskLoc prevLoop( NamespaceDetails *nsd, const DiskLoc &curr ) { - verify( nsd->capLooped() ); - DiskLoc prev = reverse()->next( curr ); - if ( !prev.isNull() ) - return prev; - return nsd->lastRecord(); - } - - ForwardCappedCursor* ForwardCappedCursor::make( NamespaceDetails* nsd, - const DiskLoc& startLoc ) { - auto_ptr<ForwardCappedCursor> ret( new ForwardCappedCursor( nsd ) ); - ret->init( startLoc ); - return ret.release(); - } - - ForwardCappedCursor::ForwardCappedCursor( NamespaceDetails* _nsd ) : - nsd( _nsd ) { - } - - void ForwardCappedCursor::init( const DiskLoc& startLoc ) { - if ( !nsd ) - return; - DiskLoc start = startLoc; - if ( start.isNull() ) { - if ( !nsd->capLooped() ) - start = nsd->firstRecord(); - else { - start = nsd->capExtent().ext()->firstRecord; - if ( !start.isNull() && start == nsd->capFirstNewRecord() ) { - start = nsd->capExtent().ext()->lastRecord; - start = nextLoop( nsd, start ); - } - } - } - curr = start; - s = this; - incNscanned(); - } - - DiskLoc ForwardCappedCursor::next( const DiskLoc &prev ) const { - verify( nsd ); - if ( !nsd->capLooped() ) - return forward()->next( prev ); - - DiskLoc i = prev; - // Last record - if ( i == nsd->capExtent().ext()->lastRecord ) - return DiskLoc(); - i = nextLoop( nsd, i ); - // If we become capFirstNewRecord from same extent, advance to next extent. - if ( i == nsd->capFirstNewRecord() && - i != nsd->capExtent().ext()->firstRecord ) - i = nextLoop( nsd, nsd->capExtent().ext()->lastRecord ); - // If we have just gotten to beginning of capExtent, skip to capFirstNewRecord - if ( i == nsd->capExtent().ext()->firstRecord ) - i = nsd->capFirstNewRecord(); - return i; - } - - ReverseCappedCursor::ReverseCappedCursor( NamespaceDetails *_nsd, const DiskLoc &startLoc ) : - nsd( _nsd ) { - if ( !nsd ) - return; - DiskLoc start = startLoc; - if ( start.isNull() ) { - if ( !nsd->capLooped() ) { - start = nsd->lastRecord(); - } - else { - start = nsd->capExtent().ext()->lastRecord; - } - } - curr = start; - s = this; - incNscanned(); - } - - DiskLoc ReverseCappedCursor::next( const DiskLoc &prev ) const { - verify( nsd ); - if ( !nsd->capLooped() ) - return reverse()->next( prev ); - - DiskLoc i = prev; - // Last record - if ( nsd->capFirstNewRecord() == nsd->capExtent().ext()->firstRecord ) { - if ( i == nextLoop( nsd, nsd->capExtent().ext()->lastRecord ) ) { - return DiskLoc(); - } - } - else { - if ( i == nsd->capExtent().ext()->firstRecord ) { - return DiskLoc(); - } - } - // If we are capFirstNewRecord, advance to prev extent, otherwise just get prev. - if ( i == nsd->capFirstNewRecord() ) - i = prevLoop( nsd, nsd->capExtent().ext()->firstRecord ); - else - i = prevLoop( nsd, i ); - // If we just became last in cap extent, advance past capFirstNewRecord - // (We know capExtent.ext()->firstRecord != capFirstNewRecord, since would - // have returned DiskLoc() earlier otherwise.) - if ( i == nsd->capExtent().ext()->lastRecord ) - i = reverse()->next( nsd->capFirstNewRecord() ); - - return i; - } -} // namespace mongo diff --git a/src/mongo/db/cursor.h b/src/mongo/db/cursor.h deleted file mode 100644 index e3cd0ede83c..00000000000 --- a/src/mongo/db/cursor.h +++ /dev/null @@ -1,336 +0,0 @@ -/** -* Copyright (C) 2008 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/>. -* -* As a special exception, the copyright holders give permission to link the -* code of portions of this program with the OpenSSL library under certain -* conditions as described in each individual source file and distribute -* linked combinations including the program with the OpenSSL library. You -* must comply with the GNU Affero General Public License in all respects for -* all of the code used other than as permitted herein. If you modify file(s) -* with this exception, you may extend this exception to your version of the -* file(s), but you are not obligated to do so. If you do not wish to do so, -* delete this exception statement from your version. If you delete this -* exception statement from all source files in the program, then also delete -* it in the license file. -*/ - -#pragma once - -#include "mongo/pch.h" - -#include "mongo/db/diskloc.h" -#include "mongo/db/jsobj.h" -#include "mongo/db/matcher.h" -#include "mongo/db/matcher_covered.h" -#include "mongo/db/projection.h" - -namespace mongo { - - class NamespaceDetails; - class Record; - class CoveredIndexMatcher; - - /** - * Query cursors, base class. This is for our internal cursors. "ClientCursor" is a separate - * concept and is for the user's cursor. - * - * WARNING concurrency: the vfunctions below are called back from within a - * ClientCursor::ccmutex. Don't cause a deadlock, you've been warned. - * - * Two general techniques may be used to ensure a Cursor is in a consistent state after a write. - * - The Cursor may be advanced before the document at its current position is deleted. - * - The Cursor may record its position and then relocate this position. - * A particular Cursor may potentially utilize only one of the above techniques, but a client - * that is Cursor subclass agnostic must implement a pattern handling both techniques. - * - * When the document at a Cursor's current position is deleted (or moved to a new location) the - * following pattern is used: - * DiskLoc toDelete = cursor->currLoc(); - * while( cursor->ok() && cursor->currLoc() == toDelete ) { - * cursor->advance(); - * } - * cursor->prepareToTouchEarlierIterate(); - * delete( toDelete ); - * cursor->recoverFromTouchingEarlierIterate(); - * - * When a cursor yields, the following pattern is used: - * cursor->prepareToYield(); - * while( Op theOp = nextOp() ) { - * if ( theOp.type() == INSERT || theOp.type() == UPDATE_IN_PLACE ) { - * theOp.run(); - * } - * else if ( theOp.type() == DELETE ) { - * if ( cursor->refLoc() == theOp.toDelete() ) { - * cursor->recoverFromYield(); - * while ( cursor->ok() && cursor->refLoc() == theOp.toDelete() ) { - * cursor->advance(); - * } - * cursor->prepareToYield(); - * } - * theOp.run(); - * } - * } - * cursor->recoverFromYield(); - * - * The break before a getMore request is typically treated as a yield, but if a Cursor supports - * getMore but not yield the following pattern is currently used: - * cursor->noteLocation(); - * runOtherOps(); - * cursor->checkLocation(); - * - * But see SERVER-5725. - * - * A Cursor may rely on additional callbacks not listed above to relocate its position after a - * write. - */ - class Cursor : boost::noncopyable { - public: - virtual ~Cursor() {} - virtual bool ok() = 0; - bool eof() { return !ok(); } - virtual Record* _current() = 0; - virtual BSONObj current() = 0; - virtual DiskLoc currLoc() = 0; - virtual bool advance() = 0; /*true=ok*/ - virtual BSONObj currKey() const { return BSONObj(); } - - // DiskLoc the cursor requires for continued operation. Before this - // DiskLoc is deleted, the cursor must be incremented or destroyed. - virtual DiskLoc refLoc() = 0; - - /* Implement these if you want the cursor to be "tailable" */ - - /* Request that the cursor starts tailing after advancing past last record. */ - /* The implementation may or may not honor this request. */ - virtual void setTailable() {} - /* indicates if tailing is enabled. */ - virtual bool tailable() { - return false; - } - - /* optional to implement. if implemented, means 'this' is a prototype */ - virtual Cursor* clone() { - return 0; - } - - virtual BSONObj indexKeyPattern() { - return BSONObj(); - } - - virtual bool supportGetMore() = 0; - - /* called after every query block is iterated -- i.e. between getMore() blocks - so you can note where we are, if necessary. - */ - virtual void noteLocation() { } - - /* called before query getmore block is iterated */ - virtual void checkLocation() { } - - /** - * Called before a document pointed at by an earlier iterate of this cursor is to be - * modified. It is ok if the current iterate also points to the document to be modified. - */ - virtual void prepareToTouchEarlierIterate() { noteLocation(); } - - /** Recover from a previous call to prepareToTouchEarlierIterate(). */ - virtual void recoverFromTouchingEarlierIterate() { checkLocation(); } - - virtual bool supportYields() = 0; - - /** Called before a ClientCursor yield. */ - virtual void prepareToYield() { noteLocation(); } - - /** Called after a ClientCursor yield. Recovers from a previous call to prepareToYield(). */ - virtual void recoverFromYield() { checkLocation(); } - - virtual string toString() { return "abstract?"; } - - /* used for multikey index traversal to avoid sending back dups. see Matcher::matches(). - if a multikey index traversal: - if loc has already been sent, returns true. - otherwise, marks loc as sent. - */ - virtual bool getsetdup(DiskLoc loc) = 0; - - virtual bool isMultiKey() const = 0; - - virtual bool autoDedup() const { return true; } - - /** - * return true if the keys in the index have been modified from the main doc - * if you have { a : 1 , b : [ 1 , 2 ] } - * an index on { a : 1 } would not be modified - * an index on { b : 1 } would be since the values of the array are put in the index - * not the array - */ - virtual bool modifiedKeys() const = 0; - - virtual BSONObj prettyIndexBounds() const { return BSONArray(); } - - /** - * If true, this is an unindexed cursor over a capped collection. Currently such cursors must - * not own a delegate ClientCursor, due to the implementation of ClientCursor::aboutToDelete(). - SERVER-4563 - */ - virtual bool capped() const { return false; } - - virtual long long nscanned() = 0; - - // The implementation may return different matchers depending on the - // position of the cursor. If matcher() is nonzero at the start, - // matcher() should be checked each time advance() is called. - // Implementations which generate their own matcher should return this - // to avoid a matcher being set manually. - // Note that the return values differ subtly here - - // Used when we want fast matcher lookup - virtual CoveredIndexMatcher *matcher() const { return 0; } - - virtual bool currentMatches( MatchDetails *details = 0 ) { - return !matcher() || matcher()->matchesCurrent( this, details ); - } - - // A convenience function for setting the value of matcher() manually - // so it may be accessed later. Implementations which must generate - // their own matcher() should assert here. - virtual void setMatcher( shared_ptr< CoveredIndexMatcher > matcher ) { - massert( 13285, "manual matcher config not allowed", false ); - } - - /** @return the covered index projector for the current iterate, if any. */ - virtual const Projection::KeyOnly *keyFieldsOnly() const { return 0; } - - /** - * Manually set the value of keyFieldsOnly() so it may be accessed later. Implementations - * that generate their own keyFieldsOnly() must assert. - */ - virtual void setKeyFieldsOnly( const shared_ptr<Projection::KeyOnly> &keyFieldsOnly ) { - massert( 16159, "manual keyFieldsOnly config not allowed", false ); - } - - virtual void explainDetails( BSONObjBuilder& b ) { return; } - - /// Should getmore handle locking for you - virtual bool requiresLock() { return true; } - - /// Should this cursor be destroyed when it's namespace is deleted - virtual bool shouldDestroyOnNSDeletion() { return true; } - }; - - // strategy object implementing direction of traversal. - class AdvanceStrategy { - public: - virtual ~AdvanceStrategy() { } - virtual DiskLoc next( const DiskLoc &prev ) const = 0; - }; - - const AdvanceStrategy *forward(); - const AdvanceStrategy *reverse(); - - /** - * table-scan style cursor - * - * A BasicCursor relies on advance() to ensure it is in a consistent state after a write. If - * the document at a BasicCursor's current position will be deleted or relocated, the cursor - * must first be advanced. The same is true of BasicCursor subclasses. - */ - class BasicCursor : public Cursor { - public: - BasicCursor(DiskLoc dl, const AdvanceStrategy *_s = forward()) : curr(dl), s( _s ), _nscanned() { - incNscanned(); - init(); - } - BasicCursor(const AdvanceStrategy *_s = forward()) : s( _s ), _nscanned() { - init(); - } - bool ok() { return !curr.isNull(); } - Record* _current() { - verify( ok() ); - return curr.rec(); - } - BSONObj current() { - Record *r = _current(); - return BSONObj::make(r); - } - virtual DiskLoc currLoc() { return curr; } - virtual DiskLoc refLoc() { return curr.isNull() ? last : curr; } - bool advance(); - virtual string toString() { return "BasicCursor"; } - virtual void setTailable() { - if ( !curr.isNull() || !last.isNull() ) - tailable_ = true; - } - virtual bool tailable() { return tailable_; } - virtual bool getsetdup(DiskLoc loc) { return false; } - virtual bool isMultiKey() const { return false; } - virtual bool modifiedKeys() const { return false; } - virtual bool supportGetMore() { return true; } - virtual bool supportYields() { return true; } - virtual CoveredIndexMatcher *matcher() const { return _matcher.get(); } - virtual void setMatcher( shared_ptr< CoveredIndexMatcher > matcher ) { _matcher = matcher; } - virtual const Projection::KeyOnly *keyFieldsOnly() const { return _keyFieldsOnly.get(); } - virtual void setKeyFieldsOnly( const shared_ptr<Projection::KeyOnly> &keyFieldsOnly ) { - _keyFieldsOnly = keyFieldsOnly; - } - virtual long long nscanned() { return _nscanned; } - - protected: - DiskLoc curr, last; - const AdvanceStrategy *s; - void incNscanned() { if ( !curr.isNull() ) { ++_nscanned; } } - private: - bool tailable_; - shared_ptr< CoveredIndexMatcher > _matcher; - shared_ptr<Projection::KeyOnly> _keyFieldsOnly; - long long _nscanned; - void init() { tailable_ = false; } - }; - - /* used for order { $natural: -1 } */ - class ReverseCursor : public BasicCursor { - public: - ReverseCursor(DiskLoc dl) : BasicCursor( dl, reverse() ) { } - ReverseCursor() : BasicCursor( reverse() ) { } - virtual string toString() { return "ReverseCursor"; } - }; - - class ForwardCappedCursor : public BasicCursor, public AdvanceStrategy { - public: - static ForwardCappedCursor* make( NamespaceDetails* nsd = 0, - const DiskLoc& startLoc = DiskLoc() ); - virtual string toString() { - return "ForwardCappedCursor"; - } - virtual DiskLoc next( const DiskLoc &prev ) const; - virtual bool capped() const { return true; } - private: - ForwardCappedCursor( NamespaceDetails* nsd ); - void init( const DiskLoc& startLoc ); - NamespaceDetails *nsd; - }; - - class ReverseCappedCursor : public BasicCursor, public AdvanceStrategy { - public: - ReverseCappedCursor( NamespaceDetails *nsd = 0, const DiskLoc &startLoc = DiskLoc() ); - virtual string toString() { - return "ReverseCappedCursor"; - } - virtual DiskLoc next( const DiskLoc &prev ) const; - virtual bool capped() const { return true; } - private: - NamespaceDetails *nsd; - }; - -} // namespace mongo diff --git a/src/mongo/db/dbcommands.cpp b/src/mongo/db/dbcommands.cpp index 269144cbbdb..ed82e5bdad6 100644 --- a/src/mongo/db/dbcommands.cpp +++ b/src/mongo/db/dbcommands.cpp @@ -60,7 +60,6 @@ #include "mongo/db/query/get_runner.h" #include "mongo/db/query/internal_plans.h" #include "mongo/db/query/query_planner.h" -#include "mongo/db/query_optimizer.h" #include "mongo/db/repl/is_master.h" #include "mongo/db/repl/oplog.h" #include "mongo/db/write_concern.h" diff --git a/src/mongo/db/dbhelpers.cpp b/src/mongo/db/dbhelpers.cpp index d015ff4d319..d6750f53d85 100644 --- a/src/mongo/db/dbhelpers.cpp +++ b/src/mongo/db/dbhelpers.cpp @@ -45,7 +45,6 @@ #include "mongo/db/ops/update_request.h" #include "mongo/db/ops/update_result.h" #include "mongo/db/pagefault.h" -#include "mongo/db/query_optimizer.h" #include "mongo/db/query_runner.h" #include "mongo/db/query/get_runner.h" #include "mongo/db/query/internal_plans.h" diff --git a/src/mongo/db/dbhelpers.h b/src/mongo/db/dbhelpers.h index 19226cc8aec..58bb1881796 100644 --- a/src/mongo/db/dbhelpers.h +++ b/src/mongo/db/dbhelpers.h @@ -41,7 +41,6 @@ namespace mongo { extern const BSONObj reverseNaturalObj; // {"$natural": -1 } class Cursor; - class CoveredIndexMatcher; /** * db helpers are helper functions and classes that let us easily manipulate the local diff --git a/src/mongo/db/exec/2d.h b/src/mongo/db/exec/2d.h index fe0aaf7c55a..4a84f695c44 100644 --- a/src/mongo/db/exec/2d.h +++ b/src/mongo/db/exec/2d.h @@ -28,6 +28,7 @@ #include "mongo/db/exec/2dcommon.h" #include "mongo/db/exec/plan_stage.h" +#include "mongo/db/geo/geoquery.h" #pragma once diff --git a/src/mongo/db/explain.cpp b/src/mongo/db/explain.cpp deleted file mode 100644 index b73f3081696..00000000000 --- a/src/mongo/db/explain.cpp +++ /dev/null @@ -1,277 +0,0 @@ -// @file explain.cpp - Helper classes for generating query explain output. - -/* Copyright 2012 10gen Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "mongo/db/explain.h" - -#include "mongo/db/server_options.h" -#include "mongo/util/mongoutils/str.h" -#include "mongo/util/net/sock.h" - -namespace mongo { - - // !!! TODO get rid of const casts - - ExplainPlanInfo::ExplainPlanInfo() : - _isMultiKey(), - _n(), - _nscannedObjects(), - _nscanned(), - _scanAndOrder(), - _indexOnly(), - _picked(), - _done() { - } - - void ExplainPlanInfo::notePlan( const Cursor &cursor, bool scanAndOrder, bool indexOnly ) { - _cursorName = const_cast<Cursor&>(cursor).toString(); - _indexBounds = cursor.prettyIndexBounds().getOwned(); - _scanAndOrder = scanAndOrder; - _indexOnly = indexOnly; - noteCursorUpdate( cursor ); - } - - void ExplainPlanInfo::noteIterate( bool match, bool loadedRecord, const Cursor &cursor ) { - if ( match ) { - ++_n; - } - if ( loadedRecord ) { - ++_nscannedObjects; - } - noteCursorUpdate( cursor ); - } - - void ExplainPlanInfo::noteDone( const Cursor &cursor ) { - _done = true; - noteCursorUpdate( cursor ); - BSONObjBuilder bob; - const_cast<Cursor&>(cursor).explainDetails( bob ); - _details = bob.obj(); - } - - void ExplainPlanInfo::notePicked() { - _picked = true; - } - - BSONObj ExplainPlanInfo::bson() const { - BSONObjBuilder bob; - bob.append( "cursor", _cursorName ); - bob.appendNumber( "n", _n ); - bob.appendNumber( "nscannedObjects", _nscannedObjects ); - bob.appendNumber( "nscanned", _nscanned ); - bob.append( "indexBounds", _indexBounds ); - return bob.obj(); - } - - BSONObj ExplainPlanInfo::pickedPlanBson( const ExplainClauseInfo &clauseInfo ) const { - BSONObjBuilder bob; - bob.append( "cursor", _cursorName ); - bob.append( "isMultiKey", _isMultiKey ); - bob.appendNumber( "n", clauseInfo.n() ); - bob.appendNumber( "nscannedObjects", clauseInfo.nscannedObjects() ); - bob.appendNumber( "nscanned", clauseInfo.nscanned() ); - bob.appendNumber( "nscannedObjectsAllPlans", clauseInfo.nscannedObjectsAllPlans() ); - bob.appendNumber( "nscannedAllPlans", clauseInfo.nscannedAllPlans() ); - bob.append( "scanAndOrder", _scanAndOrder ); - bob.append( "indexOnly", _indexOnly ); - bob.appendNumber( "nYields", clauseInfo.nYields() ); - bob.appendNumber( "nChunkSkips", clauseInfo.nChunkSkips() ); - bob.appendNumber( "millis", clauseInfo.millis() ); - bob.append( "indexBounds", _indexBounds ); - bob.appendElements( _details ); - return bob.obj(); - } - - void ExplainPlanInfo::noteCursorUpdate( const Cursor &cursor ) { - _isMultiKey = cursor.isMultiKey(); - _nscanned = const_cast<Cursor&>(cursor).nscanned(); - } - - ExplainClauseInfo::ExplainClauseInfo() : - _n(), - _nscannedObjects(), - _nChunkSkips(), - _nYields() { - } - - BSONObj ExplainClauseInfo::bson() const { - BSONObjBuilder bb; - bb.appendElements( virtualPickedPlan().pickedPlanBson( *this ) ); - BSONArrayBuilder allPlans( bb.subarrayStart( "allPlans" ) ); - for( list<shared_ptr<const ExplainPlanInfo> >::const_iterator i = _plans.begin(); - i != _plans.end(); ++i ) { - allPlans << (*i)->bson(); - } - allPlans.done(); - return bb.obj(); - } - - void ExplainClauseInfo::addPlanInfo( const shared_ptr<ExplainPlanInfo> &info ) { - _plans.push_back( info ); - } - - void ExplainClauseInfo::noteYield() { ++_nYields; } - - void ExplainClauseInfo::noteIterate( bool match, bool loadedRecord, bool chunkSkip ) { - if ( match ) { - ++_n; - } - if ( loadedRecord ) { - ++_nscannedObjects; - } - if ( chunkSkip ) { - ++_nChunkSkips; - } - } - - void ExplainClauseInfo::reviseN( long long n ) { - _n = n; - } - - void ExplainClauseInfo::stopTimer() { - _timer.stop(); - } - - long long ExplainClauseInfo::nscannedObjects() const { - if ( _plans.empty() ) { - return 0; - } - return virtualPickedPlan().nscannedObjects(); - } - - long long ExplainClauseInfo::nscanned() const { - if ( _plans.empty() ) { - return 0; - } - return virtualPickedPlan().nscanned(); - } - - long long ExplainClauseInfo::nscannedAllPlans() const { - long long ret = 0; - for( list<shared_ptr<const ExplainPlanInfo> >::const_iterator i = _plans.begin(); - i != _plans.end(); ++i ) { - ret += (*i)->nscanned(); - } - return ret; - } - - const ExplainPlanInfo &ExplainClauseInfo::virtualPickedPlan() const { - // Return a picked plan if possible. - for( list<shared_ptr<const ExplainPlanInfo> >::const_iterator i = _plans.begin(); - i != _plans.end(); ++i ) { - if ( (*i)->picked() ) { - return **i; - } - } - // Return a done plan if possible. - for( list<shared_ptr<const ExplainPlanInfo> >::const_iterator i = _plans.begin(); - i != _plans.end(); ++i ) { - if ( (*i)->done() ) { - return **i; - } - } - // Return a plan with the highest match count. - long long maxN = -1; - shared_ptr<const ExplainPlanInfo> ret; - for( list<shared_ptr<const ExplainPlanInfo> >::const_iterator i = _plans.begin(); - i != _plans.end(); ++i ) { - long long n = ( *i )->n(); - if ( n > maxN ) { - maxN = n; - ret = *i; - } - } - verify( ret ); - return *ret; - } - - void ExplainQueryInfo::noteIterate( bool match, bool loadedRecord, bool chunkSkip ) { - verify( !_clauses.empty() ); - _clauses.back()->noteIterate( match, loadedRecord, chunkSkip ); - } - - void ExplainQueryInfo::noteYield() { - verify( !_clauses.empty() ); - _clauses.back()->noteYield(); - } - - void ExplainQueryInfo::reviseN( long long n ) { - verify( !_clauses.empty() ); - _clauses.back()->reviseN( n ); - } - - void ExplainQueryInfo::setAncillaryInfo( const AncillaryInfo &ancillaryInfo ) { - _ancillaryInfo = ancillaryInfo; - } - - BSONObj ExplainQueryInfo::bson() const { - BSONObjBuilder bob; - if ( _clauses.size() == 1 ) { - bob.appendElements( _clauses.front()->bson() ); - } - else { - long long n = 0; - long long nscannedObjects = 0; - long long nscanned = 0; - long long nscannedObjectsAllPlans = 0; - long long nscannedAllPlans = 0; - BSONArrayBuilder clauseArray( bob.subarrayStart( "clauses" ) ); - for( list<shared_ptr<ExplainClauseInfo> >::const_iterator i = _clauses.begin(); - i != _clauses.end(); ++i ) { - clauseArray << (*i)->bson(); - n += (*i)->n(); - nscannedObjects += (*i)->nscannedObjects(); - nscanned += (*i)->nscanned(); - nscannedObjectsAllPlans += (*i)->nscannedObjectsAllPlans(); - nscannedAllPlans += (*i)->nscannedAllPlans(); - } - clauseArray.done(); - bob.appendNumber( "n", n ); - bob.appendNumber( "nscannedObjects", nscannedObjects ); - bob.appendNumber( "nscanned", nscanned ); - bob.appendNumber( "nscannedObjectsAllPlans", nscannedObjectsAllPlans ); - bob.appendNumber( "nscannedAllPlans", nscannedAllPlans ); - bob.appendNumber( "millis", _timer.duration() ); - } - - if ( !_ancillaryInfo._oldPlan.isEmpty() ) { - bob.append( "oldPlan", _ancillaryInfo._oldPlan ); - } - bob.append( "server", server() ); - - return bob.obj(); - } - - void ExplainQueryInfo::addClauseInfo( const shared_ptr<ExplainClauseInfo> &info ) { - if ( !_clauses.empty() ) { - _clauses.back()->stopTimer(); - } - _clauses.push_back( info ); - } - - string ExplainQueryInfo::server() { - return mongoutils::str::stream() << getHostNameCached() << ":" << serverGlobalParams.port; - } - - ExplainSinglePlanQueryInfo::ExplainSinglePlanQueryInfo() : - _planInfo( new ExplainPlanInfo() ), - _queryInfo( new ExplainQueryInfo() ) { - shared_ptr<ExplainClauseInfo> clauseInfo( new ExplainClauseInfo() ); - clauseInfo->addPlanInfo( _planInfo ); - _queryInfo->addClauseInfo( clauseInfo ); - } - -} // namespace mongo diff --git a/src/mongo/db/explain.h b/src/mongo/db/explain.h deleted file mode 100644 index 02c9bbf6c76..00000000000 --- a/src/mongo/db/explain.h +++ /dev/null @@ -1,188 +0,0 @@ -// @file explain.h - Helper classes for generating query explain output. - -/* Copyright 2012 10gen Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "mongo/db/cursor.h" -#include "mongo/util/timer.h" - -namespace mongo { - - /** - * Note: by default we filter out allPlans and oldPlan in the shell's - * explain() function. If you add any recursive structures, make sure to - * edit the JS to make sure everything gets filtered. - */ - - /** The timer starts on construction and provides the duration since then or until stopped. */ - class DurationTimer { - public: - DurationTimer() : _running( true ), _duration() {} - void stop() { _running = false; _duration = _timer.millis(); } - int duration() const { return _running ? _timer.millis() : _duration; } - private: - Timer _timer; - bool _running; - int _duration; - }; - - class ExplainClauseInfo; - - /** Data describing execution of a query plan. */ - class ExplainPlanInfo { - public: - ExplainPlanInfo(); - - /** Note information about the plan. */ - void notePlan( const Cursor &cursor, bool scanAndOrder, bool indexOnly ); - /** Note an iteration of the plan. */ - void noteIterate( bool match, bool loadedRecord, const Cursor &cursor ); - /** Note that the plan finished execution. */ - void noteDone( const Cursor &cursor ); - /** Note that the plan was chosen over others by the query optimizer. */ - void notePicked(); - - /** BSON summary of the plan. */ - BSONObj bson() const; - /** Combined details of both the plan and its clause. */ - BSONObj pickedPlanBson( const ExplainClauseInfo &clauseInfo ) const; - - bool picked() const { return _picked; } - bool done() const { return _done; } - long long n() const { return _n; } - long long nscannedObjects() const { return _nscannedObjects; } - long long nscanned() const { return _nscanned; } - - private: - void noteCursorUpdate( const Cursor &cursor ); - string _cursorName; - bool _isMultiKey; - long long _n; - long long _nscannedObjects; - long long _nscanned; - bool _scanAndOrder; - bool _indexOnly; - BSONObj _indexBounds; - bool _picked; - bool _done; - BSONObj _details; - }; - - /** Data describing execution of a query clause. */ - class ExplainClauseInfo { - public: - ExplainClauseInfo(); - - /** Note an iteration of the clause. */ - void noteIterate( bool match, bool loadedRecord, bool chunkSkip ); - /** Note a yield for the clause. */ - void noteYield(); - /** Revise the total number of documents returned to match an external count. */ - void reviseN( long long n ); - /** Stop the clauses's timer. */ - void stopTimer(); - - /** Add information about a plan to this clause. */ - void addPlanInfo( const shared_ptr<ExplainPlanInfo> &info ); - BSONObj bson() const; - - long long n() const { return _n; } - long long nscannedObjects() const; - long long nscanned() const; - long long nscannedObjectsAllPlans() const { return _nscannedObjects; } - long long nscannedAllPlans() const; - long long nChunkSkips() const { return _nChunkSkips; } - int nYields() const { return _nYields; } - int millis() const { return _timer.duration(); } - - private: - /** - * @return Plan explain information to be displayed at the top of the explain output. A - * picked() plan will be returned if one is available, otherwise a successful non picked() - * plan will be returned. - */ - const ExplainPlanInfo &virtualPickedPlan() const; - list<shared_ptr<const ExplainPlanInfo> > _plans; - long long _n; - long long _nscannedObjects; - long long _nChunkSkips; - int _nYields; - DurationTimer _timer; - }; - - /** Data describing execution of a query. */ - class ExplainQueryInfo { - public: - /** Note an iteration of the query's current clause. */ - void noteIterate( bool match, bool loadedRecord, bool chunkSkip ); - /** Note a yield of the query's current clause. */ - void noteYield(); - /** Revise the number of documents returned by the current clause. */ - void reviseN( long long n ); - - /* Additional information describing the query. */ - struct AncillaryInfo { - BSONObj _oldPlan; - }; - void setAncillaryInfo( const AncillaryInfo &ancillaryInfo ); - - /* Add information about a clause to this query. */ - void addClauseInfo( const shared_ptr<ExplainClauseInfo> &info ); - BSONObj bson() const; - - private: - static string server(); - - list<shared_ptr<ExplainClauseInfo> > _clauses; - AncillaryInfo _ancillaryInfo; - DurationTimer _timer; - }; - - /** Data describing execution of a query with a single clause and plan. */ - class ExplainSinglePlanQueryInfo { - public: - ExplainSinglePlanQueryInfo(); - - /** Note information about the plan. */ - void notePlan( const Cursor &cursor, bool scanAndOrder, bool indexOnly ) { - _planInfo->notePlan( cursor, scanAndOrder, indexOnly ); - } - /** Note an iteration of the plan and the clause. */ - void noteIterate( bool match, bool loadedRecord, bool chunkSkip, const Cursor &cursor ) { - _planInfo->noteIterate( match, loadedRecord, cursor ); - _queryInfo->noteIterate( match, loadedRecord, chunkSkip ); - } - /** Note a yield for the clause. */ - void noteYield() { - _queryInfo->noteYield(); - } - /** Note that the plan finished execution. */ - void noteDone( const Cursor &cursor ) { - _planInfo->noteDone( cursor ); - } - - /** Return the corresponding ExplainQueryInfo for further use. */ - shared_ptr<ExplainQueryInfo> queryInfo() const { - return _queryInfo; - } - - private: - shared_ptr<ExplainPlanInfo> _planInfo; - shared_ptr<ExplainQueryInfo> _queryInfo; - }; - -} // namespace mongo diff --git a/src/mongo/db/index/emulated_cursor.h b/src/mongo/db/index/emulated_cursor.h deleted file mode 100644 index 86c82072603..00000000000 --- a/src/mongo/db/index/emulated_cursor.h +++ /dev/null @@ -1,266 +0,0 @@ -/** -* Copyright (C) 2013 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/>. -* -* As a special exception, the copyright holders give permission to link the -* code of portions of this program with the OpenSSL library under certain -* conditions as described in each individual source file and distribute -* linked combinations including the program with the OpenSSL library. You -* must comply with the GNU Affero General Public License in all respects for -* all of the code used other than as permitted herein. If you modify file(s) -* with this exception, you may extend this exception to your version of the -* file(s), but you are not obligated to do so. If you do not wish to do so, -* delete this exception statement from your version. If you delete this -* exception statement from all source files in the program, then also delete -* it in the license file. -*/ - -#pragma once - -#include <boost/scoped_ptr.hpp> -#include <set> - -#include "mongo/db/cursor.h" -#include "mongo/db/index_names.h" -#include "mongo/db/index/index_access_method.h" -#include "mongo/db/index/index_descriptor.h" -#include "mongo/db/index/index_cursor.h" -#include "mongo/db/jsobj.h" -#include "mongo/platform/unordered_set.h" - -namespace mongo { - - /** - * This class is a crutch to help migrate from the old everything-is-a-Cursor world to the new - * index API world. It wraps a new IndexCursor in the old Cursor. We only use this for special - * (2d, 2dsphere, text, haystack, hash) indices. - */ - class EmulatedCursor : public Cursor { - public: - /** - * Create a new EmulatedCursor. - * Takes ownership of the provided indexAccessMethod indexCursor. - * Takes ownership of the IndexDescriptor inside the indexAccessMethod. - * - * Full semantics of numWanted: - * numWanted == 0 : Return any number of results, but try to return in batches of 101. - * numWanted == 1 : Return exactly one result. - * numWanted > 1 : Return any number of results, but try to return in batches of numWanted. - * - * In practice, your cursor can ignore numWanted, as enforcement of limits is done - * by the caller. - */ - static EmulatedCursor* make(IndexDescriptor* descriptor, - IndexAccessMethod* indexAccessMethod, - const BSONObj& query, - const BSONObj& order, - int numWanted, - const BSONObj& keyPattern) { - - verify(descriptor); - verify(indexAccessMethod); - - auto_ptr<EmulatedCursor> ret(new EmulatedCursor(descriptor, indexAccessMethod, - order, numWanted, keyPattern)); - // Why do we have to do this? No reading from disk is allowed in constructors, - // and seeking involves reading from disk. - ret->seek(query); - return ret.release(); - } - - // We defer everything we can to the underlying cursor. - virtual bool ok() { return !_indexCursor->isEOF(); } - virtual Record* _current() { return currLoc().rec(); } - virtual BSONObj current() { return BSONObj::make(_current()); } - virtual DiskLoc currLoc() { return _indexCursor->getValue(); } - virtual BSONObj currKey() const { return _indexCursor->getKey(); } - virtual DiskLoc refLoc() { - // This will sometimes get called even if we're not OK. - // See ClientCursor::prepareToYield/updateLocation. - if (!ok()) { - return DiskLoc(); - } else { - return currLoc(); - } - } - virtual long long nscanned() { return _nscanned; } - virtual string toString() { return _indexCursor->toString(); } - - virtual void explainDetails(BSONObjBuilder& b) { - _indexCursor->explainDetails(&b); - return; - } - - virtual bool advance() { - _indexCursor->next(); - if (ok()) { - ++_nscanned; - } - return ok(); - } - - virtual void noteLocation() { - verify(_supportYields || _supportGetMore); - _indexCursor->savePosition(); - } - - virtual void checkLocation() { - verify(_supportYields || _supportGetMore); - _indexCursor->restorePosition(); - // Somebody might have inserted a multikey during a yield. - checkMultiKeyProperties(); - } - - // Below this is where the Cursor <---> IndexCursor mapping breaks down. - virtual CoveredIndexMatcher* matcher() const { - return _matcher.get(); - } - - virtual BSONObj indexKeyPattern() { - return _descriptor->keyPattern(); - } - - virtual bool supportGetMore() { return _supportGetMore; } - virtual bool supportYields() { return _supportYields; } - virtual bool isMultiKey() const { return _isMultiKey; } - virtual bool modifiedKeys() const { return _modifiedKeys; } - virtual bool autoDedup() const { return _autoDedup; } - - virtual bool getsetdup(DiskLoc loc) { - if (_shouldGetSetDup) { - pair<unordered_set<DiskLoc, DiskLoc::Hasher>::iterator, bool> p = _dups.insert(loc); - return !p.second; - } else { - return false; - } - } - - private: - EmulatedCursor(IndexDescriptor* descriptor, IndexAccessMethod* indexAccessMethod, - const BSONObj& order, int numWanted, const BSONObj& keyPattern) - : _descriptor(descriptor), _indexAccessMethod(indexAccessMethod), - _keyPattern(keyPattern), _pluginName(IndexNames::findPluginName(keyPattern)) { - - IndexCursor *cursor; - indexAccessMethod->newCursor(&cursor); - _indexCursor.reset(cursor); - - CursorOptions options; - options.numWanted = numWanted; - cursor->setOptions(options); - - if (IndexNames::HASHED == _pluginName) { - _supportYields = true; - _supportGetMore = true; - _modifiedKeys = true; - _shouldGetSetDup = false; - _autoDedup = true; - } else if (IndexNames::GEO_2DSPHERE == _pluginName) { - _supportYields = true; - _supportGetMore = true; - _modifiedKeys = true; - // Note: this duplicates the de-duplication in near cursors. Fix near cursors - // to not de-dup themselves. - _shouldGetSetDup = true; - _autoDedup = false; - } else if (IndexNames::GEO_2D == _pluginName) { - _supportYields = false; - _supportGetMore = true; - _modifiedKeys = true; - _isMultiKey = false; - _shouldGetSetDup = false; - _autoDedup = false; - } else { - verify(0); - } - - // _isMultiKey and _shouldGetSetDup are set in this unless it's 2d. - checkMultiKeyProperties(); - } - - void seek(const BSONObj& query) { - Status seekStatus = _indexCursor->seek(query); - - // Our seek could be malformed. Code above us expects an exception if so. - if (Status::OK() != seekStatus) { - uasserted(seekStatus.location(), seekStatus.reason()); - } - - if (!_indexCursor->isEOF()) { - _nscanned = 1; - } else { - _nscanned = 0; - } - - if (IndexNames::HASHED == _pluginName) { - // Quoted from hashindex.cpp: - // Force a match of the query against the actual document by giving - // the cursor a matcher with an empty indexKeyPattern. This ensures the - // index is not used as a covered index. - // NOTE: this forcing is necessary due to potential hash collisions - _matcher = shared_ptr<CoveredIndexMatcher>( - new CoveredIndexMatcher(query, BSONObj())); - } else if (IndexNames::GEO_2DSPHERE == _pluginName) { - // Technically, the non-geo indexed fields are in the key, though perhaps not in the - // exact format the matcher expects (arrays). So, we match against all non-geo - // fields. This could possibly be relaxed in some fashion in the future? Requires - // query work. - BSONObjBuilder fieldsToNuke; - BSONObjIterator keyIt(_keyPattern); - - while (keyIt.more()) { - BSONElement e = keyIt.next(); - if (e.type() == String && IndexNames::GEO_2DSPHERE == e.valuestr()) { - fieldsToNuke.append(e.fieldName(), ""); - } - } - - BSONObj filteredQuery = query.filterFieldsUndotted(fieldsToNuke.obj(), false); - - _matcher = shared_ptr<CoveredIndexMatcher>( - new CoveredIndexMatcher(filteredQuery, _keyPattern)); - } else if (IndexNames::GEO_2D == _pluginName) { - // No-op matcher. - _matcher = shared_ptr<CoveredIndexMatcher>( - new CoveredIndexMatcher(BSONObj(), BSONObj())); - } - } - - void checkMultiKeyProperties() { - if (IndexNames::GEO_2D != _pluginName) { - _isMultiKey = _shouldGetSetDup = _descriptor->isMultikey(); - } - } - - unordered_set<DiskLoc, DiskLoc::Hasher> _dups; - - scoped_ptr<IndexDescriptor> _descriptor; - scoped_ptr<IndexAccessMethod> _indexAccessMethod; - scoped_ptr<IndexCursor> _indexCursor; - - long long _nscanned; - shared_ptr<CoveredIndexMatcher> _matcher; - - bool _supportYields; - bool _supportGetMore; - bool _isMultiKey; - bool _modifiedKeys; - bool _shouldGetSetDup; - bool _autoDedup; - - BSONObj _keyPattern; - string _pluginName; - }; - -} // namespace mongo diff --git a/src/mongo/db/intervalbtreecursor.cpp b/src/mongo/db/intervalbtreecursor.cpp deleted file mode 100644 index b5620691a80..00000000000 --- a/src/mongo/db/intervalbtreecursor.cpp +++ /dev/null @@ -1,232 +0,0 @@ -/** - * 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/>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the GNU Affero General Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#include "mongo/db/intervalbtreecursor.h" - -#include "mongo/db/btree.h" -#include "mongo/db/kill_current_op.h" -#include "mongo/db/namespace_details-inl.h" -#include "mongo/db/pdfile.h" - -namespace mongo { - - unordered_set<IntervalBtreeCursor*> IntervalBtreeCursor::_activeCursors; - SimpleMutex IntervalBtreeCursor::_activeCursorsMutex("active_interval_btree_cursors"); - - /** - * Advance 'loc' until it does not reference an unused key, or the end of the btree is reached. - */ - static void skipUnused( BtreeKeyLocation* loc ) { - - // While loc points to an unused key ... - while( !loc->bucket.isNull() && - loc->bucket.btree<V1>()->k( loc->pos ).isUnused() ) { - - // ... advance loc to the next key in the btree. - loc->bucket = loc->bucket.btree<V1>()->advance( loc->bucket, - loc->pos, - 1, - __FUNCTION__ ); - } - } - - IntervalBtreeCursor* IntervalBtreeCursor::make( NamespaceDetails* namespaceDetails, - const IndexDetails& indexDetails, - const BSONObj& lowerBound, - bool lowerBoundInclusive, - const BSONObj& upperBound, - bool upperBoundInclusive ) { - if ( indexDetails.version() != 1 ) { - // Only v1 indexes are supported. - return NULL; - } - auto_ptr<IntervalBtreeCursor> ret( new IntervalBtreeCursor( namespaceDetails, - indexDetails, - lowerBound, - lowerBoundInclusive, - upperBound, - upperBoundInclusive ) ); - ret->init(); - return ret.release(); - } - - IntervalBtreeCursor::IntervalBtreeCursor( NamespaceDetails* namespaceDetails, - const IndexDetails& indexDetails, - const BSONObj& lowerBound, - bool lowerBoundInclusive, - const BSONObj& upperBound, - bool upperBoundInclusive ) : - _namespaceDetails( *namespaceDetails ), - _indexNo( namespaceDetails->idxNo( indexDetails ) ), - _indexDetails( indexDetails ), - _ordering( Ordering::make( _indexDetails.keyPattern() ) ), - _lowerBound( lowerBound ), - _lowerBoundInclusive( lowerBoundInclusive ), - _upperBound( upperBound ), - _upperBoundInclusive( upperBoundInclusive ), - _currRecoverable( _indexDetails, _ordering, _curr ), - _nscanned(), - _multikeyFlag() { - - SimpleMutex::scoped_lock lock(_activeCursorsMutex); - _activeCursors.insert(this); - } - - IntervalBtreeCursor::~IntervalBtreeCursor() { - SimpleMutex::scoped_lock lock(_activeCursorsMutex); - _activeCursors.erase(this); - } - - void IntervalBtreeCursor::aboutToDeleteBucket(const DiskLoc& bucket) { - SimpleMutex::scoped_lock lock(_activeCursorsMutex); - for (unordered_set<IntervalBtreeCursor*>::iterator i = _activeCursors.begin(); - i != _activeCursors.end(); ++i) { - - IntervalBtreeCursor* ic = *i; - if (bucket == ic->_curr.bucket) { - ic->_currRecoverable.invalidateInitialLocation(); - } - } - } - - void IntervalBtreeCursor::init() { - _multikeyFlag = _namespaceDetails.isMultikey( _indexNo ); - _curr = locateKey( _lowerBound, !_lowerBoundInclusive ); - skipUnused( &_curr ); - relocateEnd(); - if ( ok() ) { - _nscanned = 1; - } - } - - bool IntervalBtreeCursor::ok() { - return !_curr.bucket.isNull(); - } - - DiskLoc IntervalBtreeCursor::currLoc() { - if ( eof() ) { - return DiskLoc(); - } - return _curr.bucket.btree<V1>()->keyNode( _curr.pos ).recordLoc; - } - - bool IntervalBtreeCursor::advance() { - RARELY killCurrentOp.checkForInterrupt(); - if ( eof() ) { - return false; - } - // Advance _curr to the next key in the btree. - _curr.bucket = _curr.bucket.btree<V1>()->advance( _curr.bucket, - _curr.pos, - 1, - __FUNCTION__ ); - skipUnused( &_curr ); - if ( _curr == _end ) { - // _curr has reached _end, so iteration is complete. - _curr.bucket.Null(); - } - else { - ++_nscanned; - } - return ok(); - } - - BSONObj IntervalBtreeCursor::currKey() const { - if ( _curr.bucket.isNull() ) { - return BSONObj(); - } - return _curr.bucket.btree<V1>()->keyNode( _curr.pos ).key.toBson(); - } - - - void IntervalBtreeCursor::noteLocation() { - _currRecoverable = LogicalBtreePosition( _indexDetails, _ordering, _curr ); - _currRecoverable.init(); - } - - void IntervalBtreeCursor::checkLocation() { - _multikeyFlag = _namespaceDetails.isMultikey( _indexNo ); - _curr = _currRecoverable.currentLocation(); - skipUnused( &_curr ); - relocateEnd(); - } - - bool IntervalBtreeCursor::getsetdup( DiskLoc loc ) { - // TODO _multikeyFlag may be set part way through an iteration by checkLocation(). In this - // case results returned earlier, when _multikeyFlag was false, will not be deduped. This - // is an old issue with all mongo btree cursor implementations. - return _multikeyFlag && !_dups.insert( loc ).second; - } - - BSONObj IntervalBtreeCursor::prettyIndexBounds() const { - return BSON( "lower" << _lowerBound.replaceFieldNames( _indexDetails.keyPattern() ) << - "upper" << _upperBound.replaceFieldNames( _indexDetails.keyPattern() ) ); - } - - BtreeKeyLocation IntervalBtreeCursor::locateKey( const BSONObj& key, bool afterKey ) { - bool found; - BtreeKeyLocation ret; - - // To find the first btree location equal to the specified key, specify a record location of - // minDiskLoc, which is below any actual Record location. To find the first btree location - // greater than the specified key, specify a record location of maxDiskLoc, which is above - // any actual Record location. - DiskLoc targetRecord = afterKey ? maxDiskLoc : minDiskLoc; - - // Find the requested location in the btree. - ret.bucket = _indexDetails.head.btree<V1>()->locate( _indexDetails, - _indexDetails.head, - key, - _ordering, - ret.pos, - found, - targetRecord, - 1 ); - return ret; - } - - void IntervalBtreeCursor::relocateEnd() { - if ( eof() ) { - return; - } - - // If the current key is above the upper bound ... - int32_t cmp = currKey().woCompare( _upperBound, _ordering, false ); - if ( cmp > 0 || ( cmp == 0 && !_upperBoundInclusive ) ) { - - // ... then iteration is complete. - _curr.bucket.Null(); - return; - } - - // Otherwise, relocate _end. - _end = locateKey( _upperBound, _upperBoundInclusive ); - skipUnused( &_end ); - } - -} // namespace mongo diff --git a/src/mongo/db/intervalbtreecursor.h b/src/mongo/db/intervalbtreecursor.h deleted file mode 100644 index 0f9770204f7..00000000000 --- a/src/mongo/db/intervalbtreecursor.h +++ /dev/null @@ -1,163 +0,0 @@ -/** - * 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/>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the GNU Affero General Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#pragma once - -#include "mongo/db/btreeposition.h" -#include "mongo/db/cursor.h" -#include "mongo/db/namespace_details.h" -#include "mongo/platform/cstdint.h" -#include "mongo/platform/unordered_set.h" - -namespace mongo { - - /** - * An optimized btree cursor that iterates through all btree keys between a lower bound and an - * upper bound. The contents of the individual keys are not examined by the implementation, - * which simply advances through the tree until reaching a predetermined end location. The goal - * is to optimize count operations where the keys must only be counted, not tested for matching. - * - * Limitations compared to a standard BtreeCursor (partial list): - * - Only supports index constraints consisting of a single interval within an index. - * - Only supports forward direction iteration. - * - Does not support covered index projections. - * - Does not support get more. - * - Only supports V1 indexes (not V0). - */ - class IntervalBtreeCursor : public Cursor { - public: - - /** - * @return a cursor, or NULL if no cursor can be created. - * @param namespaceDetails - Collection metadata that will not be modified. - * @param indexDetails - Index metadata, if not a v1 index then make() will return NULL. - * @param lowerBound - Lower bound of the key range to iterate, according to the index's - * native ordering. - * @param lowerBoundInclusive - If true, the lower bound includes the endpoint. - * @param upperBound - Upper bound of the key range to iterate. - * @param upperBoundInclusive - If true, the upper bound includes the endpoint. - */ - static IntervalBtreeCursor* make( /* const */ NamespaceDetails* namespaceDetails, - const IndexDetails& indexDetails, - const BSONObj& lowerBound, - bool lowerBoundInclusive, - const BSONObj& upperBound, - bool upperBoundInclusive ); - - /** Virtuals from Cursor. */ - - virtual bool ok(); - - virtual Record* _current() { return currLoc().rec(); } - - virtual BSONObj current() { return currLoc().obj(); } - - virtual DiskLoc currLoc(); - - virtual bool advance(); - - virtual BSONObj currKey() const; - - virtual DiskLoc refLoc() { return currLoc(); } - - static void aboutToDeleteBucket( const DiskLoc& b ); - - virtual BSONObj indexKeyPattern() { return _indexDetails.keyPattern(); } - - virtual bool supportGetMore() { return false; } - - virtual void noteLocation(); - - virtual void checkLocation(); - - virtual bool supportYields() { return true; } - - virtual string toString() { return "IntervalBtreeCursor"; } - - virtual bool getsetdup( DiskLoc loc ); - - virtual bool isMultiKey() const { return _multikeyFlag; } - - virtual bool modifiedKeys() const { return _multikeyFlag; } - - virtual BSONObj prettyIndexBounds() const; - - virtual long long nscanned() { return _nscanned; } - - virtual CoveredIndexMatcher* matcher() const { return _matcher.get(); } - - virtual void setMatcher( shared_ptr<CoveredIndexMatcher> matcher ) { _matcher = matcher; } - - virtual ~IntervalBtreeCursor(); - - private: - IntervalBtreeCursor( NamespaceDetails* namespaceDetails, - const IndexDetails& indexDetails, - const BSONObj& lowerBound, - bool lowerBoundInclusive, - const BSONObj& upperBound, - bool upperBoundInclusive ); - - // For handling bucket deletion. - static unordered_set<IntervalBtreeCursor*> _activeCursors; - static SimpleMutex _activeCursorsMutex; - - void init(); - - /** - * @return a location in the btree, determined by the parameters specified. - * @param key - The key to search for. - * @param afterKey - If true, return the first btree key greater than the supplied 'key'. - * If false, return the first key equal to the supplied 'key'. - */ - BtreeKeyLocation locateKey( const BSONObj& key, bool afterKey ); - - /** Find the iteration end location and set _end to it. */ - void relocateEnd(); - - const NamespaceDetails& _namespaceDetails; - const int32_t _indexNo; - const IndexDetails& _indexDetails; - const Ordering _ordering; - const BSONObj _lowerBound; - const bool _lowerBoundInclusive; - const BSONObj _upperBound; - const bool _upperBoundInclusive; - - BtreeKeyLocation _curr; // Current position in the btree. - LogicalBtreePosition _currRecoverable; // Helper to track the position of _curr if the - // btree is modified during a mutex yield. - BtreeKeyLocation _end; // Exclusive end location in the btree. - int64_t _nscanned; - - shared_ptr<CoveredIndexMatcher> _matcher; - bool _multikeyFlag; - unordered_set<DiskLoc,DiskLoc::Hasher> _dups; - }; - -} // namespace mongo diff --git a/src/mongo/db/matcher.h b/src/mongo/db/matcher.h index 8d2b1df8a81..f4ac8757a32 100644 --- a/src/mongo/db/matcher.h +++ b/src/mongo/db/matcher.h @@ -37,7 +37,6 @@ namespace mongo { class Cursor; - class CoveredIndexMatcher; class FieldRangeVector; struct element_lt { diff --git a/src/mongo/db/matcher_covered.cpp b/src/mongo/db/matcher_covered.cpp deleted file mode 100644 index f44ae264a85..00000000000 --- a/src/mongo/db/matcher_covered.cpp +++ /dev/null @@ -1,145 +0,0 @@ -// matcher_covered.cpp - -/* Matcher is our boolean expression evaluator for "where" clauses */ - -/** -* Copyright (C) 2008 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/>. -* -* As a special exception, the copyright holders give permission to link the -* code of portions of this program with the OpenSSL library under certain -* conditions as described in each individual source file and distribute -* linked combinations including the program with the OpenSSL library. You -* must comply with the GNU Affero General Public License in all respects for -* all of the code used other than as permitted herein. If you modify file(s) -* with this exception, you may extend this exception to your version of the -* file(s), but you are not obligated to do so. If you do not wish to do so, -* delete this exception statement from your version. If you delete this -* exception statement from all source files in the program, then also delete -* it in the license file. -*/ - -#include "mongo/pch.h" - -#include "mongo/db/cursor.h" -#include "mongo/db/matcher.h" -#include "mongo/db/matcher_covered.h" -#include "mongo/db/pdfile.h" -#include "mongo/db/queryutil.h" - -namespace mongo { - - CoveredIndexMatcher::CoveredIndexMatcher( const BSONObj &jsobj, - const BSONObj &indexKeyPattern ) : - _docMatcher( new Matcher( jsobj ) ), - _keyMatcher( *_docMatcher, indexKeyPattern ) { - init(); - } - - CoveredIndexMatcher::CoveredIndexMatcher( const CoveredIndexMatcher &prevClauseMatcher, - const shared_ptr<FieldRangeVector> &prevClauseFrv, - const BSONObj &nextClauseIndexKeyPattern ) : - _docMatcher( prevClauseMatcher._docMatcher ), - _keyMatcher( *_docMatcher, nextClauseIndexKeyPattern ), - _orDedupConstraints( prevClauseMatcher._orDedupConstraints ) { - if ( prevClauseFrv ) { - _orDedupConstraints.push_back( prevClauseFrv ); - } - init(); - } - - void CoveredIndexMatcher::init() { - _needRecord = - !_keyMatcher.keyMatch( *_docMatcher ) || - !_orDedupConstraints.empty(); - } - - bool CoveredIndexMatcher::matchesCurrent( Cursor * cursor , MatchDetails * details ) const { - // bool keyUsable = ! cursor->isMultiKey() && check for $orish like conditions in matcher SERVER-1264 - - bool keyUsable = true; - if ( cursor->indexKeyPattern().isEmpty() ) { // unindexed cursor - keyUsable = false; - } - else if ( cursor->isMultiKey() ) { - keyUsable = - _keyMatcher.singleSimpleCriterion() && - ( ! _docMatcher || _docMatcher->singleSimpleCriterion() ); - } - return matches( cursor->currKey(), - cursor->currLoc(), - details, - keyUsable ); - } - - bool CoveredIndexMatcher::matches( const BSONObj& key, const DiskLoc& recLoc, - MatchDetails* details, bool keyUsable ) const { - - LOG(5) << "CoveredIndexMatcher::matches() " << key.toString() << ' ' << recLoc.toString() << ' ' << keyUsable << endl; - - dassert( key.isValid() ); - - if ( details ) - details->resetOutput(); - - if ( keyUsable ) { - if ( !_keyMatcher.matches(key, details ) ) { - return false; - } - bool needRecordForDetails = details && details->needRecord(); - if ( !_needRecord && !needRecordForDetails ) { - return true; - } - } - - BSONObj obj = recLoc.obj(); - bool res = - _docMatcher->matches( obj, details ) && - !isOrClauseDup( obj ); - - if ( details ) - details->setLoadedRecord( true ); - - LOG(5) << "CoveredIndexMatcher _docMatcher->matches() returns " << res << endl; - return res; - } - - bool CoveredIndexMatcher::isOrClauseDup( const BSONObj &obj ) const { - for( vector<shared_ptr<FieldRangeVector> >::const_iterator i = _orDedupConstraints.begin(); - i != _orDedupConstraints.end(); ++i ) { - if ( (*i)->matches( obj ) ) { - // If a document matches a prior $or clause index range, generally it would have - // been returned while scanning that range and so is reported as a dup. - return true; - } - } - return false; - } - - string CoveredIndexMatcher::toString() const { - StringBuilder buf; - buf << "(CoveredIndexMatcher "; - - if ( _needRecord ) - buf << "needRecord "; - - buf << "keyMatcher: " << _keyMatcher.toString() << " "; - - if ( _docMatcher ) - buf << "docMatcher: " << _docMatcher->toString() << " "; - - buf << ")"; - return buf.str(); - } -} diff --git a/src/mongo/db/matcher_covered.h b/src/mongo/db/matcher_covered.h deleted file mode 100644 index 02181ad1753..00000000000 --- a/src/mongo/db/matcher_covered.h +++ /dev/null @@ -1,88 +0,0 @@ -// matcher_covered.h - -/* Matcher is our boolean expression evaluator for "where" clauses */ - -/** -* Copyright (C) 2008 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/>. -* -* As a special exception, the copyright holders give permission to link the -* code of portions of this program with the OpenSSL library under certain -* conditions as described in each individual source file and distribute -* linked combinations including the program with the OpenSSL library. You -* must comply with the GNU Affero General Public License in all respects for -* all of the code used other than as permitted herein. If you modify file(s) -* with this exception, you may extend this exception to your version of the -* file(s), but you are not obligated to do so. If you do not wish to do so, -* delete this exception statement from your version. If you delete this -* exception statement from all source files in the program, then also delete -* it in the license file. -*/ - -#pragma once - -#include "mongo/db/diskloc.h" -#include "mongo/db/geo/geoquery.h" -#include "mongo/db/jsobj.h" -#include "mongo/db/matcher.h" -#include "mongo/db/matcher/match_details.h" - -namespace mongo { - - // If match succeeds on index key, then attempt to match full document. - class CoveredIndexMatcher : boost::noncopyable { - public: - CoveredIndexMatcher(const BSONObj &pattern, const BSONObj &indexKeyPattern); - bool matchesWithSingleKeyIndex( const BSONObj& key, const DiskLoc& recLoc, - MatchDetails* details = 0 ) const { - return matches( key, recLoc, details, true ); - } - /** - * This is the preferred method for matching against a cursor, as it - * can handle both multi and single key cursors. - */ - bool matchesCurrent( Cursor * cursor , MatchDetails * details = 0 ) const; - bool needRecord() const { return _needRecord; } - - const Matcher &docMatcher() const { return *_docMatcher; } - - /** - * @return a matcher for a following $or clause. - * @param prevClauseFrs The index range scanned by the previous $or clause. May be empty. - * @param nextClauseIndexKeyPattern The index key of the following $or clause. - */ - CoveredIndexMatcher *nextClauseMatcher( const shared_ptr<FieldRangeVector>& prevClauseFrv, - const BSONObj& nextClauseIndexKeyPattern ) const { - return new CoveredIndexMatcher( *this, prevClauseFrv, nextClauseIndexKeyPattern ); - } - - string toString() const; - - private: - bool matches( const BSONObj& key, const DiskLoc& recLoc, MatchDetails* details = 0, - bool keyUsable = true ) const; - bool isOrClauseDup( const BSONObj &obj ) const; - CoveredIndexMatcher( const CoveredIndexMatcher &prevClauseMatcher, - const shared_ptr<FieldRangeVector> &prevClauseFrv, - const BSONObj &nextClauseIndexKeyPattern ); - void init(); - shared_ptr< Matcher > _docMatcher; - Matcher _keyMatcher; - vector<shared_ptr<FieldRangeVector> > _orDedupConstraints; - - bool _needRecord; // if the key itself isn't good enough to determine a positive match - }; - -} // namespace mongo - diff --git a/src/mongo/db/namespace_details.h b/src/mongo/db/namespace_details.h index c201903e2c4..bfaea894017 100644 --- a/src/mongo/db/namespace_details.h +++ b/src/mongo/db/namespace_details.h @@ -37,7 +37,6 @@ #include "mongo/db/jsobj.h" #include "mongo/db/storage/durable_mapped_file.h" #include "mongo/db/namespace_string.h" -#include "mongo/db/querypattern.h" #include "mongo/db/catalog/ondisk/namespace.h" #include "mongo/db/catalog/ondisk/namespace_index.h" #include "mongo/platform/unordered_map.h" diff --git a/src/mongo/db/ops/update.h b/src/mongo/db/ops/update.h index ba3cc9fa40c..ac00f82b57d 100644 --- a/src/mongo/db/ops/update.h +++ b/src/mongo/db/ops/update.h @@ -34,7 +34,6 @@ #include "mongo/db/curop.h" #include "mongo/db/ops/update_request.h" #include "mongo/db/ops/update_result.h" -#include "mongo/db/query_plan_selection_policy.h" namespace mongo { diff --git a/src/mongo/db/ops/update_request.h b/src/mongo/db/ops/update_request.h index 36e1b20bde1..4d4139a1601 100644 --- a/src/mongo/db/ops/update_request.h +++ b/src/mongo/db/ops/update_request.h @@ -31,7 +31,6 @@ #include "mongo/db/jsobj.h" #include "mongo/db/curop.h" #include "mongo/db/namespace_string.h" -#include "mongo/db/query_plan_selection_policy.h" #include "mongo/util/mongoutils/str.h" namespace mongo { @@ -43,11 +42,8 @@ namespace mongo { class UpdateRequest { public: - inline UpdateRequest( - const NamespaceString& nsString, - const QueryPlanSelectionPolicy& policy = QueryPlanSelectionPolicy::any() ) + inline UpdateRequest(const NamespaceString& nsString) : _nsString(nsString) - , _queryPlanPolicy(policy) , _god(false) , _upsert(false) , _multi(false) @@ -60,10 +56,6 @@ namespace mongo { return _nsString; } - const QueryPlanSelectionPolicy& getQueryPlanSelectionPolicy() const { - return _queryPlanPolicy; - } - inline void setQuery(const BSONObj& query) { _query = query; } @@ -153,7 +145,6 @@ namespace mongo { private: const NamespaceString& _nsString; - const QueryPlanSelectionPolicy& _queryPlanPolicy; // Contains the query that selects documents to update. BSONObj _query; diff --git a/src/mongo/db/ops/update_result.h b/src/mongo/db/ops/update_result.h index 9d42af10db3..3d1b3019eb3 100644 --- a/src/mongo/db/ops/update_result.h +++ b/src/mongo/db/ops/update_result.h @@ -31,7 +31,6 @@ #include "mongo/db/jsobj.h" #include "mongo/db/curop.h" #include "mongo/db/namespace_string.h" -#include "mongo/db/query_plan_selection_policy.h" #include "mongo/util/mongoutils/str.h" namespace mongo { diff --git a/src/mongo/db/pdfile.cpp b/src/mongo/db/pdfile.cpp index 34f55a24d3d..8cdd74e6aef 100644 --- a/src/mongo/db/pdfile.cpp +++ b/src/mongo/db/pdfile.cpp @@ -337,86 +337,6 @@ namespace mongo { DataFileMgr::DataFileMgr(){} - shared_ptr<Cursor> DataFileMgr::findAll(const StringData& ns, const DiskLoc &startLoc) { - Database* db = cc().database(); - Collection* collection = db->getCollection( ns ); - if ( !collection ) - return shared_ptr<Cursor>(new BasicCursor(DiskLoc())); - NamespaceDetails* d = collection->details(); - DiskLoc loc = d->firstExtent(); - if ( loc.isNull() ) - return shared_ptr<Cursor>(new BasicCursor(DiskLoc())); - Extent *e = getExtent(loc); - - DEBUGGING { - out() << "listing extents for " << ns << endl; - DiskLoc tmp = loc; - set<DiskLoc> extents; - - while ( 1 ) { - Extent *f = db->getExtentManager().getExtent(tmp); - out() << "extent: " << tmp.toString() << endl; - extents.insert(tmp); - tmp = f->xnext; - if ( tmp.isNull() ) - break; - f = db->getExtentManager().getNextExtent( f ); - } - - out() << endl; - d->dumpDeleted(&extents); - } - - if ( d->isCapped() ) - return shared_ptr<Cursor>( ForwardCappedCursor::make( d , startLoc ) ); - - if ( !startLoc.isNull() ) - return shared_ptr<Cursor>(new BasicCursor( startLoc )); - - while ( e->firstRecord.isNull() && !e->xnext.isNull() ) { - /* todo: if extent is empty, free it for reuse elsewhere. - that is a bit complicated have to clean up the freelists. - */ - RARELY out() << "info DFM::findAll(): extent " << loc.toString() << " was empty, skipping ahead. ns:" << ns << endl; - // find a nonempty extent - // it might be nice to free the whole extent here! but have to clean up free recs then. - e = db->getExtentManager().getNextExtent( e ); - } - return shared_ptr<Cursor>(new BasicCursor( e->firstRecord )); - } - - /* get a table scan cursor, but can be forward or reverse direction. - order.$natural - if set, > 0 means forward (asc), < 0 backward (desc). - */ - shared_ptr<Cursor> findTableScan(const char *ns, const BSONObj& order, const DiskLoc &startLoc) { - BSONElement el = order.getField("$natural"); // e.g., { $natural : -1 } - - if ( el.number() >= 0 ) - return DataFileMgr::findAll(ns, startLoc); - - // "reverse natural order" - Database* db = cc().database(); - Collection* collection = db->getCollection( ns ); - if ( !collection ) - return shared_ptr<Cursor>(new BasicCursor(DiskLoc())); - - NamespaceDetails* d = collection->details(); - - if ( !d->isCapped() ) { - if ( !startLoc.isNull() ) - return shared_ptr<Cursor>(new ReverseCursor( startLoc )); - Extent *e = d->lastExtent().ext(); - while ( e->lastRecord.isNull() && !e->xprev.isNull() ) { - OCCASIONALLY out() << " findTableScan: extent empty, skipping ahead" << endl; - e = db->getExtentManager().getPrevExtent(e); - } - return shared_ptr<Cursor>(new ReverseCursor( e->lastRecord )); - } - else { - return shared_ptr<Cursor>( new ReverseCappedCursor( d, startLoc ) ); - } - } - /* deletes a record, just the pdfile portion -- no index cleanup, no cursor cleanup, etc. caller must check if capped */ diff --git a/src/mongo/db/pdfile.h b/src/mongo/db/pdfile.h index 5968e4e9e7c..98aaa154b18 100644 --- a/src/mongo/db/pdfile.h +++ b/src/mongo/db/pdfile.h @@ -38,7 +38,6 @@ #pragma once #include "mongo/db/client.h" -#include "mongo/db/cursor.h" #include "mongo/db/database.h" #include "mongo/db/diskloc.h" #include "mongo/db/jsobjmanipulator.h" @@ -55,7 +54,6 @@ namespace mongo { - class Cursor; class DataFileHeader; class Extent; class OpDebug; @@ -66,7 +64,6 @@ namespace mongo { bool repairDatabase(string db, string &errmsg, bool preserveClonedFilesOnFailure = false, bool backupOriginalFiles = false); bool userCreateNS(const char *ns, BSONObj j, string& err, bool logForReplication, bool *deferIdIndex = 0); - shared_ptr<Cursor> findTableScan(const char *ns, const BSONObj& order, const DiskLoc &startLoc=DiskLoc()); bool isValidNS( const StringData& ns ); @@ -109,7 +106,6 @@ namespace mongo { bool god = false, bool mayAddIndex = true, bool* addedID = 0); - static shared_ptr<Cursor> findAll(const StringData& ns, const DiskLoc &startLoc = DiskLoc()); /* special version of insert for transaction logging -- streamlined a bit. assumes ns is capped and no indexes diff --git a/src/mongo/db/pipeline/pipeline_d.cpp b/src/mongo/db/pipeline/pipeline_d.cpp index e633edcd398..30ce9c29962 100644 --- a/src/mongo/db/pipeline/pipeline_d.cpp +++ b/src/mongo/db/pipeline/pipeline_d.cpp @@ -31,7 +31,6 @@ #include "mongo/db/pipeline/pipeline_d.h" #include "mongo/client/dbclientinterface.h" -#include "mongo/db/cursor.h" #include "mongo/db/instance.h" #include "mongo/db/parsed_query.h" #include "mongo/db/pipeline/document_source.h" diff --git a/src/mongo/db/query_optimizer.cpp b/src/mongo/db/query_optimizer.cpp deleted file mode 100644 index f514f77f3f3..00000000000 --- a/src/mongo/db/query_optimizer.cpp +++ /dev/null @@ -1,84 +0,0 @@ -/** - * Copyright (C) 2011 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/>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the GNU Affero General Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#include "mongo/db/query_optimizer.h" - -#include "mongo/db/query_optimizer_internal.h" -#include "mongo/db/queryoptimizercursorimpl.h" -#include "mongo/db/queryutil.h" - -namespace mongo { - - shared_ptr<Cursor> getOptimizedCursor( const StringData& ns, - const BSONObj& query, - const BSONObj& order, - const QueryPlanSelectionPolicy& planPolicy, - const shared_ptr<const ParsedQuery>& parsedQuery, - bool requireOrder, - QueryPlanSummary* singlePlanSummary ) { - CursorGenerator generator( ns, - query, - order, - planPolicy, - parsedQuery, - requireOrder, - singlePlanSummary ); - return generator.generate(); - } - - shared_ptr<Cursor> getBestGuessCursor( const char* ns, - const BSONObj& query, - const BSONObj& sort ) { - - auto_ptr<FieldRangeSetPair> frsp( new FieldRangeSetPair( ns, query, true ) ); - auto_ptr<FieldRangeSetPair> origFrsp( new FieldRangeSetPair( *frsp ) ); - - scoped_ptr<QueryPlanSet> qps( QueryPlanSet::make( ns, - frsp, - origFrsp, - query, - sort, - shared_ptr<const ParsedQuery>(), - BSONObj(), - QueryPlanGenerator::UseIfInOrder, - BSONObj(), - BSONObj(), - true ) ); - QueryPlanSet::QueryPlanPtr qpp = qps->getBestGuess(); - if( ! qpp.get() ) return shared_ptr<Cursor>(); - - shared_ptr<Cursor> ret = qpp->newCursor(); - - // If we don't already have a matcher, supply one. - if ( !query.isEmpty() && ! ret->matcher() ) { - ret->setMatcher( qpp->matcher() ); - } - return ret; - } - -} // namespace mongo; diff --git a/src/mongo/db/query_optimizer.h b/src/mongo/db/query_optimizer.h deleted file mode 100644 index 1a1c0a24afe..00000000000 --- a/src/mongo/db/query_optimizer.h +++ /dev/null @@ -1,96 +0,0 @@ -/** - * Copyright (C) 2008 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/>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the GNU Affero General Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#pragma once - -#include "mongo/db/query_plan_selection_policy.h" - -namespace mongo { - - class Cursor; - class ParsedQuery; - struct QueryPlanSummary; - - /** - * @return a cursor interface to the query optimizer. The implementation may utilize a - * single query plan or interleave results from multiple query plans before settling on a - * single query plan. Note that the schema of currKey() documents, indexKeyPattern(), the - * matcher(), and the isMultiKey() nature of the cursor may change over the course of - * iteration. - * - * @param query - Query used to select indexes and populate matchers; not copied if unowned - * (see bsonobj.h). - * - * @param order - Required ordering spec for documents produced by this cursor, empty object - * default indicates no order requirement. If no index exists that satisfies the required - * sort order, an empty shared_ptr is returned unless parsedQuery is also provided. This is - * not copied if unowned. - * - * @param planPolicy - A policy for selecting query plans - see queryoptimizercursor.h - * - * @param parsedQuery - Additional query parameters, as from a client query request. - * - * @param requireOrder - If false, the resulting cursor may return results in an order - * inconsistent with the @param order spec. See queryoptimizercursor.h for information on - * handling these results properly. - * - * @param singlePlanSummary - Query plan summary information that may be provided when a - * cursor running a single plan is returned. - * - * The returned cursor may @throw inside of advance() or recoverFromYield() in certain error - * cases, for example if a capped overrun occurred during a yield. This indicates that the - * cursor was unable to perform a complete scan. - * - * This is a work in progress. Partial list of features not yet implemented through this - * interface: - * - * - covered indexes - * - in memory sorting - */ - shared_ptr<Cursor> getOptimizedCursor( const StringData& ns, - const BSONObj& query, - const BSONObj& order = BSONObj(), - const QueryPlanSelectionPolicy& planPolicy = - QueryPlanSelectionPolicy::any(), - const shared_ptr<const ParsedQuery>& parsedQuery = - shared_ptr<const ParsedQuery>(), - bool requireOrder = true, - QueryPlanSummary* singlePlanSummary = NULL ); - - /** - * @return a single cursor that may work well for the given query. A $or style query will - * produce a single cursor, not a MultiCursor. - * It is possible no cursor is returned if the sort is not supported by an index. Clients - * are responsible for checking this if they are not sure an index for a sort exists, and - * defaulting to a non-sort if no suitable indices exist. - */ - shared_ptr<Cursor> getBestGuessCursor( const char* ns, - const BSONObj& query, - const BSONObj& sort ); - -} // namespace mongo diff --git a/src/mongo/db/query_optimizer_internal.cpp b/src/mongo/db/query_optimizer_internal.cpp deleted file mode 100644 index df1cf1aaecf..00000000000 --- a/src/mongo/db/query_optimizer_internal.cpp +++ /dev/null @@ -1,1645 +0,0 @@ -/** - * Copyright (C) 2008 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/>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the GNU Affero General Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#include "mongo/pch.h" - -#include "mongo/db/query_optimizer_internal.h" - -#include "mongo/client/dbclientinterface.h" -#include "mongo/db/db.h" -#include "mongo/db/index/catalog_hack.h" -#include "mongo/db/index_selection.h" -#include "mongo/db/pagefault.h" -#include "mongo/db/parsed_query.h" -#include "mongo/db/query_plan_selection_policy.h" -#include "mongo/db/queryutil.h" -#include "mongo/db/structure/collection.h" - -//#define DEBUGQO(x) cout << x << endl; -#define DEBUGQO(x) - -namespace mongo { - - // returns an IndexDetails* for a hint, 0 if hint is $natural. - // hint must not be eoo() - IndexDetails* parseHint( const BSONElement& hint, NamespaceDetails* d ) { - massert( 13292, "hint eoo", !hint.eoo() ); - if ( hint.type() == String ) { - string hintstr = hint.valuestr(); - NamespaceDetails::IndexIterator i = d->ii(); - while( i.more() ) { - IndexDetails& ii = i.next(); - if ( ii.indexName() == hintstr ) { - return ⅈ - } - } - } - else if ( hint.type() == Object ) { - BSONObj hintobj = hint.embeddedObject(); - uassert( 10112, "bad hint", !hintobj.isEmpty() ); - if ( !strcmp( hintobj.firstElementFieldName(), "$natural" ) ) { - return 0; - } - NamespaceDetails::IndexIterator i = d->ii(); - while( i.more() ) { - IndexDetails& ii = i.next(); - if( ii.keyPattern().woCompare(hintobj) == 0 ) { - return ⅈ - } - } - } - uassert( 10113, "bad hint", false ); - return 0; - } - - CachedMatchCounter::CachedMatchCounter( long long& aggregateNscanned, - int cumulativeCount ) : - _aggregateNscanned( aggregateNscanned ), - _nscanned(), - _cumulativeCount( cumulativeCount ), - _count(), - _checkDups(), - _match( Unknown ), - _counted() { - } - - void CachedMatchCounter::resetMatch() { - _match = Unknown; - _counted = false; - } - - bool CachedMatchCounter::setMatch( bool match ) { - MatchState oldMatch = _match; - _match = match ? True : False; - return _match == True && oldMatch != True; - } - - void CachedMatchCounter::incMatch( const DiskLoc& loc ) { - if ( !_counted && _match == True && !getsetdup( loc ) ) { - ++_cumulativeCount; - ++_count; - _counted = true; - } - } - - bool CachedMatchCounter::wouldIncMatch( const DiskLoc& loc ) const { - return !_counted && _match == True && !getdup( loc ); - } - - bool CachedMatchCounter::enoughCumulativeMatchesToChooseAPlan() const { - // This is equivalent to the default condition for switching from - // a query to a getMore, which was the historical default match count for - // choosing a plan. - return _cumulativeCount >= 101; - } - - bool CachedMatchCounter::enoughMatchesToRecordPlan() const { - // Recording after 50 matches is a historical default (101 default limit / 2). - return _count > 50; - } - - void CachedMatchCounter::updateNscanned( long long nscanned ) { - _aggregateNscanned += ( nscanned - _nscanned ); - _nscanned = nscanned; - } - - bool CachedMatchCounter::getsetdup( const DiskLoc& loc ) { - if ( !_checkDups ) { - return false; - } - pair<set<DiskLoc>::iterator, bool> p = _dups.insert( loc ); - return !p.second; - } - - bool CachedMatchCounter::getdup( const DiskLoc& loc ) const { - if ( !_checkDups ) { - return false; - } - return _dups.find( loc ) != _dups.end(); - } - - QueryPlanRunner::QueryPlanRunner( long long& aggregateNscanned, - const QueryPlanSelectionPolicy& selectionPolicy, - const bool& requireOrder, - bool alwaysCountMatches, - int cumulativeCount ) : - _complete(), - _stopRequested(), - _queryPlan(), - _error(), - _matchCounter( aggregateNscanned, cumulativeCount ), - _countingMatches(), - _mustAdvance(), - _capped(), - _selectionPolicy( selectionPolicy ), - _requireOrder( requireOrder ), - _alwaysCountMatches( alwaysCountMatches ) { - } - - void QueryPlanRunner::next() { - checkCursorOrdering(); - - mayAdvance(); - - if ( countMatches() && _matchCounter.enoughCumulativeMatchesToChooseAPlan() ) { - setStop(); - if ( _explainPlanInfo ) _explainPlanInfo->notePicked(); - return; - } - if ( !_c || !_c->ok() ) { - if ( _explainPlanInfo && _c ) _explainPlanInfo->noteDone( *_c ); - setComplete(); - return; - } - - _mustAdvance = true; - } - - long long QueryPlanRunner::nscanned() const { - return _c ? _c->nscanned() : _matchCounter.nscanned(); - } - - void QueryPlanRunner::prepareToYield() { - if ( _c && !_cc ) { - _cc.reset( new ClientCursor( QueryOption_NoCursorTimeout, _c, queryPlan().ns() ) ); - // Set 'doing deletes' as deletes may occur; if there are no deletes this has no - // effect. - _cc->setDoingDeletes( true ); - } - if ( _cc ) { - recordCursorLocation(); - _cc->prepareToYield( _yieldData ); - } - } - - void QueryPlanRunner::recoverFromYield() { - if ( _cc && !ClientCursor::recoverFromYield( _yieldData ) ) { - // !!! The collection may be gone, and any namespace or index specific memory may - // have become invalid. - _c.reset(); - _cc.reset(); - - if ( _capped ) { - msgassertedNoTrace( 13338, - str::stream() << "capped cursor overrun: " - << queryPlan().ns() ); - } - msgassertedNoTrace( 15892, - str::stream() << - "QueryPlanRunner::recoverFromYield() failed to recover" ); - } - else { - checkCursorAdvanced(); - } - } - - void QueryPlanRunner::prepareToTouchEarlierIterate() { - recordCursorLocation(); - if ( _c ) { - _c->prepareToTouchEarlierIterate(); - } - } - - void QueryPlanRunner::recoverFromTouchingEarlierIterate() { - if ( _c ) { - _c->recoverFromTouchingEarlierIterate(); - } - checkCursorAdvanced(); - } - - bool QueryPlanRunner::currentMatches( MatchDetails* details ) { - if ( !_c || !_c->ok() ) { - _matchCounter.setMatch( false ); - return false; - } - - MatchDetails myDetails; - if ( !details && _explainPlanInfo ) { - details = &myDetails; - } - - bool match = queryPlan().matcher()->matchesCurrent( _c.get(), details ); - // Cache the match, so we can count it in mayAdvance(). - bool newMatch = _matchCounter.setMatch( match ); - - if ( _explainPlanInfo ) { - // Note iterate results as if this is the only query plan running. But do not account - // for query parameters that may be appled to the whole result set (results from - // interleaved plans), for example the 'skip' parameter. - bool countableMatch = newMatch && _matchCounter.wouldIncMatch( _c->currLoc() ); - bool matchWouldBeLoadedForReturn = countableMatch && hasDocumentLoadingQueryPlan(); - _explainPlanInfo->noteIterate( countableMatch, - details->hasLoadedRecord() || - matchWouldBeLoadedForReturn, - *_c ); - } - - return match; - } - - bool QueryPlanRunner::mayRecordPlan() const { - return complete() && ( !stopRequested() || _matchCounter.enoughMatchesToRecordPlan() ); - } - - QueryPlanRunner* QueryPlanRunner::createChild() const { - return new QueryPlanRunner( _matchCounter.aggregateNscanned(), - _selectionPolicy, - _requireOrder, - _alwaysCountMatches, - _matchCounter.cumulativeCount() ); - } - - void QueryPlanRunner::setQueryPlan( const QueryPlan* queryPlan ) { - _queryPlan = queryPlan; - verify( _queryPlan != NULL ); - } - - void QueryPlanRunner::init() { - checkCursorOrdering(); - if ( !_selectionPolicy.permitPlan( queryPlan() ) ) { - throw MsgAssertionException( 9011, - str::stream() - << "Plan not permitted by query plan selection policy '" - << _selectionPolicy.name() - << "'" ); - } - - _c = queryPlan().newCursor(); - // The basic and btree cursors used by this implementation do not supply their own - // matchers, and a matcher from a query plan will be used instead. - verify( !_c->matcher() ); - // Such cursors all support deduplication. - verify( _c->autoDedup() ); - - // The query plan must have a matcher. The matcher's constructor performs some aspects - // of query validation that should occur as part of this class's init() if not handled - // already. - fassert( 16249, queryPlan().matcher() ); - - // All candidate cursors must support yields for QueryOptimizerCursorImpl's - // prepareToYield() and prepareToTouchEarlierIterate() to work. - verify( _c->supportYields() ); - _capped = _c->capped(); - - // TODO This violates the current Cursor interface abstraction, but for now it's simpler to keep our own set of - // dups rather than avoid poisoning the cursor's dup set with unreturned documents. Deduping documents - // matched in this QueryOptimizerCursorOp will run against the takeover cursor. - _matchCounter.setCheckDups( countMatches() && _c->isMultiKey() ); - // TODO ok if cursor becomes multikey later? - - _matchCounter.updateNscanned( _c->nscanned() ); - } - - void QueryPlanRunner::setException( const DBException& e ) { - _error = true; - _exception = e.getInfo(); - } - - shared_ptr<ExplainPlanInfo> QueryPlanRunner::generateExplainInfo() { - if ( !_c ) { - return shared_ptr<ExplainPlanInfo>( new ExplainPlanInfo() ); - } - _explainPlanInfo.reset( new ExplainPlanInfo() ); - _explainPlanInfo->notePlan( *_c, - queryPlan().scanAndOrderRequired(), - queryPlan().keyFieldsOnly() ); - return _explainPlanInfo; - } - - void QueryPlanRunner::mayAdvance() { - if ( !_c ) { - return; - } - if ( countingMatches() ) { - // Check match if not yet known. - if ( !_matchCounter.knowMatch() ) { - currentMatches( 0 ); - } - _matchCounter.incMatch( currLoc() ); - } - if ( _mustAdvance ) { - _c->advance(); - handleCursorAdvanced(); - } - _matchCounter.updateNscanned( _c->nscanned() ); - } - - bool QueryPlanRunner::countingMatches() { - if ( _countingMatches ) { - return true; - } - if ( countMatches() ) { - // Only count matches after the first call to next(), which occurs before the first - // result is returned. - _countingMatches = true; - } - return false; - } - - bool QueryPlanRunner::countMatches() const { - return _alwaysCountMatches || !queryPlan().scanAndOrderRequired(); - } - - bool QueryPlanRunner::hasDocumentLoadingQueryPlan() const { - if ( queryPlan().parsedQuery() && queryPlan().parsedQuery()->returnKey() ) { - // Index keys will be returned using $returnKey. - return false; - } - if ( queryPlan().scanAndOrderRequired() ) { - // The in memory sort implementation operates on full documents. - return true; - } - if ( keyFieldsOnly() ) { - // A covered index projection will be used. - return false; - } - // Documents will be loaded for a standard query. - return true; - } - - void QueryPlanRunner::recordCursorLocation() { - _posBeforeYield = currLoc(); - } - - void QueryPlanRunner::checkCursorAdvanced() { - // This check will not correctly determine if we are looking at a different document in - // all cases, but it is adequate for updating the query plan's match count (just used to pick - // plans, not returned to the client) and adjust iteration via _mustAdvance. - if ( _posBeforeYield != currLoc() ) { - // If the yield advanced our position, the next next() will be a no op. - handleCursorAdvanced(); - } - } - - void QueryPlanRunner::handleCursorAdvanced() { - _mustAdvance = false; - _matchCounter.resetMatch(); - } - - void QueryPlanRunner::checkCursorOrdering() { - if ( _requireOrder && queryPlan().scanAndOrderRequired() ) { - throw MsgAssertionException( OutOfOrderDocumentsAssertionCode, "order spec cannot be satisfied with index" ); - } - } - - QueryPlanGenerator::QueryPlanGenerator( QueryPlanSet& qps, - auto_ptr<FieldRangeSetPair> originalFrsp, - const shared_ptr<const ParsedQuery>& parsedQuery, - const BSONObj& hint, - RecordedPlanPolicy recordedPlanPolicy, - const BSONObj& min, - const BSONObj& max, - bool allowSpecial ) : - _qps( qps ), - _originalFrsp( originalFrsp ), - _parsedQuery( parsedQuery ), - _hint( hint.getOwned() ), - _recordedPlanPolicy( recordedPlanPolicy ), - _min( min.getOwned() ), - _max( max.getOwned() ), - _allowSpecial( allowSpecial ) { - } - - void QueryPlanGenerator::addInitialPlans() { - const char* ns = _qps.frsp().ns(); - NamespaceDetails* d = nsdetails( ns ); - - if ( addShortCircuitPlan( d ) ) { - return; - } - - addStandardPlans( d ); - warnOnCappedIdTableScan(); - } - - void QueryPlanGenerator::addFallbackPlans() { - const char* ns = _qps.frsp().ns(); - NamespaceDetails* d = nsdetails( ns ); - verify( d ); - - vector<shared_ptr<QueryPlan> > plans; - shared_ptr<QueryPlan> optimalPlan; - shared_ptr<QueryPlan> specialPlan; - for( int i = 0; i < d->getCompletedIndexCount(); ++i ) { - - if ( !QueryUtilIndexed::indexUseful( _qps.frsp(), d, i, _qps.order() ) ) { - continue; - } - - shared_ptr<QueryPlan> p = newPlan( d, i ); - switch( p->utility() ) { - case QueryPlan::Impossible: - _qps.setSinglePlan( p ); - return; - case QueryPlan::Optimal: - if ( !optimalPlan ) { - optimalPlan = p; - } - break; - case QueryPlan::Helpful: - if ( p->special().empty() ) { - // Not a 'special' plan. - plans.push_back( p ); - } - else if ( _allowSpecial ) { - specialPlan = p; - } - break; - default: - break; - } - } - - if ( optimalPlan ) { - _qps.setSinglePlan( optimalPlan ); - // Record an optimal plan in the query cache immediately, with a small nscanned value - // that will be ignored. - optimalPlan->registerSelf - ( 0, CandidatePlanCharacter( !optimalPlan->scanAndOrderRequired(), - optimalPlan->scanAndOrderRequired() ) ); - return; - } - - // Only add a special plan if no standard btree plans have been added. SERVER-4531 - if ( plans.empty() && specialPlan ) { - _qps.setSinglePlan( specialPlan ); - return; - } - - for( vector<shared_ptr<QueryPlan> >::const_iterator i = plans.begin(); i != plans.end(); - ++i ) { - _qps.addCandidatePlan( *i ); - } - - _qps.addCandidatePlan( newPlan( d, -1 ) ); - } - - bool QueryPlanGenerator::addShortCircuitPlan( NamespaceDetails* d ) { - return - // The collection is missing. - setUnindexedPlanIf( !d, d ) || - // No match is possible. - setUnindexedPlanIf( !_qps.frsp().matchPossible(), d ) || - // The hint, min, or max parameters are specified. - addHintPlan( d ) || - // A special index operation is requested. - addSpecialPlan( d ) || - // No indexable ranges or ordering are specified. - setUnindexedPlanIf( _qps.frsp().noNonUniversalRanges() && _qps.order().isEmpty(), d ) || - // $natural sort is requested. - setUnindexedPlanIf( !_qps.order().isEmpty() && - str::equals( _qps.order().firstElementFieldName(), "$natural" ), - d ); - } - - bool QueryPlanGenerator::addHintPlan( NamespaceDetails* d ) { - BSONElement hint = _hint.firstElement(); - if ( !hint.eoo() ) { - IndexDetails* id = parseHint( hint, d ); - if ( id ) { - setHintedPlanForIndex( *id ); - } - else { - uassert( 10366, "natural order cannot be specified with $min/$max", - _min.isEmpty() && _max.isEmpty() ); - setSingleUnindexedPlan( d ); - } - return true; - } - - if ( !_min.isEmpty() || !_max.isEmpty() ) { - string errmsg; - BSONObj keyPattern; - IndexDetails *idx = indexDetailsForRange( _qps.frsp().ns(), - errmsg, - _min, - _max, - keyPattern ); - uassert( 10367, errmsg, idx ); - validateAndSetHintedPlan( newPlan( d, d->idxNo( *idx ), _min, _max ) ); - return true; - } - - return false; - } - - bool QueryPlanGenerator::addSpecialPlan( NamespaceDetails* d ) { - DEBUGQO( "\t special : " << _qps.frsp().getSpecial().toString() ); - SpecialIndices special = _qps.frsp().getSpecial(); - if (!special.empty()) { - // Try to handle the special part of the query with an index - NamespaceDetails::IndexIterator i = d->ii(); - while( i.more() ) { - int j = i.pos(); - IndexDetails& ii = i.next(); - BSONObj keyPattern = ii.keyPattern(); - string pluginName = CatalogHack::getAccessMethodName(keyPattern); - if (special.has(pluginName) && - (USELESS != IndexSelection::isSuitableFor(keyPattern, - _qps.frsp().frsForIndex(d, j), _qps.order()))) { - uassert( 16330, "'special' query operator not allowed", _allowSpecial ); - _qps.setSinglePlan( newPlan( d, j, BSONObj(), BSONObj(), pluginName)); - return true; - } - } - // If all possible special indices require an index and we don't have one, - // error. - if (special.allRequireIndex()) { - uassert(13038, "can't find any special indices: " + special.toString() - + " for: " + _qps.originalQuery().toString(), false ); - } - // Otherwise, we can get the same functionality from the matcher. - } - return false; - } - - void QueryPlanGenerator::addStandardPlans( NamespaceDetails* d ) { - if ( !addCachedPlan( d ) ) { - addFallbackPlans(); - } - } - - bool QueryPlanGenerator::addCachedPlan( NamespaceDetails* d ) { - if ( _recordedPlanPolicy == Ignore ) { - return false; - } - - CachedQueryPlan best = QueryUtilIndexed::bestIndexForPatterns( _qps.frsp(), _qps.order() ); - BSONObj bestIndex = best.indexKey(); - if ( bestIndex.isEmpty() ) { - return false; - } - - shared_ptr<QueryPlan> p; - if ( str::equals( bestIndex.firstElementFieldName(), "$natural" ) ) { - p = newPlan( d, -1 ); - } - - NamespaceDetails::IndexIterator i = d->ii(); - while( i.more() ) { - int j = i.pos(); - IndexDetails& ii = i.next(); - if( ii.keyPattern().woCompare(bestIndex) == 0 ) { - p = newPlan( d, j ); - } - } - - massert( 10368, "Unable to locate previously recorded index", p ); - - if ( p->utility() == QueryPlan::Unhelpful || - p->utility() == QueryPlan::Disallowed ) { - return false; - } - - if ( _recordedPlanPolicy == UseIfInOrder && p->scanAndOrderRequired() ) { - return false; - } - - if ( !_allowSpecial && !p->special().empty() ) { - return false; - } - - _qps.setCachedPlan( p, best ); - return true; - } - - shared_ptr<QueryPlan> QueryPlanGenerator::newPlan( NamespaceDetails* d, - int idxNo, - const BSONObj& min, - const BSONObj& max, - const string& special ) const { - shared_ptr<QueryPlan> ret( QueryPlan::make( d, - idxNo, - _qps.frsp(), - _originalFrsp.get(), - _qps.originalQuery(), - _qps.order(), - _parsedQuery, - min, - max, - special ) ); - return ret; - } - - bool QueryPlanGenerator::setUnindexedPlanIf( bool set, NamespaceDetails* d ) { - if ( set ) { - setSingleUnindexedPlan( d ); - } - return set; - } - - void QueryPlanGenerator::setSingleUnindexedPlan( NamespaceDetails* d ) { - _qps.setSinglePlan( newPlan( d, -1 ) ); - } - - void QueryPlanGenerator::setHintedPlanForIndex( IndexDetails& id ) { - if ( !_min.isEmpty() || !_max.isEmpty() ) { - string errmsg; - BSONObj keyPattern = id.keyPattern(); - // This reformats _min and _max to be used for index lookup. - massert( 10365 , errmsg, indexDetailsForRange( _qps.frsp().ns(), - errmsg, - _min, - _max, - keyPattern ) ); - } - NamespaceDetails* d = nsdetails( _qps.frsp().ns() ); - validateAndSetHintedPlan( newPlan( d, d->idxNo( id ), _min, _max ) ); - } - - void QueryPlanGenerator::validateAndSetHintedPlan( const shared_ptr<QueryPlan>& plan ) { - uassert( 16331, "'special' plan hint not allowed", - _allowSpecial || plan->special().empty() ); - _qps.setSinglePlan( plan ); - } - - void QueryPlanGenerator::warnOnCappedIdTableScan() const { - // if we are doing a table scan on _id - // and it's a capped collection - // we warn as it's a common user error - // .system. and local collections are exempt - const char* ns = _qps.frsp().ns(); - NamespaceDetails* d = nsdetails( ns ); - if ( d && - d->isCapped() && - _qps.nPlans() == 1 && - ( _qps.firstPlan()->utility() != QueryPlan::Impossible ) && - !_qps.firstPlan()->indexed() && - !_qps.firstPlan()->multikeyFrs().range( "_id" ).universal() ) { - if ( !str::contains( ns , ".system." ) && !str::startsWith( ns , "local." ) ) { - warning() << "unindexed _id query on capped collection, " - << "performance will be poor collection: " << ns << endl; - } - } - } - - QueryPlanSet* QueryPlanSet::make( const char* ns, - auto_ptr<FieldRangeSetPair> frsp, - auto_ptr<FieldRangeSetPair> originalFrsp, - const BSONObj& originalQuery, - const BSONObj& order, - const shared_ptr<const ParsedQuery>& parsedQuery, - const BSONObj& hint, - QueryPlanGenerator::RecordedPlanPolicy recordedPlanPolicy, - const BSONObj& min, - const BSONObj& max, - bool allowSpecial ) { - auto_ptr<QueryPlanSet> ret( new QueryPlanSet( ns, - frsp, - originalFrsp, - originalQuery, - order, - parsedQuery, - hint, - recordedPlanPolicy, - min, - max, - allowSpecial ) ); - ret->init(); - return ret.release(); - } - - - QueryPlanSet::QueryPlanSet( const char* ns, - auto_ptr<FieldRangeSetPair> frsp, - auto_ptr<FieldRangeSetPair> originalFrsp, - const BSONObj& originalQuery, - const BSONObj& order, - const shared_ptr<const ParsedQuery>& parsedQuery, - const BSONObj& hint, - QueryPlanGenerator::RecordedPlanPolicy recordedPlanPolicy, - const BSONObj& min, - const BSONObj& max, - bool allowSpecial ) : - _generator( *this, - originalFrsp, - parsedQuery, - hint, - recordedPlanPolicy, - min, - max, - allowSpecial ), - _originalQuery( originalQuery ), - _frsp( frsp ), - _mayRecordPlan(), - _usingCachedPlan(), - _order( order.getOwned() ), - _oldNScanned( 0 ), - _yieldSometimesTracker( 256, 20 ), - _allowSpecial( allowSpecial ) { - } - - bool QueryPlanSet::hasMultiKey() const { - for( PlanVector::const_iterator i = _plans.begin(); i != _plans.end(); ++i ) - if ( (*i)->isMultiKey() ) - return true; - return false; - } - - void QueryPlanSet::init() { - DEBUGQO( "QueryPlanSet::init " << ns << "\t" << _originalQuery ); - _plans.clear(); - _usingCachedPlan = false; - - _generator.addInitialPlans(); - } - - void QueryPlanSet::setSinglePlan( const QueryPlanPtr& plan ) { - if ( nPlans() == 0 ) { - pushPlan( plan ); - } - } - - void QueryPlanSet::setCachedPlan( const QueryPlanPtr& plan, - const CachedQueryPlan& cachedPlan ) { - verify( nPlans() == 0 ); - _usingCachedPlan = true; - _oldNScanned = cachedPlan.nScanned(); - _cachedPlanCharacter = cachedPlan.planCharacter(); - pushPlan( plan ); - } - - void QueryPlanSet::addCandidatePlan( const QueryPlanPtr& plan ) { - // If _plans is nonempty, the new plan may be supplementing a recorded plan at the first - // position of _plans. It must not duplicate the first plan. - if ( nPlans() > 0 && plan->indexKey() == firstPlan()->indexKey() ) { - return; - } - pushPlan( plan ); - _mayRecordPlan = true; - } - - void QueryPlanSet::addFallbackPlans() { - _generator.addFallbackPlans(); - _mayRecordPlan = true; - } - - void QueryPlanSet::pushPlan( const QueryPlanSet::QueryPlanPtr& plan ) { - verify( _allowSpecial || plan->special().empty() ); - _plans.push_back( plan ); - } - - bool QueryPlanSet::hasPossiblyExcludedPlans() const { - return - _usingCachedPlan && - ( nPlans() == 1 ) && - ( firstPlan()->utility() != QueryPlan::Optimal ); - } - - QueryPlanSet::QueryPlanPtr QueryPlanSet::getBestGuess() const { - verify( _plans.size() ); - if ( _plans[ 0 ]->scanAndOrderRequired() ) { - for ( unsigned i=1; i<_plans.size(); i++ ) { - if ( ! _plans[i]->scanAndOrderRequired() ) - return _plans[i]; - } - - warning() << "best guess query plan requested, but scan and order are required for all " - "plans " - << " query: " << _originalQuery - << " order: " << _order - << " choices: "; - - for ( unsigned i=0; i<_plans.size(); i++ ) - warning() << _plans[i]->indexKey() << " "; - warning() << endl; - - return QueryPlanPtr(); - } - return _plans[0]; - } - - bool QueryPlanSet::haveInOrderPlan() const { - for( PlanVector::const_iterator i = _plans.begin(); i != _plans.end(); ++i ) { - if ( !(*i)->scanAndOrderRequired() ) { - return true; - } - } - return false; - } - - bool QueryPlanSet::possibleInOrderPlan() const { - if ( haveInOrderPlan() ) { - return true; - } - return _cachedPlanCharacter.mayRunInOrderPlan(); - } - - bool QueryPlanSet::possibleOutOfOrderPlan() const { - for( PlanVector::const_iterator i = _plans.begin(); i != _plans.end(); ++i ) { - if ( (*i)->scanAndOrderRequired() ) { - return true; - } - } - return _cachedPlanCharacter.mayRunOutOfOrderPlan(); - } - - CandidatePlanCharacter QueryPlanSet::characterizeCandidatePlans() const { - return CandidatePlanCharacter( possibleInOrderPlan(), possibleOutOfOrderPlan() ); - } - - bool QueryPlanSet::prepareToRetryQuery() { - if ( !hasPossiblyExcludedPlans() || _plans.size() > 1 ) { - return false; - } - - // A cached plan was used, so clear the plan for this query pattern so the query may be - // retried without a cached plan. - QueryUtilIndexed::clearIndexesForPatterns( *_frsp, _order ); - init(); - return true; - } - - string QueryPlanSet::toString() const { - BSONArrayBuilder bab; - for( PlanVector::const_iterator i = _plans.begin(); i != _plans.end(); ++i ) { - bab << (*i)->toString(); - } - return bab.arr().jsonString(); - } - - MultiPlanScanner *MultiPlanScanner::make( const StringData& ns, - const BSONObj& query, - const BSONObj& order, - const shared_ptr<const ParsedQuery>& parsedQuery, - const BSONObj& hint, - QueryPlanGenerator::RecordedPlanPolicy - recordedPlanPolicy, - const BSONObj& min, - const BSONObj& max ) { - auto_ptr<MultiPlanScanner> ret( new MultiPlanScanner( ns, - query, - parsedQuery, - hint, - recordedPlanPolicy ) ); - ret->init( order, min, max ); - return ret.release(); - } - - shared_ptr<QueryPlanRunner> MultiPlanScanner::iterateRunnerQueue - ( QueryPlanRunner& originalRunner, bool retried ) { - - if ( _runnerQueue ) { - return _runnerQueue->next(); - } - - _runnerQueue.reset( new QueryPlanRunnerQueue( *_currentQps, originalRunner ) ); - shared_ptr<ExplainClauseInfo> explainClause; - if ( _explainQueryInfo ) { - explainClause = _runnerQueue->generateExplainInfo(); - } - - shared_ptr<QueryPlanRunner> runner = _runnerQueue->next(); - if ( runner->error() && - _currentQps->prepareToRetryQuery() ) { - - // Avoid an infinite loop here - this should never occur. - verify( !retried ); - _runnerQueue.reset(); - return iterateRunnerQueue( originalRunner, true ); - } - - if ( _explainQueryInfo ) { - _explainQueryInfo->addClauseInfo( explainClause ); - } - return runner; - } - - void MultiPlanScanner::updateCurrentQps( QueryPlanSet* qps ) { - _currentQps.reset( qps ); - _runnerQueue.reset(); - } - - QueryPlanRunnerQueue::QueryPlanRunnerQueue( QueryPlanSet& plans, - const QueryPlanRunner& prototypeRunner ) : - _prototypeRunner( prototypeRunner ), - _plans( plans ), - _done() { - } - - void QueryPlanRunnerQueue::prepareToYield() { - for( vector<shared_ptr<QueryPlanRunner> >::const_iterator i = _runners.begin(); - i != _runners.end(); ++i ) { - prepareToYieldRunner( **i ); - } - } - - void QueryPlanRunnerQueue::recoverFromYield() { - for( vector<shared_ptr<QueryPlanRunner> >::const_iterator i = _runners.begin(); - i != _runners.end(); ++i ) { - recoverFromYieldRunner( **i ); - } - } - - shared_ptr<QueryPlanRunner> QueryPlanRunnerQueue::init() { - massert( 10369, "no plans", _plans.plans().size() > 0 ); - - if ( _plans.plans().size() > 1 ) { - LOG(1) << " running multiple plans" << endl; - } - for( QueryPlanSet::PlanVector::const_iterator i = _plans.plans().begin(); - i != _plans.plans().end(); ++i ) { - shared_ptr<QueryPlanRunner> runner( _prototypeRunner.createChild() ); - runner->setQueryPlan( i->get() ); - _runners.push_back( runner ); - } - - // Initialize runners. - for( vector<shared_ptr<QueryPlanRunner> >::iterator i = _runners.begin(); - i != _runners.end(); ++i ) { - initRunner( **i ); - if ( _explainClauseInfo ) { - _explainClauseInfo->addPlanInfo( (*i)->generateExplainInfo() ); - } - } - - // See if an op has completed. - for( vector<shared_ptr<QueryPlanRunner> >::iterator i = _runners.begin(); - i != _runners.end(); ++i ) { - if ( (*i)->complete() ) { - return *i; - } - } - - // Put runnable ops in the priority queue. - for( vector<shared_ptr<QueryPlanRunner> >::iterator i = _runners.begin(); - i != _runners.end(); ++i ) { - if ( !(*i)->error() ) { - _queue.push( *i ); - } - } - - if ( _queue.empty() ) { - return _runners.front(); - } - - return shared_ptr<QueryPlanRunner>(); - } - - shared_ptr<QueryPlanRunner> QueryPlanRunnerQueue::next() { - verify( !done() ); - - if ( _runners.empty() ) { - shared_ptr<QueryPlanRunner> initialRet = init(); - if ( initialRet ) { - _done = true; - return initialRet; - } - } - - shared_ptr<QueryPlanRunner> ret; - do { - ret = _next(); - } while( ret->error() && !_queue.empty() ); - - if ( _queue.empty() ) { - _done = true; - } - - return ret; - } - - shared_ptr<QueryPlanRunner> QueryPlanRunnerQueue::_next() { - verify( !_queue.empty() ); - RunnerHolder holder = _queue.pop(); - QueryPlanRunner& runner = *holder._runner; - nextRunner( runner ); - if ( runner.complete() ) { - if ( _plans.mayRecordPlan() && runner.mayRecordPlan() ) { - runner.queryPlan().registerSelf( runner.nscanned(), - _plans.characterizeCandidatePlans() ); - } - _done = true; - return holder._runner; - } - if ( runner.error() ) { - return holder._runner; - } - if ( _plans.hasPossiblyExcludedPlans() && - runner.nscanned() > _plans.oldNScanned() * 10 ) { - verify( _plans.nPlans() == 1 && _plans.firstPlan()->special().empty() ); - holder._offset = -runner.nscanned(); - _plans.addFallbackPlans(); - QueryPlanSet::PlanVector::const_iterator i = _plans.plans().begin(); - ++i; - for( ; i != _plans.plans().end(); ++i ) { - shared_ptr<QueryPlanRunner> runner( _prototypeRunner.createChild() ); - runner->setQueryPlan( i->get() ); - _runners.push_back( runner ); - initRunner( *runner ); - if ( runner->complete() ) - return runner; - _queue.push( runner ); - } - _plans.setUsingCachedPlan( false ); - } - _queue.push( holder ); - return holder._runner; - } - -#define GUARD_RUNNER_EXCEPTION( runner, expression ) \ - try { \ - expression; \ - } \ - catch ( DBException& e ) { \ - runner.setException( e.getInfo() ); \ - } \ - catch ( const std::exception &e ) { \ - runner.setException( ExceptionInfo( e.what(), 0 ) ); \ - } \ - catch ( PageFaultException& pfe ) { \ - throw pfe; \ - } \ - catch ( ... ) { \ - runner.setException( ExceptionInfo( "Caught unknown exception", 0 ) ); \ - } - - - void QueryPlanRunnerQueue::initRunner( QueryPlanRunner &runner ) { - GUARD_RUNNER_EXCEPTION( runner, runner.init() ); - } - - void QueryPlanRunnerQueue::nextRunner( QueryPlanRunner& runner ) { - GUARD_RUNNER_EXCEPTION( runner, if ( !runner.error() ) { runner.next(); } ); - } - - void QueryPlanRunnerQueue::prepareToYieldRunner( QueryPlanRunner& runner ) { - GUARD_RUNNER_EXCEPTION( runner, if ( !runner.error() ) { runner.prepareToYield(); } ); - } - - void QueryPlanRunnerQueue::recoverFromYieldRunner( QueryPlanRunner& runner ) { - GUARD_RUNNER_EXCEPTION( runner, if ( !runner.error() ) { runner.recoverFromYield(); } ); - } - - /** - * NOTE on our $or implementation: In our current qo implementation we don't - * keep statistics on our data, but we can conceptualize the problem of - * selecting an index when statistics exist for all index ranges. The - * d-hitting set problem on k sets and n elements can be reduced to the - * problem of index selection on k $or clauses and n index ranges (where - * d is the max number of indexes, and the number of ranges n is unbounded). - * In light of the fact that d-hitting set is np complete, and we don't even - * track statistics (so cost calculations are expensive) our first - * implementation uses the following greedy approach: We take one $or clause - * at a time and treat each as a separate query for index selection purposes. - * But if an index range is scanned for a particular $or clause, we eliminate - * that range from all subsequent clauses. One could imagine an opposite - * implementation where we select indexes based on the union of index ranges - * for all $or clauses, but this can have much poorer worst case behavior. - * (An index range that suits one $or clause may not suit another, and this - * is worse than the typical case of index range choice staleness because - * with $or the clauses may likely be logically distinct.) The greedy - * implementation won't do any worse than all the $or clauses individually, - * and it can often do better. In the first cut we are intentionally using - * QueryPattern tracking to record successful plans on $or clauses for use by - * subsequent $or clauses, even though there may be a significant aggregate - * $nor component that would not be represented in QueryPattern. - */ - - MultiPlanScanner::MultiPlanScanner( const StringData& ns, - const BSONObj& query, - const shared_ptr<const ParsedQuery>& parsedQuery, - const BSONObj& hint, - QueryPlanGenerator::RecordedPlanPolicy recordedPlanPolicy ) : - _ns( ns.toString() ), - _or( !query.getField( "$or" ).eoo() ), - _query( query.getOwned() ), - _parsedQuery( parsedQuery ), - _i(), - _recordedPlanPolicy( recordedPlanPolicy ), - _hint( hint.getOwned() ), - _tableScanned(), - _doneRunners() { - } - - void MultiPlanScanner::init( const BSONObj& order, const BSONObj& min, const BSONObj& max ) { - if ( !order.isEmpty() || !min.isEmpty() || !max.isEmpty() ) { - _or = false; - } - if ( _or ) { - // Only construct an OrRangeGenerator if we may handle $or clauses. - _org.reset( new OrRangeGenerator( _ns.c_str(), _query ) ); - if ( !_org->getSpecial().empty() ) { - _or = false; - } - else if ( haveUselessOr() ) { - _or = false; - } - } - - // if _or == false, don't use or clauses for index selection - if ( !_or ) { - ++_i; - auto_ptr<FieldRangeSetPair> frsp( new FieldRangeSetPair( _ns.c_str(), _query, true ) ); - updateCurrentQps( QueryPlanSet::make( _ns.c_str(), - frsp, - auto_ptr<FieldRangeSetPair>(), - _query, - order, - _parsedQuery, - _hint, - _recordedPlanPolicy, - min, - max, - true ) ); - } - else { - BSONElement e = _query.getField( "$or" ); - massert( 13268, - "invalid $or spec", - e.type() == Array && e.embeddedObject().nFields() > 0 ); - handleBeginningOfClause(); - } - } - - void MultiPlanScanner::handleEndOfClause( const QueryPlan& clausePlan ) { - if ( clausePlan.willScanTable() ) { - _tableScanned = true; - } - else { - _org->popOrClause( clausePlan.nsd(), - clausePlan.idxNo(), - clausePlan.indexed() ? clausePlan.indexKey() : BSONObj() ); - } - } - - void MultiPlanScanner::handleBeginningOfClause() { - assertHasMoreClauses(); - ++_i; - auto_ptr<FieldRangeSetPair> frsp( _org->topFrsp() ); - auto_ptr<FieldRangeSetPair> originalFrsp( _org->topFrspOriginal() ); - updateCurrentQps( QueryPlanSet::make( _ns.c_str(), - frsp, - originalFrsp, - _query, - BSONObj(), - _parsedQuery, - _hint, - _recordedPlanPolicy, - BSONObj(), - BSONObj(), - // 'Special' plans are not supported within $or. - false ) ); - } - - bool MultiPlanScanner::mayHandleBeginningOfClause() { - if ( hasMoreClauses() ) { - handleBeginningOfClause(); - return true; - } - return false; - } - - shared_ptr<QueryPlanRunner> MultiPlanScanner::nextRunner() { - verify( !doneRunners() ); - shared_ptr<QueryPlanRunner> ret = _or ? nextRunnerOr() : nextRunnerSimple(); - if ( ret->error() || ret->complete() ) { - _doneRunners = true; - } - return ret; - } - - shared_ptr<QueryPlanRunner> MultiPlanScanner::nextRunnerSimple() { - return iterateRunnerQueue( *_baseRunner ); - } - - shared_ptr<QueryPlanRunner> MultiPlanScanner::nextRunnerOr() { - shared_ptr<QueryPlanRunner> runner; - do { - runner = nextRunnerSimple(); - if ( !runner->completeWithoutStop() ) { - return runner; - } - handleEndOfClause( runner->queryPlan() ); - _baseRunner = runner; - } while( mayHandleBeginningOfClause() ); - return runner; - } - - const QueryPlan *MultiPlanScanner::nextClauseBestGuessPlan( const QueryPlan& currentPlan ) { - assertHasMoreClauses(); - handleEndOfClause( currentPlan ); - if ( !hasMoreClauses() ) { - return 0; - } - handleBeginningOfClause(); - shared_ptr<QueryPlan> bestGuess = _currentQps->getBestGuess(); - verify( bestGuess ); - return bestGuess.get(); - } - - void MultiPlanScanner::prepareToYield() { - if ( _runnerQueue ) { - _runnerQueue->prepareToYield(); - } - } - - void MultiPlanScanner::recoverFromYield() { - if ( _runnerQueue ) { - _runnerQueue->recoverFromYield(); - } - } - - void MultiPlanScanner::clearRunnerQueue() { - if ( _runnerQueue ) { - _runnerQueue.reset(); - } - } - - int MultiPlanScanner::currentNPlans() const { - return _currentQps->nPlans(); - } - - const QueryPlan *MultiPlanScanner::singlePlan() const { - if ( _or || - _currentQps->nPlans() != 1 || - _currentQps->hasPossiblyExcludedPlans() ) { - return 0; - } - return _currentQps->firstPlan().get(); - } - - bool MultiPlanScanner::hasMoreClauses() const { - return _or ? ( !_tableScanned && !_org->orRangesExhausted() ) : _i == 0; - } - - bool MultiPlanScanner::haveUselessOr() const { - NamespaceDetails* nsd = nsdetails( _ns ); - if ( !nsd ) { - return true; - } - BSONElement hintElt = _hint.firstElement(); - if ( !hintElt.eoo() ) { - IndexDetails* id = parseHint( hintElt, nsd ); - if ( !id ) { - return true; - } - return QueryUtilIndexed::uselessOr( *_org, nsd, nsd->idxNo( *id ) ); - } - return QueryUtilIndexed::uselessOr( *_org, nsd, -1 ); - } - - BSONObj MultiPlanScanner::cachedPlanExplainSummary() const { - if ( _or || !_currentQps->usingCachedPlan() ) { - return BSONObj(); - } - QueryPlanSet::QueryPlanPtr plan = _currentQps->firstPlan(); - shared_ptr<Cursor> cursor = plan->newCursor(); - return BSON( "cursor" << cursor->toString() << - "indexBounds" << cursor->prettyIndexBounds() ); - } - - void MultiPlanScanner::clearIndexesForPatterns() const { - QueryUtilIndexed::clearIndexesForPatterns( _currentQps->frsp(), _currentQps->order() ); - } - - bool MultiPlanScanner::haveInOrderPlan() const { - return _or ? true : _currentQps->haveInOrderPlan(); - } - - bool MultiPlanScanner::possibleInOrderPlan() const { - return _or ? true : _currentQps->possibleInOrderPlan(); - } - - bool MultiPlanScanner::possibleOutOfOrderPlan() const { - return _or ? false : _currentQps->possibleOutOfOrderPlan(); - } - - string MultiPlanScanner::toString() const { - return BSON( - "or" << _or << - "currentQps" << _currentQps->toString() - ).jsonString(); - } - - MultiCursor::MultiCursor( auto_ptr<MultiPlanScanner> mps, - const shared_ptr<Cursor>& c, - const shared_ptr<CoveredIndexMatcher>& matcher, - const shared_ptr<ExplainPlanInfo>& explainPlanInfo, - const QueryPlanRunner& runner, - long long nscanned ) : - _mps( mps ), - _c( c ), - _matcher( matcher ), - _queryPlan( &runner.queryPlan() ), - _nscanned( nscanned ), - _explainPlanInfo( explainPlanInfo ) { - _mps->clearRunnerQueue(); - _mps->setRecordedPlanPolicy( QueryPlanGenerator::UseIfInOrder ); - if ( !ok() ) { - // If the supplied cursor is exhausted, try to advance it. - advance(); - } - } - - bool MultiCursor::advance() { - _c->advance(); - advanceExhaustedClauses(); - return ok(); - } - - void MultiCursor::recoverFromYield() { - Cursor::recoverFromYield(); - advanceExhaustedClauses(); - } - - void MultiCursor::advanceClause() { - _nscanned += _c->nscanned(); - if ( _explainPlanInfo ) _explainPlanInfo->noteDone( *_c ); - shared_ptr<FieldRangeVector> oldClauseFrv = _queryPlan->originalFrv(); - _queryPlan = _mps->nextClauseBestGuessPlan( *_queryPlan ); - if ( _queryPlan ) { - _matcher.reset( _matcher->nextClauseMatcher( oldClauseFrv, _queryPlan->indexKey() ) ); - _c = _queryPlan->newCursor(); - // The basic and btree cursors used by this implementation support deduplication. - verify( _c->autoDedup() ); - // All sub cursors must support yields. - verify( _c->supportYields() ); - if ( _explainPlanInfo ) { - _explainPlanInfo.reset( new ExplainPlanInfo() ); - _explainPlanInfo->notePlan( *_c, - _queryPlan->scanAndOrderRequired(), - _queryPlan->keyFieldsOnly() ); - shared_ptr<ExplainClauseInfo> clauseInfo( new ExplainClauseInfo() ); - clauseInfo->addPlanInfo( _explainPlanInfo ); - _mps->addClauseInfo( clauseInfo ); - } - } - } - - void MultiCursor::advanceExhaustedClauses() { - while( !ok() && _mps->hasMoreClauses() ) { - advanceClause(); - } - } - - void MultiCursor::noteIterate( bool match, bool loadedRecord ) { - if ( _explainPlanInfo ) _explainPlanInfo->noteIterate( match, loadedRecord, *_c ); - } - - bool indexWorks( const BSONObj& idxPattern, - const BSONObj& sampleKey, - int direction, - int firstSignificantField ) { - BSONObjIterator p( idxPattern ); - BSONObjIterator k( sampleKey ); - int i = 0; - while( 1 ) { - BSONElement pe = p.next(); - BSONElement ke = k.next(); - if ( pe.eoo() && ke.eoo() ) - return true; - if ( pe.eoo() || ke.eoo() ) - return false; - if ( strcmp( pe.fieldName(), ke.fieldName() ) != 0 ) - return false; - if ( ( i == firstSignificantField ) && !( ( direction > 0 ) == ( pe.number() > 0 ) ) ) - return false; - ++i; - } - return false; - } - - BSONObj extremeKeyForIndex( const BSONObj& idxPattern, int baseDirection ) { - BSONObjIterator i( idxPattern ); - BSONObjBuilder b; - while( i.moreWithEOO() ) { - BSONElement e = i.next(); - if ( e.eoo() ) - break; - int idxDirection = e.number() >= 0 ? 1 : -1; - int direction = idxDirection * baseDirection; - switch( direction ) { - case 1: - b.appendMaxKey( e.fieldName() ); - break; - case -1: - b.appendMinKey( e.fieldName() ); - break; - default: - verify( false ); - } - } - return b.obj(); - } - - pair<int,int> keyAudit( const BSONObj& min, const BSONObj& max ) { - int direction = 0; - int firstSignificantField = 0; - BSONObjIterator i( min ); - BSONObjIterator a( max ); - while( 1 ) { - BSONElement ie = i.next(); - BSONElement ae = a.next(); - if ( ie.eoo() && ae.eoo() ) - break; - if ( ie.eoo() || ae.eoo() || strcmp( ie.fieldName(), ae.fieldName() ) != 0 ) { - return make_pair( -1, -1 ); - } - int cmp = ie.woCompare( ae ); - if ( cmp < 0 ) - direction = 1; - if ( cmp > 0 ) - direction = -1; - if ( direction != 0 ) - break; - ++firstSignificantField; - } - return make_pair( direction, firstSignificantField ); - } - - pair<int,int> flexibleKeyAudit( const BSONObj& min, const BSONObj& max ) { - if ( min.isEmpty() || max.isEmpty() ) { - return make_pair( 1, -1 ); - } - else { - return keyAudit( min, max ); - } - } - - // NOTE min, max, and keyPattern will be updated to be consistent with the selected index. - IndexDetails* indexDetailsForRange( const char* ns, - string& errmsg, - BSONObj& min, - BSONObj& max, - BSONObj& keyPattern ) { - if ( min.isEmpty() && max.isEmpty() ) { - errmsg = "one of min or max must be specified"; - return 0; - } - - Client::Context ctx( ns ); - IndexDetails* id = 0; - NamespaceDetails* d = nsdetails( ns ); - if ( !d ) { - errmsg = "ns not found"; - return 0; - } - - pair<int,int> ret = flexibleKeyAudit( min, max ); - if ( ret == make_pair( -1, -1 ) ) { - errmsg = "min and max keys do not share pattern"; - return 0; - } - if ( keyPattern.isEmpty() ) { - NamespaceDetails::IndexIterator i = d->ii(); - while( i.more() ) { - IndexDetails& ii = i.next(); - if ( indexWorks( ii.keyPattern(), min.isEmpty() ? max : min, ret.first, ret.second ) ) { - if (CatalogHack::getAccessMethodName(ii.keyPattern()).empty()) { - id = ⅈ - keyPattern = ii.keyPattern(); - break; - } - } - } - - } - else { - if ( !indexWorks( keyPattern, min.isEmpty() ? max : min, ret.first, ret.second ) ) { - errmsg = "requested keyPattern does not match specified keys"; - return 0; - } - NamespaceDetails::IndexIterator i = d->ii(); - while( i.more() ) { - IndexDetails& ii = i.next(); - if( ii.keyPattern().woCompare(keyPattern) == 0 ) { - id = ⅈ - break; - } - if ( keyPattern.nFields() == 1 && ii.keyPattern().nFields() == 1 && - IndexDetails::isIdIndexPattern( keyPattern ) && - ii.isIdIndex() ) { - id = ⅈ - break; - } - - } - } - - if ( min.isEmpty() ) { - min = extremeKeyForIndex( keyPattern, -1 ); - } - else if ( max.isEmpty() ) { - max = extremeKeyForIndex( keyPattern, 1 ); - } - - if ( !id ) { - errmsg = str::stream() << "no index found for specified keyPattern: " - << keyPattern.toString() - << " min: " << min << " max: " << max; - return 0; - } - - min = min.extractFieldsUnDotted( keyPattern ); - max = max.extractFieldsUnDotted( keyPattern ); - - return id; - } - - bool QueryUtilIndexed::indexUseful( const FieldRangeSetPair& frsp, - NamespaceDetails* d, - int idxNo, - const BSONObj& order ) { - DEV frsp.assertValidIndex( d, idxNo ); - BSONObj keyPattern = d->idx( idxNo ).keyPattern(); - if ( !frsp.matchPossibleForIndex( d, idxNo, keyPattern ) ) { - // No matches are possible in the index so the index may be useful. - return true; - } - return USELESS != IndexSelection::isSuitableFor(keyPattern, frsp.frsForIndex( d , idxNo ) , - order ); - } - - void QueryUtilIndexed::clearIndexesForPatterns( const FieldRangeSetPair& frsp, - const BSONObj& order ) { - CachedQueryPlan noCachedPlan; - - Collection* collection = cc().database()->getCollection( frsp.ns() ); - if ( !collection ) - return; - - collection->infoCache()->registerCachedQueryPlanForPattern( frsp._singleKey.pattern( order ), - noCachedPlan ); - collection->infoCache()->registerCachedQueryPlanForPattern( frsp._multiKey.pattern( order ), - noCachedPlan ); - } - - CachedQueryPlan QueryUtilIndexed::bestIndexForPatterns( const FieldRangeSetPair& frsp, - const BSONObj& order ) { - - Collection* collection = cc().database()->getCollection( frsp.ns() ); - verify( collection ); - - // TODO Maybe it would make sense to return the index with the lowest - // nscanned if there are two possibilities. - { - QueryPattern pattern = frsp._singleKey.pattern( order ); - CachedQueryPlan cachedQueryPlan = collection->infoCache()->cachedQueryPlanForPattern( pattern ); - if ( !cachedQueryPlan.indexKey().isEmpty() ) { - return cachedQueryPlan; - } - } - { - QueryPattern pattern = frsp._multiKey.pattern( order ); - CachedQueryPlan cachedQueryPlan = collection->infoCache()->cachedQueryPlanForPattern( pattern ); - if ( !cachedQueryPlan.indexKey().isEmpty() ) { - return cachedQueryPlan; - } - } - return CachedQueryPlan(); - } - - bool QueryUtilIndexed::uselessOr( const OrRangeGenerator& org, - NamespaceDetails* d, - int hintIdx ) { - for( list<FieldRangeSetPair>::const_iterator i = org._originalOrSets.begin(); - i != org._originalOrSets.end(); - ++i ) { - if ( hintIdx != -1 ) { - if ( !indexUseful( *i, d, hintIdx, BSONObj() ) ) { - return true; - } - } - else { - bool useful = false; - for( int j = 0; j < d->getCompletedIndexCount(); ++j ) { - if ( indexUseful( *i, d, j, BSONObj() ) ) { - useful = true; - break; - } - } - if ( !useful ) { - return true; - } - } - } - return false; - } - -} // namespace mongo diff --git a/src/mongo/db/query_optimizer_internal.h b/src/mongo/db/query_optimizer_internal.h deleted file mode 100644 index b48f69774d2..00000000000 --- a/src/mongo/db/query_optimizer_internal.h +++ /dev/null @@ -1,822 +0,0 @@ -/** - * Copyright (C) 2008 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/>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the GNU Affero General Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#include "mongo/base/disallow_copying.h" -#include "mongo/db/clientcursor.h" -#include "mongo/db/cursor.h" -#include "mongo/db/explain.h" -#include "mongo/db/matcher.h" -#include "mongo/db/query_plan.h" -#include "mongo/db/queryutil.h" -#include "mongo/util/elapsed_tracker.h" - -#pragma once - -namespace mongo { - - static const int OutOfOrderDocumentsAssertionCode = 14810; - - class FieldRangeSetPair; - class QueryPlanSelectionPolicy; - class OrRangeGenerator; - class ParsedQuery; - - /** - * Helper class for a QueryPlanRunner to cache and count matches. One object of this type is - * used per candidate QueryPlan (as there is one QueryPlanRunner per QueryPlan). - * - * Typical usage: - * 1) resetMatch() - reset stored match value to Unkonwn. - * 2) setMatch() - set match value to a definite true/false value. - * 3) knowMatch() - check if setMatch() has been called. - * 4) incMatch() - increment count if match is true. - */ - class CachedMatchCounter { - public: - /** - * @param aggregateNscanned - shared count of nscanned for this and other plans. - * @param cumulativeCount - starting point for accumulated count over a series of plans. - */ - CachedMatchCounter( long long& aggregateNscanned, int cumulativeCount ); - - /** Set whether dup checking is enabled when counting. */ - void setCheckDups( bool checkDups ) { _checkDups = checkDups; } - - void resetMatch(); - - /** @return true if the match was not previously recorded. */ - bool setMatch( bool match ); - - bool knowMatch() const { return _match != Unknown; } - - void incMatch( const DiskLoc& loc ); - - bool wouldIncMatch( const DiskLoc& loc ) const; - - bool enoughCumulativeMatchesToChooseAPlan() const; - - bool enoughMatchesToRecordPlan() const; - - int cumulativeCount() const { return _cumulativeCount; } - - int count() const { return _count; } - - /** Update local and aggregate nscanned counts. */ - void updateNscanned( long long nscanned ); - - long long nscanned() const { return _nscanned; } - - long long& aggregateNscanned() const { return _aggregateNscanned; } - - private: - bool getsetdup( const DiskLoc& loc ); - - bool getdup( const DiskLoc& loc ) const; - - long long& _aggregateNscanned; - long long _nscanned; - int _cumulativeCount; - int _count; - bool _checkDups; - enum MatchState { Unknown, False, True }; - MatchState _match; - bool _counted; - set<DiskLoc> _dups; - }; - - /** - * Iterates through a QueryPlan's candidate matches, keeping track of accumulated nscanned. - * Generally used along with runners for other QueryPlans in a QueryPlanRunnerQueue priority - * queue. Eg if there are three candidate QueryPlans evaluated in parallel, there are three - * QueryPlanRunners, one checking for matches on each query. - * - * Typical usage: - * 1) A new QueryPlanRunner is generated using createChild(). - * 2) A QueryPlan is assigned using setQueryPlan(). - * 3) init() is called to initialize the runner. - * 4) next() is called repeatedly, with nscanned() checked after each call. - * 5) In one of these calls to next(), setComplete() is called internally. - * 6) The QueryPattern for the QueryPlan may be recorded as a winning plan. - */ - class QueryPlanRunner { - MONGO_DISALLOW_COPYING( QueryPlanRunner ); - public: - /** - * @param aggregateNscanned Shared long long counting total nscanned for runners for all - * cursors. - * @param selectionPolicy Characterizes the set of QueryPlans allowed for this operation. - * See queryoptimizercursor.h for more information. - * @param requireOrder Whether only ordered plans are allowed. - * @param alwaysCountMatches Whether matches are to be counted regardless of ordering. - * @param cumulativeCount Total count. - */ - QueryPlanRunner( long long& aggregateNscanned, - const QueryPlanSelectionPolicy& selectionPolicy, - const bool& requireOrder, - bool alwaysCountMatches, - int cumulativeCount = 0 ); - - /** @return QueryPlan assigned to this runner by the query optimizer. */ - const QueryPlan& queryPlan() const { return *_queryPlan; } - - /** Advance to the next potential matching document (eg using a cursor). */ - void next(); - - /** - * @return current 'nscanned' metric for this runner. Used to compare cost to other - * runners. - */ - long long nscanned() const; - - /** Take any steps necessary before the db mutex is yielded. */ - void prepareToYield(); - - /** Recover once the db mutex is regained. */ - void recoverFromYield(); - - /** Take any steps necessary before an earlier iterate of the cursor is modified. */ - void prepareToTouchEarlierIterate(); - - /** Recover after the earlier iterate is modified. */ - void recoverFromTouchingEarlierIterate(); - - DiskLoc currLoc() const { return _c ? _c->currLoc() : DiskLoc(); } - - BSONObj currKey() const { return _c ? _c->currKey() : BSONObj(); } - - bool currentMatches( MatchDetails* details ); - - /** - * @return true iff the QueryPlan for this runner may be registered - * as a winning plan. - */ - bool mayRecordPlan() const; - - shared_ptr<Cursor> cursor() const { return _c; } - - /** @return true iff the implementation called setComplete() or setStop(). */ - bool complete() const { return _complete; } - - /** @return true iff the implementation called setStop(). */ - bool stopRequested() const { return _stopRequested; } - - bool completeWithoutStop() const { return complete() && !stopRequested(); } - - /** @return true iff the implementation errored out. */ - bool error() const { return _error; } - - /** @return the error information. */ - ExceptionInfo exception() const { return _exception; } - - /** To be called by QueryPlanSet::Runner only. */ - - /** - * @return a copy of the inheriting class, which will be run with its own query plan. The - * child runner will assume its parent runner has completed execution. - */ - QueryPlanRunner* createChild() const; - - void setQueryPlan( const QueryPlan* queryPlan ); - - /** Handle initialization after a QueryPlan has been set. */ - void init(); - - void setException( const DBException& e ); - - /** @return an ExplainPlanInfo object that will be updated as the query runs. */ - shared_ptr<ExplainPlanInfo> generateExplainInfo(); - - shared_ptr<ExplainPlanInfo> explainInfo() const { return _explainPlanInfo; } - - const Projection::KeyOnly* keyFieldsOnly() const { - return queryPlan().keyFieldsOnly().get(); - } - - private: - /** Call if all results have been found. */ - void setComplete() { _complete = true; } - - /** Call if the scan is complete even if not all results have been found. */ - void setStop() { setComplete(); _stopRequested = true; } - - void mayAdvance(); - - bool countingMatches(); - - bool countMatches() const; - - /** - * @return true if the results generated by this query plan will be loaded from the record - * store (not built from an index entry). - */ - bool hasDocumentLoadingQueryPlan() const; - - void recordCursorLocation(); - - void checkCursorAdvanced(); - - void handleCursorAdvanced(); - - void checkCursorOrdering(); - - bool _complete; - bool _stopRequested; - ExceptionInfo _exception; - const QueryPlan* _queryPlan; - bool _error; - CachedMatchCounter _matchCounter; - bool _countingMatches; - bool _mustAdvance; - bool _capped; - shared_ptr<Cursor> _c; - ClientCursorHolder _cc; - DiskLoc _posBeforeYield; - ClientCursor::YieldData _yieldData; - const QueryPlanSelectionPolicy& _selectionPolicy; - const bool& _requireOrder; // TODO don't use a ref for this, but signal change explicitly - shared_ptr<ExplainPlanInfo> _explainPlanInfo; - bool _alwaysCountMatches; - }; - - /** - * This class works if T::operator< is variant unlike a regular stl priority queue, but it's - * very slow. However if _vec.size() is always very small, it would be fine, maybe even faster - * than a smart impl that does more memory allocations. - * TODO Clean up this temporary code. - */ - template<class T> - class PriorityQueue { - MONGO_DISALLOW_COPYING( PriorityQueue ); - public: - PriorityQueue() { - _vec.reserve(4); - } - int size() const { return _vec.size(); } - bool empty() const { return _vec.empty(); } - void push(const T& x) { - _vec.push_back(x); - } - T pop() { - size_t t = 0; - for( size_t i = 1; i < _vec.size(); i++ ) { - if( _vec[t] < _vec[i] ) - t = i; - } - T ret = _vec[t]; - _vec.erase(_vec.begin()+t); - return ret; - } - private: - vector<T> _vec; - }; - - class QueryPlanSet; - - /** Populates a provided QueryPlanSet with candidate query plans, when requested. */ - class QueryPlanGenerator { - public: - - /** Policies for utilizing recorded plans. */ - typedef enum { - Ignore, // Ignore the recorded plan and try all candidate plans. - UseIfInOrder, // Use the recorded plan if it is properly ordered. - Use // Always use the recorded plan. - } RecordedPlanPolicy; - - /** @param qps The QueryPlanSet to which plans will be provided. */ - QueryPlanGenerator( QueryPlanSet& qps, - auto_ptr<FieldRangeSetPair> originalFrsp, - const shared_ptr<const ParsedQuery>& parsedQuery, - const BSONObj& hint, - RecordedPlanPolicy recordedPlanPolicy, - const BSONObj& min, - const BSONObj& max, - bool allowSpecial ); - - /** Populate the provided QueryPlanSet with an initial set of plans. */ - void addInitialPlans(); - - /** Supplement a cached plan provided earlier by adding additional query plans. */ - void addFallbackPlans(); - - private: - - bool addShortCircuitPlan( NamespaceDetails* d ); - - bool addHintPlan( NamespaceDetails* d ); - - bool addSpecialPlan( NamespaceDetails* d ); - - void addStandardPlans( NamespaceDetails* d ); - - bool addCachedPlan( NamespaceDetails* d ); - - shared_ptr<QueryPlan> newPlan( NamespaceDetails* d, - int idxNo, - const BSONObj& min = BSONObj(), - const BSONObj& max = BSONObj(), - const string& special = "" ) const; - - bool setUnindexedPlanIf( bool set, NamespaceDetails* d ); - - void setSingleUnindexedPlan( NamespaceDetails* d ); - - void setHintedPlanForIndex( IndexDetails& id ); - - void validateAndSetHintedPlan( const shared_ptr<QueryPlan>& plan ); - - void warnOnCappedIdTableScan() const; - - QueryPlanSet& _qps; - auto_ptr<FieldRangeSetPair> _originalFrsp; - shared_ptr<const ParsedQuery> _parsedQuery; - BSONObj _hint; - RecordedPlanPolicy _recordedPlanPolicy; - BSONObj _min; - BSONObj _max; - bool _allowSpecial; - }; - - /** A set of candidate query plans for a query. */ - class QueryPlanSet { - public: - typedef boost::shared_ptr<QueryPlan> QueryPlanPtr; - typedef vector<QueryPlanPtr> PlanVector; - - /** - * @param originalFrsp - original constraints for this query clause; if null, frsp will be - * used. - */ - static QueryPlanSet* make( const char* ns, - auto_ptr<FieldRangeSetPair> frsp, - auto_ptr<FieldRangeSetPair> originalFrsp, - const BSONObj& originalQuery, - const BSONObj& order, - const shared_ptr<const ParsedQuery>& parsedQuery, - const BSONObj& hint, - QueryPlanGenerator::RecordedPlanPolicy recordedPlanPolicy, - const BSONObj& min, - const BSONObj& max, - bool allowSpecial ); - - /** @return number of candidate plans. */ - int nPlans() const { return _plans.size(); } - - QueryPlanPtr firstPlan() const { return _plans[ 0 ]; } - - /** @return true if a plan is selected based on previous success of this plan. */ - bool usingCachedPlan() const { return _usingCachedPlan; } - - /** @return true if some candidate plans may have been excluded due to plan caching. */ - bool hasPossiblyExcludedPlans() const; - - /** @return a single plan that may work well for the specified query. */ - QueryPlanPtr getBestGuess() const; - - const FieldRangeSetPair& frsp() const { return *_frsp; } - - BSONObj originalQuery() const { return _originalQuery; } - - BSONObj order() const { return _order; } - - /** @return true if an active plan is in order. */ - bool haveInOrderPlan() const; - - /** @return true if an active or fallback plan is in order. */ - bool possibleInOrderPlan() const; - - /** @return true if an active or fallback plan is out of order. */ - bool possibleOutOfOrderPlan() const; - - CandidatePlanCharacter characterizeCandidatePlans() const; - - bool prepareToRetryQuery(); - - string toString() const; - - /** Configure a single query plan if one has not already been provided. */ - void setSinglePlan( const QueryPlanPtr& plan ); - - /** Configure a query plan from the plan cache. */ - void setCachedPlan( const QueryPlanPtr& plan, const CachedQueryPlan& cachedPlan ); - - /** Add a candidate query plan, potentially one of many. */ - void addCandidatePlan( const QueryPlanPtr& plan ); - - const PlanVector& plans() const { return _plans; } - - bool mayRecordPlan() const { return _mayRecordPlan; } - - int oldNScanned() const { return _oldNScanned; } - - void addFallbackPlans(); - - void setUsingCachedPlan( bool usingCachedPlan ) { _usingCachedPlan = usingCachedPlan; } - - //for testing - - bool modifiedKeys() const; - - bool hasMultiKey() const; - - private: - - QueryPlanSet( const char* ns, - auto_ptr<FieldRangeSetPair> frsp, - auto_ptr<FieldRangeSetPair> originalFrsp, - const BSONObj& originalQuery, - const BSONObj& order, - const shared_ptr<const ParsedQuery>& parsedQuery, - const BSONObj& hint, - QueryPlanGenerator::RecordedPlanPolicy recordedPlanPolicy, - const BSONObj& min, - const BSONObj& max, - bool allowSpecial ); - - void init(); - - void pushPlan( const QueryPlanPtr& plan ); - - QueryPlanGenerator _generator; - BSONObj _originalQuery; - auto_ptr<FieldRangeSetPair> _frsp; - PlanVector _plans; - bool _mayRecordPlan; - bool _usingCachedPlan; - CandidatePlanCharacter _cachedPlanCharacter; - BSONObj _order; - long long _oldNScanned; - ElapsedTracker _yieldSometimesTracker; - bool _allowSpecial; - }; - - /** - * A priority queue of QueryPlanRunners ordered by their nscanned values. The QueryPlanRunners - * are iterated sequentially and reinserted into the queue until one runner completes or all - * runners error out. - */ - class QueryPlanRunnerQueue { - public: - QueryPlanRunnerQueue( QueryPlanSet& plans, const QueryPlanRunner& prototypeRunner ); - - /** - * Pull a runner from the priority queue, advance it if possible, re-insert it into the - * queue if it is not done, and return it. But if this runner errors out, retry with - * another runner until a non error runner is found or all runners have errored out. - * @return the next non error runner if there is one, otherwise an error runner. - * If the returned runner is complete() or error(), this queue becomes done(). - */ - shared_ptr<QueryPlanRunner> next(); - - /** @return true if done iterating. */ - bool done() const { return _done; } - - /** Prepare all runners for a database mutex yield. */ - void prepareToYield(); - - /** Restore all runners after a database mutex yield. */ - void recoverFromYield(); - - /** @return an ExplainClauseInfo object that will be updated as the query runs. */ - shared_ptr<ExplainClauseInfo> generateExplainInfo() { - _explainClauseInfo.reset( new ExplainClauseInfo() ); - return _explainClauseInfo; - } - - private: - const QueryPlanRunner& _prototypeRunner; - QueryPlanSet& _plans; - - static void initRunner( QueryPlanRunner& runner ); - - static void nextRunner( QueryPlanRunner& runner ); - - static void prepareToYieldRunner( QueryPlanRunner& runner ); - - static void recoverFromYieldRunner( QueryPlanRunner& runner ); - - /** Initialize the Runner. */ - shared_ptr<QueryPlanRunner> init(); - - /** Move the Runner forward one iteration, and @return the plan for the iteration. */ - shared_ptr<QueryPlanRunner> _next(); - - vector<shared_ptr<QueryPlanRunner> > _runners; - struct RunnerHolder { - RunnerHolder( const shared_ptr<QueryPlanRunner>& runner ) : - _runner( runner ), - _offset() { - } - shared_ptr<QueryPlanRunner> _runner; - long long _offset; - bool operator<( const RunnerHolder& other ) const { - return _runner->nscanned() + _offset > other._runner->nscanned() + other._offset; - } - }; - PriorityQueue<RunnerHolder> _queue; - shared_ptr<ExplainClauseInfo> _explainClauseInfo; - bool _done; - }; - - /** Handles $or type queries by generating a QueryPlanSet for each $or clause. */ - class MultiPlanScanner { - public: - - static MultiPlanScanner* make( const StringData& ns, - const BSONObj& query, - const BSONObj& order, - const shared_ptr<const ParsedQuery>& parsedQuery = - shared_ptr<const ParsedQuery>(), - const BSONObj& hint = BSONObj(), - QueryPlanGenerator::RecordedPlanPolicy recordedPlanPolicy = - QueryPlanGenerator::Use, - const BSONObj& min = BSONObj(), - const BSONObj& max = BSONObj() ); - - /** Set the originalRunner for QueryPlanSet iteration. */ - void initialRunner( const shared_ptr<QueryPlanRunner>& originalRunner ) { - _baseRunner = originalRunner; - } - - /** - * Advance to the next runner, if not doneRunners(). - * @return the next non error runner if there is one, otherwise an error runner. - * If the returned runner is complete() or error(), the MultiPlanScanner becomes - * doneRunners() and no further runner iteration is possible. - */ - shared_ptr<QueryPlanRunner> nextRunner(); - - /** @return true if done with runner iteration. */ - bool doneRunners() const { return _doneRunners; } - - /** - * Advance to the next $or clause; hasMoreClauses() must be true. - * @param currentPlan QueryPlan of the current $or clause - * @return best guess query plan of the next $or clause, 0 if there is no such plan. - */ - const QueryPlan* nextClauseBestGuessPlan( const QueryPlan& currentPlan ); - - /** Add explain information for a new clause. */ - void addClauseInfo( const shared_ptr<ExplainClauseInfo>& clauseInfo ) { - verify( _explainQueryInfo ); - _explainQueryInfo->addClauseInfo( clauseInfo ); - } - - /** @return an ExplainQueryInfo object that will be updated as the query runs. */ - shared_ptr<ExplainQueryInfo> generateExplainInfo() { - _explainQueryInfo.reset( new ExplainQueryInfo() ); - return _explainQueryInfo; - } - - /** Yield the runner member. */ - - void prepareToYield(); - - void recoverFromYield(); - - /** Clear the runner member. */ - void clearRunnerQueue(); - - void setRecordedPlanPolicy( QueryPlanGenerator::RecordedPlanPolicy recordedPlanPolicy ) { - _recordedPlanPolicy = recordedPlanPolicy; - } - - int currentNPlans() const; - - /** - * @return the query plan that would be used if the scanner would run a single - * cursor for this query, otherwise 0. The returned plan is invalid if this - * MultiPlanScanner is destroyed, hence we return a raw pointer. - */ - const QueryPlan* singlePlan() const; - - /** @return true if more $or clauses need to be scanned. */ - bool hasMoreClauses() const; - - /** - * @return plan information if there is a cached plan for a non $or query, otherwise an - * empty object. - */ - BSONObj cachedPlanExplainSummary() const; - - /** - * @return true if this is not a $or query and some candidate plans may have been excluded - * due to plan caching. - */ - bool hasPossiblyExcludedPlans() const { - return !_or && _currentQps->hasPossiblyExcludedPlans(); - } - - bool hasMultiKey() const { return _currentQps->hasMultiKey(); } - - /** Clear recorded indexes for the current QueryPlanSet's patterns. */ - void clearIndexesForPatterns() const; - - /** @return true if an active plan of _currentQps is in order. */ - bool haveInOrderPlan() const; - - /** @return true if an active or fallback plan of _currentQps is in order. */ - bool possibleInOrderPlan() const; - - /** @return true if an active or fallback plan of _currentQps is out of order. */ - bool possibleOutOfOrderPlan() const; - - int i() const { return _i; } - - string toString() const; - - private: - - MultiPlanScanner( const StringData& ns, - const BSONObj& query, - const shared_ptr<const ParsedQuery>& parsedQuery, - const BSONObj& hint, - QueryPlanGenerator::RecordedPlanPolicy recordedPlanPolicy ); - - void init( const BSONObj& order, - const BSONObj& min, - const BSONObj& max ); - - /** Initialize or iterate a runner generated from @param originalOp. */ - shared_ptr<QueryPlanRunner> iterateRunnerQueue( QueryPlanRunner& originalRunner, - bool retried = false ); - - shared_ptr<QueryPlanRunner> nextRunnerSimple(); - - shared_ptr<QueryPlanRunner> nextRunnerOr(); - - void updateCurrentQps( QueryPlanSet* qps ); - - void assertNotOr() const { - massert( 13266, "not implemented for $or query", !_or ); - } - - void assertHasMoreClauses() const { - massert( 13271, "no more clauses", hasMoreClauses() ); - } - - void handleEndOfClause( const QueryPlan& clausePlan ); - - void handleBeginningOfClause(); - - bool mayHandleBeginningOfClause(); - - bool haveUselessOr() const; - - const string _ns; - bool _or; - BSONObj _query; - shared_ptr<const ParsedQuery> _parsedQuery; - scoped_ptr<OrRangeGenerator> _org; // May be null in certain non $or query cases. - scoped_ptr<QueryPlanSet> _currentQps; - int _i; - QueryPlanGenerator::RecordedPlanPolicy _recordedPlanPolicy; - BSONObj _hint; - bool _tableScanned; - shared_ptr<QueryPlanRunner> _baseRunner; - scoped_ptr<QueryPlanRunnerQueue> _runnerQueue; - shared_ptr<ExplainQueryInfo> _explainQueryInfo; - bool _doneRunners; - }; - - /** - * Provides a cursor interface for serial single Cursor iteration using a MultiPlanScanner. - * Currently used internally by a QueryOptimizerCursor. - * - * A MultiCursor is backed by one BasicCursor or BtreeCursor at a time and forwards calls for - * ensuring a consistent state after a write to its backing Cursor. - */ - class MultiCursor : public Cursor { - public: - /** @param nscanned is the initial nscanned value. */ - MultiCursor( auto_ptr<MultiPlanScanner> mps, - const shared_ptr<Cursor>& c, - const shared_ptr<CoveredIndexMatcher>& matcher, - const shared_ptr<ExplainPlanInfo>& explainPlanInfo, - const QueryPlanRunner& runner, - long long nscanned ); - - virtual bool ok() { return _c->ok(); } - - virtual Record* _current() { return _c->_current(); } - - virtual BSONObj current() { return _c->current(); } - - virtual DiskLoc currLoc() { return _c->currLoc(); } - - virtual bool advance(); - - virtual BSONObj currKey() const { return _c->currKey(); } - - virtual DiskLoc refLoc() { return _c->refLoc(); } - - virtual void noteLocation() { _c->noteLocation(); } - - virtual void checkLocation() { _c->checkLocation(); } - - virtual void recoverFromYield(); - - virtual bool supportGetMore() { return true; } - - virtual bool supportYields() { return true; } - - virtual BSONObj indexKeyPattern() { return _c->indexKeyPattern(); } - - /** Deduping documents from a prior cursor is handled by the matcher. */ - virtual bool getsetdup(DiskLoc loc) { return _c->getsetdup( loc ); } - - virtual bool modifiedKeys() const { return true; } - - virtual bool isMultiKey() const { return _mps->hasMultiKey(); } - - virtual CoveredIndexMatcher* matcher() const { return _matcher.get(); } - - virtual bool capped() const { return _c->capped(); } - - virtual long long nscanned() { return _nscanned + _c->nscanned(); } - - void noteIterate( bool match, bool loadedRecord ); - - const QueryPlan& queryPlan() const { - verify( _c->ok() && _queryPlan ); - return *_queryPlan; - } - - const Projection::KeyOnly* keyFieldsOnly() const { - verify( _c->ok() && _queryPlan ); - return _queryPlan->keyFieldsOnly().get(); - } - - private: - void advanceClause(); - - void advanceExhaustedClauses(); - - auto_ptr<MultiPlanScanner> _mps; - shared_ptr<Cursor> _c; - shared_ptr<CoveredIndexMatcher> _matcher; - const QueryPlan* _queryPlan; - long long _nscanned; - shared_ptr<ExplainPlanInfo> _explainPlanInfo; - }; - - /** NOTE min, max, and keyPattern will be updated to be consistent with the selected index. */ - IndexDetails* indexDetailsForRange( const char* ns, - string& errmsg, - BSONObj& min, - BSONObj& max, - BSONObj& keyPattern ); - - class CachedQueryPlan; - - /** - * Add-on functionality for queryutil classes requiring access to indexing - * functionality not currently linked to mongos. - * TODO Clean this up a bit, possibly with separate sharded and non sharded - * implementations for the appropriate queryutil classes or by pulling index - * related functionality into separate wrapper classes. - */ - struct QueryUtilIndexed { - - /** @return true if the index may be useful according to its KeySpec. */ - static bool indexUseful( const FieldRangeSetPair& frsp, - NamespaceDetails* d, - int idxNo, - const BSONObj& order ); - - /** Clear any indexes recorded as the best for either the single or multi key pattern. */ - static void clearIndexesForPatterns( const FieldRangeSetPair& frsp, const BSONObj& order ); - - /** Return a recorded best index for the single or multi key pattern. */ - static CachedQueryPlan bestIndexForPatterns( const FieldRangeSetPair& frsp, - const BSONObj& order ); - - static bool uselessOr( const OrRangeGenerator& org, NamespaceDetails* d, int hintIdx ); - }; - -} // namespace mongo diff --git a/src/mongo/db/query_plan.cpp b/src/mongo/db/query_plan.cpp deleted file mode 100644 index ea954a92b38..00000000000 --- a/src/mongo/db/query_plan.cpp +++ /dev/null @@ -1,492 +0,0 @@ -/** - * Copyright (C) 2008 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/>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the GNU Affero General Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#include "mongo/db/query_plan.h" - -#include "mongo/db/btreecursor.h" -#include "mongo/db/index_selection.h" -#include "mongo/db/index/catalog_hack.h" -#include "mongo/db/index/emulated_cursor.h" -#include "mongo/db/index/index_access_method.h" -#include "mongo/db/index/index_descriptor.h" -#include "mongo/db/intervalbtreecursor.h" -#include "mongo/db/pdfile.h" -#include "mongo/db/parsed_query.h" -#include "mongo/db/query_plan_summary.h" -#include "mongo/db/queryutil.h" -#include "mongo/db/structure/collection.h" -#include "mongo/server.h" - -namespace mongo { - - QueryPlanSummary QueryPlan::summary() const { - QueryPlanSummary summary; - summary.fieldRangeSetMulti.reset( new FieldRangeSet( multikeyFrs() ) ); - summary.keyFieldsOnly = keyFieldsOnly(); - summary.scanAndOrderRequired = scanAndOrderRequired(); - return summary; - } - - double elementDirection( const BSONElement& e ) { - if ( e.isNumber() ) - return e.number(); - return 1; - } - - QueryPlan* QueryPlan::make( NamespaceDetails* d, - int idxNo, - const FieldRangeSetPair& frsp, - const FieldRangeSetPair* originalFrsp, - const BSONObj& originalQuery, - const BSONObj& order, - const shared_ptr<const ParsedQuery>& parsedQuery, - const BSONObj& startKey, - const BSONObj& endKey, - const std::string& special ) { - auto_ptr<QueryPlan> ret( new QueryPlan( d, - idxNo, - frsp, - originalQuery, - order, - parsedQuery, - special ) ); - ret->init( originalFrsp, startKey, endKey ); - return ret.release(); - } - - QueryPlan::QueryPlan( NamespaceDetails* d, - int idxNo, - const FieldRangeSetPair& frsp, - const BSONObj& originalQuery, - const BSONObj& order, - const shared_ptr<const ParsedQuery>& parsedQuery, - const std::string& special ) : - _d( d ), - _idxNo( idxNo ), - _frs( frsp.frsForIndex( _d, _idxNo ) ), - _frsMulti( frsp.frsForIndex( _d, -1 ) ), - _originalQuery( originalQuery ), - _order( order ), - _parsedQuery( parsedQuery ), - _index( 0 ), - _scanAndOrderRequired( true ), - _matcherNecessary( true ), - _direction( 0 ), - _endKeyInclusive(), - _utility( Helpful ), - _special( special ), - _startOrEndSpec() { - } - - void QueryPlan::init( const FieldRangeSetPair* originalFrsp, - const BSONObj& startKey, - const BSONObj& endKey ) { - _endKeyInclusive = endKey.isEmpty(); - _startOrEndSpec = !startKey.isEmpty() || !endKey.isEmpty(); - - BSONObj idxKey = _idxNo < 0 ? BSONObj() : _d->idx( _idxNo ).keyPattern(); - - if ( !_frs.matchPossibleForIndex( idxKey ) ) { - _utility = Impossible; - _scanAndOrderRequired = false; - return; - } - - if ( willScanTable() ) { - if ( _order.isEmpty() || !strcmp( _order.firstElementFieldName(), "$natural" ) ) - _scanAndOrderRequired = false; - return; - } - - _descriptor.reset(CatalogHack::getDescriptor(_d, _idxNo)); - _index = &_d->idx(_idxNo); - - // If the parsing or index indicates this is a special query, don't continue the processing - if (!_special.empty() || - ( ("" != CatalogHack::getAccessMethodName(_descriptor->keyPattern())) && (USELESS != - IndexSelection::isSuitableFor(_descriptor->keyPattern(), _frs, _order)))) { - - _specialIndexName = CatalogHack::getAccessMethodName(_descriptor->keyPattern()); - if (_special.empty()) _special = _specialIndexName; - - massert( 13040 , (string)"no type for special: " + _special , "" != _specialIndexName); - // hopefully safe to use original query in these contexts; - // don't think we can mix special with $or clause separation yet - _scanAndOrderRequired = !_order.isEmpty(); - return; - } - - BSONObjIterator o( _order ); - BSONObjIterator k( idxKey ); - if ( !o.moreWithEOO() ) - _scanAndOrderRequired = false; - while( o.moreWithEOO() ) { - BSONElement oe = o.next(); - if ( oe.eoo() ) { - _scanAndOrderRequired = false; - break; - } - if ( !k.moreWithEOO() ) - break; - BSONElement ke; - while( 1 ) { - ke = k.next(); - if ( ke.eoo() ) - goto doneCheckOrder; - if ( strcmp( oe.fieldName(), ke.fieldName() ) == 0 ) - break; - if ( !_frs.range( ke.fieldName() ).equality() ) - goto doneCheckOrder; - } - int d = elementDirection( oe ) == elementDirection( ke ) ? 1 : -1; - if ( _direction == 0 ) - _direction = d; - else if ( _direction != d ) - break; - } -doneCheckOrder: - if ( _scanAndOrderRequired ) - _direction = 0; - BSONObjIterator i( idxKey ); - int exactIndexedQueryCount = 0; - int optimalIndexedQueryCount = 0; - bool awaitingLastOptimalField = true; - set<string> orderFieldsUnindexed; - _order.getFieldNames( orderFieldsUnindexed ); - while( i.moreWithEOO() ) { - BSONElement e = i.next(); - if ( e.eoo() ) - break; - const FieldRange& fr = _frs.range( e.fieldName() ); - if ( awaitingLastOptimalField ) { - if ( !fr.universal() ) - ++optimalIndexedQueryCount; - if ( !fr.equality() ) - awaitingLastOptimalField = false; - } - else { - if ( !fr.universal() ) - optimalIndexedQueryCount = -1; - } - if ( fr.equality() ) { - BSONElement e = fr.max(); - if ( !e.isNumber() && !e.mayEncapsulate() && e.type() != RegEx ) - ++exactIndexedQueryCount; - } - orderFieldsUnindexed.erase( e.fieldName() ); - } - if ( !_scanAndOrderRequired && - ( optimalIndexedQueryCount == _frs.numNonUniversalRanges() ) ) - _utility = Optimal; - _frv.reset( new FieldRangeVector( _frs, _descriptor->keyPattern(), _direction ) ); - - if ( // If all field range constraints are on indexed fields and ... - _utility == Optimal && - // ... the field ranges exactly represent the query and ... - _frs.mustBeExactMatchRepresentation() && - // ... all indexed ranges are represented in the field range vector ... - _frv->hasAllIndexedRanges() ) { - - // ... then the field range vector is sufficient to perform query matching against index - // keys. No matcher is required. - _matcherNecessary = false; - } - - if ( originalFrsp ) { - _originalFrv.reset( new FieldRangeVector( originalFrsp->frsForIndex( _d, _idxNo ), - _descriptor->keyPattern(), - _direction ) ); - } - else { - _originalFrv = _frv; - } - if ( _startOrEndSpec ) { - BSONObj newStart, newEnd; - if ( !startKey.isEmpty() ) - _startKey = startKey; - else - _startKey = _frv->startKey(); - if ( !endKey.isEmpty() ) - _endKey = endKey; - else - _endKey = _frv->endKey(); - } - - if ( ( _scanAndOrderRequired || _order.isEmpty() ) && - _frs.range( idxKey.firstElementFieldName() ).universal() ) { // NOTE SERVER-2140 - _utility = Unhelpful; - } - - if ( _descriptor->isSparse() && hasPossibleExistsFalsePredicate() ) { - _utility = Disallowed; - } - - if ( _parsedQuery && _parsedQuery->getFields() && !_d->isMultikey( _idxNo ) ) { - // Does not check modifiedKeys() - _keyFieldsOnly.reset( _parsedQuery->getFields()->checkKey( _index->keyPattern() ) ); - } - } - - shared_ptr<Cursor> QueryPlan::newCursor( const DiskLoc& startLoc, - bool requestIntervalCursor ) const { - - if ("" != _specialIndexName) { - // hopefully safe to use original query in these contexts - don't think we can mix type - // with $or clause separation yet - int numWanted = 0; - if ( _parsedQuery ) { - // SERVER-5390 - numWanted = _parsedQuery->getSkip() + _parsedQuery->getNumToReturn(); - } - - // Why do we get new objects here? Because EmulatedCursor takes ownership of them. - IndexDescriptor* descriptor = CatalogHack::getDescriptor(_d, _idxNo); - IndexAccessMethod* iam = CatalogHack::getIndex(descriptor); - return shared_ptr<Cursor>(EmulatedCursor::make(descriptor, iam, _originalQuery, - _order, numWanted, - descriptor->keyPattern())); - } - - if ( _utility == Impossible ) { - // Dummy table scan cursor returning no results. Allowed in --notablescan mode. - return shared_ptr<Cursor>( new BasicCursor( DiskLoc() ) ); - } - - if ( willScanTable() ) { - checkTableScanAllowed(); - return findTableScan( _frs.ns(), _order, startLoc ); - } - - massert( 10363, - "newCursor() with start location not implemented for indexed plans", - startLoc.isNull() ); - - if ( _startOrEndSpec ) { - // we are sure to spec _endKeyInclusive - return shared_ptr<Cursor>( BtreeCursor::make( _d, - *_index, - _startKey, - _endKey, - _endKeyInclusive, - _direction >= 0 ? 1 : -1 ) ); - } - - if ( "" != CatalogHack::getAccessMethodName(_descriptor->keyPattern())) { - return shared_ptr<Cursor>( BtreeCursor::make( _d, - *_index, - _frv->startKey(), - _frv->endKey(), - true, - _direction >= 0 ? 1 : -1 ) ); - } - - // An IntervalBtreeCursor is returned if explicitly requested AND _frv is exactly - // represented by a single interval within the btree. - if ( // If an interval cursor is requested and ... - requestIntervalCursor && - // ... equalities come before ranges (a requirement of Optimal) and ... - _utility == Optimal && - // ... the field range vector exactly represents a single interval ... - _frv->isSingleInterval() ) { - // ... and an interval cursor can be created ... - shared_ptr<Cursor> ret( IntervalBtreeCursor::make( _d, - *_index, - _frv->startKey(), - _frv->startKeyInclusive(), - _frv->endKey(), - _frv->endKeyInclusive() ) ); - if ( ret ) { - // ... then return the interval cursor. - return ret; - } - } - - return shared_ptr<Cursor>( BtreeCursor::make( _d, - *_index, - _frv, - independentRangesSingleIntervalLimit(), - _direction >= 0 ? 1 : -1 ) ); - } - - shared_ptr<Cursor> QueryPlan::newReverseCursor() const { - if ( willScanTable() ) { - int orderSpec = _order.getIntField( "$natural" ); - if ( orderSpec == INT_MIN ) - orderSpec = 1; - return findTableScan( _frs.ns(), BSON( "$natural" << -orderSpec ) ); - } - massert( 10364, "newReverseCursor() not implemented for indexed plans", false ); - return shared_ptr<Cursor>(); - } - - BSONObj QueryPlan::indexKey() const { - if ( !_index ) - return BSON( "$natural" << 1 ); - return _index->keyPattern(); - } - - const char* QueryPlan::ns() const { - return _frs.ns(); - } - - void QueryPlan::registerSelf( long long nScanned, - CandidatePlanCharacter candidatePlans ) const { - // Impossible query constraints can be detected before scanning and historically could not - // generate a QueryPattern. - if ( _utility == Impossible ) { - return; - } - - QueryPattern queryPattern = _frs.pattern( _order ); - CachedQueryPlan queryPlanToCache( indexKey(), nScanned, candidatePlans ); - - Collection* collection = cc().database()->getCollection( ns() ); - verify( collection ); - collection->infoCache()->registerCachedQueryPlanForPattern( queryPattern, queryPlanToCache ); - } - - void QueryPlan::checkTableScanAllowed() const { - if (likely(!storageGlobalParams.noTableScan)) - return; - - // TODO - is this desirable? See SERVER-2222. - if ( _frs.numNonUniversalRanges() == 0 ) - return; - - if ( strstr( ns(), ".system." ) ) - return; - - if( str::startsWith( ns(), "local." ) ) - return; - - if ( !nsdetails( ns() ) ) - return; - - uassert(10111, (string)"table scans not allowed:" + ns(), - !storageGlobalParams.noTableScan); - } - - int QueryPlan::independentRangesSingleIntervalLimit() const { - if ( _scanAndOrderRequired && - _parsedQuery && - !_parsedQuery->wantMore() && - !isMultiKey() && - queryBoundsExactOrderSuffix() ) { - verify( _direction == 0 ); - // Limit the results for each compound interval. SERVER-5063 - return _parsedQuery->getSkip() + _parsedQuery->getNumToReturn(); - } - return 0; - } - - - - bool QueryPlan::hasPossibleExistsFalsePredicate() const { - return matcher()->docMatcher().hasExistsFalse(); - } - - bool QueryPlan::queryBoundsExactOrderSuffix() const { - if ( !indexed() || - !_frs.matchPossible() || - !_frs.mustBeExactMatchRepresentation() ) { - return false; - } - BSONObj idxKey = indexKey(); - BSONObjIterator index( idxKey ); - BSONObjIterator order( _order ); - int coveredNonUniversalRanges = 0; - while( index.more() ) { - const FieldRange& indexFieldRange = _frs.range( (*index).fieldName() ); - if ( !indexFieldRange.isPointIntervalSet() ) { - if ( !indexFieldRange.universal() ) { - // The last indexed range may be a non point set containing a single interval. - // SERVER-5777 - if ( indexFieldRange.intervals().size() > 1 ) { - return false; - } - ++coveredNonUniversalRanges; - } - break; - } - ++coveredNonUniversalRanges; - if ( order.more() && str::equals( (*index).fieldName(), (*order).fieldName() ) ) { - ++order; - } - ++index; - } - if ( coveredNonUniversalRanges != _frs.numNonUniversalRanges() ) { - return false; - } - while( index.more() && order.more() ) { - if ( !str::equals( (*index).fieldName(), (*order).fieldName() ) ) { - return false; - } - if ( ( elementDirection( *index ) < 0 ) != ( elementDirection( *order ) < 0 ) ) { - return false; - } - ++order; - ++index; - } - return !order.more(); - } - - string QueryPlan::toString() const { - return BSON( - "index" << indexKey() << - "frv" << ( _frv ? _frv->toString() : "" ) << - "order" << _order - ).jsonString(); - } - - shared_ptr<CoveredIndexMatcher> QueryPlan::matcher() const { - if ( !_matcher ) { - _matcher.reset( new CoveredIndexMatcher( originalQuery(), indexKey() ) ); - } - return _matcher; - } - - bool QueryPlan::isMultiKey() const { - if ( _idxNo < 0 ) - return false; - return _d->isMultikey( _idxNo ); - } - - std::ostream& operator<< ( std::ostream& out, const QueryPlan::Utility& utility ) { - out << "QueryPlan::"; - switch( utility ) { - case QueryPlan::Impossible: return out << "Impossible"; - case QueryPlan::Optimal: return out << "Optimal"; - case QueryPlan::Helpful: return out << "Helpful"; - case QueryPlan::Unhelpful: return out << "Unhelpful"; - case QueryPlan::Disallowed: return out << "Disallowed"; - default: - return out << "UNKNOWN(" << utility << ")"; - } - } - -} // namespace mongo diff --git a/src/mongo/db/query_plan.h b/src/mongo/db/query_plan.h deleted file mode 100644 index d2891ce4a2d..00000000000 --- a/src/mongo/db/query_plan.h +++ /dev/null @@ -1,199 +0,0 @@ -/** - * Copyright (C) 2008 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/>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the GNU Affero General Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#pragma once - -#include "mongo/db/diskloc.h" -#include "mongo/db/index/index_descriptor.h" -#include "mongo/db/jsobj.h" -#include "mongo/db/matcher.h" -#include "mongo/db/projection.h" -#include "mongo/db/querypattern.h" - -namespace mongo { - - class Cursor; - class FieldRangeSet; - class FieldRangeSetPair; - class IndexDetails; - class NamespaceDetails; - class ParsedQuery; - struct QueryPlanSummary; - - /** - * A plan for executing a query using the given index spec and FieldRangeSet. An object of this - * class may only be used by one thread at a time. - */ - class QueryPlan : boost::noncopyable { - public: - - /** - * @param originalFrsp - original constraints for this query clause. If null, frsp will be - * used instead. - */ - static QueryPlan* make( NamespaceDetails* d, - int idxNo, // -1 = no index - const FieldRangeSetPair& frsp, - const FieldRangeSetPair* originalFrsp, - const BSONObj& originalQuery, - const BSONObj& order, - const shared_ptr<const ParsedQuery>& parsedQuery = - shared_ptr<const ParsedQuery>(), - const BSONObj& startKey = BSONObj(), - const BSONObj& endKey = BSONObj(), - const std::string& special = "" ); - - /** Categorical classification of a QueryPlan's utility. */ - enum Utility { - Impossible, // Cannot produce any matches, so the query must have an empty result set. - // No other plans need to be considered. - Optimal, // Should run as the only candidate plan in the absence of an Impossible - // plan. - Helpful, // Should be considered. - Unhelpful, // Should not be considered. - Disallowed // Must not be considered unless explicitly hinted. May produce a - // semantically incorrect result set. - }; - - Utility utility() const { return _utility; } - - /** @return true if ScanAndOrder processing will be required for result set. */ - bool scanAndOrderRequired() const { return _scanAndOrderRequired; } - - /** - * @return false if document matching can be determined entirely using index keys and the - * FieldRangeSetPair generated for the query, without using a Matcher. This function may - * return false positives but not false negatives. For example, if the field range set's - * mustBeExactMatchRepresentation() returns a false negative, this function will return a - * false positive. - */ - bool mayBeMatcherNecessary() const { return _matcherNecessary; } - - /** @return true if this QueryPlan would perform an unindexed scan. */ - bool willScanTable() const { return _idxNo < 0 && ( _utility != Impossible ); } - - /** - * @return 'special' attribute of the plan, which was either set explicitly or generated - * from the index. - */ - const string& special() const { return _special; } - - /** @return a new cursor based on this QueryPlan's index and FieldRangeSet. */ - shared_ptr<Cursor> newCursor( const DiskLoc& startLoc = DiskLoc(), - bool requestIntervalCursor = false ) const; - - /** @return a new reverse cursor if this is an unindexed plan. */ - shared_ptr<Cursor> newReverseCursor() const; - - /** Register this plan as a winner for its QueryPattern, with specified 'nscanned'. */ - void registerSelf( long long nScanned, CandidatePlanCharacter candidatePlans ) const; - - int direction() const { return _direction; } - - BSONObj indexKey() const; - - bool indexed() const { return _index != 0; } - - const IndexDetails* index() const { return _index; } - - int idxNo() const { return _idxNo; } - - const char* ns() const; - - NamespaceDetails* nsd() const { return _d; } - - BSONObj originalQuery() const { return _originalQuery; } - - shared_ptr<FieldRangeVector> originalFrv() const { return _originalFrv; } - - const FieldRangeSet& multikeyFrs() const { return _frsMulti; } - - shared_ptr<Projection::KeyOnly> keyFieldsOnly() const { return _keyFieldsOnly; } - - const ParsedQuery* parsedQuery() const { return _parsedQuery.get(); } - - /** @return a shared, lazily initialized matcher for the query plan. */ - shared_ptr<CoveredIndexMatcher> matcher() const; - - QueryPlanSummary summary() const; - - // The following member functions are for testing, or public for testing. - - shared_ptr<FieldRangeVector> frv() const { return _frv; } - bool isMultiKey() const; - string toString() const; - bool queryBoundsExactOrderSuffix() const; - - private: - - QueryPlan( NamespaceDetails* d, - int idxNo, - const FieldRangeSetPair& frsp, - const BSONObj& originalQuery, - const BSONObj& order, - const shared_ptr<const ParsedQuery>& parsedQuery, - const std::string& special ); - void init( const FieldRangeSetPair* originalFrsp, - const BSONObj& startKey, - const BSONObj& endKey ); - - void checkTableScanAllowed() const; - - int independentRangesSingleIntervalLimit() const; - - /** @return true when the plan's query may contains an $exists:false predicate. */ - bool hasPossibleExistsFalsePredicate() const; - - NamespaceDetails* _d; - int _idxNo; - const FieldRangeSet& _frs; - const FieldRangeSet& _frsMulti; - const BSONObj _originalQuery; - const BSONObj _order; - shared_ptr<const ParsedQuery> _parsedQuery; - const IndexDetails* _index; - bool _scanAndOrderRequired; - bool _matcherNecessary; - int _direction; - shared_ptr<FieldRangeVector> _frv; - shared_ptr<FieldRangeVector> _originalFrv; - BSONObj _startKey; - BSONObj _endKey; - bool _endKeyInclusive; - Utility _utility; - string _special; - bool _startOrEndSpec; - shared_ptr<Projection::KeyOnly> _keyFieldsOnly; - mutable shared_ptr<CoveredIndexMatcher> _matcher; // Lazy initialization. - auto_ptr<IndexDescriptor> _descriptor; - string _specialIndexName; - }; - - std::ostream &operator<< ( std::ostream& out, const QueryPlan::Utility& utility ); - -} // namespace mongo diff --git a/src/mongo/db/query_plan_selection_policy.cpp b/src/mongo/db/query_plan_selection_policy.cpp deleted file mode 100644 index ee653637e79..00000000000 --- a/src/mongo/db/query_plan_selection_policy.cpp +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright (C) 2011 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/>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the GNU Affero General Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#include "mongo/db/query_plan_selection_policy.h" - -#include "mongo/db/query_optimizer_internal.h" - -namespace mongo { - - QueryPlanSelectionPolicy::Any QueryPlanSelectionPolicy::__any; - const QueryPlanSelectionPolicy& QueryPlanSelectionPolicy::any() { return __any; } - - bool QueryPlanSelectionPolicy::IndexOnly::permitPlan( const QueryPlan& plan ) const { - return !plan.willScanTable(); - } - QueryPlanSelectionPolicy::IndexOnly QueryPlanSelectionPolicy::__indexOnly; - const QueryPlanSelectionPolicy& QueryPlanSelectionPolicy::indexOnly() { return __indexOnly; } - - bool QueryPlanSelectionPolicy::IdElseNatural::permitPlan( const QueryPlan& plan ) const { - return !plan.indexed() || plan.index()->isIdIndex(); - } - BSONObj QueryPlanSelectionPolicy::IdElseNatural::planHint( const StringData& ns ) const { - NamespaceDetails* nsd = nsdetails( ns ); - if ( !nsd || !nsd->haveIdIndex() ) { - return BSON( "$hint" << BSON( "$natural" << 1 ) ); - } - return BSON( "$hint" << nsd->idx( nsd->findIdIndex() ).indexName() ); - } - QueryPlanSelectionPolicy::IdElseNatural QueryPlanSelectionPolicy::__idElseNatural; - const QueryPlanSelectionPolicy& QueryPlanSelectionPolicy::idElseNatural() { - return __idElseNatural; - } - -} // namespace mongo diff --git a/src/mongo/db/query_plan_selection_policy.h b/src/mongo/db/query_plan_selection_policy.h deleted file mode 100644 index 2ae1ccdc45e..00000000000 --- a/src/mongo/db/query_plan_selection_policy.h +++ /dev/null @@ -1,110 +0,0 @@ -/** - * Copyright (C) 2011 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/>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the GNU Affero General Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#pragma once - -#include "mongo/db/jsobj.h" - -namespace mongo { - - class QueryPlan; - - /** - * An interface for policies overriding the query optimizer's default behavior for selecting - * query plans and creating cursors. - */ - class QueryPlanSelectionPolicy { - public: - virtual ~QueryPlanSelectionPolicy() {} - virtual string name() const = 0; - virtual bool permitOptimalNaturalPlan() const { return true; } - virtual bool permitOptimalIdPlan() const { return true; } - virtual bool permitPlan( const QueryPlan& plan ) const { return true; } - virtual BSONObj planHint( const StringData& ns ) const { return BSONObj(); } - - /** - * @return true to request that a created Cursor provide a matcher(). If false, the - * Cursor's matcher() may be NULL if the Cursor can perform accurate query matching - * internally using a non Matcher mechanism. One case where a Matcher might be requested - * even though not strictly necessary to select matching documents is if metadata about - * matches may be requested using MatchDetails. NOTE This is a hint that the Cursor use a - * Matcher, but the hint may be ignored. In some cases the Cursor may not provide - * a Matcher even if 'requestMatcher' is true. - */ - virtual bool requestMatcher() const { return true; } - - /** - * @return true to request creating an IntervalBtreeCursor rather than a BtreeCursor when - * possible. An IntervalBtreeCursor is optimized for counting the number of documents - * between two endpoints in a btree. NOTE This is a hint to create an interval cursor, but - * the hint may be ignored. In some cases a different cursor type may be created even if - * 'requestIntervalCursor' is true. - */ - virtual bool requestIntervalCursor() const { return false; } - - /** Allow any query plan selection, permitting the query optimizer's default behavior. */ - static const QueryPlanSelectionPolicy& any(); - - /** Prevent unindexed collection scans. */ - static const QueryPlanSelectionPolicy& indexOnly(); - - /** - * Generally hints to use the _id plan, falling back to the $natural plan. However, the - * $natural plan will always be used if optimal for the query. - */ - static const QueryPlanSelectionPolicy& idElseNatural(); - - private: - class Any; - static Any __any; - class IndexOnly; - static IndexOnly __indexOnly; - class IdElseNatural; - static IdElseNatural __idElseNatural; - }; - - class QueryPlanSelectionPolicy::Any : public QueryPlanSelectionPolicy { - public: - virtual string name() const { return "any"; } - }; - - class QueryPlanSelectionPolicy::IndexOnly : public QueryPlanSelectionPolicy { - public: - virtual string name() const { return "indexOnly"; } - virtual bool permitOptimalNaturalPlan() const { return false; } - virtual bool permitPlan( const QueryPlan& plan ) const; - }; - - class QueryPlanSelectionPolicy::IdElseNatural : public QueryPlanSelectionPolicy { - public: - virtual string name() const { return "idElseNatural"; } - virtual bool permitPlan( const QueryPlan& plan ) const; - virtual BSONObj planHint( const StringData& ns ) const; - }; - -} // namespace mongo diff --git a/src/mongo/db/query_plan_summary.h b/src/mongo/db/query_plan_summary.h deleted file mode 100644 index 30a9fd14408..00000000000 --- a/src/mongo/db/query_plan_summary.h +++ /dev/null @@ -1,64 +0,0 @@ -/** -* Copyright (C) 2008 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/>. -* -* As a special exception, the copyright holders give permission to link the -* code of portions of this program with the OpenSSL library under certain -* conditions as described in each individual source file and distribute -* linked combinations including the program with the OpenSSL library. You -* must comply with the GNU Affero General Public License in all respects for -* all of the code used other than as permitted herein. If you modify file(s) -* with this exception, you may extend this exception to your version of the -* file(s), but you are not obligated to do so. If you do not wish to do so, -* delete this exception statement from your version. If you delete this -* exception statement from all source files in the program, then also delete -* it in the license file. -*/ - -#pragma once - -#include "mongo/db/projection.h" - -namespace mongo { - - class FieldRangeSet; - - /** - * A partial description of a QueryPlan that provides access to relevant plan attributes outside - * of query optimizer internal code. - */ - struct QueryPlanSummary { - QueryPlanSummary() : - scanAndOrderRequired() { - } - - /** - * The 'fieldRangeMulti' attribute is required, and its presence indicates the object has - * been configured with a query plan. - */ - bool valid() const { return fieldRangeSetMulti; } - - // A description of the valid values for the fields of a query, in the context of a multikey - // index or in memory sort. - shared_ptr<FieldRangeSet> fieldRangeSetMulti; - - // A helper object used to implement covered index queries. This attribute is only non-NULL - // if the query plan supports covered index queries. - shared_ptr<Projection::KeyOnly> keyFieldsOnly; - - // True if the query plan results must be reordered to match a requested ordering. - bool scanAndOrderRequired; - }; - -} // namespace mongo diff --git a/src/mongo/db/queryoptimizercursor.h b/src/mongo/db/queryoptimizercursor.h deleted file mode 100644 index dbddee8b02f..00000000000 --- a/src/mongo/db/queryoptimizercursor.h +++ /dev/null @@ -1,81 +0,0 @@ -/** - * Copyright (C) 2011 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/>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the GNU Affero General Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#pragma once - -#include "cursor.h" - -namespace mongo { - - class CandidatePlanCharacter; - class ExplainQueryInfo; - class FieldRangeSet; - - /** - * Adds functionality to Cursor for running multiple plans, running out of order plans, - * utilizing covered indexes, and generating explain output. - */ - class QueryOptimizerCursor : public Cursor { - public: - - /** Candidate plans for the query before it begins running. */ - virtual CandidatePlanCharacter initialCandidatePlans() const = 0; - /** FieldRangeSet for the query before it begins running. */ - virtual const FieldRangeSet *initialFieldRangeSet() const = 0; - - /** @return true if the plan for the current iterate is out of order. */ - virtual bool currentPlanScanAndOrderRequired() const = 0; - - /** @return true when there may be multiple plans running and some are in order. */ - virtual bool runningInitialInOrderPlan() const = 0; - /** - * @return true when some query plans may have been excluded due to plan caching, for a - * non-$or query. - */ - virtual bool hasPossiblyExcludedPlans() const = 0; - - /** - * @return true when both in order and out of order candidate plans were available, and - * an out of order candidate plan completed iteration. - */ - virtual bool completePlanOfHybridSetScanAndOrderRequired() const = 0; - - /** Clear recorded indexes for the current clause's query patterns. */ - virtual void clearIndexesForPatterns() = 0; - /** Stop returning results from out of order plans and do not allow them to complete. */ - virtual void abortOutOfOrderPlans() = 0; - - /** Note match information for the current iterate, to generate explain output. */ - virtual void noteIterate( bool match, bool loadedDocument, bool chunkSkip ) = 0; - /** Note a lock yield for explain output reporting. */ - virtual void noteYield() = 0; - /** @return explain output for the query run by this cursor. */ - virtual shared_ptr<ExplainQueryInfo> explainQueryInfo() const = 0; - }; - -} // namespace mongo diff --git a/src/mongo/db/queryoptimizercursorimpl.cpp b/src/mongo/db/queryoptimizercursorimpl.cpp deleted file mode 100644 index 0545aa7ea9f..00000000000 --- a/src/mongo/db/queryoptimizercursorimpl.cpp +++ /dev/null @@ -1,513 +0,0 @@ -/** - * Copyright (C) 2011 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/>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the GNU Affero General Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#include "mongo/pch.h" - -#include "mongo/db/queryoptimizercursorimpl.h" - -#include "mongo/db/btreecursor.h" -#include "mongo/db/query_plan_selection_policy.h" -#include "mongo/db/query_plan_summary.h" -#include "mongo/db/query_optimizer_internal.h" -#include "mongo/db/queryutil.h" - -namespace mongo { - - QueryOptimizerCursorImpl* QueryOptimizerCursorImpl::make - ( auto_ptr<MultiPlanScanner>& mps, - const QueryPlanSelectionPolicy& planPolicy, - bool requireOrder, - bool explain ) { - auto_ptr<QueryOptimizerCursorImpl> ret( new QueryOptimizerCursorImpl( mps, planPolicy, - requireOrder ) ); - ret->init( explain ); - return ret.release(); - } - - bool QueryOptimizerCursorImpl::ok() { - return _takeover ? _takeover->ok() : !currLoc().isNull(); - } - - Record* QueryOptimizerCursorImpl::_current() { - if ( _takeover ) { - return _takeover->_current(); - } - assertOk(); - return currLoc().rec(); - } - - BSONObj QueryOptimizerCursorImpl::current() { - if ( _takeover ) { - return _takeover->current(); - } - assertOk(); - return currLoc().obj(); - } - - DiskLoc QueryOptimizerCursorImpl::currLoc() { - return _takeover ? _takeover->currLoc() : _currLoc(); - } - - DiskLoc QueryOptimizerCursorImpl::_currLoc() const { - dassert( !_takeover ); - return _currRunner ? _currRunner->currLoc() : DiskLoc(); - } - - bool QueryOptimizerCursorImpl::advance() { - return _advance( false ); - } - - BSONObj QueryOptimizerCursorImpl::currKey() const { - if ( _takeover ) { - return _takeover->currKey(); - } - assertOk(); - return _currRunner->currKey(); - } - - DiskLoc QueryOptimizerCursorImpl::refLoc() { - return _takeover ? _takeover->refLoc() : DiskLoc(); - } - - BSONObj QueryOptimizerCursorImpl::indexKeyPattern() { - if ( _takeover ) { - return _takeover->indexKeyPattern(); - } - assertOk(); - return _currRunner->cursor()->indexKeyPattern(); - } - - void QueryOptimizerCursorImpl::prepareToTouchEarlierIterate() { - if ( _takeover ) { - _takeover->prepareToTouchEarlierIterate(); - } - else if ( _currRunner ) { - if ( _mps->currentNPlans() == 1 ) { - // This single plan version is a bit more performant, so we use it when possible. - _currRunner->prepareToTouchEarlierIterate(); - } - else { - // With multiple plans, the 'earlier iterate' could be the current iterate of one of - // the component plans. We do a full yield of all plans, using ClientCursors. - _mps->prepareToYield(); - } - } - } - - void QueryOptimizerCursorImpl::recoverFromTouchingEarlierIterate() { - if ( _takeover ) { - _takeover->recoverFromTouchingEarlierIterate(); - } - else if ( _currRunner ) { - if ( _mps->currentNPlans() == 1 ) { - _currRunner->recoverFromTouchingEarlierIterate(); - } - else { - recoverFromYield(); - } - } - } - - void QueryOptimizerCursorImpl::prepareToYield() { - if ( _takeover ) { - _takeover->prepareToYield(); - } - else if ( _currRunner ) { - _mps->prepareToYield(); - } - } - - void QueryOptimizerCursorImpl::recoverFromYield() { - if ( _takeover ) { - _takeover->recoverFromYield(); - return; - } - if ( _currRunner ) { - _mps->recoverFromYield(); - if ( _currRunner->error() || !ok() ) { - // Advance to a non error op if one of the ops errored out. - // Advance to a following $or clause if the $or clause returned all results. - verify( !_mps->doneRunners() ); - _advance( true ); - } - } - } - - bool QueryOptimizerCursorImpl::getsetdup(DiskLoc loc) { - if ( _takeover ) { - if ( getdupInternal( loc ) ) { - return true; - } - return _takeover->getsetdup( loc ); - } - assertOk(); - return getsetdupInternal( loc ); - } - - bool QueryOptimizerCursorImpl::isMultiKey() const { - if ( _takeover ) { - return _takeover->isMultiKey(); - } - assertOk(); - return _currRunner->cursor()->isMultiKey(); - } - - bool QueryOptimizerCursorImpl::capped() const { - // Initial capped wrapping cases (before takeover) are handled internally by a component - // ClientCursor. - return _takeover ? _takeover->capped() : false; - } - - long long QueryOptimizerCursorImpl::nscanned() { - return _takeover ? _takeover->nscanned() : _nscanned; - } - - CoveredIndexMatcher* QueryOptimizerCursorImpl::matcher() const { - if ( _takeover ) { - return _takeover->matcher(); - } - assertOk(); - return _currRunner->queryPlan().matcher().get(); - } - - bool QueryOptimizerCursorImpl::currentMatches( MatchDetails* details ) { - if ( _takeover ) { - return _takeover->currentMatches( details ); - } - assertOk(); - return _currRunner->currentMatches( details ); - } - - const FieldRangeSet* QueryOptimizerCursorImpl::initialFieldRangeSet() const { - if ( _takeover ) { - return 0; - } - assertOk(); - return &_currRunner->queryPlan().multikeyFrs(); - } - - bool QueryOptimizerCursorImpl::currentPlanScanAndOrderRequired() const { - if ( _takeover ) { - return _takeover->queryPlan().scanAndOrderRequired(); - } - assertOk(); - return _currRunner->queryPlan().scanAndOrderRequired(); - } - - const Projection::KeyOnly* QueryOptimizerCursorImpl::keyFieldsOnly() const { - if ( _takeover ) { - return _takeover->keyFieldsOnly(); - } - assertOk(); - return _currRunner->keyFieldsOnly(); - } - - bool QueryOptimizerCursorImpl::runningInitialInOrderPlan() const { - if ( _takeover ) { - return false; - } - assertOk(); - return _mps->haveInOrderPlan(); - } - - bool QueryOptimizerCursorImpl::hasPossiblyExcludedPlans() const { - if ( _takeover ) { - return false; - } - assertOk(); - return _mps->hasPossiblyExcludedPlans(); - } - - void QueryOptimizerCursorImpl::clearIndexesForPatterns() { - if ( !_takeover ) { - _mps->clearIndexesForPatterns(); - } - } - - void QueryOptimizerCursorImpl::abortOutOfOrderPlans() { - _requireOrder = true; - } - - void QueryOptimizerCursorImpl::noteIterate( bool match, bool loadedDocument, bool chunkSkip ) { - if ( _explainQueryInfo ) { - _explainQueryInfo->noteIterate( match, loadedDocument, chunkSkip ); - } - if ( _takeover ) { - _takeover->noteIterate( match, loadedDocument ); - } - } - - void QueryOptimizerCursorImpl::noteYield() { - if ( _explainQueryInfo ) { - _explainQueryInfo->noteYield(); - } - } - - QueryOptimizerCursorImpl::QueryOptimizerCursorImpl( auto_ptr<MultiPlanScanner>& mps, - const QueryPlanSelectionPolicy& planPolicy, - bool requireOrder ) : - _requireOrder( requireOrder ), - _mps( mps ), - _initialCandidatePlans( _mps->possibleInOrderPlan(), _mps->possibleOutOfOrderPlan() ), - _originalRunner( new QueryPlanRunner( _nscanned, - planPolicy, - _requireOrder, - !_initialCandidatePlans.hybridPlanSet() ) ), - _currRunner(), - _completePlanOfHybridSetScanAndOrderRequired(), - _nscanned() { - } - - void QueryOptimizerCursorImpl::init( bool explain ) { - _mps->initialRunner( _originalRunner ); - if ( explain ) { - _explainQueryInfo = _mps->generateExplainInfo(); - } - shared_ptr<QueryPlanRunner> runner = _mps->nextRunner(); - rethrowOnError( runner ); - if ( !runner->complete() ) { - _currRunner = runner.get(); - } - } - - bool QueryOptimizerCursorImpl::_advance( bool force ) { - if ( _takeover ) { - return _takeover->advance(); - } - - if ( !force && !ok() ) { - return false; - } - - _currRunner = 0; - shared_ptr<QueryPlanRunner> runner = _mps->nextRunner(); - rethrowOnError( runner ); - - if ( !runner->complete() ) { - // The 'runner' will be valid until we call _mps->nextOp() again. We return 'current' - // values from this op. - _currRunner = runner.get(); - } - else if ( runner->stopRequested() ) { - if ( runner->cursor() ) { - _takeover.reset( new MultiCursor( _mps, - runner->cursor(), - runner->queryPlan().matcher(), - runner->explainInfo(), - *runner, - _nscanned - runner->cursor()->nscanned() ) ); - } - } - else { - if ( _initialCandidatePlans.hybridPlanSet() ) { - _completePlanOfHybridSetScanAndOrderRequired = - runner->queryPlan().scanAndOrderRequired(); - } - } - - return ok(); - } - - /** Forward an exception when the runner errs out. */ - void QueryOptimizerCursorImpl::rethrowOnError( const shared_ptr< QueryPlanRunner > &runner ) { - if ( runner->error() ) { - throw MsgAssertionException( runner->exception() ); - } - } - - bool QueryOptimizerCursorImpl::getsetdupInternal(const DiskLoc &loc) { - return _dups.getsetdup( loc ); - } - - bool QueryOptimizerCursorImpl::getdupInternal(const DiskLoc &loc) { - dassert( _takeover ); - return _dups.getdup( loc ); - } - - shared_ptr<Cursor> newQueryOptimizerCursor( auto_ptr<MultiPlanScanner> mps, - const QueryPlanSelectionPolicy &planPolicy, - bool requireOrder, bool explain ) { - try { - shared_ptr<QueryOptimizerCursorImpl> ret - ( QueryOptimizerCursorImpl::make( mps, planPolicy, requireOrder, explain ) ); - return ret; - } catch( const AssertionException &e ) { - if ( e.getCode() == OutOfOrderDocumentsAssertionCode ) { - // If no indexes follow the requested sort order, return an - // empty pointer. This is legacy behavior based on bestGuessCursor(). - return shared_ptr<Cursor>(); - } - throw; - } - return shared_ptr<Cursor>(); - } - - CursorGenerator::CursorGenerator( const StringData &ns, - const BSONObj &query, - const BSONObj &order, - const QueryPlanSelectionPolicy &planPolicy, - const shared_ptr<const ParsedQuery> &parsedQuery, - bool requireOrder, - QueryPlanSummary *singlePlanSummary ) : - _ns( ns ), - _query( query ), - _order( order ), - _planPolicy( planPolicy ), - _parsedQuery( parsedQuery ), - _requireOrder( requireOrder ), - _singlePlanSummary( singlePlanSummary ) { - // Initialize optional return variables. - if ( _singlePlanSummary ) { - *_singlePlanSummary = QueryPlanSummary(); - } - } - - BSONObj CursorGenerator::hint() const { - return _argumentsHint.isEmpty() ? _planPolicy.planHint( _ns ) : _argumentsHint; - } - - void CursorGenerator::setArgumentsHint() { - if (storageGlobalParams.useHints && _parsedQuery) { - _argumentsHint = _parsedQuery->getHint(); - } - - if ( snapshot() ) { - NamespaceDetails *d = nsdetails( _ns ); - if ( d ) { - int i = d->findIdIndex(); - if( i < 0 ) { - if ( _ns.find( ".system." ) == string::npos ) - log() << "warning: no _id index on $snapshot query, ns:" << _ns << endl; - } - else { - /* [dm] the name of an _id index tends to vary, so we build the hint the hard - way here. probably need a better way to specify "use the _id index" as a hint. - if someone is in the query optimizer please fix this then! - */ - _argumentsHint = BSON( "$hint" << d->idx(i).indexName() ); - } - } - } - } - - shared_ptr<Cursor> CursorGenerator::shortcutCursor() const { - if ( !mayShortcutQueryOptimizer() ) { - return shared_ptr<Cursor>(); - } - - if ( _planPolicy.permitOptimalNaturalPlan() && _query.isEmpty() && _order.isEmpty() ) { - return theDataFileMgr.findAll( _ns ); - } - if ( _planPolicy.permitOptimalIdPlan() && isSimpleIdQuery( _query ) ) { - Database *database = cc().database(); - verify( database ); - NamespaceDetails *d = database->namespaceIndex().details( _ns ); - if ( d ) { - int idxNo = d->findIdIndex(); - if ( idxNo >= 0 ) { - IndexDetails& i = d->idx( idxNo ); - BSONObj key = i.getKeyFromQuery( _query ); - return shared_ptr<Cursor>( BtreeCursor::make( d, i, key, key, true, 1 ) ); - } - } - } - - return shared_ptr<Cursor>(); - } - - void CursorGenerator::setMultiPlanScanner() { - _mps.reset( MultiPlanScanner::make( _ns, _query, _order, _parsedQuery, hint(), - explain() ? QueryPlanGenerator::Ignore : - QueryPlanGenerator::Use, - min(), max() ) ); - } - - shared_ptr<Cursor> CursorGenerator::singlePlanCursor() { - const QueryPlan *singlePlan = _mps->singlePlan(); - if ( !singlePlan || ( isOrderRequired() && singlePlan->scanAndOrderRequired() ) ) { - return shared_ptr<Cursor>(); - } - if ( !_planPolicy.permitPlan( *singlePlan ) ) { - return shared_ptr<Cursor>(); - } - - if ( _singlePlanSummary ) { - *_singlePlanSummary = singlePlan->summary(); - } - shared_ptr<Cursor> single = singlePlan->newCursor( DiskLoc(), - _planPolicy.requestIntervalCursor() ); - if ( !_query.isEmpty() && !single->matcher() ) { - - // The query plan must have a matcher. The matcher's constructor performs some aspects - // of query validation that should occur before a cursor is returned. - fassert( 16449, singlePlan->matcher() ); - - if ( // If a matcher is requested or ... - _planPolicy.requestMatcher() || - // ... the index ranges do not exactly match the query or ... - singlePlan->mayBeMatcherNecessary() || - // ... the matcher must look at the full record ... - singlePlan->matcher()->needRecord() ) { - - // ... then set the cursor's matcher to the query plan's matcher. - single->setMatcher( singlePlan->matcher() ); - } - } - if ( singlePlan->keyFieldsOnly() ) { - single->setKeyFieldsOnly( singlePlan->keyFieldsOnly() ); - } - return single; - } - - shared_ptr<Cursor> CursorGenerator::generate() { - - setArgumentsHint(); - shared_ptr<Cursor> cursor = shortcutCursor(); - if ( cursor ) { - return cursor; - } - - setMultiPlanScanner(); - cursor = singlePlanCursor(); - if ( cursor ) { - return cursor; - } - - return newQueryOptimizerCursor( _mps, _planPolicy, isOrderRequired(), explain() ); - } - - /** This interface is just available for testing. */ - shared_ptr<Cursor> newQueryOptimizerCursor - ( const char *ns, const BSONObj &query, const BSONObj &order, - const QueryPlanSelectionPolicy &planPolicy, bool requireOrder, - const shared_ptr<const ParsedQuery> &parsedQuery ) { - auto_ptr<MultiPlanScanner> mps( MultiPlanScanner::make( ns, query, order, parsedQuery ) ); - return newQueryOptimizerCursor( mps, planPolicy, requireOrder, false ); - } - -} // namespace mongo; diff --git a/src/mongo/db/queryoptimizercursorimpl.h b/src/mongo/db/queryoptimizercursorimpl.h deleted file mode 100644 index ddd24f19940..00000000000 --- a/src/mongo/db/queryoptimizercursorimpl.h +++ /dev/null @@ -1,307 +0,0 @@ -/** - * Copyright (C) 2011 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/>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the GNU Affero General Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#pragma once - -#include "mongo/db/parsed_query.h" -#include "mongo/db/queryoptimizercursor.h" -#include "mongo/db/querypattern.h" - -namespace mongo { - - class MultiCursor; - class MultiPlanScanner; - class QueryPlanRunner; - class QueryPlanSelectionPolicy; - struct QueryPlanSummary; - - /** Dup tracking class, optimizing one common case with small set and few initial reads. */ - class SmallDupSet { - public: - SmallDupSet() : _accesses() { - _vec.reserve( 250 ); - } - /** @return true if @param 'loc' already added to the set, false if adding to the set in this call. */ - bool getsetdup( const DiskLoc &loc ) { - access(); - return vec() ? getsetdupVec( loc ) : getsetdupSet( loc ); - } - /** @return true when @param loc in the set. */ - bool getdup( const DiskLoc &loc ) { - access(); - return vec() ? getdupVec( loc ) : getdupSet( loc ); - } - private: - void access() { - ++_accesses; - mayUpgrade(); - } - void mayUpgrade() { - if ( vec() && _accesses > 500 ) { - _set.insert( _vec.begin(), _vec.end() ); - } - } - bool vec() const { - return _set.size() == 0; - } - bool getsetdupVec( const DiskLoc &loc ) { - if ( getdupVec( loc ) ) { - return true; - } - _vec.push_back( loc ); - return false; - } - bool getdupVec( const DiskLoc &loc ) const { - for( vector<DiskLoc>::const_iterator i = _vec.begin(); i != _vec.end(); ++i ) { - if ( *i == loc ) { - return true; - } - } - return false; - } - bool getsetdupSet( const DiskLoc &loc ) { - pair<set<DiskLoc>::iterator, bool> p = _set.insert(loc); - return !p.second; - } - bool getdupSet( const DiskLoc &loc ) { - return _set.count( loc ) > 0; - } - vector<DiskLoc> _vec; - set<DiskLoc> _set; - long long _accesses; - }; - - /** - * This cursor runs a MultiPlanScanner iteratively and returns results from - * the scanner's cursors as they become available. Once the scanner chooses - * a single plan, this cursor becomes a simple wrapper around that single - * plan's cursor (called the 'takeover' cursor). - * - * A QueryOptimizerCursor employs a delegation strategy to ensure consistency after writes - * during its initial phase when multiple delegate Cursors may be active (before _takeover is - * set). - * - * Before takeover, the return value of refLoc() will be isNull(), causing ClientCursor to - * ignore a QueryOptimizerCursor (though not its delegate Cursors) when a delete occurs. - * Requests to prepareToYield() or recoverFromYield() will be forwarded to - * prepareToYield()/recoverFromYield() on ClientCursors of delegate Cursors. If a delegate - * Cursor becomes eof() or invalid after a yield recovery, - * QueryOptimizerCursor::recoverFromYield() may advance _currRunner to another delegate Cursor. - * - * Requests to prepareToTouchEarlierIterate() or recoverFromTouchingEarlierIterate() are - * forwarded as prepareToTouchEarlierIterate()/recoverFromTouchingEarlierIterate() to the - * delegate Cursor when a single delegate Cursor is active. If multiple delegate Cursors are - * active, the advance() call preceeding prepareToTouchEarlierIterate() may not properly advance - * all delegate Cursors, so the calls are forwarded as prepareToYield()/recoverFromYield() to a - * ClientCursor for each delegate Cursor. - * - * After _takeover is set, consistency after writes is ensured by delegation to the _takeover - * MultiCursor. - */ - class QueryOptimizerCursorImpl : public QueryOptimizerCursor { - public: - static QueryOptimizerCursorImpl* make( auto_ptr<MultiPlanScanner>& mps, - const QueryPlanSelectionPolicy& planPolicy, - bool requireOrder, - bool explain ); - - virtual bool ok(); - - virtual Record* _current(); - - virtual BSONObj current(); - - virtual DiskLoc currLoc(); - - DiskLoc _currLoc() const; - - virtual bool advance(); - - virtual BSONObj currKey() const; - - /** - * When return value isNull(), our cursor will be ignored for deletions by the ClientCursor - * implementation. In such cases, internal ClientCursors will update the positions of - * component Cursors when necessary. - * !!! Use care if changing this behavior, as some ClientCursor functionality may not work - * recursively. - */ - virtual DiskLoc refLoc(); - - virtual BSONObj indexKeyPattern(); - - virtual bool supportGetMore() { return true; } - - virtual bool supportYields() { return true; } - - virtual void prepareToTouchEarlierIterate(); - - virtual void recoverFromTouchingEarlierIterate(); - - virtual void prepareToYield(); - - virtual void recoverFromYield(); - - virtual string toString() { return "QueryOptimizerCursor"; } - - virtual bool getsetdup(DiskLoc loc); - - /** Matcher needs to know if the the cursor being forwarded to is multikey. */ - virtual bool isMultiKey() const; - - // TODO fix - virtual bool modifiedKeys() const { return true; } - - virtual bool capped() const; - - virtual long long nscanned(); - - virtual CoveredIndexMatcher *matcher() const; - - virtual bool currentMatches( MatchDetails* details = 0 ); - - virtual CandidatePlanCharacter initialCandidatePlans() const { - return _initialCandidatePlans; - } - - virtual const FieldRangeSet* initialFieldRangeSet() const; - - virtual bool currentPlanScanAndOrderRequired() const; - - virtual const Projection::KeyOnly* keyFieldsOnly() const; - - virtual bool runningInitialInOrderPlan() const; - - virtual bool hasPossiblyExcludedPlans() const; - - virtual bool completePlanOfHybridSetScanAndOrderRequired() const { - return _completePlanOfHybridSetScanAndOrderRequired; - } - - virtual void clearIndexesForPatterns(); - - virtual void abortOutOfOrderPlans(); - - virtual void noteIterate( bool match, bool loadedDocument, bool chunkSkip ); - - virtual void noteYield(); - - virtual shared_ptr<ExplainQueryInfo> explainQueryInfo() const { - return _explainQueryInfo; - } - - private: - - QueryOptimizerCursorImpl( auto_ptr<MultiPlanScanner>& mps, - const QueryPlanSelectionPolicy& planPolicy, - bool requireOrder ); - - void init( bool explain ); - - /** - * Advances the QueryPlanSet::Runner. - * @param force - advance even if the current query op is not valid. The 'force' param should only be specified - * when there are plans left in the runner. - */ - bool _advance( bool force ); - - /** Forward an exception when the runner errs out. */ - void rethrowOnError( const shared_ptr< QueryPlanRunner >& runner ); - - void assertOk() const { - massert( 14809, "Invalid access for cursor that is not ok()", !_currLoc().isNull() ); - } - - /** Insert and check for dups before takeover occurs */ - bool getsetdupInternal(const DiskLoc& loc); - - /** Just check for dups - after takeover occurs */ - bool getdupInternal(const DiskLoc& loc); - - bool _requireOrder; - auto_ptr<MultiPlanScanner> _mps; - CandidatePlanCharacter _initialCandidatePlans; - shared_ptr<QueryPlanRunner> _originalRunner; - QueryPlanRunner* _currRunner; - bool _completePlanOfHybridSetScanAndOrderRequired; - shared_ptr<MultiCursor> _takeover; - long long _nscanned; - // Using a SmallDupSet seems a bit hokey, but I've measured a 5% performance improvement - // with ~100 document non multi key scans. - SmallDupSet _dups; - shared_ptr<ExplainQueryInfo> _explainQueryInfo; - }; - - /** - * Helper class for generating a simple Cursor or QueryOptimizerCursor from a set of query - * parameters. This class was refactored from a single function call and is not expected to - * outlive its constructor arguments. - */ - class CursorGenerator { - public: - CursorGenerator( const StringData& ns, - const BSONObj &query, - const BSONObj &order, - const QueryPlanSelectionPolicy &planPolicy, - const shared_ptr<const ParsedQuery> &parsedQuery, - bool requireOrder, - QueryPlanSummary *singlePlanSummary ); - - shared_ptr<Cursor> generate(); - - private: - bool snapshot() const { return _parsedQuery && _parsedQuery->isSnapshot(); } - bool explain() const { return _parsedQuery && _parsedQuery->isExplain(); } - BSONObj min() const { return _parsedQuery ? _parsedQuery->getMin() : BSONObj(); } - BSONObj max() const { return _parsedQuery ? _parsedQuery->getMax() : BSONObj(); } - bool hasFields() const { return _parsedQuery && _parsedQuery->getFieldPtr(); } - - bool isOrderRequired() const { return _requireOrder; } - bool mayShortcutQueryOptimizer() const { - return min().isEmpty() && max().isEmpty() && !hasFields() && _argumentsHint.isEmpty(); - } - BSONObj hint() const; - - void setArgumentsHint(); - shared_ptr<Cursor> shortcutCursor() const; - void setMultiPlanScanner(); - shared_ptr<Cursor> singlePlanCursor(); - - const StringData _ns; - BSONObj _query; - BSONObj _order; - const QueryPlanSelectionPolicy &_planPolicy; - shared_ptr<const ParsedQuery> _parsedQuery; - bool _requireOrder; - QueryPlanSummary *_singlePlanSummary; - - BSONObj _argumentsHint; - auto_ptr<MultiPlanScanner> _mps; - }; - -} // namespace mongo diff --git a/src/mongo/db/querypattern.cpp b/src/mongo/db/querypattern.cpp deleted file mode 100644 index 8abdc8c281a..00000000000 --- a/src/mongo/db/querypattern.cpp +++ /dev/null @@ -1,117 +0,0 @@ -// @file querypattern.cpp - Query pattern matching for selecting similar plans given similar queries. - -/* Copyright 2011 10gen Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "querypattern.h" -#include "mongo/db/queryutil.h" - -namespace mongo { - - QueryPattern::QueryPattern( const FieldRangeSet &frs, const BSONObj &sort ) { - for( map<string,FieldRange>::const_iterator i = frs.ranges().begin(); i != frs.ranges().end(); ++i ) { - if ( i->second.equality() ) { - _fieldTypes[ i->first ] = QueryPattern::Equality; - } - else if ( i->second.empty() ) { - _fieldTypes[ i->first ] = QueryPattern::Empty; - } - else if ( !i->second.universal() ) { - bool upper = i->second.max().type() != MaxKey; - bool lower = i->second.min().type() != MinKey; - if ( upper && lower ) { - _fieldTypes[ i->first ] = QueryPattern::UpperAndLowerBound; - } - else if ( upper ) { - _fieldTypes[ i->first ] = QueryPattern::UpperBound; - } - else if ( lower ) { - _fieldTypes[ i->first ] = QueryPattern::LowerBound; - } - else { - _fieldTypes[ i->first ] = QueryPattern::ConstraintPresent; - } - } - } - setSort( sort ); - } - - /** for testing only - speed unimportant */ - bool QueryPattern::operator==( const QueryPattern &other ) const { - bool less = operator<( other ); - bool more = other.operator<( *this ); - verify( !( less && more ) ); - return !( less || more ); - } - - /** for testing only - speed unimportant */ - bool QueryPattern::operator!=( const QueryPattern &other ) const { - return !operator==( other ); - } - - string typeToString( enum QueryPattern::Type t ) { - switch (t) { - case QueryPattern::Empty: - return "Empty"; - case QueryPattern::Equality: - return "Equality"; - case QueryPattern::LowerBound: - return "LowerBound"; - case QueryPattern::UpperBound: - return "UpperBound"; - case QueryPattern::UpperAndLowerBound: - return "UpperAndLowerBound"; - case QueryPattern::ConstraintPresent: - return "ConstraintPresent"; - } - return ""; - } - - string QueryPattern::toString() const { - BSONObjBuilder b; - for( map<string,Type>::const_iterator i = _fieldTypes.begin(); i != _fieldTypes.end(); ++i ) { - b << i->first << typeToString( i->second ); - } - return BSON( "query" << b.done() << "sort" << _sort ).toString(); - } - - void QueryPattern::setSort( const BSONObj sort ) { - _sort = normalizeSort( sort ); - } - - BSONObj QueryPattern::normalizeSort( const BSONObj &spec ) { - if ( spec.isEmpty() ) - return spec; - int direction = ( spec.firstElement().number() >= 0 ) ? 1 : -1; - BSONObjIterator i( spec ); - BSONObjBuilder b; - while( i.moreWithEOO() ) { - BSONElement e = i.next(); - if ( e.eoo() ) - break; - b.append( e.fieldName(), direction * ( ( e.number() >= 0 ) ? -1 : 1 ) ); - } - return b.obj(); - } - - CachedQueryPlan::CachedQueryPlan( const BSONObj &indexKey, long long nScanned, - CandidatePlanCharacter planCharacter ) : - _indexKey( indexKey ), - _nScanned( nScanned ), - _planCharacter( planCharacter ) { - } - - -} // namespace mongo diff --git a/src/mongo/db/querypattern.h b/src/mongo/db/querypattern.h deleted file mode 100644 index 3c7ad07fa9b..00000000000 --- a/src/mongo/db/querypattern.h +++ /dev/null @@ -1,118 +0,0 @@ -// @file querypattern.h - Query pattern matching for selecting similar plans given similar queries. - -/* Copyright 2011 10gen Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "jsobj.h" - -namespace mongo { - - class FieldRangeSet; - - /** - * Implements query pattern matching, used to determine if a query is - * similar to an earlier query and should use the same plan. - * - * Two queries will generate the same QueryPattern, and therefore match each - * other, if their fields have the same Types and they have the same sort - * spec. - */ - class QueryPattern { - public: - QueryPattern( const FieldRangeSet &frs, const BSONObj &sort ); - enum Type { - Empty, - Equality, - LowerBound, - UpperBound, - UpperAndLowerBound, - ConstraintPresent - }; - bool operator<( const QueryPattern &other ) const; - /** for testing only */ - bool operator==( const QueryPattern &other ) const; - /** for testing only */ - bool operator!=( const QueryPattern &other ) const; - /** for development / debugging */ - string toString() const; - private: - void setSort( const BSONObj sort ); - static BSONObj normalizeSort( const BSONObj &spec ); - map<string,Type> _fieldTypes; - BSONObj _sort; - }; - - /** Summarizes the candidate plans that may run for a query. */ - class CandidatePlanCharacter { - public: - CandidatePlanCharacter( bool mayRunInOrderPlan, bool mayRunOutOfOrderPlan ) : - _mayRunInOrderPlan( mayRunInOrderPlan ), - _mayRunOutOfOrderPlan( mayRunOutOfOrderPlan ) { - } - CandidatePlanCharacter() : - _mayRunInOrderPlan(), - _mayRunOutOfOrderPlan() { - } - bool mayRunInOrderPlan() const { return _mayRunInOrderPlan; } - bool mayRunOutOfOrderPlan() const { return _mayRunOutOfOrderPlan; } - bool valid() const { return mayRunInOrderPlan() || mayRunOutOfOrderPlan(); } - bool hybridPlanSet() const { return mayRunInOrderPlan() && mayRunOutOfOrderPlan(); } - private: - bool _mayRunInOrderPlan; - bool _mayRunOutOfOrderPlan; - }; - - /** Information about a query plan that ran successfully for a QueryPattern. */ - class CachedQueryPlan { - public: - CachedQueryPlan() : - _nScanned() { - } - CachedQueryPlan( const BSONObj &indexKey, long long nScanned, - CandidatePlanCharacter planCharacter ); - BSONObj indexKey() const { return _indexKey; } - long long nScanned() const { return _nScanned; } - CandidatePlanCharacter planCharacter() const { return _planCharacter; } - private: - BSONObj _indexKey; - long long _nScanned; - CandidatePlanCharacter _planCharacter; - }; - - inline bool QueryPattern::operator<( const QueryPattern &other ) const { - map<string,Type>::const_iterator i = _fieldTypes.begin(); - map<string,Type>::const_iterator j = other._fieldTypes.begin(); - while( i != _fieldTypes.end() ) { - if ( j == other._fieldTypes.end() ) - return false; - if ( i->first < j->first ) - return true; - else if ( i->first > j->first ) - return false; - if ( i->second < j->second ) - return true; - else if ( i->second > j->second ) - return false; - ++i; - ++j; - } - if ( j != other._fieldTypes.end() ) - return true; - return _sort.woCompare( other._sort ) < 0; - } - -} // namespace mongo diff --git a/src/mongo/db/queryutil.cpp b/src/mongo/db/queryutil.cpp index 33fb3040449..773833d78ae 100644 --- a/src/mongo/db/queryutil.cpp +++ b/src/mongo/db/queryutil.cpp @@ -18,6 +18,7 @@ #include "mongo/db/queryutil.h" #include "mongo/db/index_names.h" +#include "mongo/db/matcher.h" #include "mongo/db/pdfile.h" #include "mongo/util/mongoutils/str.h" @@ -1388,10 +1389,6 @@ namespace mongo { return true; } - QueryPattern FieldRangeSet::pattern( const BSONObj &sort ) const { - return QueryPattern( *this, sort ); - } - int FieldRangeSet::numNonUniversalRanges() const { int count = 0; for( map<string,FieldRange>::const_iterator i = _ranges.begin(); i != _ranges.end(); ++i ) { diff --git a/src/mongo/db/queryutil.h b/src/mongo/db/queryutil.h index a1eaf78bcf8..9dfc7a0cf3d 100644 --- a/src/mongo/db/queryutil.h +++ b/src/mongo/db/queryutil.h @@ -212,8 +212,6 @@ namespace mongo { // element having field name '$elemMatch'. }; - class QueryPattern; - /** * A set of FieldRanges determined from constraints on the fields of a query, * that may be used to determine index bounds. @@ -279,7 +277,7 @@ namespace mongo { const char *ns() const { return _ns.c_str(); } - QueryPattern pattern( const BSONObj &sort = BSONObj() ) const; + // QueryPattern pattern( const BSONObj &sort = BSONObj() ) const; SpecialIndices getSpecial() const; /** diff --git a/src/mongo/db/repl/oplog.cpp b/src/mongo/db/repl/oplog.cpp index 2560b0944f3..b6ed66e9a65 100644 --- a/src/mongo/db/repl/oplog.cpp +++ b/src/mongo/db/repl/oplog.cpp @@ -516,7 +516,7 @@ namespace mongo { Timer t; const NamespaceString requestNs(ns); - UpdateRequest request(requestNs, QueryPlanSelectionPolicy::idElseNatural()); + UpdateRequest request(requestNs); request.setQuery(o); request.setUpdates(o); @@ -545,7 +545,7 @@ namespace mongo { b.append(_id); const NamespaceString requestNs(ns); - UpdateRequest request(requestNs, QueryPlanSelectionPolicy::idElseNatural()); + UpdateRequest request(requestNs); request.setQuery(b.done()); request.setUpdates(o); @@ -572,7 +572,7 @@ namespace mongo { const bool upsert = valueB || convertUpdateToUpsert; const NamespaceString requestNs(ns); - UpdateRequest request(requestNs, QueryPlanSelectionPolicy::idElseNatural()); + UpdateRequest request(requestNs); request.setQuery(updateCriteria); request.setUpdates(o); diff --git a/src/mongo/db/repl/rs_rollback.cpp b/src/mongo/db/repl/rs_rollback.cpp index e5419aaa6b4..8017c965b53 100644 --- a/src/mongo/db/repl/rs_rollback.cpp +++ b/src/mongo/db/repl/rs_rollback.cpp @@ -37,6 +37,7 @@ #include "mongo/db/ops/update_request.h" #include "mongo/db/ops/update_lifecycle_impl.h" #include "mongo/db/ops/delete.h" +#include "mongo/db/query/internal_plans.h" #include "mongo/db/repl/oplog.h" #include "mongo/db/repl/rs.h" @@ -225,11 +226,19 @@ namespace mongo { static void syncRollbackFindCommonPoint(DBClientConnection *them, HowToFixUp& h) { verify( Lock::isLocked() ); Client::Context c(rsoplog); + NamespaceDetails *nsd = nsdetails(rsoplog); verify(nsd); - ReverseCappedCursor u(nsd); - if( !u.ok() ) + + boost::scoped_ptr<Runner> runner( + InternalPlanner::collectionScan(rsoplog, InternalPlanner::BACKWARD)); + + BSONObj ourObj; + DiskLoc ourLoc; + + if (Runner::RUNNER_ADVANCED != runner->getNext(&ourObj, &ourLoc)) { throw rsfatal("our oplog empty or unreadable"); + } const Query q = Query().sort(reverseNaturalObj); const bo fields = BSON( "ts" << 1 << "h" << 1 ); @@ -241,7 +250,6 @@ namespace mongo { if( t.get() == 0 || !t->more() ) throw rsfatal("remote oplog empty or unreadable"); - BSONObj ourObj = u.current(); OpTime ourTime = ourObj["ts"]._opTime(); BSONObj theirObj = t->nextSafe(); OpTime theirTime = theirObj["ts"]._opTime(); @@ -270,7 +278,7 @@ namespace mongo { log() << "replSet rollback found matching events at " << ourTime.toStringPretty() << rsLog; log() << "replSet rollback findcommonpoint scanned : " << scanned << rsLog; h.commonPoint = ourTime; - h.commonPointOurDiskloc = u.currLoc(); + h.commonPointOurDiskloc = ourLoc; return; } @@ -286,15 +294,13 @@ namespace mongo { theirObj = t->nextSafe(); theirTime = theirObj["ts"]._opTime(); - u.advance(); - if( !u.ok() ) { + if (Runner::RUNNER_ADVANCED != runner->getNext(&ourObj, &ourLoc)) { log() << "replSet rollback error RS101 reached beginning of local oplog" << rsLog; log() << "replSet them: " << them->toString() << " scanned: " << scanned << rsLog; log() << "replSet theirTime: " << theirTime.toStringLong() << rsLog; log() << "replSet ourTime: " << ourTime.toStringLong() << rsLog; throw rsfatal("RS101 reached beginning of local oplog [1]"); } - ourObj = u.current(); ourTime = ourObj["ts"]._opTime(); } else if( theirTime > ourTime ) { @@ -311,15 +317,13 @@ namespace mongo { else { // theirTime < ourTime refetch(h, ourObj); - u.advance(); - if( !u.ok() ) { + if (Runner::RUNNER_ADVANCED != runner->getNext(&ourObj, &ourLoc)) { log() << "replSet rollback error RS101 reached beginning of local oplog" << rsLog; log() << "replSet them: " << them->toString() << " scanned: " << scanned << rsLog; log() << "replSet theirTime: " << theirTime.toStringLong() << rsLog; log() << "replSet ourTime: " << ourTime.toStringLong() << rsLog; throw rsfatal("RS101 reached beginning of local oplog [2]"); } - ourObj = u.current(); ourTime = ourObj["ts"]._opTime(); } } diff --git a/src/mongo/db/scanandorder.cpp b/src/mongo/db/scanandorder.cpp deleted file mode 100644 index 40b5eb9edaf..00000000000 --- a/src/mongo/db/scanandorder.cpp +++ /dev/null @@ -1,138 +0,0 @@ -/* scanandorder.cpp - Order results (that aren't already indexes and in order.) -*/ - -/** - * Copyright (C) 2008 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/>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the GNU Affero General Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#include "mongo/pch.h" - -#include "mongo/db/scanandorder.h" - -#include "mongo/db/index/btree_key_generator.h" -#include "mongo/db/matcher.h" -#include "mongo/db/parsed_query.h" -#include "mongo/util/mongoutils/str.h" - -namespace mongo { - - const unsigned ScanAndOrder::MaxScanAndOrderBytes = 32 * 1024 * 1024; - - void ScanAndOrder::add(const BSONObj& o, const DiskLoc* loc) { - verify( o.isValid() ); - BSONObj k; - try { - k = _order.getKeyFromObject(o); - } - catch (UserException &e) { - if ( e.getCode() == BtreeKeyGenerator::ParallelArraysCode) { // cannot get keys for parallel arrays - // fix lasterror text to be more accurate. - uasserted( 15925, "cannot sort with keys that are parallel arrays" ); - } - else - throw; - } - - if ( k.isEmpty() ) { - return; - } - if ( (int) _best.size() < _limit ) { - _add(k, o, loc); - return; - } - BestMap::iterator i; - verify( _best.end() != _best.begin() ); - i = _best.end(); - i--; - _addIfBetter(k, o, i, loc); - } - - - void ScanAndOrder::fill( BufBuilder& b, const ParsedQuery *parsedQuery, int& nout ) const { - int n = 0; - int nFilled = 0; - Projection *projection = parsedQuery ? parsedQuery->getFields() : NULL; - scoped_ptr<Matcher> arrayMatcher; - scoped_ptr<MatchDetails> details; - if ( projection && projection->getArrayOpType() == Projection::ARRAY_OP_POSITIONAL ) { - // the projection specified an array positional match operator; create a new matcher - // for the projected array - arrayMatcher.reset( new Matcher( parsedQuery->getFilter() ) ); - details.reset( new MatchDetails ); - details->requestElemMatchKey(); - } - for ( BestMap::const_iterator i = _best.begin(); i != _best.end(); i++ ) { - n++; - if ( n <= _startFrom ) - continue; - const BSONObj& o = i->second; - massert( 16355, "positional operator specified, but no array match", - ! arrayMatcher || arrayMatcher->matches( o, details.get() ) ); - fillQueryResultFromObj( b, projection, o, details.get() ); - nFilled++; - if ( nFilled >= _limit ) - break; - } - nout = nFilled; - } - - void ScanAndOrder::_add(const BSONObj& k, const BSONObj& o, const DiskLoc* loc) { - BSONObj docToReturn = o; - if ( loc ) { - BSONObjBuilder b; - b.appendElements(o); - b.append("$diskLoc", loc->toBSONObj()); - docToReturn = b.obj(); - } - _validateAndUpdateApproxSize( k.objsize() + docToReturn.objsize() ); - _best.insert(make_pair(k.getOwned(),docToReturn.getOwned())); - } - - void ScanAndOrder::_addIfBetter(const BSONObj& k, const BSONObj& o, const BestMap::iterator& i, - const DiskLoc* loc) { - const BSONObj& worstBestKey = i->first; - int cmp = worstBestKey.woCompare(k, _order._keyPattern); - if ( cmp > 0 ) { - // k is better, 'upgrade' - _validateAndUpdateApproxSize( -i->first.objsize() + -i->second.objsize() ); - _best.erase(i); - _add(k, o, loc); - } - } - - void ScanAndOrder::_validateAndUpdateApproxSize( const int approxSizeDelta ) { - // note : adjust when bson return limit adjusts. note this limit should be a bit higher. - int newApproxSize = _approxSize + approxSizeDelta; - verify( newApproxSize >= 0 ); - uassert( ScanAndOrderMemoryLimitExceededAssertionCode, - "too much data for sort() with no index. add an index or specify a smaller limit", - (unsigned)newApproxSize < MaxScanAndOrderBytes ); - _approxSize = newApproxSize; - } - -} // namespace mongo diff --git a/src/mongo/db/scanandorder.h b/src/mongo/db/scanandorder.h deleted file mode 100644 index 1b34b4f2808..00000000000 --- a/src/mongo/db/scanandorder.h +++ /dev/null @@ -1,139 +0,0 @@ -/* scanandorder.h - Order results (that aren't already indexes and in order.) -*/ - -/** -* Copyright (C) 2008 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/>. -* -* As a special exception, the copyright holders give permission to link the -* code of portions of this program with the OpenSSL library under certain -* conditions as described in each individual source file and distribute -* linked combinations including the program with the OpenSSL library. You -* must comply with the GNU Affero General Public License in all respects for -* all of the code used other than as permitted herein. If you modify file(s) -* with this exception, you may extend this exception to your version of the -* file(s), but you are not obligated to do so. If you do not wish to do so, -* delete this exception statement from your version. If you delete this -* exception statement from all source files in the program, then also delete -* it in the license file. -*/ - -#pragma once - -#include "mongo/db/diskloc.h" -#include "mongo/db/projection.h" -#include "mongo/db/queryutil.h" - -namespace mongo { - - class ParsedQuery; - - static const int ScanAndOrderMemoryLimitExceededAssertionCode = 10128; - - class KeyType : boost::noncopyable { - public: - BSONObj _keyPattern; - FieldRangeVector _keyCutter; - public: - KeyType(const BSONObj &pattern, const FieldRangeSet &frs) - : _keyPattern(pattern), _keyCutter(frs, pattern, 1) { - verify(!pattern.isEmpty()); - } - - /** - * @return first key of the object that would be encountered while - * scanning an index with keySpec 'pattern' using constraints 'frs', or - * BSONObj() if no such key. - */ - BSONObj getKeyFromObject(const BSONObj &o) const { - return _keyCutter.firstMatch(o); - } - }; - - /* todo: - _ response size limit from runquery; push it up a bit. - */ - - inline void fillQueryResultFromObj(BufBuilder& bb, const Projection *filter, const BSONObj& js, - const MatchDetails* details = NULL, - const DiskLoc* loc=NULL) { - if ( filter ) { - BSONObjBuilder b( bb ); - filter->transform( js , b, details ); - if (loc) - b.append("$diskLoc", loc->toBSONObj()); - b.done(); - } - else if (loc) { - BSONObjBuilder b( bb ); - b.appendElements(js); - b.append("$diskLoc", loc->toBSONObj()); - b.done(); - } - else { - bb.appendBuf((void*) js.objdata(), js.objsize()); - } - } - - typedef multimap<BSONObj,BSONObj,BSONObjCmp> BestMap; - class ScanAndOrder { - public: - static const unsigned MaxScanAndOrderBytes; - - ScanAndOrder(int startFrom, int limit, const BSONObj &order, const FieldRangeSet &frs) : - _best( BSONObjCmp( order ) ), - _startFrom(startFrom), _order(order, frs) { - _limit = limit > 0 ? limit + _startFrom : 0x7fffffff; - _approxSize = 0; - } - - int size() const { return _best.size(); } - - /** - * @throw ScanAndOrderMemoryLimitExceededAssertionCode if adding would grow memory usage - * to ScanAndOrder::MaxScanAndOrderBytes. - */ - void add(const BSONObj &o, const DiskLoc* loc); - - /* scanning complete. stick the query result in b for n objects. */ - void fill(BufBuilder& b, const ParsedQuery *query, int& nout) const; - - /** Functions for testing. */ - protected: - - unsigned approxSize() const { return _approxSize; } - - private: - - void _add(const BSONObj& k, const BSONObj& o, const DiskLoc* loc); - - void _addIfBetter(const BSONObj& k, const BSONObj& o, const BestMap::iterator& i, - const DiskLoc* loc); - - /** - * @throw ScanAndOrderMemoryLimitExceededAssertionCode if approxSize would grow too high, - * otherwise update _approxSize. - */ - void _validateAndUpdateApproxSize( const int approxSizeDelta ); - - BestMap _best; // key -> full object - int _startFrom; - int _limit; // max to send back. - KeyType _order; - unsigned _approxSize; - - }; - -} // namespace mongo diff --git a/src/mongo/db/structure/collection_info_cache.cpp b/src/mongo/db/structure/collection_info_cache.cpp index 8a2bf7ec0ef..39921acd4ac 100644 --- a/src/mongo/db/structure/collection_info_cache.cpp +++ b/src/mongo/db/structure/collection_info_cache.cpp @@ -43,9 +43,7 @@ namespace mongo { CollectionInfoCache::CollectionInfoCache( Collection* collection ) : _collection( collection ), - _keysComputed( false ), - _qcCacheMutex( "_qcCacheMutex" ), - _qcWriteCount( 0 ) {} + _keysComputed( false ) { } void CollectionInfoCache::reset() { Lock::assertWriteLocked( _collection->ns().ns() ); @@ -73,33 +71,11 @@ namespace mongo { } void CollectionInfoCache::notifyOfWriteOp() { - scoped_lock lk( _qcCacheMutex ); - if ( _qcCache.empty() ) - return; - if ( ++_qcWriteCount >= 100 ) - _clearQueryCache_inlock(); + // TODO: hook up w/new cache when impl } void CollectionInfoCache::clearQueryCache() { - scoped_lock lk( _qcCacheMutex ); - _clearQueryCache_inlock(); - } - - void CollectionInfoCache::_clearQueryCache_inlock() { - _qcCache.clear(); - _qcWriteCount = 0; - } - - CachedQueryPlan CollectionInfoCache::cachedQueryPlanForPattern( const QueryPattern &pattern ) { - scoped_lock lk( _qcCacheMutex ); - return _qcCache[ pattern ]; - } - - - void CollectionInfoCache::registerCachedQueryPlanForPattern( const QueryPattern &pattern, - const CachedQueryPlan &cachedQueryPlan ) { - scoped_lock lk( _qcCacheMutex ); - _qcCache[ pattern ] = cachedQueryPlan; + // TODO: hook up w/new cache when impl } } diff --git a/src/mongo/db/structure/collection_info_cache.h b/src/mongo/db/structure/collection_info_cache.h index be88b246559..32a87696394 100644 --- a/src/mongo/db/structure/collection_info_cache.h +++ b/src/mongo/db/structure/collection_info_cache.h @@ -31,8 +31,6 @@ #pragma once #include "mongo/db/index_set.h" -#include "mongo/db/querypattern.h" - namespace mongo { @@ -72,11 +70,6 @@ namespace mongo { /* you must notify the cache if you are doing writes, as query plan utility will change */ void notifyOfWriteOp(); - CachedQueryPlan cachedQueryPlanForPattern( const QueryPattern &pattern ); - - void registerCachedQueryPlanForPattern( const QueryPattern &pattern, - const CachedQueryPlan &cachedQueryPlan ); - private: Collection* _collection; // not owned @@ -86,15 +79,6 @@ namespace mongo { IndexPathSet _indexedPaths; void computeIndexKeys(); - - // --- for old query optimizer - - void _clearQueryCache_inlock(); - - mutex _qcCacheMutex; - int _qcWriteCount; - std::map<QueryPattern,CachedQueryPlan> _qcCache; - }; } diff --git a/src/mongo/dbtests/btreebuildertests.cpp b/src/mongo/dbtests/btreebuildertests.cpp index bd668c6c45f..5e23f6b4c3f 100644 --- a/src/mongo/dbtests/btreebuildertests.cpp +++ b/src/mongo/dbtests/btreebuildertests.cpp @@ -30,7 +30,6 @@ #include "mongo/db/btreebuilder.h" -#include "mongo/db/btreecursor.h" #include "mongo/db/catalog/index_catalog.h" #include "mongo/db/pdfile.h" #include "mongo/db/structure/collection.h" @@ -88,45 +87,6 @@ namespace BtreeBuilderTests { }; /** - * BtreeBuilder::commit() constructs a btree from the keys provided to BtreeBuilder::addKey(). - */ - class Commit : public IndexBuildBase { - public: - void run() { - IndexDetails& id = addIndexWithInfo(); - // Create a btree builder. - BtreeBuilder<V1> builder( false, id ); - // Add some keys to the builder, in order. - int32_t nKeys = 1000; - for( int32_t i = 0; i < nKeys; ++i ) { - BSONObj key = BSON( "a" << i ); - builder.addKey( key, /* dummy location */ DiskLoc() ); - } - // The root of the index has not yet been set. - ASSERT( id.head.isNull() ); - // Call commit on the builder to finish building the btree. - builder.commit( true ); - // The root of the index is now set. - ASSERT( !id.head.isNull() ); - // Create a cursor over the index. - scoped_ptr<BtreeCursor> cursor( - BtreeCursor::make( nsdetails( _ns ), - id, - BSON( "" << -1 ), // startKey below minimum key value. - BSON( "" << nKeys ), // endKey above maximum key value. - true, // endKeyInclusive true. - 1 // direction forward. - ) ); - // Check that the keys in the index are the expected ones. - int32_t expectedKey = 0; - for( ; cursor->ok(); cursor->advance(), ++expectedKey ) { - ASSERT_EQUALS( expectedKey, cursor->currKey().firstElement().number() ); - } - ASSERT_EQUALS( nKeys, expectedKey ); - } - }; - - /** * BtreeBuilder::commit() is interrupted if there is a request to kill the current operation. */ class InterruptCommit : public IndexBuildBase { @@ -174,7 +134,6 @@ namespace BtreeBuilderTests { } void setupTests() { - add<Commit>(); add<InterruptCommit>( false ); add<InterruptCommit>( true ); } diff --git a/src/mongo/dbtests/btreepositiontests.cpp b/src/mongo/dbtests/btreepositiontests.cpp deleted file mode 100644 index 34681507fee..00000000000 --- a/src/mongo/dbtests/btreepositiontests.cpp +++ /dev/null @@ -1,339 +0,0 @@ -/** - * 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/>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the GNU Affero General Public License in all respects - * for all of the code used other than as permitted herein. If you modify - * file(s) with this exception, you may extend this exception to your - * version of the file(s), but you are not obligated to do so. If you do not - * wish to do so, delete this exception statement from your version. If you - * delete this exception statement from all source files in the program, - * then also delete it in the license file. - */ - -#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<V1> { - 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<TestableBtree*>( l.btreemod<V1>() ); - } - - /** 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<V1>::KeyNode keyNode = - location.bucket.btree<V1>()->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<BtreeKeyLocation::Equality>(); - add<LogicalBtreePosition::RecoverPositionBucketUnchanged>(); - add<LogicalBtreePosition::RecoverPositionBucketDeallocated>(); - add<LogicalBtreePosition::RecoverPositionKeyIndexInvalid>(); - add<LogicalBtreePosition::RecoverPositionKeyRemoved>(); - add<LogicalBtreePosition::RecoverPositionKeyRemovedWithMatchingRecord>(); - } - } myall; - -} // BtreePositionTests diff --git a/src/mongo/dbtests/btreetests.cpp b/src/mongo/dbtests/btreetests.cpp index bf982a6f4d5..f04406e49a3 100644 --- a/src/mongo/dbtests/btreetests.cpp +++ b/src/mongo/dbtests/btreetests.cpp @@ -32,7 +32,6 @@ #include "mongo/pch.h" #include "mongo/db/btree.h" -#include "mongo/db/btreecursor.h" #include "mongo/db/db.h" #include "mongo/db/json.h" #include "mongo/dbtests/dbtests.h" diff --git a/src/mongo/dbtests/btreetests.inl b/src/mongo/dbtests/btreetests.inl index d06b8c7b12a..200aa19997c 100644 --- a/src/mongo/dbtests/btreetests.inl +++ b/src/mongo/dbtests/btreetests.inl @@ -308,6 +308,8 @@ } }; +/* +// QUERY_MIGRATION: port later class PackUnused : public Base { public: void run() { @@ -413,6 +415,7 @@ Base::insert( k ); } }; + */ class MergeBuckets : public Base { public: @@ -1642,8 +1645,9 @@ add< MissingLocateMultiBucket >(); add< SERVER983 >(); add< DontReuseUnused >(); - add< PackUnused >(); - add< DontDropReferenceKey >(); + // QUERY_MIGRATION + // add< PackUnused >(); + // add< DontDropReferenceKey >(); add< MergeBucketsLeft >(); add< MergeBucketsRight >(); // add< MergeBucketsHead >(); diff --git a/src/mongo/dbtests/counttests.cpp b/src/mongo/dbtests/counttests.cpp index cdf778eac88..5528093916f 100644 --- a/src/mongo/dbtests/counttests.cpp +++ b/src/mongo/dbtests/counttests.cpp @@ -30,7 +30,6 @@ #include <boost/thread/thread.hpp> -#include "mongo/db/cursor.h" #include "mongo/db/db.h" #include "mongo/db/json.h" #include "mongo/db/ops/count.h" diff --git a/src/mongo/dbtests/cursortests.cpp b/src/mongo/dbtests/cursortests.cpp deleted file mode 100644 index 1c4de2102ff..00000000000 --- a/src/mongo/dbtests/cursortests.cpp +++ /dev/null @@ -1,814 +0,0 @@ -// cusrortests.cpp // cursor related unit tests -// - -/** - * Copyright (C) 2009 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/>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the GNU Affero General Public License in all respects - * for all of the code used other than as permitted herein. If you modify - * file(s) with this exception, you may extend this exception to your - * version of the file(s), but you are not obligated to do so. If you do not - * wish to do so, delete this exception statement from your version. If you - * delete this exception statement from all source files in the program, - * then also delete it in the license file. - */ - -#include "mongo/pch.h" - -#include "mongo/db/btreecursor.h" -#include "mongo/db/clientcursor.h" -#include "mongo/db/instance.h" -#include "mongo/db/json.h" -#include "mongo/db/query_optimizer.h" -#include "mongo/db/queryutil.h" -#include "mongo/dbtests/dbtests.h" - -namespace CursorTests { - - namespace BtreeCursor { - - using mongo::BtreeCursor; - - // The ranges expressed in these tests are impossible given our query - // syntax, so going to do them a hacky way. - - class Base { - protected: - static const char *ns() { return "unittests.cursortests.Base"; } - FieldRangeVector *vec( int *vals, int len, int direction = 1 ) { - FieldRangeSet s( "", BSON( "a" << 1 ), true, true ); - for( int i = 0; i < len; i += 2 ) { - _objs.push_back( BSON( "a" << BSON( "$gte" << vals[ i ] << "$lte" << vals[ i + 1 ] ) ) ); - FieldRangeSet s2( "", _objs.back(), true, true ); - if ( i == 0 ) { - s.range( "a" ) = s2.range( "a" ); - } - else { - s.range( "a" ) |= s2.range( "a" ); - } - } - // orphan idxSpec for this test - BSONObj kp = BSON( "a" << 1 ); - return new FieldRangeVector( s, kp, direction ); - } - DBDirectClient _c; - private: - vector< BSONObj > _objs; - }; - - class MultiRangeForward : public Base { - public: - void run() { - 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 ) ) ); - } - int v[] = { 1, 2, 4, 6 }; - boost::shared_ptr< FieldRangeVector > frv( vec( v, 4 ) ); - Client::WriteContext ctx( ns ); - scoped_ptr<BtreeCursor> _c( BtreeCursor::make( nsdetails( ns ), - nsdetails( ns )->idx(1), - frv, - 0, - 1 ) ); - BtreeCursor &c = *_c.get(); - 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 Base { - public: - void run() { - 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 ) ) ); - } - int v[] = { -50, 2, 40, 60, 109, 200 }; - boost::shared_ptr< FieldRangeVector > frv( vec( v, 6 ) ); - Client::WriteContext ctx( ns ); - scoped_ptr<BtreeCursor> _c( BtreeCursor::make( nsdetails( ns ), - nsdetails( ns )->idx(1), - frv, - 0, - 1 ) ); - BtreeCursor &c = *_c.get(); - 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 Base { - public: - void run() { - 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 ) ) ); - } - int v[] = { 1, 2, 4, 6 }; - boost::shared_ptr< FieldRangeVector > frv( vec( v, 4, -1 ) ); - Client::WriteContext ctx( ns ); - scoped_ptr<BtreeCursor> _c( BtreeCursor::make( nsdetails( ns ), - nsdetails( ns )->idx(1), - frv, - 0, - -1 ) ); - BtreeCursor& c = *_c.get(); - 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() ); - } - }; - - class Base2 { - public: - virtual ~Base2() { _c.dropCollection( ns() ); } - protected: - static const char *ns() { return "unittests.cursortests.Base2"; } - DBDirectClient _c; - virtual BSONObj idx() const = 0; - virtual int direction() const { return 1; } - void insert( const BSONObj &o ) { - _objs.push_back( o ); - _c.insert( ns(), o ); - } - void check( const BSONObj &spec ) { - { - BSONObj keypat = idx(); - //cout << keypat.toString() << endl; - _c.ensureIndex( ns(), idx() ); - } - - Client::WriteContext ctx( ns() ); - FieldRangeSet frs( ns(), spec, true, true ); - boost::shared_ptr< FieldRangeVector > frv( new FieldRangeVector( frs, idx(), direction() ) ); - scoped_ptr<BtreeCursor> c( BtreeCursor::make( nsdetails( ns() ), - nsdetails( ns() )->idx( 1 ), - frv, - 0, - direction() ) ); - Matcher m( spec ); - int count = 0; - while( c->ok() ) { - ASSERT( m.matches( c->current() ) ); - c->advance(); - ++count; - } - int expectedCount = 0; - for( vector< BSONObj >::const_iterator i = _objs.begin(); i != _objs.end(); ++i ) { - if ( m.matches( *i ) ) { - ++expectedCount; - } - } - ASSERT_EQUALS( expectedCount, count ); - } - private: - vector< BSONObj > _objs; - }; - - class EqEq : public Base2 { - public: - void run() { - insert( BSON( "a" << 4 << "b" << 5 ) ); - insert( BSON( "a" << 4 << "b" << 5 ) ); - insert( BSON( "a" << 4 << "b" << 4 ) ); - insert( BSON( "a" << 5 << "b" << 4 ) ); - check( BSON( "a" << 4 << "b" << 5 ) ); - } - virtual BSONObj idx() const { return BSON( "a" << 1 << "b" << 1 ); } - }; - - class EqRange : public Base2 { - public: - void run() { - insert( BSON( "a" << 3 << "b" << 5 ) ); - insert( BSON( "a" << 4 << "b" << 0 ) ); - insert( BSON( "a" << 4 << "b" << 5 ) ); - insert( BSON( "a" << 4 << "b" << 6 ) ); - insert( BSON( "a" << 4 << "b" << 6 ) ); - insert( BSON( "a" << 4 << "b" << 10 ) ); - insert( BSON( "a" << 4 << "b" << 11 ) ); - insert( BSON( "a" << 5 << "b" << 5 ) ); - check( BSON( "a" << 4 << "b" << BSON( "$gte" << 1 << "$lte" << 10 ) ) ); - } - virtual BSONObj idx() const { return BSON( "a" << 1 << "b" << 1 ); } - }; - - class EqIn : public Base2 { - public: - void run() { - insert( BSON( "a" << 3 << "b" << 5 ) ); - insert( BSON( "a" << 4 << "b" << 0 ) ); - insert( BSON( "a" << 4 << "b" << 5 ) ); - insert( BSON( "a" << 4 << "b" << 6 ) ); - insert( BSON( "a" << 4 << "b" << 6 ) ); - insert( BSON( "a" << 4 << "b" << 10 ) ); - insert( BSON( "a" << 4 << "b" << 11 ) ); - insert( BSON( "a" << 5 << "b" << 5 ) ); - check( BSON( "a" << 4 << "b" << BSON( "$in" << BSON_ARRAY( 5 << 6 << 11 ) ) ) ); - } - virtual BSONObj idx() const { return BSON( "a" << 1 << "b" << 1 ); } - }; - - class RangeEq : public Base2 { - public: - void run() { - insert( BSON( "a" << 0 << "b" << 4 ) ); - insert( BSON( "a" << 1 << "b" << 4 ) ); - insert( BSON( "a" << 4 << "b" << 3 ) ); - insert( BSON( "a" << 5 << "b" << 4 ) ); - insert( BSON( "a" << 7 << "b" << 4 ) ); - insert( BSON( "a" << 4 << "b" << 4 ) ); - insert( BSON( "a" << 9 << "b" << 6 ) ); - insert( BSON( "a" << 11 << "b" << 1 ) ); - insert( BSON( "a" << 11 << "b" << 4 ) ); - check( BSON( "a" << BSON( "$gte" << 1 << "$lte" << 10 ) << "b" << 4 ) ); - } - virtual BSONObj idx() const { return BSON( "a" << 1 << "b" << 1 ); } - }; - - class RangeIn : public Base2 { - public: - void run() { - insert( BSON( "a" << 0 << "b" << 4 ) ); - insert( BSON( "a" << 1 << "b" << 5 ) ); - insert( BSON( "a" << 4 << "b" << 3 ) ); - insert( BSON( "a" << 5 << "b" << 4 ) ); - insert( BSON( "a" << 7 << "b" << 5 ) ); - insert( BSON( "a" << 4 << "b" << 4 ) ); - insert( BSON( "a" << 9 << "b" << 6 ) ); - insert( BSON( "a" << 11 << "b" << 1 ) ); - insert( BSON( "a" << 11 << "b" << 4 ) ); - check( BSON( "a" << BSON( "$gte" << 1 << "$lte" << 10 ) << "b" << BSON( "$in" << BSON_ARRAY( 4 << 6 ) ) ) ); - } - virtual BSONObj idx() const { return BSON( "a" << 1 << "b" << 1 ); } - }; - - /** - * BtreeCursor::advance() may skip to new btree positions multiple times. A cutoff (tested - * here) has been implemented to avoid excessive iteration in such cases. See SERVER-3448. - */ - class AbortImplicitScan : public Base { - public: - void run() { - _c.dropCollection( ns() ); - // Set up a compound index with some data. - BSONObj kp = BSON( "a" << 1 << "b" << 1 ); - _c.ensureIndex( ns(), kp); - for( int i = 0; i < 300; ++i ) { - _c.insert( ns(), BSON( "a" << i << "b" << i ) ); - } - _c.insert( ns(), BSON( "a" << 300 << "b" << 30 ) ); - - // Set up a cursor on the { a:1, b:1 } index, the same cursor that would be created - // for the query { b:30 }. Because this query has no constraint on 'a' (the - // first field of the compound index), the cursor will examine every distinct value - // of 'a' in the index and check for an index key with that value for 'a' and 'b' - // equal to 30. - FieldRangeSet frs( ns(), BSON( "b" << 30 ), true, true ); - boost::shared_ptr<FieldRangeVector> frv( new FieldRangeVector( frs, kp, 1 ) ); - Client::WriteContext ctx( ns() ); - scoped_ptr<BtreeCursor> c( BtreeCursor::make( nsdetails( ns() ), - nsdetails( ns() )->idx(1), - frv, - 0, - 1 ) ); - - // BtreeCursor::init() and BtreeCursor::advance() attempt to advance the cursor to - // the next matching key, which may entail examining many successive distinct values - // of 'a' having no index key where b equals 30. To prevent excessive iteration - // within init() and advance(), examining distinct 'a' values is aborted once an - // nscanned cutoff is reached. We test here that this cutoff is applied, and that - // if it is applied before a matching key is found, then - // BtreeCursor::currentMatches() returns false appropriately. - - ASSERT( c->ok() ); - // The starting iterate found by BtreeCursor::init() does not match. This is a key - // before the {'':30,'':30} key, because init() is aborted prematurely. - ASSERT( !c->currentMatches() ); - // And init() stopped iterating before scanning the whole btree (with ~300 keys). - ASSERT( c->nscanned() < 200 ); - - ASSERT( c->advance() ); - // The next iterate matches (this is the {'':30,'':30} key). - ASSERT( c->currentMatches() ); - - int oldNscanned = c->nscanned(); - ASSERT( c->advance() ); - // Check that nscanned has increased ... - ASSERT( c->nscanned() > oldNscanned ); - // ... but that advance() stopped iterating before the whole btree (with ~300 keys) - // was scanned. - ASSERT( c->nscanned() < 200 ); - // Because advance() is aborted prematurely, the current iterate does not match. - ASSERT( !c->currentMatches() ); - - // Iterate through the remainder of the btree. - bool foundLastMatch = false; - while( c->advance() ) { - bool bMatches = ( c->current()[ "b" ].number() == 30 ); - // The current iterate only matches if it has the proper 'b' value. - ASSERT_EQUALS( bMatches, c->currentMatches() ); - if ( bMatches ) { - foundLastMatch = true; - } - } - // Check that the final match, on key {'':300,'':30}, is found. - ASSERT( foundLastMatch ); - } - }; - - class RequestMatcherFalse : public QueryPlanSelectionPolicy { - virtual string name() const { return "RequestMatcherFalse"; } - virtual bool requestMatcher() const { return false; } - } _requestMatcherFalse; - - /** - * A BtreeCursor typically moves from one index match to another when its advance() method - * is called. However, to prevent excessive iteration advance() may bail out early before - * the next index match is identified (SERVER-3448). The BtreeCursor must indicate that - * these iterates are not matches in matchesCurrent() to prevent them from being matched - * when requestMatcher == false. - */ - class DontMatchOutOfIndexBoundsDocuments : public Base { - public: - void run() { - _c.dropCollection( ns() ); - _c.ensureIndex( ns(), BSON( "a" << 1 ) ); - // Save 'a' values 0, 0.5, 1.5, 2.5 ... 97.5, 98.5, 99. - _c.insert( ns(), BSON( "a" << 0 ) ); - _c.insert( ns(), BSON( "a" << 99 ) ); - for( int i = 0; i < 99; ++i ) { - _c.insert( ns(), BSON( "a" << ( i + 0.5 ) ) ); - } - // Query 'a' values $in 0, 1, 2, ..., 99. - BSONArrayBuilder inVals; - for( int i = 0; i < 100; ++i ) { - inVals << i; - } - BSONObj query = BSON( "a" << BSON( "$in" << inVals.arr() ) ); - int matchCount = 0; - Client::ReadContext ctx( ns() ); - boost::shared_ptr<Cursor> c = getOptimizedCursor( ns(), - query, - BSONObj(), - _requestMatcherFalse ); - // The BtreeCursor attempts to find each of the values 0, 1, 2, ... etc in the - // btree. Because the values 0.5, 1.5, etc are present in the btree, the - // BtreeCursor will explicitly look for all the values in the $in list during - // successive calls to advance(). Because there are a large number of $in values to - // iterate over, BtreeCursor::advance() will bail out on intermediate values of 'a' - // (for example 20.5) that do not match the query if nscanned increases by more than - // 20. We test here that these intermediate results are not matched. Only the two - // correct matches a:0 and a:99 are matched. - while( c->ok() ) { - ASSERT( !c->matcher() ); - if ( c->currentMatches() ) { - double aVal = c->current()[ "a" ].number(); - // Only the expected values of a are matched. - ASSERT( aVal == 0 || aVal == 99 ); - ++matchCount; - } - c->advance(); - } - // Only the two expected documents a:0 and a:99 are matched. - ASSERT_EQUALS( 2, matchCount ); - } - }; - - /** - * When using a multikey index, two constraints on the same field cannot be intersected for - * a non $elemMatch query (SERVER-958). For example, using a single key index on { a:1 } - * the query { a:{ $gt:0, $lt:5 } } would generate the field range [[ 0, 5 ]]. But for a - * multikey index the field range is [[ 0, max_number ]]. In this case, the field range - * does not exactly represent the query, so a Matcher is required. - */ - class MatcherRequiredTwoConstraintsSameField : public Base { - public: - void run() { - _c.dropCollection( ns() ); - _c.ensureIndex( ns(), BSON( "a" << 1 ) ); - _c.insert( ns(), BSON( "_id" << 0 << "a" << BSON_ARRAY( 1 << 2 ) ) ); - _c.insert( ns(), BSON( "_id" << 1 << "a" << 9 ) ); - Client::ReadContext ctx( ns() ); - boost::shared_ptr<Cursor> c = getOptimizedCursor( ns(), - BSON( "a" << GT << 0 << LT << 5 ), - BSONObj(), - _requestMatcherFalse ); - while( c->ok() ) { - // A Matcher is provided even though 'requestMatcher' is false. - ASSERT( c->matcher() ); - if ( c->currentMatches() ) { - // Even though a:9 is in the field range [[ 0, max_number ]], that result - // does not match because the Matcher rejects it. Only the _id:0 document - // matches. - ASSERT_EQUALS( 0, c->current()[ "_id" ].number() ); - } - c->advance(); - } - } - }; - - /** - * When using a multikey index, two constraints on fields with a shared parent cannot be - * intersected for a non $elemMatch query (SERVER-958). For example, using a single key - * compound index on { 'a.b':1, 'a.c':1 } the query { 'a.b':2, 'a.c':2 } would generate the - * field range vector [ [[ 2, 2 ]], [[ 2, 2 ]] ]. But for a multikey index the field range - * vector is [ [[ 2, 2 ]], [[ minkey, maxkey ]] ]. In this case, the field range does not - * exactly represent the query, so a Matcher is required. - */ - class MatcherRequiredTwoConstraintsDifferentFields : public Base { - public: - void run() { - _c.dropCollection( ns() ); - _c.ensureIndex( ns(), BSON( "a.b" << 1 << "a.c" << 1 ) ); - _c.insert( ns(), BSON( "a" << BSON_ARRAY( BSON( "b" << 2 << "c" << 3 ) << - BSONObj() ) ) ); - Client::ReadContext ctx( ns() ); - boost::shared_ptr<Cursor> c = getOptimizedCursor( ns(), - BSON( "a.b" << 2 << "a.c" << 2 ), - BSONObj(), - _requestMatcherFalse ); - while( c->ok() ) { - // A Matcher is provided even though 'requestMatcher' is false. - ASSERT( c->matcher() ); - // Even though { a:[ { b:2, c:3 } ] } is matched by the field range vector - // [ [[ 2, 2 ]], [[ minkey, maxkey ]] ], that resut is not matched because the - // Matcher rejects the document. - ASSERT( !c->currentMatches() ); - c->advance(); - } - } - }; - - /** - * The upper bound of a $gt:string query is the empty object. This upper bound must be - * exclusive so that empty objects do not match without a Matcher. - */ - class TypeBracketedUpperBoundWithoutMatcher : public Base { - public: - void run() { - _c.dropCollection( ns() ); - _c.ensureIndex( ns(), BSON( "a" << 1 ) ); - _c.insert( ns(), BSON( "_id" << 0 << "a" << "a" ) ); - _c.insert( ns(), BSON( "_id" << 1 << "a" << BSONObj() ) ); - Client::ReadContext ctx( ns() ); - boost::shared_ptr<Cursor> c = getOptimizedCursor( ns(), - BSON( "a" << GTE << "" ), - BSONObj(), - _requestMatcherFalse ); - while( c->ok() ) { - ASSERT( !c->matcher() ); - if ( c->currentMatches() ) { - // Only a:'a' matches, not a:{}. - ASSERT_EQUALS( 0, c->current()[ "_id" ].number() ); - } - c->advance(); - } - } - }; - - /** - * The lower bound of a $lt:date query is the bson value 'true'. This lower bound must be - * exclusive so that 'true' values do not match without a Matcher. - */ - class TypeBracketedLowerBoundWithoutMatcher : public Base { - public: - void run() { - _c.dropCollection( ns() ); - _c.ensureIndex( ns(), BSON( "a" << 1 ) ); - _c.insert( ns(), BSON( "_id" << 0 << "a" << Date_t( 1 ) ) ); - _c.insert( ns(), BSON( "_id" << 1 << "a" << true ) ); - Client::ReadContext ctx( ns() ); - boost::shared_ptr<Cursor> c = getOptimizedCursor( ns(), - BSON( "a" << LTE << Date_t( 1 ) ), - BSONObj(), - _requestMatcherFalse ); - while( c->ok() ) { - ASSERT( !c->matcher() ); - if ( c->currentMatches() ) { - // Only a:Date_t( 1 ) matches, not a:true. - ASSERT_EQUALS( 0, c->current()[ "_id" ].number() ); - } - c->advance(); - } - } - }; - - /** Test iteration of a reverse direction btree cursor between start and end keys. */ - class ReverseDirectionStartEndKeys : public Base { - public: - void run() { - _c.dropCollection( ns() ); - _c.ensureIndex( ns(), BSON( "a" << 1 ) ); - // Add documents a:4 and a:5 - _c.insert( ns(), BSON( "a" << 4 ) ); - _c.insert( ns(), BSON( "a" << 5 ) ); - Client::ReadContext ctx( ns() ); - scoped_ptr<Cursor> cursor( BtreeCursor::make( nsdetails( ns() ), - nsdetails( ns() )->idx( 1 ), - /* startKey */ BSON( "" << 5 ), - /* endKey */ BSON( "" << 4 ), - /* endKeyInclusive */ true, - /* direction */ -1 ) ); - // Check that the iterator produces the expected results, in the expected order. - ASSERT( cursor->ok() ); - ASSERT_EQUALS( 5, cursor->current()[ "a" ].Int() ); - ASSERT( cursor->advance() ); - ASSERT_EQUALS( 4, cursor->current()[ "a" ].Int() ); - ASSERT( !cursor->advance() ); - } - }; - - } // namespace BtreeCursor - - namespace ClientCursor { - - using mongo::ClientCursor; - - static const char * const ns() { return "unittests.cursortests.clientcursor"; } - DBDirectClient client; - - class Base { - public: - virtual ~Base() { - client.dropCollection( ns() ); - } - }; - - /** - * A cursor is advanced when the document at its current iterate is removed. - */ - class HandleDelete : public Base { - public: - void run() { - for( int i = 0; i < 150; ++i ) { - client.insert( ns(), BSON( "_id" << i ) ); - } - - boost::shared_ptr<Cursor> cursor; - ClientCursorHolder clientCursor; - ClientCursor::YieldData yieldData; - - { - Client::ReadContext ctx( ns() ); - // The query will utilize the _id index for both the first and second clauses. - cursor = getOptimizedCursor( ns(), - fromjson( "{$or:[{_id:{$gte:0,$lte:148}}," - "{_id:149}]}" ) ); - clientCursor.reset( new ClientCursor( QueryOption_NoCursorTimeout, cursor, - ns() ) ); - // Advance to the last iterate of the first clause. - ASSERT( cursor->ok() ); - while( cursor->current()[ "_id" ].number() != 148 ) { - ASSERT( cursor->advance() ); - } - clientCursor->prepareToYield( yieldData ); - } - - // Remove the document at the cursor's current position, which will cause the - // cursor to be advanced. - client.remove( ns(), BSON( "_id" << 148 ) ); - - { - Client::ReadContext ctx( ns() ); - clientCursor->recoverFromYield( yieldData ); - // Verify that the cursor has another iterate, _id:149, after it is advanced due - // to _id:148's removal. - ASSERT( cursor->ok() ); - ASSERT_EQUALS( 149, cursor->current()[ "_id" ].number() ); - } - } - }; - - /** - * ClientCursor::aboutToDelete() advances a ClientCursor with a refLoc() matching the - * document to be deleted. - */ - class AboutToDelete : public Base { - public: - void run() { - populateData(); - Client::WriteContext ctx( ns() ); - - // Generate a cursor from the supplied query and advance it to the iterate to be - // deleted. - boost::shared_ptr<Cursor> cursor = getOptimizedCursor( ns(), query() ); - while( !isExpectedIterate( cursor->current() ) ) { - ASSERT( cursor->advance() ); - } - ClientCursorHolder clientCursor( new ClientCursor( QueryOption_NoCursorTimeout, - cursor, ns() ) ); - DiskLoc loc = clientCursor->currLoc(); - ASSERT( !loc.isNull() ); - - // Yield the cursor. - ClientCursor::YieldData data; - clientCursor->prepareToYield( data ); - // The cursor will be advanced in aboutToDelete(). - ClientCursor::aboutToDelete( ns(), nsdetails( ns() ), loc ); - clientCursor->recoverFromYield( data ); - ASSERT( clientCursor->ok() ); - - // Validate the expected cursor advancement. - validateIterateAfterYield( clientCursor->current() ); - } - protected: - virtual void populateData() const { - client.insert( ns(), BSON( "a" << 1 ) ); - client.insert( ns(), BSON( "a" << 2 ) ); - } - virtual BSONObj query() const { return BSONObj(); } - virtual bool isExpectedIterate( const BSONObj ¤t ) const { - return 1 == current[ "a" ].number(); - } - virtual void validateIterateAfterYield( const BSONObj ¤t ) const { - ASSERT_EQUALS( 2, current[ "a" ].number() ); - } - }; - - /** aboutToDelete() advances past a document referenced by adjacent cursor iterates. */ - class AboutToDeleteDuplicate : public AboutToDelete { - virtual void populateData() const { - client.insert( ns(), BSON( "a" << BSON_ARRAY( 1 << 2 ) ) ); - client.insert( ns(), BSON( "a" << 3 ) ); - client.ensureIndex( ns(), BSON( "a" << 1 ) ); - } - virtual BSONObj query() const { return BSON( "a" << GT << 0 ); } - virtual bool isExpectedIterate( const BSONObj ¤t ) const { - return BSON_ARRAY( 1 << 2 ) == current[ "a" ].embeddedObject(); - } - virtual void validateIterateAfterYield( const BSONObj ¤t ) const { - ASSERT_EQUALS( 3, current[ "a" ].number() ); - } - }; - - /** aboutToDelete() advances past a document referenced by adjacent cursor clauses. */ - class AboutToDeleteDuplicateNextClause : public AboutToDelete { - virtual void populateData() const { - for( int i = 119; i >= 0; --i ) { - client.insert( ns(), BSON( "a" << i ) ); - } - client.ensureIndex( ns(), BSON( "a" << 1 ) ); - client.ensureIndex( ns(), BSON( "a" << 1 << "b" << 1 ) ); - } - virtual BSONObj query() const { return fromjson( "{$or:[{a:{$gte:0}},{b:1}]}" ); } - virtual bool isExpectedIterate( const BSONObj ¤t ) const { - // In the absence of the aboutToDelete() call, the next iterate will be a:119 from - // an unindexed cursor over the second clause. - return 119 == current[ "a" ].number(); - } - virtual void validateIterateAfterYield( const BSONObj ¤t ) const { - // After the aboutToDelete() call, the a:119 iterate of the unindexed cursor is - // skipped, advancing to the a:118 iterate. - ASSERT_EQUALS( 118, current[ "a" ].number() ); - } - }; - - namespace Pin { - - class Base { - public: - Base() : - _ctx( ns() ), - _cursor( theDataFileMgr.findAll( ns() ) ) { - ASSERT( _cursor ); - _clientCursor.reset( new ClientCursor( 0, _cursor, ns() ) ); - } - protected: - CursorId cursorid() const { return _clientCursor->cursorid(); } - private: - Client::WriteContext _ctx; - boost::shared_ptr<Cursor> _cursor; - ClientCursorHolder _clientCursor; - }; - - /** Pin pins a ClientCursor over its lifetime. */ - class PinCursor : public Base { - public: - void run() { - assertNotPinned(); - { - ClientCursorPin pin( cursorid() ); - assertPinned(); - ASSERT_THROWS( erase(), AssertionException ); - } - assertNotPinned(); - ASSERT( erase() ); - } - private: - void assertPinned() const { - ASSERT( ClientCursor::find( cursorid() ) ); - } - void assertNotPinned() const { - ASSERT_THROWS( ClientCursor::find( cursorid() ), AssertionException ); - } - bool erase() const { - return ClientCursor::erase( cursorid() ); - } - }; - - /** A ClientCursor cannot be pinned twice. */ - class PinTwice : public Base { - public: - void run() { - ClientCursorPin pin( cursorid() ); - ASSERT_THROWS( pinCursor(), AssertionException ); - } - private: - void pinCursor() const { - ClientCursorPin pin( cursorid() ); - } - }; - - /** Pin behaves properly if its ClientCursor is destroyed early. */ - class CursorDeleted : public Base { - public: - void run() { - ClientCursorPin pin( cursorid() ); - ASSERT( pin.c() ); - // Delete the pinned cursor. - ClientCursor::invalidate( ns() ); - ASSERT( !pin.c() ); - // pin is destroyed safely, even though its ClientCursor was already destroyed. - } - }; - - } // namespace Pin - - } // namespace ClientCursor - - class All : public Suite { - public: - All() : Suite( "cursor" ) {} - - void setupTests() { - add<BtreeCursor::MultiRangeForward>(); - add<BtreeCursor::MultiRangeGap>(); - add<BtreeCursor::MultiRangeReverse>(); - add<BtreeCursor::EqEq>(); - add<BtreeCursor::EqRange>(); - add<BtreeCursor::EqIn>(); - add<BtreeCursor::RangeEq>(); - add<BtreeCursor::RangeIn>(); - add<BtreeCursor::AbortImplicitScan>(); - add<BtreeCursor::DontMatchOutOfIndexBoundsDocuments>(); - add<BtreeCursor::MatcherRequiredTwoConstraintsSameField>(); - add<BtreeCursor::MatcherRequiredTwoConstraintsDifferentFields>(); - add<BtreeCursor::TypeBracketedUpperBoundWithoutMatcher>(); - add<BtreeCursor::TypeBracketedLowerBoundWithoutMatcher>(); - add<BtreeCursor::ReverseDirectionStartEndKeys>(); - add<ClientCursor::HandleDelete>(); - add<ClientCursor::AboutToDelete>(); - add<ClientCursor::AboutToDeleteDuplicate>(); - add<ClientCursor::AboutToDeleteDuplicateNextClause>(); - add<ClientCursor::Pin::PinCursor>(); - add<ClientCursor::Pin::PinTwice>(); - add<ClientCursor::Pin::CursorDeleted>(); - } - } myall; -} // namespace CursorTests diff --git a/src/mongo/dbtests/indexupdatetests.cpp b/src/mongo/dbtests/indexupdatetests.cpp index ab754608a31..8caf6f6c7da 100644 --- a/src/mongo/dbtests/indexupdatetests.cpp +++ b/src/mongo/dbtests/indexupdatetests.cpp @@ -29,10 +29,10 @@ */ #include "mongo/db/btree.h" -#include "mongo/db/btreecursor.h" #include "mongo/db/catalog/index_catalog.h" #include "mongo/db/dbhelpers.h" #include "mongo/db/index/btree_based_builder.h" +#include "mongo/db/index/index_descriptor.h" #include "mongo/db/kill_current_op.h" #include "mongo/db/sort_phase_one.h" #include "mongo/db/structure/collection.h" @@ -171,6 +171,8 @@ namespace IndexUpdateTests { bool _mayInterrupt; }; + // QUERY_MIGRATION +#if 0 /** buildBottomUpPhases2And3() builds a btree from the keys in an external sorter. */ class BuildBottomUp : public IndexBuildBase { public: @@ -227,6 +229,7 @@ namespace IndexUpdateTests { ASSERT_EQUALS( nKeys, expectedKey ); } }; +#endif /** buildBottomUpPhases2And3() aborts if the current operation is interrupted. */ class InterruptBuildBottomUp : public IndexBuildBase { @@ -771,7 +774,8 @@ namespace IndexUpdateTests { add<AddKeysToPhaseOne>(); add<InterruptAddKeysToPhaseOne>( false ); add<InterruptAddKeysToPhaseOne>( true ); - add<BuildBottomUp>(); + // QUERY_MIGRATION + // add<BuildBottomUp>(); add<InterruptBuildBottomUp>( false ); add<InterruptBuildBottomUp>( true ); add<DoDropDups>(); diff --git a/src/mongo/dbtests/intervalbtreecursortests.cpp b/src/mongo/dbtests/intervalbtreecursortests.cpp deleted file mode 100644 index c02bd22315c..00000000000 --- a/src/mongo/dbtests/intervalbtreecursortests.cpp +++ /dev/null @@ -1,672 +0,0 @@ -/** - * 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/>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the GNU Affero General Public License in all respects - * for all of the code used other than as permitted herein. If you modify - * file(s) with this exception, you may extend this exception to your - * version of the file(s), but you are not obligated to do so. If you do not - * wish to do so, delete this exception statement from your version. If you - * delete this exception statement from all source files in the program, - * then also delete it in the license file. - */ - -#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 diff --git a/src/mongo/dbtests/matchertests.cpp b/src/mongo/dbtests/matchertests.cpp index e94b9e345f9..a42986bddf2 100644 --- a/src/mongo/dbtests/matchertests.cpp +++ b/src/mongo/dbtests/matchertests.cpp @@ -31,12 +31,10 @@ #include "mongo/pch.h" -#include "mongo/db/cursor.h" #include "mongo/db/json.h" #include "mongo/db/matcher.h" #include "mongo/db/matcher/matcher.h" #include "mongo/db/namespace_details.h" -#include "mongo/db/query_optimizer.h" #include "mongo/dbtests/dbtests.h" #include "mongo/util/timer.h" @@ -223,87 +221,6 @@ namespace MatcherTests { } }; - namespace Covered { // Tests for CoveredIndexMatcher. - - /** - * Test that MatchDetails::elemMatchKey() is set correctly after an unindexed cursor match. - */ - class ElemMatchKeyUnindexed : public CollectionBase { - public: - void run() { - client().insert( ns(), fromjson( "{ a:[ {}, { b:1 } ] }" ) ); - - Client::ReadContext context( ns() ); - - CoveredIndexMatcher matcher( BSON( "a.b" << 1 ), BSON( "$natural" << 1 ) ); - MatchDetails details; - details.requestElemMatchKey(); - boost::shared_ptr<Cursor> cursor = getOptimizedCursor( ns(), BSONObj() ); - // Verify that the cursor is unindexed. - ASSERT_EQUALS( "BasicCursor", cursor->toString() ); - ASSERT( matcher.matchesCurrent( cursor.get(), &details ) ); - // The '1' entry of the 'a' array is matched. - ASSERT( details.hasElemMatchKey() ); - ASSERT_EQUALS( string( "1" ), details.elemMatchKey() ); - } - }; - - /** - * Test that MatchDetails::elemMatchKey() is set correctly after an indexed cursor match. - */ - class ElemMatchKeyIndexed : public CollectionBase { - public: - void run() { - client().ensureIndex( ns(), BSON( "a.b" << 1 ) ); - client().insert( ns(), fromjson( "{ a:[ {}, { b:9 }, { b:1 } ] }" ) ); - - Client::ReadContext context( ns() ); - - BSONObj query = BSON( "a.b" << 1 ); - CoveredIndexMatcher matcher( query, BSON( "a.b" << 1 ) ); - MatchDetails details; - details.requestElemMatchKey(); - boost::shared_ptr<Cursor> cursor = getOptimizedCursor( ns(), query ); - // Verify that the cursor is indexed. - ASSERT_EQUALS( "BtreeCursor a.b_1", cursor->toString() ); - ASSERT( matcher.matchesCurrent( cursor.get(), &details ) ); - // The '2' entry of the 'a' array is matched. - ASSERT( details.hasElemMatchKey() ); - ASSERT_EQUALS( string( "2" ), details.elemMatchKey() ); - } - }; - - /** - * Test that MatchDetails::elemMatchKey() is set correctly after an indexed cursor match - * on a non multikey index. - */ - class ElemMatchKeyIndexedSingleKey : public CollectionBase { - public: - void run() { - client().ensureIndex( ns(), BSON( "a.b" << 1 ) ); - client().insert( ns(), fromjson( "{ a:[ { b:1 } ] }" ) ); - - Client::ReadContext context( ns() ); - - BSONObj query = BSON( "a.b" << 1 ); - CoveredIndexMatcher matcher( query, BSON( "a.b" << 1 ) ); - MatchDetails details; - details.requestElemMatchKey(); - boost::shared_ptr<Cursor> cursor = getOptimizedCursor( ns(), query ); - // Verify that the cursor is indexed. - ASSERT_EQUALS( "BtreeCursor a.b_1", cursor->toString() ); - // Verify that the cursor is not multikey. - ASSERT( !cursor->isMultiKey() ); - ASSERT( matcher.matchesCurrent( cursor.get(), &details ) ); - // The '0' entry of the 'a' array is matched. - ASSERT( details.hasElemMatchKey() ); - ASSERT_EQUALS( string( "0" ), details.elemMatchKey() ); - } - }; - - } // namespace Covered - - template< typename M > class TimingBase { public: @@ -472,9 +389,6 @@ namespace MatcherTests { ADD_BOTH(MixedNumericEmbedded); ADD_BOTH(ElemMatchKey); ADD_BOTH(WhereSimple1); - add<Covered::ElemMatchKeyUnindexed>(); - add<Covered::ElemMatchKeyIndexed>(); - add<Covered::ElemMatchKeyIndexedSingleKey>(); ADD_BOTH(AllTiming); ADD_BOTH(WithinBox); ADD_BOTH(WithinCenter); diff --git a/src/mongo/dbtests/namespacetests.cpp b/src/mongo/dbtests/namespacetests.cpp index 5768f4427a6..b1ee4a469e7 100644 --- a/src/mongo/dbtests/namespacetests.cpp +++ b/src/mongo/dbtests/namespacetests.cpp @@ -38,6 +38,7 @@ #include "mongo/db/index_legacy.h" #include "mongo/db/index_selection.h" #include "mongo/db/json.h" +#include "mongo/db/query/internal_plans.h" #include "mongo/db/queryutil.h" #include "mongo/db/catalog/ondisk/namespace.h" #include "mongo/db/structure/collection.h" @@ -2150,13 +2151,15 @@ namespace NamespaceTests { DiskLoc last, first; { - ReverseCappedCursor c(nsd); - last = c.currLoc(); + auto_ptr<Runner> runner( + InternalPlanner::collectionScan(ns(), InternalPlanner::BACKWARD)); + runner->getNext(NULL, &last); ASSERT( !last.isNull() ); } { - scoped_ptr<ForwardCappedCursor> c( ForwardCappedCursor::make( nsd ) ); - first = c->currLoc(); + auto_ptr<Runner> runner( + InternalPlanner::collectionScan(ns(), InternalPlanner::FORWARD)); + runner->getNext(NULL, &first); ASSERT( !first.isNull() ); ASSERT( first != last ) ; } @@ -2165,12 +2168,18 @@ namespace NamespaceTests { ASSERT_EQUALS( nsd->numRecords() , 28 ); { - scoped_ptr<ForwardCappedCursor> c( ForwardCappedCursor::make( nsd ) ); - ASSERT( first == c->currLoc() ); + DiskLoc loc; + auto_ptr<Runner> runner( + InternalPlanner::collectionScan(ns(), InternalPlanner::FORWARD)); + runner->getNext(NULL, &loc); + ASSERT( first == loc); } { - ReverseCappedCursor c(nsd); - ASSERT( last != c.currLoc() ); // old last should be deleted + auto_ptr<Runner> runner( + InternalPlanner::collectionScan(ns(), InternalPlanner::BACKWARD)); + DiskLoc loc; + runner->getNext(NULL, &loc); + ASSERT( last != loc ); ASSERT( !last.isNull() ); } @@ -2246,49 +2255,6 @@ namespace NamespaceTests { } }; - class CachedPlanBase : public Base { - public: - CachedPlanBase() : - _fieldRangeSet( ns(), BSON( "a" << 1 ), true, true ), - _pattern( _fieldRangeSet, BSONObj() ) { - create(); - } - protected: - void assertCachedIndexKey( const BSONObj &indexKey ) const { - ASSERT_EQUALS( indexKey, - infoCache()->cachedQueryPlanForPattern( _pattern ).indexKey() ); - } - void registerIndexKey( const BSONObj &indexKey ) { - infoCache()->registerCachedQueryPlanForPattern( _pattern, - CachedQueryPlan( indexKey, 1, CandidatePlanCharacter( true, false ) ) ); - } - FieldRangeSet _fieldRangeSet; - QueryPattern _pattern; - }; - - /** - * setIndexIsMultikey() sets the multikey flag for an index and clears the query plan - * cache. - */ - class SetIndexIsMultikey : public CachedPlanBase { - public: - void run() { - DBDirectClient client; - client.ensureIndex( ns(), BSON( "a" << 1 ) ); - registerIndexKey( BSON( "a" << 1 ) ); - - ASSERT( !nsd()->isMultikey( 1 ) ); - - indexCatalog()->markMultikey( indexCatalog()->getDescriptor( 1 ) ); - ASSERT( nsd()->isMultikey( 1 ) ); - assertCachedIndexKey( BSONObj() ); - - registerIndexKey( BSON( "a" << 1 ) ); - indexCatalog()->markMultikey( indexCatalog()->getDescriptor( 1 ) ); - assertCachedIndexKey( BSON( "a" << 1 ) ); - } - }; - class SwapIndexEntriesTest : public Base { public: void run() { @@ -2325,24 +2291,6 @@ namespace NamespaceTests { } // namespace NamespaceDetailsTests - namespace CollectionInfoCacheTests { - - /** clearQueryCache() clears the query plan cache. */ - class ClearQueryCache : public NamespaceDetailsTests::CachedPlanBase { - public: - void run() { - // Register a query plan in the query plan cache. - registerIndexKey( BSON( "a" << 1 ) ); - assertCachedIndexKey( BSON( "a" << 1 ) ); - - // The query plan is cleared. - infoCache()->clearQueryCache(); - assertCachedIndexKey( BSONObj() ); - } - }; - - } // namespace CollectionInfoCacheTests - class All : public Suite { public: All() : Suite( "namespace" ) { @@ -2455,8 +2403,6 @@ namespace NamespaceTests { add< NamespaceDetailsTests::SwapIndexEntriesTest >(); // add< NamespaceDetailsTests::BigCollection >(); add< NamespaceDetailsTests::Size >(); - add< NamespaceDetailsTests::SetIndexIsMultikey >(); - add< CollectionInfoCacheTests::ClearQueryCache >(); add< MissingFieldTests::BtreeIndexMissingField >(); add< MissingFieldTests::TwoDIndexMissingField >(); add< MissingFieldTests::HashedIndexMissingField >(); diff --git a/src/mongo/dbtests/pdfiletests.cpp b/src/mongo/dbtests/pdfiletests.cpp index 442b87021d4..dd1283b14ad 100644 --- a/src/mongo/dbtests/pdfiletests.cpp +++ b/src/mongo/dbtests/pdfiletests.cpp @@ -38,248 +38,6 @@ namespace PdfileTests { - // XXX: These tests have been ported to query_stage_collscan.cpp and are deprecated here. - namespace ScanCapped { - - class Base { - public: - Base() : _context( ns() ) { - } - virtual ~Base() { - if ( !nsd() ) - return; - _context.db()->dropCollection( ns() ); - } - void run() { - stringstream spec; - spec << "{\"capped\":true,\"size\":2000,\"$nExtents\":" << nExtents() << "}"; - string err; - ASSERT( userCreateNS( ns(), fromjson( spec.str() ), err, false ) ); - prepare(); - int j = 0; - for ( boost::shared_ptr<Cursor> i = theDataFileMgr.findAll( ns() ); - i->ok(); i->advance(), ++j ) - ASSERT_EQUALS( j, i->current().firstElement().number() ); - ASSERT_EQUALS( count(), j ); - - j = count() - 1; - for ( boost::shared_ptr<Cursor> i = - findTableScan( ns(), fromjson( "{\"$natural\":-1}" ) ); - i->ok(); i->advance(), --j ) - ASSERT_EQUALS( j, i->current().firstElement().number() ); - ASSERT_EQUALS( -1, j ); - } - protected: - virtual void prepare() = 0; - virtual int count() const = 0; - virtual int nExtents() const { - return 0; - } - // bypass standard alloc/insert routines to use the extent we want. - static DiskLoc insert( const DiskLoc& ext, int i ) { - BSONObjBuilder b; - b.append( "a", i ); - BSONObj o = b.done(); - int len = o.objsize(); - Extent *e = ext.ext(); - e = getDur().writing(e); - int ofs; - if ( e->lastRecord.isNull() ) - ofs = ext.getOfs() + ( e->_extentData - (char *)e ); - else - ofs = e->lastRecord.getOfs() + e->lastRecord.rec()->lengthWithHeaders(); - DiskLoc dl( ext.a(), ofs ); - Record *r = dl.rec(); - r = (Record*) getDur().writingPtr(r, Record::HeaderSize + len); - r->lengthWithHeaders() = Record::HeaderSize + len; - r->extentOfs() = e->myLoc.getOfs(); - r->nextOfs() = DiskLoc::NullOfs; - r->prevOfs() = e->lastRecord.isNull() ? DiskLoc::NullOfs : e->lastRecord.getOfs(); - memcpy( r->data(), o.objdata(), len ); - if ( e->firstRecord.isNull() ) - e->firstRecord = dl; - else - getDur().writingInt(e->lastRecord.rec()->nextOfs()) = ofs; - e->lastRecord = dl; - return dl; - } - static const char *ns() { - return "unittests.ScanCapped"; - } - static NamespaceDetails *nsd() { - return nsdetails( ns() ); - } - private: - Lock::GlobalWrite lk_; - Client::Context _context; - }; - - class Empty : public Base { - virtual void prepare() {} - virtual int count() const { - return 0; - } - }; - - class EmptyLooped : public Base { - virtual void prepare() { - nsd()->writingWithExtra()->capFirstNewRecord() = DiskLoc(); - } - virtual int count() const { - return 0; - } - }; - - class EmptyMultiExtentLooped : public Base { - virtual void prepare() { - nsd()->writingWithExtra()->capFirstNewRecord() = DiskLoc(); - } - virtual int count() const { - return 0; - } - virtual int nExtents() const { - return 3; - } - }; - - class Single : public Base { - virtual void prepare() { - nsd()->writingWithExtra()->capFirstNewRecord() = insert( nsd()->capExtent(), 0 ); - } - virtual int count() const { - return 1; - } - }; - - class NewCapFirst : public Base { - virtual void prepare() { - DiskLoc x = insert( nsd()->capExtent(), 0 ); - nsd()->writingWithExtra()->capFirstNewRecord() = x; - insert( nsd()->capExtent(), 1 ); - } - virtual int count() const { - return 2; - } - }; - - class NewCapLast : public Base { - virtual void prepare() { - insert( nsd()->capExtent(), 0 ); - nsd()->capFirstNewRecord().writing() = insert( nsd()->capExtent(), 1 ); - } - virtual int count() const { - return 2; - } - }; - - class NewCapMiddle : public Base { - virtual void prepare() { - insert( nsd()->capExtent(), 0 ); - nsd()->capFirstNewRecord().writing() = insert( nsd()->capExtent(), 1 ); - insert( nsd()->capExtent(), 2 ); - } - virtual int count() const { - return 3; - } - }; - - class FirstExtent : public Base { - virtual void prepare() { - insert( nsd()->capExtent(), 0 ); - insert( nsd()->lastExtent(), 1 ); - nsd()->capFirstNewRecord().writing() = insert( nsd()->capExtent(), 2 ); - insert( nsd()->capExtent(), 3 ); - } - virtual int count() const { - return 4; - } - virtual int nExtents() const { - return 2; - } - }; - - class LastExtent : public Base { - virtual void prepare() { - nsd()->capExtent().writing() = nsd()->lastExtent(); - insert( nsd()->capExtent(), 0 ); - insert( nsd()->firstExtent(), 1 ); - nsd()->capFirstNewRecord().writing() = insert( nsd()->capExtent(), 2 ); - insert( nsd()->capExtent(), 3 ); - } - virtual int count() const { - return 4; - } - virtual int nExtents() const { - return 2; - } - }; - - class MidExtent : public Base { - virtual void prepare() { - nsd()->capExtent().writing() = nsd()->firstExtent().ext()->xnext; - insert( nsd()->capExtent(), 0 ); - insert( nsd()->lastExtent(), 1 ); - insert( nsd()->firstExtent(), 2 ); - nsd()->capFirstNewRecord().writing() = insert( nsd()->capExtent(), 3 ); - insert( nsd()->capExtent(), 4 ); - } - virtual int count() const { - return 5; - } - virtual int nExtents() const { - return 3; - } - }; - - class AloneInExtent : public Base { - virtual void prepare() { - nsd()->capExtent().writing() = nsd()->firstExtent().ext()->xnext; - insert( nsd()->lastExtent(), 0 ); - insert( nsd()->firstExtent(), 1 ); - nsd()->capFirstNewRecord().writing() = insert( nsd()->capExtent(), 2 ); - } - virtual int count() const { - return 3; - } - virtual int nExtents() const { - return 3; - } - }; - - class FirstInExtent : public Base { - virtual void prepare() { - nsd()->capExtent().writing() = nsd()->firstExtent().ext()->xnext; - insert( nsd()->lastExtent(), 0 ); - insert( nsd()->firstExtent(), 1 ); - nsd()->capFirstNewRecord().writing() = insert( nsd()->capExtent(), 2 ); - insert( nsd()->capExtent(), 3 ); - } - virtual int count() const { - return 4; - } - virtual int nExtents() const { - return 3; - } - }; - - class LastInExtent : public Base { - virtual void prepare() { - nsd()->capExtent().writing() = nsd()->firstExtent().ext()->xnext; - insert( nsd()->capExtent(), 0 ); - insert( nsd()->lastExtent(), 1 ); - insert( nsd()->firstExtent(), 2 ); - nsd()->capFirstNewRecord().writing() = insert( nsd()->capExtent(), 3 ); - } - virtual int count() const { - return 4; - } - virtual int nExtents() const { - return 3; - } - }; - - } // namespace ScanCapped - namespace Insert { class Base { public: @@ -385,19 +143,6 @@ namespace PdfileTests { All() : Suite( "pdfile" ) {} void setupTests() { - add< ScanCapped::Empty >(); - add< ScanCapped::EmptyLooped >(); - add< ScanCapped::EmptyMultiExtentLooped >(); - add< ScanCapped::Single >(); - add< ScanCapped::NewCapFirst >(); - add< ScanCapped::NewCapLast >(); - add< ScanCapped::NewCapMiddle >(); - add< ScanCapped::FirstExtent >(); - add< ScanCapped::LastExtent >(); - add< ScanCapped::MidExtent >(); - add< ScanCapped::AloneInExtent >(); - add< ScanCapped::FirstInExtent >(); - add< ScanCapped::LastInExtent >(); add< Insert::InsertAddId >(); add< Insert::UpdateDate >(); add< ExtentSizing >(); diff --git a/src/mongo/dbtests/perf/perftest.cpp b/src/mongo/dbtests/perf/perftest.cpp index f4a6c98ff39..2afa74f68fa 100644 --- a/src/mongo/dbtests/perf/perftest.cpp +++ b/src/mongo/dbtests/perf/perftest.cpp @@ -37,7 +37,6 @@ #include "mongo/client/dbclientcursor.h" #include "mongo/db/instance.h" #include "mongo/db/json.h" -#include "mongo/db/query_optimizer_internal.h" #include "mongo/dbtests/dbtests.h" #include "mongo/dbtests/framework.h" #include "mongo/util/file_allocator.h" @@ -665,6 +664,8 @@ namespace Count { namespace Plan { + // QUERY_MIGRATION: what is this really testing? +#if 0 class Hint { public: Hint() : ns_( testNs( this ) ) { @@ -741,6 +742,7 @@ namespace Plan { add< Query >(); } } all; +#endif } // namespace Plan namespace Misc { diff --git a/src/mongo/dbtests/query_stage_fetch.cpp b/src/mongo/dbtests/query_stage_fetch.cpp index 5ef65be9688..23e6f7f0d0e 100644 --- a/src/mongo/dbtests/query_stage_fetch.cpp +++ b/src/mongo/dbtests/query_stage_fetch.cpp @@ -33,7 +33,6 @@ #include <boost/shared_ptr.hpp> #include "mongo/client/dbclientcursor.h" -#include "mongo/db/cursor.h" #include "mongo/db/database.h" #include "mongo/db/exec/fetch.h" #include "mongo/db/exec/plan_stage.h" diff --git a/src/mongo/dbtests/queryoptimizercursortests.cpp b/src/mongo/dbtests/queryoptimizercursortests.cpp deleted file mode 100644 index f599549e32f..00000000000 --- a/src/mongo/dbtests/queryoptimizercursortests.cpp +++ /dev/null @@ -1,4984 +0,0 @@ -// queryoptimizertests.cpp : query optimizer unit tests -// - -/** - * Copyright (C) 2009 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/>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the GNU Affero General Public License in all respects - * for all of the code used other than as permitted herein. If you modify - * file(s) with this exception, you may extend this exception to your - * version of the file(s), but you are not obligated to do so. If you do not - * wish to do so, delete this exception statement from your version. If you - * delete this exception statement from all source files in the program, - * then also delete it in the license file. - */ - -#include "mongo/pch.h" - - -#include "mongo/client/dbclientcursor.h" -#include "mongo/db/clientcursor.h" -#include "mongo/db/instance.h" -#include "mongo/db/json.h" -#include "mongo/db/pdfile.h" -#include "mongo/db/query_optimizer.h" -#include "mongo/db/query_optimizer_internal.h" -#include "mongo/db/queryoptimizercursorimpl.h" -#include "mongo/db/queryutil.h" -#include "mongo/db/structure/collection.h" -#include "mongo/dbtests/dbtests.h" - -namespace mongo { - shared_ptr<Cursor> newQueryOptimizerCursor( const char *ns, const BSONObj &query, - const BSONObj &order = BSONObj(), - const QueryPlanSelectionPolicy &planPolicy = - QueryPlanSelectionPolicy::any(), - bool requireOrder = true, - const shared_ptr<const ParsedQuery> &parsedQuery = - shared_ptr<const ParsedQuery>() ); -} // namespace mongo - -namespace QueryOptimizerCursorTests { - - using boost::shared_ptr; - - namespace CachedMatchCounter { - - using mongo::CachedMatchCounter; - - class Count { - public: - void run() { - long long aggregateNscanned; - CachedMatchCounter c( aggregateNscanned, 0 ); - ASSERT_EQUALS( 0, c.count() ); - ASSERT_EQUALS( 0, c.cumulativeCount() ); - - c.resetMatch(); - ASSERT( !c.knowMatch() ); - - c.setMatch( false ); - ASSERT( c.knowMatch() ); - - c.incMatch( DiskLoc() ); - ASSERT_EQUALS( 0, c.count() ); - ASSERT_EQUALS( 0, c.cumulativeCount() ); - - c.resetMatch(); - ASSERT( !c.knowMatch() ); - - c.setMatch( true ); - ASSERT( c.knowMatch() ); - - c.incMatch( DiskLoc() ); - ASSERT_EQUALS( 1, c.count() ); - ASSERT_EQUALS( 1, c.cumulativeCount() ); - - // Don't count the same match twice, without checking the document location. - c.incMatch( DiskLoc( 1, 1 ) ); - ASSERT_EQUALS( 1, c.count() ); - ASSERT_EQUALS( 1, c.cumulativeCount() ); - - // Reset and count another match. - c.resetMatch(); - c.setMatch( true ); - c.incMatch( DiskLoc( 1, 1 ) ); - ASSERT_EQUALS( 2, c.count() ); - ASSERT_EQUALS( 2, c.cumulativeCount() ); - } - }; - - class Accumulate { - public: - void run() { - long long aggregateNscanned; - CachedMatchCounter c( aggregateNscanned, 10 ); - ASSERT_EQUALS( 0, c.count() ); - ASSERT_EQUALS( 10, c.cumulativeCount() ); - - c.setMatch( true ); - c.incMatch( DiskLoc() ); - ASSERT_EQUALS( 1, c.count() ); - ASSERT_EQUALS( 11, c.cumulativeCount() ); - } - }; - - class Dedup { - public: - void run() { - long long aggregateNscanned; - CachedMatchCounter c( aggregateNscanned, 0 ); - - c.setCheckDups( true ); - c.setMatch( true ); - c.incMatch( DiskLoc() ); - ASSERT_EQUALS( 1, c.count() ); - - c.resetMatch(); - c.setMatch( true ); - c.incMatch( DiskLoc() ); - ASSERT_EQUALS( 1, c.count() ); - } - }; - - class Nscanned { - public: - void run() { - long long aggregateNscanned = 5; - CachedMatchCounter c( aggregateNscanned, 0 ); - ASSERT_EQUALS( 0, c.nscanned() ); - ASSERT_EQUALS( 5, c.aggregateNscanned() ); - - c.updateNscanned( 4 ); - ASSERT_EQUALS( 4, c.nscanned() ); - ASSERT_EQUALS( 9, c.aggregateNscanned() ); - } - }; - - } // namespace CachedMatchCounter - - namespace SmallDupSet { - - using mongo::SmallDupSet; - - class Upgrade { - public: - void run() { - SmallDupSet d; - for( int i = 0; i < 100; ++i ) { - ASSERT( !d.getsetdup( DiskLoc( 0, i ) ) ); - for( int j = 0; j <= i; ++j ) { - ASSERT( d.getdup( DiskLoc( 0, j ) ) ); - } - } - } - }; - - class UpgradeRead { - public: - void run() { - SmallDupSet d; - d.getsetdup( DiskLoc( 0, 0 ) ); - for( int i = 0; i < 550; ++i ) { - ASSERT( d.getdup( DiskLoc( 0, 0 ) ) ); - } - ASSERT( d.getsetdup( DiskLoc( 0, 0 ) ) ); - } - }; - - class UpgradeWrite { - public: - void run() { - SmallDupSet d; - for( int i = 0; i < 550; ++i ) { - ASSERT( !d.getsetdup( DiskLoc( 0, i ) ) ); - } - for( int i = 0; i < 550; ++i ) { - ASSERT( d.getsetdup( DiskLoc( 0, i ) ) ); - } - } - }; - - } // namespace SmallDupSet - - class DurationTimerStop { - public: - void run() { - DurationTimer t; - while( t.duration() == 0 ); - ASSERT( t.duration() > 0 ); - t.stop(); - ASSERT( t.duration() > 0 ); - ASSERT( t.duration() > 0 ); - } - }; - - class Base { - public: - Base() { - Lock::GlobalWrite lk; - Client::Context ctx( ns() ); - string err; - userCreateNS( ns(), BSONObj(), err, false ); - ctx.db()->dropCollection( ns() ); - } - ~Base() { - cc().curop()->reset(); - } - protected: - DBDirectClient _cli; - static const char *ns() { return "unittests.QueryOptimizerTests"; } - void setQueryOptimizerCursor( const BSONObj &query, const BSONObj &order = BSONObj() ) { - setQueryOptimizerCursorWithoutAdvancing( query, order ); - if ( ok() && !mayReturnCurrent() ) { - advance(); - } - } - void setQueryOptimizerCursorWithoutAdvancing( const BSONObj &query, const BSONObj &order = BSONObj() ) { - _c = newQueryOptimizerCursor( ns(), query, order ); - } - bool ok() const { return _c->ok(); } - /** Handles matching and deduping. */ - bool advance() { - while( _c->advance() && !mayReturnCurrent() ); - return ok(); - } - int itcount() { - int ret = 0; - while( ok() ) { - ++ret; - advance(); - } - return ret; - } - BSONObj current() const { return _c->current(); } - DiskLoc currLoc() const { return _c->currLoc(); } - void prepareToTouchEarlierIterate() { _c->prepareToTouchEarlierIterate(); } - void recoverFromTouchingEarlierIterate() { _c->recoverFromTouchingEarlierIterate(); } - bool mayReturnCurrent() { -// return _c->currentMatches() && !_c->getsetdup( _c->currLoc() ); - return ( !_c->matcher() || _c->matcher()->matchesCurrent( _c.get() ) ) && !_c->getsetdup( _c->currLoc() ); - } - void prepareToYield() const { _c->prepareToYield(); } - void recoverFromYield() { - _c->recoverFromYield(); - if ( ok() && !mayReturnCurrent() ) { - advance(); - } - } - shared_ptr<Cursor> c() { return _c; } - long long nscanned() const { return _c->nscanned(); } - unsigned nNsCursors() const { - set<CursorId> nsCursors; - ClientCursor::find( ns(), nsCursors ); - return nsCursors.size(); - } - BSONObj cachedIndexForQuery( const BSONObj &query, const BSONObj &order = BSONObj() ) { - QueryPattern queryPattern = FieldRangeSet( ns(), query, true, true ).pattern( order ); - - Client* client = currentClient.get(); - verify( client ); - - Collection* collection = client->database()->getCollection( ns() ); - if ( !collection ) - return BSONObj(); - - return collection->infoCache()->cachedQueryPlanForPattern( queryPattern ).indexKey(); - } - private: - shared_ptr<Cursor> _c; - }; - - /** No results for empty collection. */ - class Empty : public Base { - public: - void run() { - Lock::GlobalWrite lk; - Client::Context ctx( ns() ); - shared_ptr<QueryOptimizerCursor> c = - dynamic_pointer_cast<QueryOptimizerCursor> - ( newQueryOptimizerCursor( ns(), BSONObj() ) ); - ASSERT( !c->ok() ); - ASSERT_THROWS( c->_current(), AssertionException ); - ASSERT_THROWS( c->current(), AssertionException ); - ASSERT( c->currLoc().isNull() ); - ASSERT( !c->advance() ); - ASSERT_THROWS( c->currKey(), AssertionException ); - ASSERT_THROWS( c->getsetdup( DiskLoc() ), AssertionException ); - ASSERT_THROWS( c->isMultiKey(), AssertionException ); - ASSERT_THROWS( c->matcher(), AssertionException ); - - ASSERT_THROWS( c->initialFieldRangeSet(), AssertionException ); - ASSERT_THROWS( c->currentPlanScanAndOrderRequired(), AssertionException ); - ASSERT_THROWS( c->keyFieldsOnly(), AssertionException ); - ASSERT_THROWS( c->runningInitialInOrderPlan(), AssertionException ); - ASSERT_THROWS( c->hasPossiblyExcludedPlans(), AssertionException ); - - // ok - c->initialCandidatePlans(); - c->completePlanOfHybridSetScanAndOrderRequired(); - c->clearIndexesForPatterns(); - c->abortOutOfOrderPlans(); - c->noteIterate( false, false, false ); - c->explainQueryInfo(); - } - }; - - /** Simple table scan. */ - class Unindexed : public Base { - public: - void run() { - _cli.insert( ns(), BSON( "_id" << 1 ) ); - _cli.insert( ns(), BSON( "_id" << 2 ) ); - - Lock::GlobalWrite lk; - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSONObj() ); - ASSERT_EQUALS( 2, itcount() ); - } - }; - - /** Basic test with two indexes and deduping requirement. */ - class Basic : public Base { - public: - void run() { - _cli.insert( ns(), BSON( "_id" << 1 << "a" << 2 ) ); - _cli.insert( ns(), BSON( "_id" << 2 << "a" << 1 ) ); - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - - Lock::GlobalWrite lk; - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSON( "_id" << GT << 0 << "a" << GT << 0 ) ); - ASSERT( ok() ); - ASSERT_EQUALS( BSON( "_id" << 1 << "a" << 2 ), current() ); - ASSERT( advance() ); - ASSERT_EQUALS( BSON( "_id" << 2 << "a" << 1 ), current() ); - ASSERT( !advance() ); - ASSERT( !ok() ); - } - }; - - class NoMatch : public Base { - public: - void run() { - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - - Lock::GlobalWrite lk; - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSON( "_id" << GT << 5 << LT << 4 << "a" << GT << 0 ) ); - ASSERT( !ok() ); - } - }; - - /** Order of results indicates that interleaving is occurring. */ - class Interleaved : public Base { - public: - void run() { - _cli.insert( ns(), BSON( "_id" << 1 << "a" << 2 ) ); - _cli.insert( ns(), BSON( "_id" << 3 << "a" << 1 ) ); - _cli.insert( ns(), BSON( "_id" << 2 << "a" << 2 ) ); - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - - Lock::GlobalWrite lk; - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSON( "_id" << GT << 0 << "a" << GT << 0 ) ); - ASSERT( ok() ); - ASSERT_EQUALS( BSON( "_id" << 1 << "a" << 2 ), current() ); - ASSERT( advance() ); - ASSERT_EQUALS( BSON( "_id" << 3 << "a" << 1 ), current() ); - ASSERT( advance() ); - ASSERT_EQUALS( BSON( "_id" << 2 << "a" << 2 ), current() ); - ASSERT( !advance() ); - ASSERT( !ok() ); - } - }; - - /** Some values on each index do not match. */ - class NotMatch : public Base { - public: - void run() { - _cli.insert( ns(), BSON( "_id" << 0 << "a" << 10 ) ); - _cli.insert( ns(), BSON( "_id" << 10 << "a" << 0 ) ); - _cli.insert( ns(), BSON( "_id" << 11 << "a" << 12 ) ); - _cli.insert( ns(), BSON( "_id" << 12 << "a" << 11 ) ); - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - - Lock::GlobalWrite lk; - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSON( "_id" << GT << 5 << "a" << GT << 5 ) ); - ASSERT( ok() ); - ASSERT_EQUALS( BSON( "_id" << 11 << "a" << 12 ), current() ); - ASSERT( advance() ); - ASSERT_EQUALS( BSON( "_id" << 12 << "a" << 11 ), current() ); - ASSERT( !advance() ); - ASSERT( !ok() ); - } - }; - - /** After the first 101 matches for a plan, we stop interleaving the plans. */ - class StopInterleaving : public Base { - public: - void run() { - for( int i = 0; i < 101; ++i ) { - _cli.insert( ns(), BSON( "_id" << i << "a" << i ) ); - } - for( int i = 101; i < 200; ++i ) { - _cli.insert( ns(), BSON( "_id" << i << "a" << (301-i) ) ); - } - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - - Lock::GlobalWrite lk; - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSON( "_id" << GT << -1 << "a" << GT << -1 ) ); - for( int i = 0; i < 200; ++i ) { - ASSERT( ok() ); - ASSERT_EQUALS( i, current().getIntField( "_id" ) ); - advance(); - } - ASSERT( !advance() ); - ASSERT( !ok() ); - } - }; - - /** Test correct deduping with the takeover cursor. */ - class TakeoverWithDup : public Base { - public: - void run() { - for( int i = 0; i < 101; ++i ) { - _cli.insert( ns(), BSON( "_id" << i << "a" << i ) ); - } - _cli.insert( ns(), BSON( "_id" << 500 << "a" << BSON_ARRAY( 0 << 300 ) ) ); - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - Lock::GlobalWrite lk; - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSON( "_id" << GT << -1 << "a" << GT << -1 ) ); - ASSERT_EQUALS( 102, itcount() ); - } - }; - - /** Test usage of matcher with takeover cursor. */ - class TakeoverWithNonMatches : public Base { - public: - void run() { - for( int i = 0; i < 101; ++i ) { - _cli.insert( ns(), BSON( "_id" << i << "a" << i ) ); - } - _cli.insert( ns(), BSON( "_id" << 101 << "a" << 600 ) ); - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - Lock::GlobalWrite lk; - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSON( "_id" << GT << -1 << "a" << LT << 500 ) ); - ASSERT_EQUALS( 101, itcount() ); - } - }; - - /** Check deduping of dups within just the takeover cursor. */ - class TakeoverWithTakeoverDup : public Base { - public: - void run() { - for( int i = 0; i < 101; ++i ) { - _cli.insert( ns(), BSON( "_id" << i*2 << "a" << 0 ) ); - _cli.insert( ns(), BSON( "_id" << i*2+1 << "a" << 1 ) ); - } - _cli.insert( ns(), BSON( "_id" << 202 << "a" << BSON_ARRAY( 2 << 3 ) ) ); - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - Lock::GlobalWrite lk; - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSON( "_id" << GT << -1 << "a" << GT << 0) ); - ASSERT_EQUALS( 102, itcount() ); - } - }; - - /** Basic test with $or query. */ - class BasicOr : public Base { - public: - void run() { - _cli.insert( ns(), BSON( "_id" << 0 << "a" << 0 ) ); - _cli.insert( ns(), BSON( "_id" << 1 << "a" << 1 ) ); - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - Lock::GlobalWrite lk; - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSON( "$or" << BSON_ARRAY( BSON( "_id" << 0 ) << BSON( "a" << 1 ) ) ) ); - ASSERT_EQUALS( BSON( "_id" << 0 << "a" << 0 ), current() ); - ASSERT( advance() ); - ASSERT_EQUALS( BSON( "_id" << 1 << "a" << 1 ), current() ); - ASSERT( !advance() ); - } - }; - - /** $or first clause empty. */ - class OrFirstClauseEmpty : public Base { - public: - void run() { - _cli.insert( ns(), BSON( "_id" << 0 << "a" << 1 ) ); - _cli.insert( ns(), BSON( "_id" << 1 << "a" << 1 ) ); - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - Lock::GlobalWrite lk; - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSON( "$or" << BSON_ARRAY( BSON( "_id" << -1 ) << BSON( "a" << 1 ) ) ) ); - ASSERT_EQUALS( BSON( "_id" << 0 << "a" << 1 ), current() ); - ASSERT( advance() ); - ASSERT_EQUALS( BSON( "_id" << 1 << "a" << 1 ), current() ); - ASSERT( !advance() ); - } - }; - - /** $or second clause empty. */ - class OrSecondClauseEmpty : public Base { - public: - void run() { - _cli.insert( ns(), BSON( "_id" << 0 << "a" << 1 ) ); - _cli.insert( ns(), BSON( "_id" << 1 << "a" << 1 ) ); - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - Lock::GlobalWrite lk; - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSON( "$or" << BSON_ARRAY( BSON( "_id" << 0 ) << BSON( "_id" << -1 ) << BSON( "a" << 1 ) ) ) ); - ASSERT_EQUALS( BSON( "_id" << 0 << "a" << 1 ), current() ); - ASSERT( advance() ); - ASSERT_EQUALS( BSON( "_id" << 1 << "a" << 1 ), current() ); - ASSERT( !advance() ); - } - }; - - /** $or multiple clauses empty empty. */ - class OrMultipleClausesEmpty : public Base { - public: - void run() { - _cli.insert( ns(), BSON( "_id" << 0 << "a" << 1 ) ); - _cli.insert( ns(), BSON( "_id" << 1 << "a" << 1 ) ); - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - Lock::GlobalWrite lk; - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSON( "$or" << BSON_ARRAY( BSON( "_id" << 2 ) << BSON( "_id" << 4 ) << BSON( "_id" << 0 ) << BSON( "_id" << -1 ) << BSON( "_id" << 6 ) << BSON( "a" << 1 ) << BSON( "_id" << 9 ) ) ) ); - ASSERT_EQUALS( BSON( "_id" << 0 << "a" << 1 ), current() ); - ASSERT( advance() ); - ASSERT_EQUALS( BSON( "_id" << 1 << "a" << 1 ), current() ); - ASSERT( !advance() ); - } - }; - - /** Check that takeover occurs at proper match count with $or clauses */ - class TakeoverCountOr : public Base { - public: - void run() { - for( int i = 0; i < 60; ++i ) { - _cli.insert( ns(), BSON( "_id" << i << "a" << 0 ) ); - } - for( int i = 60; i < 120; ++i ) { - _cli.insert( ns(), BSON( "_id" << i << "a" << 1 ) ); - } - for( int i = 120; i < 150; ++i ) { - _cli.insert( ns(), BSON( "_id" << i << "a" << (200-i) ) ); - } - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - Lock::GlobalWrite lk; - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSON( "$or" << BSON_ARRAY( BSON( "a" << 0 ) << BSON( "a" << 1 ) << BSON( "_id" << GTE << 120 << "a" << GT << 1 ) ) ) ); - for( int i = 0; i < 120; ++i ) { - ASSERT( ok() ); - advance(); - } - // Expect to be scanning on _id index only. - for( int i = 120; i < 150; ++i ) { - ASSERT_EQUALS( i, current().getIntField( "_id" ) ); - advance(); - } - ASSERT( !ok() ); - } - }; - - /** Takeover just at end of clause. */ - class TakeoverEndOfOrClause : public Base { - public: - void run() { - for( int i = 0; i < 102; ++i ) { - _cli.insert( ns(), BSON( "_id" << i ) ); - } - Lock::GlobalWrite lk; - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSON( "$or" << BSON_ARRAY( BSON( "_id" << LT << 101 ) << BSON( "_id" << 101 ) ) ) ); - for( int i = 0; i < 102; ++i ) { - ASSERT_EQUALS( i, current().getIntField( "_id" ) ); - advance(); - } - ASSERT( !ok() ); - } - }; - - class TakeoverBeforeEndOfOrClause : public Base { - public: - void run() { - for( int i = 0; i < 101; ++i ) { - _cli.insert( ns(), BSON( "_id" << i ) ); - } - Lock::GlobalWrite lk; - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSON( "$or" << BSON_ARRAY( BSON( "_id" << LT << 100 ) << BSON( "_id" << 100 ) ) ) ); - for( int i = 0; i < 101; ++i ) { - ASSERT_EQUALS( i, current().getIntField( "_id" ) ); - advance(); - } - ASSERT( !ok() ); - } - }; - - class TakeoverAfterEndOfOrClause : public Base { - public: - void run() { - for( int i = 0; i < 103; ++i ) { - _cli.insert( ns(), BSON( "_id" << i ) ); - } - Lock::GlobalWrite lk; - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSON( "$or" << BSON_ARRAY( BSON( "_id" << LT << 102 ) << BSON( "_id" << 102 ) ) ) ); - for( int i = 0; i < 103; ++i ) { - ASSERT_EQUALS( i, current().getIntField( "_id" ) ); - advance(); - } - ASSERT( !ok() ); - } - }; - - /** Test matching and deduping done manually by cursor client. */ - class ManualMatchingDeduping : public Base { - public: - void run() { - _cli.insert( ns(), BSON( "_id" << 0 << "a" << 10 ) ); - _cli.insert( ns(), BSON( "_id" << 10 << "a" << 0 ) ); - _cli.insert( ns(), BSON( "_id" << 11 << "a" << 12 ) ); - _cli.insert( ns(), BSON( "_id" << 12 << "a" << 11 ) ); - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - - Lock::GlobalWrite lk; - Client::Context ctx( ns() ); - shared_ptr< Cursor > c = newQueryOptimizerCursor( ns(), BSON( "_id" << GT << 5 << "a" << GT << 5 ) ); - ASSERT( c->ok() ); - - // _id 10 {_id:1} - ASSERT_EQUALS( 10, c->current().getIntField( "_id" ) ); - ASSERT( !c->matcher()->matchesCurrent( c.get() ) ); - ASSERT( c->advance() ); - - // _id 0 {a:1} - ASSERT_EQUALS( 0, c->current().getIntField( "_id" ) ); - ASSERT( !c->matcher()->matchesCurrent( c.get() ) ); - ASSERT( c->advance() ); - - // _id 0 {$natural:1} - ASSERT_EQUALS( 0, c->current().getIntField( "_id" ) ); - ASSERT( !c->matcher()->matchesCurrent( c.get() ) ); - ASSERT( c->advance() ); - - // _id 11 {_id:1} - ASSERT_EQUALS( BSON( "_id" << 11 << "a" << 12 ), c->current() ); - ASSERT( c->matcher()->matchesCurrent( c.get() ) ); - ASSERT( !c->getsetdup( c->currLoc() ) ); - ASSERT( c->advance() ); - - // _id 12 {a:1} - ASSERT_EQUALS( BSON( "_id" << 12 << "a" << 11 ), c->current() ); - ASSERT( c->matcher()->matchesCurrent( c.get() ) ); - ASSERT( !c->getsetdup( c->currLoc() ) ); - ASSERT( c->advance() ); - - // _id 10 {$natural:1} - ASSERT_EQUALS( 10, c->current().getIntField( "_id" ) ); - ASSERT( !c->matcher()->matchesCurrent( c.get() ) ); - ASSERT( c->advance() ); - - // _id 12 {_id:1} - ASSERT_EQUALS( BSON( "_id" << 12 << "a" << 11 ), c->current() ); - ASSERT( c->matcher()->matchesCurrent( c.get() ) ); - ASSERT( c->getsetdup( c->currLoc() ) ); - ASSERT( c->advance() ); - - // _id 11 {a:1} - ASSERT_EQUALS( BSON( "_id" << 11 << "a" << 12 ), c->current() ); - ASSERT( c->matcher()->matchesCurrent( c.get() ) ); - ASSERT( c->getsetdup( c->currLoc() ) ); - ASSERT( c->advance() ); - - // _id 11 {$natural:1} - ASSERT_EQUALS( 11, c->current().getIntField( "_id" ) ); - ASSERT( c->matcher()->matchesCurrent( c.get() ) ); - ASSERT( c->getsetdup( c->currLoc() ) ); - - // {_id:1} scan is complete. - ASSERT( !c->advance() ); - ASSERT( !c->ok() ); - - // Scan the results again - this time the winning plan has been - // recorded. - c = newQueryOptimizerCursor( ns(), BSON( "_id" << GT << 5 << "a" << GT << 5 ) ); - ASSERT( c->ok() ); - - // _id 10 {_id:1} - ASSERT_EQUALS( 10, c->current().getIntField( "_id" ) ); - ASSERT( !c->matcher()->matchesCurrent( c.get() ) ); - ASSERT( c->advance() ); - - // _id 11 {_id:1} - ASSERT_EQUALS( BSON( "_id" << 11 << "a" << 12 ), c->current() ); - ASSERT( c->matcher()->matchesCurrent( c.get() ) ); - ASSERT( !c->getsetdup( c->currLoc() ) ); - ASSERT( c->advance() ); - - // _id 12 {_id:1} - ASSERT_EQUALS( BSON( "_id" << 12 << "a" << 11 ), c->current() ); - ASSERT( c->matcher()->matchesCurrent( c.get() ) ); - ASSERT( !c->getsetdup( c->currLoc() ) ); - - // {_id:1} scan complete - ASSERT( !c->advance() ); - ASSERT( !c->ok() ); - } - }; - - /** Curr key must be correct for currLoc for correct matching. */ - class ManualMatchingUsingCurrKey : public Base { - public: - void run() { - _cli.insert( ns(), BSON( "_id" << "a" ) ); - _cli.insert( ns(), BSON( "_id" << "b" ) ); - _cli.insert( ns(), BSON( "_id" << "ba" ) ); - - Lock::GlobalWrite lk; - Client::Context ctx( ns() ); - shared_ptr< Cursor > c = newQueryOptimizerCursor( ns(), fromjson( "{_id:/a/}" ) ); - ASSERT( c->ok() ); - // "a" - ASSERT( c->matcher()->matchesCurrent( c.get() ) ); - ASSERT( !c->getsetdup( c->currLoc() ) ); - ASSERT( c->advance() ); - ASSERT( c->ok() ); - - // "b" - ASSERT( !c->matcher()->matchesCurrent( c.get() ) ); - ASSERT( c->advance() ); - ASSERT( c->ok() ); - - // "ba" - ASSERT( c->matcher()->matchesCurrent( c.get() ) ); - ASSERT( !c->getsetdup( c->currLoc() ) ); - ASSERT( !c->advance() ); - } - }; - - /** Test matching and deduping done manually by cursor client. */ - class ManualMatchingDedupingTakeover : public Base { - public: - void run() { - for( int i = 0; i < 150; ++i ) { - _cli.insert( ns(), BSON( "_id" << i << "a" << 0 ) ); - } - _cli.insert( ns(), BSON( "_id" << 300 << "a" << 1 ) ); - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - - Lock::GlobalWrite lk; - Client::Context ctx( ns() ); - shared_ptr< Cursor > c = newQueryOptimizerCursor( ns(), BSON( "$or" << BSON_ARRAY( BSON( "_id" << LT << 300 ) << BSON( "a" << 1 ) ) ) ); - for( int i = 0; i < 151; ++i ) { - ASSERT( c->ok() ); - ASSERT( c->matcher()->matchesCurrent( c.get() ) ); - ASSERT( !c->getsetdup( c->currLoc() ) ); - c->advance(); - } - ASSERT( !c->ok() ); - } - }; - - /** Test single key matching bounds. */ - class Singlekey : public Base { - public: - void run() { - _cli.insert( ns(), BSON( "a" << "10" ) ); - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - - Lock::GlobalWrite lk; - Client::Context ctx( ns() ); - shared_ptr< Cursor > c = newQueryOptimizerCursor( ns(), BSON( "a" << GT << 1 << LT << 5 ) ); - // Two sided bounds work. - ASSERT( !c->ok() ); - } - }; - - /** Test multi key matching bounds. */ - class Multikey : public Base { - public: - void run() { - _cli.insert( ns(), BSON( "a" << BSON_ARRAY( 1 << 10 ) ) ); - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - - Lock::GlobalWrite lk; - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSON( "a" << GT << 5 << LT << 3 ) ); - // Multi key bounds work. - ASSERT( ok() ); - } - }; - - /** Add other plans when the recorded one is doing more poorly than expected. */ - class AddOtherPlans : public Base { - public: - void run() { - _cli.insert( ns(), BSON( "_id" << 0 << "a" << 0 << "b" << 0 ) ); - _cli.insert( ns(), BSON( "_id" << 1 << "a" << 1 << "b" << 0 ) ); - for( int i = 100; i < 150; ++i ) { - _cli.insert( ns(), BSON( "_id" << i << "a" << 100 << "b" << i ) ); - } - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - _cli.ensureIndex( ns(), BSON( "b" << 1 ) ); - - Lock::GlobalWrite lk; - Client::Context ctx( ns() ); - shared_ptr<Cursor> c = newQueryOptimizerCursor( ns(), BSON( "a" << 0 << "b" << 0 ) ); - - ASSERT_EQUALS( BSON( "_id" << 0 << "a" << 0 << "b" << 0 ), c->current() ); - ASSERT_EQUALS( BSON( "a" << 1 ), c->indexKeyPattern() ); - - ASSERT( c->advance() ); - ASSERT_EQUALS( BSON( "_id" << 0 << "a" << 0 << "b" << 0 ), c->current() ); - ASSERT_EQUALS( BSON( "b" << 1 ), c->indexKeyPattern() ); - - ASSERT( c->advance() ); - ASSERT_EQUALS( BSON( "_id" << 0 << "a" << 0 << "b" << 0 ), c->current() ); - // Unindexed plan - ASSERT_EQUALS( BSONObj(), c->indexKeyPattern() ); - ASSERT( !c->advance() ); - - c = newQueryOptimizerCursor( ns(), BSON( "a" << 100 << "b" << 149 ) ); - // Try {a:1}, which was successful previously. - for( int i = 0; i < 12; ++i ) { - ASSERT( 149 != c->current().getIntField( "b" ) ); - ASSERT( c->advance() ); - } - bool sawB1Index = false; - do { - if ( c->indexKeyPattern() == BSON( "b" << 1 ) ) { - ASSERT_EQUALS( 149, c->current().getIntField( "b" ) ); - // We should try the {b:1} index and only see one result from it. - ASSERT( !sawB1Index ); - sawB1Index = true; - } - } while ( c->advance() ); - ASSERT( sawB1Index ); - } - }; - - /** Add other plans when the recorded one is doing more poorly than expected, with deletion. */ - class AddOtherPlansDelete : public Base { - public: - void run() { - _cli.insert( ns(), BSON( "_id" << 0 << "a" << 0 << "b" << 0 ) ); - _cli.insert( ns(), BSON( "_id" << 1 << "a" << 1 << "b" << 0 ) ); - for( int i = 100; i < 120; ++i ) { - _cli.insert( ns(), BSON( "_id" << i << "a" << 100 << "b" << i ) ); - } - for( int i = 199; i >= 150; --i ) { - _cli.insert( ns(), BSON( "_id" << i << "a" << 100 << "b" << 150 ) ); - } - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - _cli.ensureIndex( ns(), BSON( "b" << 1 ) ); - - Lock::GlobalWrite lk; - Client::Context ctx( ns() ); - shared_ptr<Cursor> c = newQueryOptimizerCursor( ns(), BSON( "a" << 0 << "b" << 0 ) ); - - ASSERT_EQUALS( BSON( "_id" << 0 << "a" << 0 << "b" << 0 ), c->current() ); - ASSERT_EQUALS( BSON( "a" << 1 ), c->indexKeyPattern() ); - - ASSERT( c->advance() ); - ASSERT_EQUALS( BSON( "_id" << 0 << "a" << 0 << "b" << 0 ), c->current() ); - ASSERT_EQUALS( BSON( "b" << 1 ), c->indexKeyPattern() ); - - ASSERT( c->advance() ); - ASSERT_EQUALS( BSON( "_id" << 0 << "a" << 0 << "b" << 0 ), c->current() ); - // Unindexed plan - ASSERT_EQUALS( BSONObj(), c->indexKeyPattern() ); - ASSERT( !c->advance() ); - - c = newQueryOptimizerCursor( ns(), BSON( "a" << 100 << "b" << 150 ) ); - // Try {a:1}, which was successful previously. - for( int i = 0; i < 12; ++i ) { - ASSERT( 150 != c->current().getIntField( "b" ) ); - ASSERT_EQUALS( BSON( "a" << 1 ), c->indexKeyPattern() ); - ASSERT( c->advance() ); - } - // Now try {b:1} plan. - ASSERT_EQUALS( BSON( "b" << 1 ), c->indexKeyPattern() ); - ASSERT_EQUALS( 150, c->current().getIntField( "b" ) ); - ASSERT( c->currentMatches() ); - int id = c->current().getIntField( "_id" ); - c->advance(); - c->prepareToTouchEarlierIterate(); - _cli.remove( ns(), BSON( "_id" << id ) ); - c->recoverFromTouchingEarlierIterate(); - int count = 1; - while( c->ok() ) { - if ( c->currentMatches() ) { - ++count; - int id = c->current().getIntField( "_id" ); - c->advance(); - c->prepareToTouchEarlierIterate(); - _cli.remove( ns(), BSON( "_id" << id ) ); - c->recoverFromTouchingEarlierIterate(); - } - else { - c->advance(); - } - } - ASSERT_EQUALS( 50, count ); - } - }; - - /** - * Add other plans when the recorded one is doing more poorly than expected, with deletion before - * and after adding the additional plans. - */ - class AddOtherPlansContinuousDelete : public Base { - public: - void run() { - _cli.insert( ns(), BSON( "_id" << 0 << "a" << 0 << "b" << 0 ) ); - _cli.insert( ns(), BSON( "_id" << 1 << "a" << 1 << "b" << 0 ) ); - for( int i = 100; i < 400; ++i ) { - _cli.insert( ns(), BSON( "_id" << i << "a" << i << "b" << ( 499 - i ) ) ); - } - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - _cli.ensureIndex( ns(), BSON( "b" << 1 ) ); - - Lock::GlobalWrite lk; - Client::Context ctx( ns() ); - shared_ptr<Cursor> c = newQueryOptimizerCursor( ns(), BSON( "a" << GTE << -1 << LTE << 0 << "b" << GTE << -1 << LTE << 0 ) ); - while( c->advance() ); - // {a:1} plan should be recorded now. - - c = newQueryOptimizerCursor( ns(), BSON( "a" << GTE << 100 << LTE << 400 << "b" << GTE << 100 << LTE << 400 ) ); - int count = 0; - while( c->ok() ) { - if ( c->currentMatches() ) { - ASSERT( !c->getsetdup( c->currLoc() ) ); - ++count; - int id = c->current().getIntField( "_id" ); - c->advance(); - c->prepareToTouchEarlierIterate(); - _cli.remove( ns(), BSON( "_id" << id ) ); - c->recoverFromTouchingEarlierIterate(); - } else { - c->advance(); - } - } - ASSERT_EQUALS( 300, count ); - ASSERT_EQUALS( 2U, _cli.count( ns(), BSONObj() ) ); - } - }; - - /** - * When an index becomes multikey and ceases to be optimal for a query, attempt other plans - * quickly. - */ - class AddOtherPlansWhenOptimalBecomesNonOptimal : public Base { - public: - void run() { - _cli.ensureIndex( ns(), BSON( "a" << 1 << "b" << 1 ) ); - - { - // Create a btree cursor on an optimal a:1,b:1 plan. - Client::ReadContext ctx( ns() ); - shared_ptr<Cursor> cursor = getCursor(); - ASSERT_EQUALS( "BtreeCursor a_1_b_1", cursor->toString() ); - - // The optimal a:1,b:1 plan is recorded. - ASSERT_EQUALS( BSON( "a" << 1 << "b" << 1 ), - cachedIndexForQuery( BSON( "a" << 1 ), BSON( "b" << 1 ) ) ); - } - - // Make the a:1,b:1 index multikey. - _cli.insert( ns(), BSON( "a" << 1 << "b" << BSON_ARRAY( 1 << 2 ) ) ); - - // Create a QueryOptimizerCursor, without an optimal plan. - Client::ReadContext ctx( ns() ); - shared_ptr<Cursor> cursor = getCursor(); - ASSERT_EQUALS( "QueryOptimizerCursor", cursor->toString() ); - ASSERT_EQUALS( BSON( "a" << 1 << "b" << 1 ), cursor->indexKeyPattern() ); - ASSERT( cursor->advance() ); - // An alternative plan is quickly attempted. - ASSERT_EQUALS( BSONObj(), cursor->indexKeyPattern() ); - } - private: - static shared_ptr<Cursor> getCursor() { - // The a:1,b:1 index will be optimal for this query and sort if single key, but if - // the index is multi key only one of the upper or lower constraints will be applied and - // the index will not be optimal. - BSONObj query = BSON( "a" << GTE << 1 << LTE << 1 ); - BSONObj order = BSON( "b" << 1 ); - shared_ptr<ParsedQuery> parsedQuery - ( new ParsedQuery( ns(), 0, 0, 0, - BSON( "$query" << query << "$orderby" << order ), - BSONObj() ) ); - return getOptimizedCursor( ns(), - query, - order, - QueryPlanSelectionPolicy::any(), - parsedQuery, - false ); - } - }; - - /** Check $or clause range elimination. */ - class OrRangeElimination : public Base { - public: - void run() { - _cli.insert( ns(), BSON( "_id" << 1 ) ); - - Lock::GlobalWrite lk; - Client::Context ctx( ns() ); - shared_ptr<Cursor> c = newQueryOptimizerCursor( ns(), BSON( "$or" << BSON_ARRAY( BSON( "_id" << GT << 0 ) << BSON( "_id" << 1 ) ) ) ); - ASSERT( c->ok() ); - ASSERT( !c->advance() ); - } - }; - - /** Check $or match deduping - in takeover cursor. */ - class OrDedup : public Base { - public: - void run() { - for( int i = 0; i < 150; ++i ) { - _cli.insert( ns(), BSON( "_id" << i << "a" << i ) ); - } - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - - Lock::GlobalWrite lk; - Client::Context ctx( ns() ); - shared_ptr<Cursor> c = newQueryOptimizerCursor( ns(), BSON( "$or" << BSON_ARRAY( BSON( "_id" << LT << 140 ) << BSON( "_id" << 145 ) << BSON( "a" << 145 ) ) ) ); - - while( c->current().getIntField( "_id" ) < 140 ) { - ASSERT( c->advance() ); - } - // Match from second $or clause. - ASSERT_EQUALS( 145, c->current().getIntField( "_id" ) ); - ASSERT( c->matcher()->matchesCurrent( c.get() ) ); - ASSERT( c->advance() ); - // Match from third $or clause. - ASSERT_EQUALS( 145, c->current().getIntField( "_id" ) ); - // $or deduping is handled by the matcher. - ASSERT( !c->matcher()->matchesCurrent( c.get() ) ); - ASSERT( !c->advance() ); - } - }; - - /** Standard dups with a multikey cursor. */ - class EarlyDups : public Base { - public: - void run() { - _cli.insert( ns(), BSON( "a" << BSON_ARRAY( 0 << 1 << 200 ) ) ); - for( int i = 2; i < 150; ++i ) { - _cli.insert( ns(), BSON( "a" << i ) ); - } - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - - Lock::GlobalWrite lk; - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSON( "a" << GT << -1 ) ); - ASSERT_EQUALS( 149, itcount() ); - } - }; - - /** Pop or clause in takeover cursor. */ - class OrPopInTakeover : public Base { - public: - void run() { - for( int i = 0; i < 150; ++i ) { - _cli.insert( ns(), BSON( "_id" << i ) ); - } - - Lock::GlobalWrite lk; - Client::Context ctx( ns() ); - shared_ptr<Cursor> c = newQueryOptimizerCursor( ns(), BSON( "$or" << BSON_ARRAY( BSON( "_id" << LTE << 147 ) << BSON( "_id" << 148 ) << BSON( "_id" << 149 ) ) ) ); - for( int i = 0; i < 150; ++i ) { - ASSERT( c->ok() ); - ASSERT_EQUALS( i, c->current().getIntField( "_id" ) ); - c->advance(); - } - ASSERT( !c->ok() ); - } - }; - - /** Or clause iteration abandoned once full collection scan is performed. */ - class OrCollectionScanAbort : public Base { - public: - void run() { - _cli.insert( ns(), BSON( "_id" << 0 << "a" << BSON_ARRAY( 1 << 2 << 3 << 4 << 5 ) << "b" << 4 ) ); - _cli.insert( ns(), BSON( "_id" << 1 << "a" << BSON_ARRAY( 6 << 7 << 8 << 9 << 10 ) << "b" << 4 ) ); - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - - Lock::GlobalWrite lk; - Client::Context ctx( ns() ); - shared_ptr<Cursor> c = newQueryOptimizerCursor( ns(), BSON( "$or" << BSON_ARRAY( BSON( "a" << LT << 6 << "b" << 4 ) << BSON( "a" << GTE << 6 << "b" << 4 ) ) ) ); - - ASSERT( c->ok() ); - - // _id 0 on {a:1} - ASSERT_EQUALS( 0, c->current().getIntField( "_id" ) ); - ASSERT( c->matcher()->matchesCurrent( c.get() ) ); - ASSERT( !c->getsetdup( c->currLoc() ) ); - c->advance(); - - // _id 0 on {$natural:1} - ASSERT_EQUALS( 0, c->current().getIntField( "_id" ) ); - ASSERT( c->matcher()->matchesCurrent( c.get() ) ); - ASSERT( c->getsetdup( c->currLoc() ) ); - c->advance(); - - // _id 0 on {a:1} - ASSERT_EQUALS( 0, c->current().getIntField( "_id" ) ); - ASSERT( c->matcher()->matchesCurrent( c.get() ) ); - ASSERT( c->getsetdup( c->currLoc() ) ); - c->advance(); - - // _id 1 on {$natural:1} - ASSERT_EQUALS( 1, c->current().getIntField( "_id" ) ); - ASSERT( c->matcher()->matchesCurrent( c.get() ) ); - ASSERT( !c->getsetdup( c->currLoc() ) ); - c->advance(); - - // _id 0 on {a:1} - ASSERT_EQUALS( 0, c->current().getIntField( "_id" ) ); - ASSERT( c->matcher()->matchesCurrent( c.get() ) ); - ASSERT( c->getsetdup( c->currLoc() ) ); - c->advance(); - - // {$natural:1} finished - ASSERT( !c->ok() ); - } - }; - - namespace Yield { - - /** Yield cursor and delete current entry, then continue iteration. */ - class NoOp : public Base { - public: - void run() { - _cli.insert( ns(), BSON( "_id" << 1 ) ); - _cli.insert( ns(), BSON( "_id" << 2 ) ); - - { - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSON( "_id" << GT << 0 ) ); - ASSERT_EQUALS( 1, current().getIntField( "_id" ) ); - prepareToYield(); - } - - { - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - recoverFromYield(); - ASSERT( ok() ); - ASSERT_EQUALS( 2, current().getIntField( "_id" ) ); - ASSERT( !advance() ); - ASSERT( !ok() ); - prepareToYield(); - recoverFromYield(); - } - } - }; - - /** Yield cursor and delete current entry. */ - class Delete : public Base { - public: - void run() { - _cli.insert( ns(), BSON( "_id" << 1 ) ); - - { - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSON( "_id" << 1 ) ); - ASSERT_EQUALS( 1, current().getIntField( "_id" ) ); - prepareToYield(); - } - - _cli.remove( ns(), BSON( "_id" << 1 ) ); - - { - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - recoverFromYield(); - ASSERT( !ok() ); - ASSERT( !advance() ); - } - } - }; - - /** Yield cursor and delete current entry, then continue iteration. */ - class DeleteContinue : public Base { - public: - void run() { - _cli.insert( ns(), BSON( "_id" << 1 ) ); - _cli.insert( ns(), BSON( "_id" << 2 ) ); - - { - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSON( "_id" << GT << 0 ) ); - ASSERT_EQUALS( 1, current().getIntField( "_id" ) ); - prepareToYield(); - } - - _cli.remove( ns(), BSON( "_id" << 1 ) ); - - { - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - recoverFromYield(); - ASSERT( ok() ); - ASSERT_EQUALS( 2, current().getIntField( "_id" ) ); - ASSERT( !advance() ); - ASSERT( !ok() ); - } - } - }; - - /** Yield cursor and delete current entry, then continue iteration. */ - class DeleteContinueFurther : public Base { - public: - void run() { - _cli.insert( ns(), BSON( "_id" << 1 ) ); - _cli.insert( ns(), BSON( "_id" << 2 ) ); - _cli.insert( ns(), BSON( "_id" << 3 ) ); - - { - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSON( "_id" << GT << 0 ) ); - ASSERT_EQUALS( 1, current().getIntField( "_id" ) ); - prepareToYield(); - } - - _cli.remove( ns(), BSON( "_id" << 1 ) ); - - { - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - recoverFromYield(); - ASSERT( ok() ); - ASSERT_EQUALS( 2, current().getIntField( "_id" ) ); - ASSERT( advance() ); - ASSERT_EQUALS( 3, current().getIntField( "_id" ) ); - ASSERT( !advance() ); - ASSERT( !ok() ); - } - } - }; - - /** Yield and update current. */ - class Update : public Base { - public: - void run() { - _cli.insert( ns(), BSON( "a" << 1 ) ); - _cli.insert( ns(), BSON( "a" << 2 ) ); - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - - { - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSON( "a" << GT << 0 ) ); - ASSERT_EQUALS( 1, current().getIntField( "a" ) ); - prepareToYield(); - } - - _cli.update( ns(), BSON( "a" << 1 ), BSON( "$set" << BSON( "a" << 3 ) ) ); - - { - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - recoverFromYield(); - ASSERT( ok() ); - ASSERT_EQUALS( 2, current().getIntField( "a" ) ); - ASSERT( !advance() ); - ASSERT( !ok() ); - } - } - }; - - /** Yield and drop collection. */ - class Drop : public Base { - public: - void run() { - _cli.insert( ns(), BSON( "_id" << 1 ) ); - _cli.insert( ns(), BSON( "_id" << 2 ) ); - - { - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSON( "_id" << GT << 0 ) ); - ASSERT_EQUALS( 1, current().getIntField( "_id" ) ); - prepareToYield(); - } - - _cli.dropCollection( ns() ); - - { - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - ASSERT_THROWS( recoverFromYield(), MsgAssertionException ); - ASSERT( !ok() ); - } - } - }; - - /** Yield and drop collection with $or query. */ - class DropOr : public Base { - public: - void run() { - _cli.insert( ns(), BSON( "_id" << 1 ) ); - _cli.insert( ns(), BSON( "_id" << 2 ) ); - - { - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSON( "$or" << BSON_ARRAY( BSON( "_id" << 1 ) << BSON( "_id" << 2 ) ) ) ); - ASSERT_EQUALS( 1, current().getIntField( "_id" ) ); - prepareToYield(); - } - - _cli.dropCollection( ns() ); - - { - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - ASSERT_THROWS( recoverFromYield(), MsgAssertionException ); - ASSERT( !ok() ); - } - } - }; - - /** Yield and remove document with $or query. */ - class RemoveOr : public Base { - public: - void run() { - _cli.insert( ns(), BSON( "_id" << 1 ) ); - _cli.insert( ns(), BSON( "_id" << 2 ) ); - - { - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSON( "$or" << BSON_ARRAY( BSON( "_id" << 1 ) << BSON( "_id" << 2 ) ) ) ); - ASSERT_EQUALS( 1, current().getIntField( "_id" ) ); - prepareToYield(); - } - - _cli.remove( ns(), BSON( "_id" << 1 ) ); - - { - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - recoverFromYield(); - ASSERT( ok() ); - ASSERT_EQUALS( 2, current().getIntField( "_id" ) ); - } - } - }; - - /** Yield and overwrite current in capped collection. */ - class CappedOverwrite : public Base { - public: - void run() { - _cli.createCollection( ns(), 1000, true ); - _cli.insert( ns(), BSON( "x" << 1 ) ); - - { - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSON( "x" << GT << 0 ) ); - ASSERT_EQUALS( 1, current().getIntField( "x" ) ); - prepareToYield(); - } - - int x = 2; - while( _cli.count( ns(), BSON( "x" << 1 ) ) > 0 ) { - _cli.insert( ns(), BSON( "x" << x++ ) ); - } - - { - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - ASSERT_THROWS( recoverFromYield(), MsgAssertionException ); - ASSERT( !ok() ); - } - } - }; - - /** Yield and drop unrelated index - see SERVER-2454. */ - class DropIndex : public Base { - public: - void run() { - _cli.insert( ns(), BSON( "_id" << 1 ) ); - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - - { - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSON( "_id" << 1 ) ); - ASSERT_EQUALS( 1, current().getIntField( "_id" ) ); - prepareToYield(); - } - - _cli.dropIndex( ns(), BSON( "a" << 1 ) ); - - { - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - ASSERT_THROWS( recoverFromYield(), MsgAssertionException ); - ASSERT( !ok() ); - } - } - }; - - /** Yielding with multiple plans active. */ - class MultiplePlansNoOp : public Base { - public: - void run() { - _cli.insert( ns(), BSON( "_id" << 1 << "a" << 2 ) ); - _cli.insert( ns(), BSON( "_id" << 2 << "a" << 1 ) ); - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - - { - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSON( "_id" << GT << 0 << "a" << GT << 0 ) ); - ASSERT_EQUALS( 1, current().getIntField( "_id" ) ); - prepareToYield(); - } - - { - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - recoverFromYield(); - ASSERT( ok() ); - ASSERT_EQUALS( 2, current().getIntField( "_id" ) ); - ASSERT( !advance() ); - ASSERT( !ok() ); - } - } - }; - - /** Yielding with advance and multiple plans active. */ - class MultiplePlansAdvanceNoOp : public Base { - public: - void run() { - _cli.insert( ns(), BSON( "_id" << 1 << "a" << 2 ) ); - _cli.insert( ns(), BSON( "_id" << 2 << "a" << 1 ) ); - _cli.insert( ns(), BSON( "_id" << 3 << "a" << 3 ) ); - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - - { - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSON( "_id" << GT << 0 << "a" << GT << 0 ) ); - ASSERT_EQUALS( 1, current().getIntField( "_id" ) ); - advance(); - ASSERT_EQUALS( 2, current().getIntField( "_id" ) ); - prepareToYield(); - } - - { - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - recoverFromYield(); - ASSERT( ok() ); - ASSERT_EQUALS( 3, current().getIntField( "_id" ) ); - ASSERT( !advance() ); - ASSERT( !ok() ); - } - } - }; - - /** Yielding with delete and multiple plans active. */ - class MultiplePlansDelete : public Base { - public: - void run() { - _cli.insert( ns(), BSON( "_id" << 1 << "a" << 2 ) ); - _cli.insert( ns(), BSON( "_id" << 2 << "a" << 1 ) ); - _cli.insert( ns(), BSON( "_id" << 3 << "a" << 4 ) ); - _cli.insert( ns(), BSON( "_id" << 4 << "a" << 3 ) ); - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - - { - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSON( "_id" << GT << 0 << "a" << GT << 0 ) ); - ASSERT_EQUALS( 1, current().getIntField( "_id" ) ); - advance(); - ASSERT_EQUALS( 2, current().getIntField( "_id" ) ); - prepareToYield(); - } - - _cli.remove( ns(), BSON( "_id" << 2 ) ); - - { - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - c()->recoverFromYield(); - ASSERT( ok() ); - // index {a:1} active during yield - ASSERT_EQUALS( 1, current().getIntField( "_id" ) ); - ASSERT( advance() ); - ASSERT_EQUALS( 3, current().getIntField( "_id" ) ); - ASSERT( advance() ); - ASSERT_EQUALS( 4, current().getIntField( "_id" ) ); - ASSERT( !advance() ); - ASSERT( !ok() ); - } - } - }; - - /** Yielding with delete, multiple plans active, and $or clause. */ - class MultiplePlansDeleteOr : public Base { - public: - void run() { - _cli.insert( ns(), BSON( "_id" << 1 << "a" << 2 ) ); - _cli.insert( ns(), BSON( "_id" << 2 << "a" << 1 ) ); - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - - { - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSON( "$or" << BSON_ARRAY( BSON( "_id" << 1 << "a" << 2 ) << BSON( "_id" << 2 << "a" << 1 ) ) ) ); - ASSERT_EQUALS( 1, current().getIntField( "_id" ) ); - prepareToYield(); - } - - _cli.remove( ns(), BSON( "_id" << 1 ) ); - - { - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - c()->recoverFromYield(); - ASSERT( ok() ); - ASSERT_EQUALS( 2, current().getIntField( "_id" ) ); - ASSERT( !advance() ); - ASSERT( !ok() ); - } - } - }; - - /** Yielding with delete, multiple plans active with advancement to the second, and $or clause. */ - class MultiplePlansDeleteOrAdvance : public Base { - public: - void run() { - _cli.insert( ns(), BSON( "_id" << 1 << "a" << 2 ) ); - _cli.insert( ns(), BSON( "_id" << 2 << "a" << 1 ) ); - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - - { - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSON( "$or" << BSON_ARRAY( BSON( "_id" << 1 << "a" << 2 ) << BSON( "_id" << 2 << "a" << 1 ) ) ) ); - ASSERT_EQUALS( 1, current().getIntField( "_id" ) ); - prepareToYield(); - c()->advance(); - ASSERT_EQUALS( 1, current().getIntField( "_id" ) ); - } - - _cli.remove( ns(), BSON( "_id" << 1 ) ); - - { - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - c()->recoverFromYield(); - ASSERT( ok() ); - ASSERT_EQUALS( 2, current().getIntField( "_id" ) ); - ASSERT( !advance() ); - ASSERT( !ok() ); - } - } - }; - - /** Yielding with multiple plans and capped overwrite. */ - class MultiplePlansCappedOverwrite : public Base { - public: - void run() { - _cli.createCollection( ns(), 1000, true ); - _cli.insert( ns(), BSON( "_id" << 1 << "a" << 1 ) ); - _cli.ensureIndex( ns(), BSON( "_id" << 1 ) ); - - { - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSON( "_id" << GT << 0 << "a" << GT << 0 ) ); - ASSERT_EQUALS( 1, current().getIntField( "_id" ) ); - prepareToYield(); - } - - int i = 1; - while( _cli.count( ns(), BSON( "_id" << 1 ) ) > 0 ) { - ++i; - _cli.insert( ns(), BSON( "_id" << i << "a" << i ) ); - } - - { - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - recoverFromYield(); - ASSERT( ok() ); - // {$natural:1} plan does not recover, {_id:1} plan does. - ASSERT( 1 < current().getIntField( "_id" ) ); - } - } - }; - - /** - * Yielding with multiple plans and capped overwrite with unrecoverable cursor - * active at time of yield. - */ - class MultiplePlansCappedOverwriteManual : public Base { - public: - void run() { - _cli.createCollection( ns(), 1000, true ); - _cli.insert( ns(), BSON( "a" << 1 << "b" << 1 ) ); - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - - shared_ptr<Cursor> c; - { - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - c = newQueryOptimizerCursor( ns(), BSON( "a" << GT << 0 << "b" << GT << 0 ) ); - ASSERT_EQUALS( 1, c->current().getIntField( "a" ) ); - ASSERT( !c->getsetdup( c->currLoc() ) ); - c->advance(); - ASSERT_EQUALS( 1, c->current().getIntField( "a" ) ); - ASSERT( c->getsetdup( c->currLoc() ) ); - c->prepareToYield(); - } - - int i = 1; - while( _cli.count( ns(), BSON( "a" << 1 ) ) > 0 ) { - ++i; - _cli.insert( ns(), BSON( "a" << i << "b" << i ) ); - } - - { - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - c->recoverFromYield(); - ASSERT( c->ok() ); - // {$natural:1} plan does not recover, {_id:1} plan does. - ASSERT( 1 < c->current().getIntField( "a" ) ); - } - } - }; - - /** - * Yielding with multiple plans and capped overwrite with unrecoverable cursor - * inctive at time of yield. - */ - class MultiplePlansCappedOverwriteManual2 : public Base { - public: - void run() { - _cli.createCollection( ns(), 1000, true ); - _cli.insert( ns(), BSON( "_id" << 1 << "a" << 1 ) ); - _cli.ensureIndex( ns(), BSON( "_id" << 1 ) ); - - shared_ptr<Cursor> c; - { - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - c = newQueryOptimizerCursor( ns(), BSON( "_id" << GT << 0 << "a" << GT << 0 ) ); - ASSERT_EQUALS( 1, c->current().getIntField( "_id" ) ); - ASSERT( !c->getsetdup( c->currLoc() ) ); - c->prepareToYield(); - } - - int n = 1; - while( _cli.count( ns(), BSON( "_id" << 1 ) ) > 0 ) { - ++n; - _cli.insert( ns(), BSON( "_id" << n << "a" << n ) ); - } - - { - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - c->recoverFromYield(); - ASSERT( c->ok() ); - // {$natural:1} plan does not recover, {_id:1} plan does. - ASSERT( 1 < c->current().getIntField( "_id" ) ); - ASSERT( !c->getsetdup( c->currLoc() ) ); - int i = c->current().getIntField( "_id" ); - ASSERT( c->advance() ); - ASSERT( c->getsetdup( c->currLoc() ) ); - while( i < n ) { - ASSERT( c->advance() ); - ++i; - ASSERT_EQUALS( i, c->current().getIntField( "_id" ) ); - } - } - } - }; - - /** Yield with takeover cursor. */ - class Takeover : public Base { - public: - void run() { - for( int i = 0; i < 150; ++i ) { - _cli.insert( ns(), BSON( "_id" << i << "a" << i ) ); - } - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - - { - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSON( "_id" << GTE << 0 << "a" << GTE << 0 ) ); - for( int i = 0; i < 120; ++i ) { - ASSERT( advance() ); - } - ASSERT( ok() ); - ASSERT_EQUALS( 120, current().getIntField( "_id" ) ); - prepareToYield(); - } - - _cli.remove( ns(), BSON( "_id" << 120 ) ); - - { - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - recoverFromYield(); - ASSERT( ok() ); - ASSERT_EQUALS( 121, current().getIntField( "_id" ) ); - ASSERT( advance() ); - ASSERT_EQUALS( 122, current().getIntField( "_id" ) ); - } - } - }; - - /** Yield with BasicCursor takeover cursor. */ - class TakeoverBasic : public Base { - public: - void run() { - for( int i = 0; i < 150; ++i ) { - _cli.insert( ns(), BSON( "_id" << i << "a" << BSON_ARRAY( i << i+1 ) ) ); - } - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - - auto_ptr<ClientCursor> cc; - auto_ptr<ClientCursor::YieldData> data( new ClientCursor::YieldData() ); - { - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSON( "b" << NE << 0 << "a" << GTE << 0 ) ); - cc.reset( new ClientCursor( QueryOption_NoCursorTimeout, c(), ns() ) ); - for( int i = 0; i < 120; ++i ) { - ASSERT( advance() ); - } - ASSERT( ok() ); - ASSERT_EQUALS( 120, current().getIntField( "_id" ) ); - cc->prepareToYield( *data ); - } - _cli.remove( ns(), BSON( "_id" << 120 ) ); - - { - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - ASSERT( ClientCursor::recoverFromYield( *data ) ); - ASSERT( ok() ); - ASSERT_EQUALS( 121, current().getIntField( "_id" ) ); - ASSERT( advance() ); - ASSERT_EQUALS( 122, current().getIntField( "_id" ) ); - } - } - }; - - /** Yield with advance of inactive cursor. */ - class InactiveCursorAdvance : public Base { - public: - void run() { - for( int i = 0; i < 10; ++i ) { - _cli.insert( ns(), BSON( "_id" << i << "a" << 10 - i ) ); - } - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - - { - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSON( "_id" << GT << 0 << "a" << GT << 0 ) ); - ASSERT( ok() ); - ASSERT_EQUALS( 1, current().getIntField( "_id" ) ); - ASSERT( advance() ); - ASSERT_EQUALS( 9, current().getIntField( "_id" ) ); - ASSERT( advance() ); - ASSERT_EQUALS( 2, current().getIntField( "_id" ) ); - prepareToYield(); - } - - _cli.remove( ns(), BSON( "_id" << 9 ) ); - - { - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - recoverFromYield(); - ASSERT( ok() ); - ASSERT_EQUALS( 8, current().getIntField( "_id" ) ); - ASSERT( advance() ); - ASSERT_EQUALS( 3, current().getIntField( "_id" ) ); - ASSERT( advance() ); - ASSERT_EQUALS( 7, current().getIntField( "_id" ) ); - } - } - }; - - /** - * This test, and every other test that assumes that you get documents back in the order you - * inserted them in, is unreliable. - * - * Documents in an index are ordered by (extracted keypattern fields in doc, diskloc). We - * use the diskloc to break ties. In this test, all the "extracted keypattern fields in - * doc" values are the same, so the documents are in fact ordered in the Btree by their - * diskloc. - * - * This test expects to retrieve docs in the order in which it inserts them. To get the - * docs back in the order you insert them in, DiskLoc_of_insert_N < DiskLoc_of_insert_N+1 - * must always hold. This just happens to be true "by default" (when you run ./test) but - * it's not true in general. So, this test (and other similar tests) will fail if you run a - * subset of the dbtests, or really anything that modifies the free list such that the - * DiskLocs are not strictly sequential. - */ - class TakeoverUpdateBase : public Base { - public: - TakeoverUpdateBase() : - _lastId( -1 ) { - populateWithKey( "a" ); - populateWithKey( "b" ); - _cli.ensureIndex( ns(), BSON( "c" << 1 ) ); - } - virtual ~TakeoverUpdateBase() {} - void run() { - advanceToEndOfARangeAndUpdate(); - advanceThroughBRange(); - } - protected: - virtual BSONObj query() const = 0; - void advanceToEndOfARangeAndUpdate() { - { - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - setQueryOptimizerCursor( query() ); - for( int i = 0; i < 149; ++i ) { - advance(); - } - ASSERT( ok() ); - // The current iterate corresponds to the last 'a' document. - ASSERT_EQUALS( BSON( "_id" << 149 << "a" << 1 ), current() ); - ASSERT_EQUALS( BSON( "a" << 1 ), c()->indexKeyPattern() ); - prepareToYield(); - } - - _cli.update( ns(), BSON( "_id" << 149 ), BSON( "$set" << BSON( "a" << -1 ) ) ); - } - void advanceThroughBRange() { - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - recoverFromYield(); - ASSERT( ok() ); - // The current iterate corresponds to the first 'b' document. - ASSERT_EQUALS( BSON( "_id" << 150 << "b" << 1 ), current() ); - ASSERT_EQUALS( BSON( "b" << 1 ), c()->indexKeyPattern() ); - // Eventually the last 'b' document is reached. - while( current() != BSON( "_id" << 299 << "b" << 1 ) ) { - ASSERT( advance() ); - } - ASSERT( !advance() ); - } - private: - void populateWithKey( const string &key ) { - for( int i = 0; i < 150; ++i ) { - _cli.insert( ns(), BSON( "_id" << nextId() << key << 1 ) ); - } - _cli.ensureIndex( ns(), BSON( key << 1 ) ); - } - int nextId() { - return ++_lastId; - } - int _lastId; - }; - - /** An update causing an index key change advances the cursor past the last iterate. */ - class TakeoverUpdateKeyAtEndOfIteration : public TakeoverUpdateBase { - public: - virtual BSONObj query() const { return BSON( "a" << 1 ); } - void run() { - advanceToEndOfARangeAndUpdate(); - - { - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - recoverFromYield(); - ASSERT( !ok() ); - } - } - }; - - /** - * An update causing an index key change advances the cursor past the last iterate of a $or - * clause. - */ - class TakeoverUpdateKeyAtEndOfClause : public TakeoverUpdateBase { - virtual BSONObj query() const { return fromjson( "{$or:[{a:1},{b:1}]}" ); } - }; - - /** - * An update causing an index key change advances the cursor past the last iterate of a $or - * clause, and also past an empty clause. - */ - class TakeoverUpdateKeyPrecedingEmptyClause : public TakeoverUpdateBase { - virtual BSONObj query() const { return fromjson( "{$or:[{a:1},{c:1},{b:1}]}" ); } - }; - - } // namespace Yield - - class OrderId : public Base { - public: - void run() { - for( int i = 0; i < 10; ++i ) { - _cli.insert( ns(), BSON( "_id" << i ) ); - } - - Lock::GlobalWrite lk; - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSONObj(), BSON( "_id" << 1 ) ); - - for( int i = 0; i < 10; ++i, advance() ) { - ASSERT( ok() ); - ASSERT_EQUALS( i, current().getIntField( "_id" ) ); - } - } - }; - - class OrderMultiIndex : public Base { - public: - void run() { - for( int i = 0; i < 10; ++i ) { - _cli.insert( ns(), BSON( "_id" << i << "a" << 1 ) ); - } - _cli.ensureIndex( ns(), BSON( "_id" << 1 << "a" << 1 ) ); - - Lock::GlobalWrite lk; - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSON( "_id" << GTE << 0 << "a" << GTE << 0 ), BSON( "_id" << 1 ) ); - - for( int i = 0; i < 10; ++i, advance() ) { - ASSERT( ok() ); - ASSERT_EQUALS( i, current().getIntField( "_id" ) ); - } - } - }; - - class OrderReject : public Base { - public: - void run() { - for( int i = 0; i < 10; ++i ) { - _cli.insert( ns(), BSON( "_id" << i << "a" << i % 5 ) ); - } - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - - Lock::GlobalWrite lk; - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSON( "a" << GTE << 3 ), BSON( "_id" << 1 ) ); - - ASSERT( ok() ); - ASSERT_EQUALS( 3, current().getIntField( "_id" ) ); - ASSERT( advance() ); - ASSERT_EQUALS( 4, current().getIntField( "_id" ) ); - ASSERT( advance() ); - ASSERT_EQUALS( 8, current().getIntField( "_id" ) ); - ASSERT( advance() ); - ASSERT_EQUALS( 9, current().getIntField( "_id" ) ); - ASSERT( !advance() ); - } - }; - - class OrderNatural : public Base { - public: - void run() { - _cli.insert( ns(), BSON( "_id" << 5 ) ); - _cli.insert( ns(), BSON( "_id" << 4 ) ); - _cli.insert( ns(), BSON( "_id" << 6 ) ); - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - - Lock::GlobalWrite lk; - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSON( "_id" << GT << 0 ), BSON( "$natural" << 1 ) ); - - ASSERT( ok() ); - ASSERT_EQUALS( 5, current().getIntField( "_id" ) ); - ASSERT( advance() ); - ASSERT_EQUALS( 4, current().getIntField( "_id" ) ); - ASSERT( advance() ); - ASSERT_EQUALS( 6, current().getIntField( "_id" ) ); - ASSERT( !advance() ); - } - }; - - class OrderUnindexed : public Base { - public: - void run() { - Lock::GlobalWrite lk; - Client::Context ctx( ns() ); - ASSERT( !newQueryOptimizerCursor( ns(), BSONObj(), BSON( "a" << 1 ) ).get() ); - } - }; - - class RecordedOrderInvalid : public Base { - public: - void run() { - _cli.insert( ns(), BSON( "a" << 1 << "b" << 1 ) ); - _cli.insert( ns(), BSON( "a" << 2 << "b" << 2 ) ); - _cli.insert( ns(), BSON( "a" << 3 << "b" << 3 ) ); - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - _cli.ensureIndex( ns(), BSON( "b" << 1 ) ); - // Plan {a:1} will be chosen and recorded. - ASSERT( _cli.query( ns(), QUERY( "a" << 2 ).sort( "b" ) )->more() ); - - Lock::GlobalWrite lk; - Client::Context ctx( ns() ); - shared_ptr<Cursor> c = newQueryOptimizerCursor( ns(), BSON( "a" << 2 ), - BSON( "b" << 1 ) ); - // Check that we are scanning {b:1} not {a:1}, since {a:1} is not properly ordered. - for( int i = 0; i < 3; ++i ) { - ASSERT( c->ok() ); - c->advance(); - } - ASSERT( !c->ok() ); - } - }; - - class KillOp : public Base { - public: - void run() { - _cli.insert( ns(), BSON( "_id" << 1 << "b" << 1 ) ); - _cli.insert( ns(), BSON( "_id" << 2 << "b" << 2 ) ); - _cli.ensureIndex( ns(), BSON( "b" << 1 ) ); - - Client::ReadContext ctx( ns() ); - setQueryOptimizerCursor( BSON( "_id" << GT << 0 << "b" << GT << 0 ) ); - ASSERT( ok() ); - cc().curop()->kill(); - // First advance() call throws, subsequent calls just fail. - ASSERT_THROWS( advance(), MsgAssertionException ); - ASSERT( !advance() ); - } - }; - - class KillOpFirstClause : public Base { - public: - void run() { - _cli.insert( ns(), BSON( "_id" << 1 << "b" << 1 ) ); - _cli.insert( ns(), BSON( "_id" << 2 << "b" << 2 ) ); - _cli.ensureIndex( ns(), BSON( "b" << 1 ) ); - - Client::ReadContext ctx( ns() ); - shared_ptr<Cursor> c = newQueryOptimizerCursor( ns(), BSON( "$or" << BSON_ARRAY( BSON( "_id" << GT << 0 ) << BSON( "b" << GT << 0 ) ) ) ); - ASSERT( c->ok() ); - cc().curop()->kill(); - // First advance() call throws, subsequent calls just fail. - ASSERT_THROWS( c->advance(), MsgAssertionException ); - ASSERT( !c->advance() ); - } - }; - - class Nscanned : public Base { - public: - void run() { - for( int i = 0; i < 120; ++i ) { - _cli.insert( ns(), BSON( "_id" << i << "a" << i ) ); - } - - Lock::GlobalWrite lk; - Client::Context ctx( ns() ); - shared_ptr<Cursor> c = newQueryOptimizerCursor( ns(), BSON( "_id" << GTE << 0 << "a" << GTE << 0 ) ); - ASSERT( c->ok() ); - ASSERT_EQUALS( 2, c->nscanned() ); - c->advance(); - ASSERT( c->ok() ); - ASSERT_EQUALS( 2, c->nscanned() ); - c->advance(); - for( int i = 3; i < 222; ++i ) { - ASSERT( c->ok() ); - c->advance(); - } - ASSERT( !c->ok() ); - } - }; - - namespace TouchEarlierIterate { - - /* Test 'touching earlier iterate' without doc modifications. */ - class Basic : public Base { - public: - void run() { - _cli.insert( ns(), BSON( "_id" << 1 << "b" << 1 ) ); - _cli.insert( ns(), BSON( "_id" << 2 << "b" << 2 ) ); - _cli.ensureIndex( ns(), BSON( "b" << 1 ) ); - - Client::ReadContext ctx( ns() ); - shared_ptr<Cursor> c = newQueryOptimizerCursor( ns(), BSON( "_id" << GT << 0 << "b" << GT << 0 ) ); - - ASSERT( c->ok() ); - while( c->ok() ) { - DiskLoc loc = c->currLoc(); - BSONObj obj = c->current(); - c->prepareToTouchEarlierIterate(); - c->recoverFromTouchingEarlierIterate(); - ASSERT( loc == c->currLoc() ); - ASSERT_EQUALS( obj, c->current() ); - c->advance(); - } - } - }; - - /* Test 'touching earlier iterate' with doc modifications. */ - class Delete : public Base { - public: - void run() { - _cli.insert( ns(), BSON( "_id" << 1 << "b" << 1 ) ); - _cli.insert( ns(), BSON( "_id" << 2 << "b" << 2 ) ); - _cli.ensureIndex( ns(), BSON( "b" << 1 ) ); - - DiskLoc firstLoc; - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSON( "_id" << GT << 0 << "b" << GT << 0 ) ); - ASSERT( ok() ); - firstLoc = currLoc(); - ASSERT( c()->advance() ); - prepareToTouchEarlierIterate(); - - _cli.remove( ns(), BSON( "_id" << 1 ), true ); - - recoverFromTouchingEarlierIterate(); - ASSERT( ok() ); - while( ok() ) { - ASSERT( firstLoc != currLoc() ); - c()->advance(); - } - } - }; - - /* Test 'touch earlier iterate' with several doc modifications. */ - class DeleteMultiple : public Base { - public: - void run() { - for( int i = 1; i < 10; ++i ) { - _cli.insert( ns(), BSON( "_id" << i << "b" << i ) ); - } - _cli.ensureIndex( ns(), BSON( "b" << 1 ) ); - - set<DiskLoc> deleted; - int id = 0; - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSON( "_id" << GT << 0 << "b" << GT << 0 ) ); - while( 1 ) { - if ( !ok() ) { - break; - } - ASSERT( deleted.count( currLoc() ) == 0 ); - id = current()["_id"].Int(); - deleted.insert( currLoc() ); - c()->advance(); - prepareToTouchEarlierIterate(); - - _cli.remove( ns(), BSON( "_id" << id ), true ); - - recoverFromTouchingEarlierIterate(); - } - ASSERT_EQUALS( 9U, deleted.size() ); - } - }; - - /* Test 'touch earlier iterate' after an earlier yield. */ - class DeleteAfterYield : public Base { - public: - void run() { - for( int i = 0; i < 3; ++i ) { - _cli.insert( ns(), BSON( "b" << i ) ); - } - - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSONObj() ); - ASSERT( ok() ); - ASSERT_EQUALS( 0, c()->current()[ "b" ].Int() ); - - // Record the position of document b:0 in the cursor's component ClientCursor. - c()->prepareToYield(); - c()->recoverFromYield(); - ASSERT( ok() ); - - // Advance the cursor past document b:0. - ASSERT( c()->advance() ); - ASSERT_EQUALS( 1, current()[ "b" ].Int() ); - - // Remove document b:0. - c()->prepareToTouchEarlierIterate(); - // A warning message will be logged for the component ClientCursor if it is not - // configured with 'doing deletes'. - _cli.remove( ns(), BSON( "b" << 0 ), true ); - c()->recoverFromTouchingEarlierIterate(); - - // Check that the cursor recovers properly after b:0 is deleted. - ASSERT( ok() ); - ASSERT_EQUALS( 1, current()[ "b" ].Int() ); - ASSERT( c()->advance() ); - ASSERT_EQUALS( 2, current()[ "b" ].Int() ); - ASSERT( !c()->advance() ); - } - }; - - /* Test 'touch earlier iterate' with takeover. */ - class Takeover : public Base { - public: - void run() { - for( int i = 1; i < 600; ++i ) { - _cli.insert( ns(), BSON( "_id" << i << "b" << i ) ); - } - _cli.ensureIndex( ns(), BSON( "b" << 1 ) ); - - Client::ReadContext ctx( ns() ); - setQueryOptimizerCursor( BSON( "_id" << GT << 0 << "b" << GT << 0 ) ); - - ASSERT( ok() ); - int count = 1; - while( ok() ) { - DiskLoc loc = currLoc(); - BSONObj obj = current(); - prepareToTouchEarlierIterate(); - recoverFromTouchingEarlierIterate(); - ASSERT( loc == currLoc() ); - ASSERT_EQUALS( obj, current() ); - count += mayReturnCurrent(); - c()->advance(); - } - ASSERT_EQUALS( 599, count ); - } - }; - - /* Test 'touch earlier iterate' with takeover and deletes. */ - class TakeoverDeleteMultiple : public Base { - public: - void run() { - for( int i = 1; i < 600; ++i ) { - _cli.insert( ns(), BSON( "_id" << i << "b" << i ) ); - } - _cli.ensureIndex( ns(), BSON( "b" << 1 ) ); - - set<DiskLoc> deleted; - int id = 0; - - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - setQueryOptimizerCursorWithoutAdvancing( BSON( "_id" << GT << 0 << "b" << GT << 0 ) ); - while( 1 ) { - if ( !ok() ) { - break; - } - ASSERT( deleted.count( currLoc() ) == 0 ); - id = current()["_id"].Int(); - ASSERT( c()->currentMatches() ); - ASSERT( !c()->getsetdup( currLoc() ) ); - deleted.insert( currLoc() ); - c()->advance(); - prepareToTouchEarlierIterate(); - - _cli.remove( ns(), BSON( "_id" << id ), true ); - - recoverFromTouchingEarlierIterate(); - } - ASSERT_EQUALS( 599U, deleted.size() ); - } - }; - - /* Test 'touch earlier iterate' with unindexed cursor takeover and deletes. */ - class UnindexedTakeoverDeleteMultiple : public Base { - public: - void run() { - for( int i = 1; i < 600; ++i ) { - _cli.insert( ns(), BSON( "a" << BSON_ARRAY( i << i+1 ) << "b" << BSON_ARRAY( i << i+1 ) << "_id" << i ) ); - } - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - _cli.ensureIndex( ns(), BSON( "b" << 1 ) ); - - set<DiskLoc> deleted; - int id = 0; - - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - setQueryOptimizerCursorWithoutAdvancing( BSON( "a" << GT << 0 << "b" << GT << 0 ) ); - while( 1 ) { - if ( !ok() ) { - break; - } - ASSERT( deleted.count( currLoc() ) == 0 ); - id = current()["_id"].Int(); - ASSERT( c()->currentMatches() ); - ASSERT( !c()->getsetdup( currLoc() ) ); - deleted.insert( currLoc() ); - // Advance past the document before deleting it. - DiskLoc loc = currLoc(); - while( ok() && loc == currLoc() ) { - c()->advance(); - } - prepareToTouchEarlierIterate(); - - _cli.remove( ns(), BSON( "_id" << id ), true ); - - recoverFromTouchingEarlierIterate(); - } - ASSERT_EQUALS( 599U, deleted.size() ); - } - }; - - /* Test 'touch earlier iterate' with takeover and deletes, with multiple advances in a row. */ - class TakeoverDeleteMultipleMultiAdvance : public Base { - public: - void run() { - for( int i = 1; i < 600; ++i ) { - _cli.insert( ns(), BSON( "_id" << i << "b" << i ) ); - } - _cli.ensureIndex( ns(), BSON( "b" << 1 ) ); - - set<DiskLoc> deleted; - int id = 0; - - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSON( "_id" << GT << 0 << "b" << GT << 0 ) ); - while( 1 ) { - if ( !ok() ) { - break; - } - ASSERT( deleted.count( currLoc() ) == 0 ); - id = current()["_id"].Int(); - ASSERT( c()->currentMatches() ); - deleted.insert( currLoc() ); - advance(); - prepareToTouchEarlierIterate(); - - _cli.remove( ns(), BSON( "_id" << id ), true ); - - recoverFromTouchingEarlierIterate(); - } - ASSERT_EQUALS( 599U, deleted.size() ); - } - }; - - } // namespace TouchEarlierIterate - - /* Test yield recovery failure of component capped cursor. */ - class InitialCappedWrapYieldRecoveryFailure : public Base { - public: - void run() { - _cli.createCollection( ns(), 1000, true ); - _cli.insert( ns(), BSON( "_id" << 1 << "x" << 1 ) ); - - Lock::GlobalWrite lk; - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSON( "x" << GT << 0 ) ); - ASSERT_EQUALS( 1, current().getIntField( "x" ) ); - - ClientCursorHolder p( new ClientCursor( QueryOption_NoCursorTimeout, c(), ns() ) ); - ClientCursor::YieldData yieldData; - p->prepareToYield( yieldData ); - - int x = 2; - while( _cli.count( ns(), BSON( "x" << 1 ) ) > 0 ) { - _cli.insert( ns(), BSON( "_id" << x << "x" << x ) ); - ++x; - } - - // TODO - Might be preferable to return false rather than assert here. - ASSERT_THROWS( ClientCursor::recoverFromYield( yieldData ), AssertionException ); - } - }; - - /* Test yield recovery failure of takeover capped cursor. */ - class TakeoverCappedWrapYieldRecoveryFailure : public Base { - public: - void run() { - _cli.createCollection( ns(), 10000, true ); - for( int i = 0; i < 300; ++i ) { - _cli.insert( ns(), BSON( "_id" << i << "x" << i ) ); - } - - ClientCursorHolder p; - ClientCursor::YieldData yieldData; - { - Lock::GlobalWrite lk; - Client::Context ctx( ns() ); - setQueryOptimizerCursor( BSON( "x" << GTE << 0 ) ); - for( int i = 0; i < 299; ++i ) { - advance(); - } - ASSERT_EQUALS( 299, current().getIntField( "x" ) ); - - p.reset( new ClientCursor( QueryOption_NoCursorTimeout, c(), ns() ) ); - p->prepareToYield( yieldData ); - } - - int i = 300; - while( _cli.count( ns(), BSON( "x" << 299 ) ) > 0 ) { - _cli.insert( ns(), BSON( "_id" << i << "x" << i ) ); - ++i; - } - - Lock::GlobalWrite lk; - Client::Context ctx( ns() ); - ASSERT( !ClientCursor::recoverFromYield( yieldData ) ); - } - }; - - namespace ClientCursor { - - using mongo::ClientCursor; - - /** Test that a ClientCursor holding a QueryOptimizerCursor may be safely invalidated. */ - class Invalidate : public Base { - public: - void run() { - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - _cli.ensureIndex( ns(), BSON( "b" << 1 ) ); - _cli.insert( ns(), BSON( "a" << 1 << "b" << 1 ) ); - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - ClientCursorHolder p - ( new ClientCursor - ( QueryOption_NoCursorTimeout, - getOptimizedCursor - ( ns(), BSON( "a" << GTE << 0 << "b" << GTE << 0 ) ), - ns() ) ); - ClientCursor::invalidate( ns() ); - ASSERT_EQUALS( 0U, nNsCursors() ); - } - }; - - /** Test that a ClientCursor holding a QueryOptimizerCursor may be safely timed out. */ - class TimeoutClientCursorHolder : public Base { - public: - void run() { - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - _cli.ensureIndex( ns(), BSON( "b" << 1 ) ); - _cli.insert( ns(), BSON( "a" << 1 << "b" << 1 ) ); - Lock::GlobalWrite lk; - Client::Context ctx( ns() ); - ClientCursorHolder p - ( new ClientCursor - ( 0, - getOptimizedCursor - ( ns(), BSON( "a" << GTE << 0 << "b" << GTE << 0 ) ), - ns() ) ); - - // Construct component client cursors. - ClientCursor::YieldData yieldData; - p->prepareToYield( yieldData ); - ASSERT( nNsCursors() > 1 ); - - ClientCursor::invalidate( ns() ); - ASSERT_EQUALS( 0U, nNsCursors() ); - } - }; - - /** Test that a ClientCursor holding a QueryOptimizerCursor may be safely timed out. */ - class Timeout : public Base { - public: - void run() { - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - _cli.ensureIndex( ns(), BSON( "b" << 1 ) ); - _cli.insert( ns(), BSON( "a" << 1 << "b" << 1 ) ); - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - ClientCursorHolder p - ( new ClientCursor - ( 0, - getOptimizedCursor - ( ns(), BSON( "a" << GTE << 0 << "b" << GTE << 0 ) ), - ns() ) ); - - // Construct component client cursors. - ClientCursor::YieldData yieldData; - p->prepareToYield( yieldData ); - ASSERT( nNsCursors() > 1 ); - - ClientCursor::idleTimeReport( 600001 ); - ASSERT_EQUALS( 0U, nNsCursors() ); - } - }; - - /** - * Test that a ClientCursor properly recovers a QueryOptimizerCursor after a btree - * modification in preparation for a pre delete advance. - */ - class AboutToDeleteRecoverFromYield : public Base { - public: - void run() { - // Create a sparse index, so we can easily remove entries from it with an update. - _cli.insert( NamespaceString( ns() ).getSystemIndexesCollection(), - BSON( "ns" << ns() << "key" << BSON( "a" << 1 ) << "name" << "idx" << - "sparse" << true ) ); - - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - for( int i = 0; i < 150; ++i ) { - _cli.insert( ns(), BSON( "a" << i << "b" << 0 ) ); - } - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - ClientCursorHolder p - ( new ClientCursor - ( QueryOption_NoCursorTimeout, - getOptimizedCursor - ( ns(), BSON( "a" << GTE << 0 << "b" << 0 ) ), - ns() ) ); - - // Iterate until after MultiCursor takes over. - int readTo = 110; - while( p->current()[ "a" ].number() < readTo ) { - p->advance(); - } - - // Check that the btree plan was picked. - ASSERT_EQUALS( BSON( "a" << 1 ), p->c()->indexKeyPattern() ); - - // Yield the cursor. - ClientCursor::YieldData yieldData; - ASSERT( p->prepareToYield( yieldData ) ); - - // Remove keys from the a:1 index, invalidating the cursor's position. - _cli.update( ns(), BSON( "a" << LT << 100 ), BSON( "$unset" << BSON( "a" << 1 ) ), - false, true ); - - // Delete the cursor's current document. If the cursor's position is recovered - // improperly in preparation for deleting the document, this will cause an - // assertion. - _cli.remove( ns(), BSON( "a" << readTo ) ); - - // Check that the document was deleted. - ASSERT_EQUALS( BSONObj(), _cli.findOne( ns(), BSON( "a" << readTo ) ) ); - - // Recover the cursor. - ASSERT( p->recoverFromYield( yieldData ) ); - - // Check that the remaining documents are iterated as expected. - for( int i = 111; i < 150; ++i ) { - ASSERT_EQUALS( i, p->current()[ "a" ].number() ); - p->advance(); - } - ASSERT( !p->ok() ); - } - }; - - /** - * Test that a ClientCursor properly prepares a QueryOptimizerCursor to yield after a pre - * delete advance. - */ - class AboutToDeletePrepareToYield : public Base { - public: - void run() { - // Create two indexes for serial $or clause traversal. - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - _cli.ensureIndex( ns(), BSON( "b" << 1 ) ); - for( int i = 0; i < 110; ++i ) { - _cli.insert( ns(), BSON( "a" << i ) ); - } - _cli.insert( ns(), BSON( "b" << 1 ) ); - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - ClientCursorHolder p - ( new ClientCursor - ( QueryOption_NoCursorTimeout, - getOptimizedCursor - ( ns(), OR( BSON( "a" << GTE << 0 ), BSON( "b" << 1 ) ) ), - ns() ) ); - - // Iterate until after MultiCursor takes over. - int readTo = 109; - while( p->current()[ "a" ].number() < readTo ) { - p->advance(); - } - - // Check the key pattern. - ASSERT_EQUALS( BSON( "a" << 1 ), p->c()->indexKeyPattern() ); - - // Yield the cursor. - ClientCursor::YieldData yieldData; - ASSERT( p->prepareToYield( yieldData ) ); - - // Delete the cursor's current document. The cursor should advance to the b:1 - // index. - _cli.remove( ns(), BSON( "a" << readTo ) ); - - // Check that the document was deleted. - ASSERT_EQUALS( BSONObj(), _cli.findOne( ns(), BSON( "a" << readTo ) ) ); - - // Recover the cursor. If the cursor was not properly prepared for yielding - // after the pre deletion advance, this will assert. - ASSERT( p->recoverFromYield( yieldData ) ); - - // Check that the remaining documents are as expected. - ASSERT_EQUALS( 1, p->current()[ "b" ].number() ); - ASSERT( !p->advance() ); - } - }; - - /** The collection of a QueryOptimizerCursor stored in a ClientCursor is dropped. */ - class Drop : public Base { - public: - void run() { - _cli.insert( ns(), BSON( "_id" << 1 ) ); - _cli.insert( ns(), BSON( "_id" << 2 ) ); - - ClientCursor::YieldData yieldData; - { - Lock::DBWrite lk(ns()); - Client::Context ctx( ns() ); - ClientCursorHolder p - ( new ClientCursor - ( QueryOption_NoCursorTimeout, - getOptimizedCursor - ( ns(), BSON( "_id" << GT << 0 << "z" << 0 ) ), - ns() ) ); - - ASSERT_EQUALS( "QueryOptimizerCursor", p->c()->toString() ); - ASSERT_EQUALS( 1, p->c()->current().getIntField( "_id" ) ); - ASSERT( p->prepareToYield( yieldData ) ); - } - - // No assertion is expected when the collection is dropped and the cursor cannot be - // recovered. - - _cli.dropCollection( ns() ); - - { - Lock::DBWrite lk(ns()); - Client::Context ctx( ns() ); - ASSERT( !ClientCursor::recoverFromYield( yieldData ) ); - } - } - }; - - } // namespace ClientCursor - - class AllowOutOfOrderPlan : public Base { - public: - void run() { - Lock::DBWrite lk(ns()); - Client::Context ctx( ns() ); - shared_ptr<Cursor> c = - newQueryOptimizerCursor( ns(), BSONObj(), BSON( "a" << 1 ), - QueryPlanSelectionPolicy::any(), false ); - ASSERT( c ); - } - }; - - class NoTakeoverByOutOfOrderPlan : public Base { - public: - void run() { - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - // Add enough early matches that the {$natural:1} plan would be chosen if it did not - // require scan and order. - for( int i = 0; i < 150; ++i ) { - _cli.insert( ns(), BSON( "a" << 2 << "b" << 1 ) ); - } - // Add non matches early on the {a:1} plan. - for( int i = 0; i < 150; ++i ) { - _cli.insert( ns(), BSON( "a" << 1 << "b" << 5 ) ); - } - // Add enough matches outside the {a:1} index range that the {$natural:1} scan will not - // complete before the {a:1} plan records 101 matches and is selected for takeover. - for( int i = 0; i < 150; ++i ) { - _cli.insert( ns(), BSON( "a" << 3 << "b" << 10 ) ); - } - Lock::DBWrite lk(ns()); - Client::Context ctx( ns() ); - shared_ptr<Cursor> c = - newQueryOptimizerCursor( ns(), BSON( "a" << LT << 3 << "b" << 1 ), BSON( "a" << 1 ), - QueryPlanSelectionPolicy::any(), false ); - ASSERT( c ); - BSONObj idxKey; - while( c->ok() ) { - idxKey = c->indexKeyPattern(); - c->advance(); - } - // Check that the ordered plan {a:1} took over, despite the unordered plan {$natural:1} - // seeing > 101 matches. - ASSERT_EQUALS( BSON( "a" << 1 ), idxKey ); - } - }; - - /** If no in order plans are possible, an out of order plan may take over. */ - class OutOfOrderOnlyTakeover : public Base { - public: - void run() { - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - for( int i = 0; i < 300; ++i ) { - _cli.insert( ns(), BSON( "a" << 1 ) ); - _cli.insert( ns(), BSON( "a" << 2 ) ); - } - - Lock::DBWrite lk(ns()); - Client::Context ctx( ns() ); - shared_ptr<Cursor> c = - newQueryOptimizerCursor( ns(), BSON( "a" << 1 ), BSON( "b" << 1 ), - QueryPlanSelectionPolicy::any(), false ); - ASSERT( c ); - while( c->advance() ); - // Check that one of the plans took over, and we didn't scan both plans until the a:1 - // index completed (which would yield an nscanned near 600). - ASSERT( c->nscanned() < 500 ); - } - }; - - class CoveredIndex : public Base { - public: - void run() { - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - _cli.ensureIndex( ns(), BSON( "b" << 1 ) ); - _cli.insert( ns(), BSON( "a" << 1 << "b" << 10 ) ); - - Lock::DBWrite lk(ns()); - Client::Context ctx( ns() ); - shared_ptr<ParsedQuery> parsedQuery - ( new ParsedQuery( ns(), 0, 0, 0, BSONObj(), BSON( "_id" << 0 << "a" << 1 ) ) ); - shared_ptr<QueryOptimizerCursor> c = - dynamic_pointer_cast<QueryOptimizerCursor> - ( newQueryOptimizerCursor( ns(), BSON( "a" << GTE << 0 << "b" << GTE << 0 ), - BSON( "a" << 1 ), QueryPlanSelectionPolicy::any(), false, - parsedQuery ) ); - bool foundA = false; - bool foundB = false; - while( c->ok() ) { - if ( c->indexKeyPattern() == BSON( "a" << 1 ) ) { - foundA = true; - ASSERT( c->keyFieldsOnly() ); - ASSERT_EQUALS( BSON( "a" << 1 ), c->keyFieldsOnly()->hydrate( c->currKey() ) ); - } - if ( c->indexKeyPattern() == BSON( "b" << 1 ) ) { - foundB = true; - ASSERT( !c->keyFieldsOnly() ); - } - c->advance(); - } - ASSERT( foundA ); - ASSERT( foundB ); - } - }; - - class CoveredIndexTakeover : public Base { - public: - void run() { - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - _cli.ensureIndex( ns(), BSON( "b" << 1 ) ); - for( int i = 0; i < 150; ++i ) { - _cli.insert( ns(), BSON( "a" << 1 << "b" << 1 ) ); - } - _cli.insert( ns(), BSON( "a" << 2 ) ); - - Lock::DBWrite lk(ns()); - Client::Context ctx( ns() ); - shared_ptr<ParsedQuery> parsedQuery - ( new ParsedQuery( ns(), 0, 0, 0, BSONObj(), BSON( "_id" << 0 << "a" << 1 ) ) ); - shared_ptr<QueryOptimizerCursor> c = - dynamic_pointer_cast<QueryOptimizerCursor> - ( newQueryOptimizerCursor( ns(), fromjson( "{$or:[{a:1},{b:1},{a:2}]}" ), BSONObj(), - QueryPlanSelectionPolicy::any(), false, parsedQuery ) ); - bool foundA = false; - bool foundB = false; - while( c->ok() ) { - if ( c->indexKeyPattern() == BSON( "a" << 1 ) ) { - foundA = true; - ASSERT( c->keyFieldsOnly() ); - ASSERT( BSON( "a" << 1 ) == c->keyFieldsOnly()->hydrate( c->currKey() ) || - BSON( "a" << 2 ) == c->keyFieldsOnly()->hydrate( c->currKey() ) ); - } - if ( c->indexKeyPattern() == BSON( "b" << 1 ) ) { - foundB = true; - ASSERT( !c->keyFieldsOnly() ); - } - c->advance(); - } - ASSERT( foundA ); - ASSERT( foundB ); - } - }; - - class PlanChecking : public Base { - public: - virtual ~PlanChecking() {} - protected: - void nPlans( int n, const BSONObj &query, const BSONObj &order ) { - auto_ptr< FieldRangeSetPair > frsp( new FieldRangeSetPair( ns(), query ) ); - auto_ptr< FieldRangeSetPair > frspOrig( new FieldRangeSetPair( *frsp ) ); - scoped_ptr<QueryPlanSet> s( QueryPlanSet::make( ns(), frsp, frspOrig, query, order, - shared_ptr<const ParsedQuery>(), - BSONObj(), QueryPlanGenerator::Use, - BSONObj(), BSONObj(), true ) ); - ASSERT_EQUALS( n, s->nPlans() ); - } - static shared_ptr<QueryOptimizerCursor> getCursor( const BSONObj &query, - const BSONObj &order ) { - shared_ptr<ParsedQuery> parsedQuery - ( new ParsedQuery( ns(), 0, 0, 0, - BSON( "$query" << query << "$orderby" << order ), - BSONObj() ) ); - shared_ptr<Cursor> cursor = getOptimizedCursor( ns(), - query, - order, - QueryPlanSelectionPolicy::any(), - parsedQuery, - false ); - shared_ptr<QueryOptimizerCursor> ret = - dynamic_pointer_cast<QueryOptimizerCursor>( cursor ); - ASSERT( ret ); - return ret; - } - void runQuery( const BSONObj &query, const BSONObj &order ) { - shared_ptr<QueryOptimizerCursor> cursor = getCursor( query, order ); - while( cursor->advance() ); - } - }; - - class SaveGoodIndex : public PlanChecking { - public: - void run() { - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - _cli.ensureIndex( ns(), BSON( "b" << 1 ) ); - - Lock::DBWrite lk(ns()); - Client::Context ctx( ns() ); - - // No best plan - all must be tried. - nPlans( 3 ); - runQuery(); - // Best plan selected by query. - nPlans( 1 ); - nPlans( 1 ); - Helpers::ensureIndex( ns(), BSON( "c" << 1 ), false, "c_1" ); - // Best plan cleared when new index added. - nPlans( 3 ); - runQuery(); - // Best plan selected by query. - nPlans( 1 ); - - { - DBDirectClient client; - for( int i = 0; i < 334; ++i ) { - client.insert( ns(), BSON( "i" << i ) ); - client.update( ns(), QUERY( "i" << i ), BSON( "i" << i + 1 ) ); - client.remove( ns(), BSON( "i" << i + 1 ) ); - } - } - // Best plan cleared by ~1000 writes. - nPlans( 3 ); - - shared_ptr<ParsedQuery> parsedQuery - ( new ParsedQuery( ns(), 0, 0, 0, - BSON( "$query" << BSON( "a" << 4 ) << - "$hint" << BSON( "$natural" << 1 ) ), - BSON( "b" << 1 ) ) ); - shared_ptr<Cursor> cursor = getOptimizedCursor( ns(), - BSON( "a" << 4 ), - BSONObj(), - QueryPlanSelectionPolicy::any(), - parsedQuery, - false ); - while( cursor->advance() ); - // No plan recorded when a hint is used. - nPlans( 3 ); - - shared_ptr<ParsedQuery> parsedQuery2 - ( new ParsedQuery( ns(), 0, 0, 0, - BSON( "$query" << BSON( "a" << 4 ) << - "$orderby" << BSON( "b" << 1 << "c" << 1 ) ), - BSONObj() ) ); - shared_ptr<Cursor> cursor2 = getOptimizedCursor( ns(), - BSON( "a" << 4 ), - BSON( "b" << 1 << "c" << 1 ), - QueryPlanSelectionPolicy::any(), - parsedQuery2, - false ); - while( cursor2->advance() ); - // Plan recorded was for a different query pattern (different sort spec). - nPlans( 3 ); - - // Best plan still selected by query after all these other tests. - runQuery(); - nPlans( 1 ); - } - private: - void nPlans( int n ) { - return PlanChecking::nPlans( n, BSON( "a" << 4 ), BSON( "b" << 1 ) ); - } - void runQuery() { - return PlanChecking::runQuery( BSON( "a" << 4 ), BSON( "b" << 1 ) ); - } - }; - - class PossiblePlans : public PlanChecking { - protected: - void checkCursor( bool mayRunInOrderPlan, bool mayRunOutOfOrderPlan, - bool runningInitialInOrderPlan, bool possiblyExcludedPlans ) { - CandidatePlanCharacter plans = _cursor->initialCandidatePlans(); - ASSERT_EQUALS( mayRunInOrderPlan, plans.mayRunInOrderPlan() ); - ASSERT_EQUALS( mayRunOutOfOrderPlan, plans.mayRunOutOfOrderPlan() ); - ASSERT_EQUALS( runningInitialInOrderPlan, _cursor->runningInitialInOrderPlan() ); - ASSERT_EQUALS( possiblyExcludedPlans, _cursor->hasPossiblyExcludedPlans() ); - } - void setCursor( const BSONObj &query, const BSONObj &order ) { - _cursor = PlanChecking::getCursor( query, order ); - } - void runCursor( bool completePlanOfHybridSetScanAndOrderRequired = false ) { - while( _cursor->ok() ) { - checkIterate( _cursor ); - _cursor->advance(); - } - ASSERT_EQUALS( completePlanOfHybridSetScanAndOrderRequired, - _cursor->completePlanOfHybridSetScanAndOrderRequired() ); - } - void runCursorUntilTakeover() { - // This is a bit of a hack, relying on initialFieldRangeSet() being nonzero before - // takeover and zero after takeover. - while( _cursor->ok() && _cursor->initialFieldRangeSet() ) { - checkIterate( _cursor ); - _cursor->advance(); - } - } - void checkTakeoverCursor( bool currentPlanScanAndOrderRequired ) { - ASSERT( !_cursor->initialFieldRangeSet() ); - ASSERT_EQUALS( currentPlanScanAndOrderRequired, - _cursor->currentPlanScanAndOrderRequired() ); - ASSERT( !_cursor->completePlanOfHybridSetScanAndOrderRequired() ); - ASSERT( !_cursor->runningInitialInOrderPlan() ); - ASSERT( !_cursor->hasPossiblyExcludedPlans() ); - } - virtual void checkIterate( const shared_ptr<QueryOptimizerCursor> &cursor ) const = 0; - shared_ptr<QueryOptimizerCursor> _cursor; - }; - - class PossibleInOrderPlans : public PossiblePlans { - public: - void run() { - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - _cli.insert( ns(), BSON( "a" << 1 ) ); - for( int i = 0; i < 20; ++i ) { - _cli.insert( ns(), BSON( "a" << 2 ) ); - } - - Lock::DBWrite lk(ns()); - Client::Context ctx( ns() ); - nPlans( 2, BSON( "a" << 1 << "x" << 1 ), BSONObj() ); - setCursor( BSON( "a" << 1 << "x" << 1 ), BSONObj() ); - checkCursor( false ); - ASSERT( _cursor->initialFieldRangeSet()->range( "a" ).equality() ); - ASSERT( !_cursor->initialFieldRangeSet()->range( "b" ).equality() ); - ASSERT( _cursor->initialFieldRangeSet()->range( "x" ).equality() ); - - // Without running the (nonempty) cursor, no cached plan is recorded. - setCursor( BSON( "a" << 1 << "x" << 1 ), BSONObj() ); - checkCursor( false ); - - // Running the cursor records the plan. - runCursor(); - nPlans( 1, BSON( "a" << 1 << "x" << 1 ), BSONObj() ); - setCursor( BSON( "a" << 1 << "x" << 1 ), BSONObj() ); - checkCursor( true ); - - // Other plans may be added. - setCursor( BSON( "a" << 2 << "x" << 1 ), BSONObj() ); - checkCursor( true ); - for( int i = 0; i < 10; ++i, _cursor->advance() ); - // The natural plan has been added in. - checkCursor( false ); - nPlans( 1, BSON( "a" << 2 << "x" << 1 ), BSONObj() ); - runCursor(); - - // The a:1 plan was recorded again. - nPlans( 1, BSON( "a" << 2 << "x" << 1 ), BSONObj() ); - setCursor( BSON( "a" << 2 << "x" << 1 ), BSONObj() ); - checkCursor( true ); - - // Clear the recorded plan manually. - _cursor->clearIndexesForPatterns(); - nPlans( 2, BSON( "a" << 2 << "x" << 1 ), BSONObj() ); - setCursor( BSON( "a" << 2 << "x" << 1 ), BSONObj() ); - checkCursor( false ); - - // Add more data, and run until takeover occurs. - for( int i = 0; i < 120; ++i ) { - _cli.insert( ns(), BSON( "a" << 3 << "x" << 1 ) ); - } - - setCursor( BSON( "a" << 3 << "x" << 1 ), BSONObj() ); - checkCursor( false ); - runCursorUntilTakeover(); - ASSERT( _cursor->ok() ); - checkTakeoverCursor( false ); - - // Try again, with a cached plan this time. - setCursor( BSON( "a" << 3 << "x" << 1 ), BSONObj() ); - checkCursor( true ); - runCursorUntilTakeover(); - checkTakeoverCursor( false ); - } - private: - void checkCursor( bool runningInitialCachedPlan ) { - return PossiblePlans::checkCursor( true, false, true, runningInitialCachedPlan ); - } - virtual void checkIterate( const shared_ptr<QueryOptimizerCursor> &cursor ) const { - ASSERT( !cursor->currentPlanScanAndOrderRequired() ); - ASSERT( !cursor->completePlanOfHybridSetScanAndOrderRequired() ); - } - }; - - class PossibleOutOfOrderPlans : public PossiblePlans { - public: - void run() { - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - _cli.ensureIndex( ns(), BSON( "b" << 1 ) ); - _cli.insert( ns(), BSON( "a" << 1 << "b" << 1 ) ); - for( int i = 0; i < 20; ++i ) { - _cli.insert( ns(), BSON( "a" << 2 ) ); - } - _cli.insert( ns(), BSON( "b" << 2 ) ); - - Lock::DBWrite lk(ns()); - Client::Context ctx( ns() ); - nPlans( 3, BSON( "a" << 1 << "b" << 1 ), BSON( "x" << 1 ) ); - setCursor( BSON( "a" << 1 << "b" << 1 ), BSON( "x" << 1 ) ); - checkCursor( false ); - ASSERT( _cursor->initialFieldRangeSet()->range( "a" ).equality() ); - ASSERT( _cursor->initialFieldRangeSet()->range( "b" ).equality() ); - ASSERT( !_cursor->initialFieldRangeSet()->range( "x" ).equality() ); - - // Without running the (nonempty) cursor, no cached plan is recorded. - setCursor( BSON( "a" << 1 << "b" << 1 ), BSON( "x" << 1 ) ); - checkCursor( false ); - - // Running the cursor records the plan. - runCursor(); - nPlans( 1, BSON( "a" << 1 << "b" << 1 ), BSON( "x" << 1 ) ); - setCursor( BSON( "a" << 1 << "b" << 1 ), BSON( "x" << 1 ) ); - checkCursor( true ); - - // Other plans may be added. - setCursor( BSON( "a" << 2 << "b" << 2 ), BSON( "x" << 1 ) ); - checkCursor( true ); - for( int i = 0; i < 10; ++i, _cursor->advance() ); - // The other plans have been added in. - checkCursor( false ); - runCursor(); - - // The b:1 plan was recorded. - setCursor( BSON( "a" << 1 << "b" << 1 ), BSON( "x" << 1 ) ); - checkCursor( true ); - - // Clear the recorded plan manually. - _cursor->clearIndexesForPatterns(); - setCursor( BSON( "a" << 2 << "x" << 1 ), BSON( "x" << 1 ) ); - checkCursor( false ); - - // Add more data, and run until takeover occurs. - for( int i = 0; i < 120; ++i ) { - _cli.insert( ns(), BSON( "a" << 3 << "b" << 3 ) ); - } - - setCursor( BSON( "a" << 3 << "b" << 3 ), BSON( "x" << 1 ) ); - checkCursor( false ); - runCursorUntilTakeover(); - ASSERT( _cursor->ok() ); - checkTakeoverCursor( true ); - - // Try again, with a cached plan this time. - setCursor( BSON( "a" << 3 << "b" << 3 ), BSON( "x" << 1 ) ); - checkCursor( true ); - runCursorUntilTakeover(); - checkTakeoverCursor( true ); - } - private: - void checkCursor( bool runningInitialCachedPlan ) { - return PossiblePlans::checkCursor( false, true, false, runningInitialCachedPlan ); - } - virtual void checkIterate( const shared_ptr<QueryOptimizerCursor> &cursor ) const { - ASSERT( cursor->currentPlanScanAndOrderRequired() ); - ASSERT( !cursor->completePlanOfHybridSetScanAndOrderRequired() ); - } - }; - - class PossibleBothPlans : public PossiblePlans { - public: - void run() { - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - _cli.ensureIndex( ns(), BSON( "b" << 1 ) ); - _cli.insert( ns(), BSON( "a" << 1 << "b" << 1 ) ); - _cli.insert( ns(), BSON( "a" << 2 << "b" << 1 ) ); - for( int i = 0; i < 20; ++i ) { - _cli.insert( ns(), BSON( "a" << 2 ) ); - if ( i % 10 == 0 ) { - _cli.insert( ns(), BSON( "b" << 2 ) ); - } - } - - Lock::DBWrite lk(ns()); - Client::Context ctx( ns() ); - nPlans( 3, BSON( "a" << 1 << "b" << 1 ), BSON( "b" << 1 ) ); - setCursor( BSON( "a" << 1 << "b" << 1 ), BSON( "b" << 1 ) ); - checkCursor( true, false ); - ASSERT( _cursor->initialFieldRangeSet()->range( "a" ).equality() ); - ASSERT( _cursor->initialFieldRangeSet()->range( "b" ).equality() ); - ASSERT( !_cursor->initialFieldRangeSet()->range( "x" ).equality() ); - - // Without running the (nonempty) cursor, no cached plan is recorded. - setCursor( BSON( "a" << 1 << "b" << 1 ), BSON( "b" << 1 ) ); - checkCursor( true, false ); - - // Running the cursor records the a:1 plan. - runCursor( true ); - nPlans( 1, BSON( "a" << 1 << "b" << 1 ), BSON( "b" << 1 ) ); - setCursor( BSON( "a" << 1 << "b" << 1 ), BSON( "b" << 1 ) ); - checkCursor( false, true ); - - // Other plans may be added. - setCursor( BSON( "a" << 2 << "b" << 2 ), BSON( "b" << 1 ) ); - checkCursor( false, true ); - for( int i = 0; i < 10; ++i, _cursor->advance() ); - // The other plans have been added in (including ordered b:1). - checkCursor( true, false ); - runCursor( false ); - - // The b:1 plan was recorded. - setCursor( BSON( "a" << 1 << "b" << 1 ), BSON( "b" << 1 ) ); - checkCursor( true, true ); - - // Clear the recorded plan manually. - _cursor->clearIndexesForPatterns(); - setCursor( BSON( "a" << 2 << "b" << 1 ), BSON( "b" << 1 ) ); - checkCursor( true, false ); - - // Add more data, and run until takeover occurs. - for( int i = 0; i < 120; ++i ) { - _cli.insert( ns(), BSON( "a" << 3 << "b" << 3 ) ); - } - - setCursor( BSON( "a" << 3 << "b" << 3 ), BSON( "b" << 1 ) ); - checkCursor( true, false ); - runCursorUntilTakeover(); - ASSERT( _cursor->ok() ); - checkTakeoverCursor( false ); - ASSERT_EQUALS( BSON( "b" << 1 ), _cursor->indexKeyPattern() ); - - // Try again, with a cached plan this time. - setCursor( BSON( "a" << 3 << "b" << 3 ), BSON( "b" << 1 ) ); - checkCursor( true, true ); - runCursorUntilTakeover(); - checkTakeoverCursor( false ); - ASSERT_EQUALS( BSON( "b" << 1 ), _cursor->indexKeyPattern() ); - } - private: - void checkCursor( bool runningInitialInOrderPlan, bool runningInitialCachedPlan ) { - return PossiblePlans::checkCursor( true, true, runningInitialInOrderPlan, - runningInitialCachedPlan ); - } - virtual void checkIterate( const shared_ptr<QueryOptimizerCursor> &cursor ) const { - if ( cursor->indexKeyPattern() == BSON( "b" << 1 ) ) { - ASSERT( !cursor->currentPlanScanAndOrderRequired() ); - } - else { - ASSERT( cursor->currentPlanScanAndOrderRequired() ); - } - ASSERT( !cursor->completePlanOfHybridSetScanAndOrderRequired() ); - } - }; - - class AbortOutOfOrderPlans : public PlanChecking { - public: - void run() { - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - for( int i = 0; i < 10; ++i ) { - _cli.insert( ns(), BSON( "a" << 1 ) ); - } - - Lock::DBWrite lk(ns()); - Client::Context ctx( ns() ); - - shared_ptr<QueryOptimizerCursor> c = getCursor( BSON( "a" << 1 << "b" << BSONNULL ), - BSON( "a" << 1 ) ); - // Wait until a $natural plan result is returned. - while( c->indexKeyPattern() != BSONObj() ) { - c->advance(); - } - // Abort the natural plan. - c->abortOutOfOrderPlans(); - c->advance(); - // Check that no more results from the natural plan are returned. - ASSERT( c->ok() ); - while( c->ok() ) { - ASSERT_EQUALS( BSON( "a" << 1 ), c->indexKeyPattern() ); - c->advance(); - } - ASSERT( !c->completePlanOfHybridSetScanAndOrderRequired() ); - } - }; - - class AbortOutOfOrderPlanOnLastMatch : public PlanChecking { - public: - void run() { - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - for( int i = 0; i < 10; ++i ) { - _cli.insert( ns(), BSON( "a" << BSON_ARRAY( 1 << 2 ) ) ); - } - - Lock::DBWrite lk(ns()); - Client::Context ctx( ns() ); - - shared_ptr<QueryOptimizerCursor> c = - getCursor( BSON( "a" << GTE << 1 << "b" << BSONNULL ), BSON( "a" << 1 ) ); - // Wait until 10 (all) $natural plan results are returned. - for( int i = 0; i < 10; ++i ) { - while( c->indexKeyPattern() != BSONObj() ) { - c->advance(); - } - c->advance(); - } - // Abort the natural plan. - c->abortOutOfOrderPlans(); - c->advance(); - // Check that no more results from the natural plan are returned, and the cursor is not - // done iterating. - ASSERT( c->ok() ); - while( c->ok() ) { - ASSERT_EQUALS( BSON( "a" << 1 ), c->indexKeyPattern() ); - c->advance(); - } - ASSERT( !c->completePlanOfHybridSetScanAndOrderRequired() ); - } - }; - - /** Out of order plans are not added after abortOutOfOrderPlans() is called. */ - class AbortOutOfOrderPlansBeforeAddOtherPlans : public PlanChecking { - public: - void run() { - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - _cli.ensureIndex( ns(), BSON( "b" << 1 ) ); - _cli.insert( ns(), BSON( "a" << -1 << "b" << 0 ) ); - for( int b = 0; b < 2; ++b ) { - for( int a = 0; a < 10; ++a ) { - _cli.insert( ns(), BSON( "a" << a << "b" << b ) ); - } - } - - // Selectivity is better for the a:1 index. - _aPreferableQuery = BSON( "a" << GTE << -100 << LTE << -1 << "b" << 0 ); - // Selectivity is better for the b:1 index. - _bPreferableQuery = BSON( "a" << GTE << 0 << LTE << 100 << "b" << 0 ); - - Client::ReadContext ctx( ns() ); - - // If abortOutOfOrderPlans() is not set, other plans will be attempted. - recordAIndex(); - _cursor = getCursor( _bPreferableQuery, BSON( "a" << 1 ) ); - checkInitialIteratePlans(); - // The b:1 index is attempted later. - checkBIndexUsed( true ); - - // If abortOutOfOrderPlans() is set, other plans will not be attempted. - recordAIndex(); - _cursor = getCursor( _bPreferableQuery, BSON( "a" << 1 ) ); - checkInitialIteratePlans(); - _cursor->abortOutOfOrderPlans(); - // The b:1 index is not attempted. - checkBIndexUsed( false ); - } - private: - /** Record the a:1 index for the query pattern of interest. */ - void recordAIndex() const { - Collection* collection = cc().database()->getCollection( ns() ); - CollectionInfoCache* cache = collection->infoCache(); - cache->clearQueryCache(); - shared_ptr<QueryOptimizerCursor> c = getCursor( _aPreferableQuery, BSON( "a" << 1 ) ); - while( c->advance() ); - FieldRangeSet aPreferableFields( ns(), _aPreferableQuery, true, true ); - ASSERT_EQUALS( BSON( "a" << 1 ), - cache->cachedQueryPlanForPattern - ( aPreferableFields.pattern( BSON( "a" << 1 ) ) ).indexKey() ); - } - /** The first results come from the recorded index. */ - void checkInitialIteratePlans() const { - for( int i = 0; i < 5; ++i ) { - ASSERT_EQUALS( BSON( "a" << 1 ), _cursor->indexKeyPattern() ); - } - } - /** Check if the b:1 index is used during iteration. */ - void checkBIndexUsed( bool expected ) const { - bool bIndexUsed = false; - while( _cursor->advance() ) { - if ( BSON( "b" << 1 ) == _cursor->indexKeyPattern() ) { - bIndexUsed = true; - } - } - ASSERT_EQUALS( expected, bIndexUsed ); - } - BSONObj _aPreferableQuery; - BSONObj _bPreferableQuery; - shared_ptr<QueryOptimizerCursor> _cursor; - }; - - class TakeoverOrRangeElimination : public PlanChecking { - public: - void run() { - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - for( int i = 0; i < 120; ++i ) { - _cli.insert( ns(), BSON( "a" << 1 ) ); - } - for( int i = 0; i < 20; ++i ) { - _cli.insert( ns(), BSON( "a" << 2 ) ); - } - for( int i = 0; i < 20; ++i ) { - _cli.insert( ns(), BSON( "a" << 3 ) ); - } - - Lock::DBWrite lk(ns()); - Client::Context ctx( ns() ); - - shared_ptr<QueryOptimizerCursor> c = - getCursor( fromjson( "{$or:[{a:{$lte:2}},{a:{$gte:2}},{a:9}]}" ), BSONObj() ); - - int count = 0; - while( c->ok() ) { - c->advance(); - ++count; - } - ASSERT_EQUALS( 160, count ); - } - }; - - class TakeoverOrDedups : public PlanChecking { - public: - void run() { - _cli.ensureIndex( ns(), BSON( "a" << 1 << "b" << 1 ) ); - for( int i = 0; i < 120; ++i ) { - _cli.insert( ns(), BSON( "a" << 1 << "b" << 1 ) ); - } - for( int i = 0; i < 20; ++i ) { - _cli.insert( ns(), BSON( "a" << 2 << "b" << 2 ) ); - } - for( int i = 0; i < 20; ++i ) { - _cli.insert( ns(), BSON( "a" << 3 << "b" << 3 ) ); - } - - Lock::DBWrite lk(ns()); - Client::Context ctx( ns() ); - - BSONObj query = - BSON( - "$or" << BSON_ARRAY( - BSON( - "a" << GTE << 0 << LTE << 2 << - "b" << GTE << 0 << LTE << 2 - ) << - BSON( - "a" << GTE << 1 << LTE << 3 << - "b" << GTE << 1 << LTE << 3 - ) << - BSON( - "a" << GTE << 1 << LTE << 4 << - "b" << GTE << 1 << LTE << 4 - ) - ) - ); - - shared_ptr<QueryOptimizerCursor> c = getCursor( query, BSONObj() ); - - int count = 0; - while( c->ok() ) { - if ( ( c->indexKeyPattern() == BSON( "a" << 1 << "b" << 1 ) ) && - c->currentMatches() ) { - ++count; - } - c->advance(); - } - ASSERT_EQUALS( 160, count ); - } - }; - - /** Proper index matching when transitioning between $or clauses after a takeover. */ - class TakeoverOrDifferentIndex : public PlanChecking { - public: - void run() { - for( int i = 0; i < 120; ++i ) { - _cli.insert( ns(), BSON( "a" << 1 << "b" << 2 ) ); - } - _cli.ensureIndex( ns(), BSON( "a" << 1 << "b" << 1 ) ); - for( int i = 0; i < 130; ++i ) { - _cli.insert( ns(), BSON( "b" << 3 << "a" << 4 ) ); - } - _cli.ensureIndex( ns(), BSON( "b" << 1 << "a" << 1 ) ); - - Lock::DBWrite lk(ns()); - Client::Context ctx( ns() ); - - // This $or query will scan index a:1,b:1 then b:1,a:1. If the key pattern is specified - // incorrectly for the second clause, matching will fail. - setQueryOptimizerCursor( fromjson( "{$or:[{a:1,b:{$gte:0}},{b:3,a:{$gte:0}}]}" ) ); - // All documents match, and there are no dups. - ASSERT_EQUALS( 250, itcount() ); - } - }; - - /** - * An ordered plan returns all results, including when it takes over, even when it duplicates an - * entry of an out of order plan. - */ - class TakeoverOrderedPlanDupsOutOfOrderPlan : public PlanChecking { - public: - void run() { - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - for( int i = 1; i < 200; ++i ) { - _cli.insert( ns(), BSON( "a" << i << "b" << 0 ) ); - } - // Insert this document last, so that most documents are read from the $natural cursor - // before the a:1 cursor. - _cli.insert( ns(), BSON( "a" << 0 << "b" << 0 ) ); - - Client::ReadContext ctx( ns() ); - shared_ptr<QueryOptimizerCursor> cursor = - getCursor( BSON( "a" << GTE << 0 << "b" << 0 ), BSON( "a" << 1 ) ); - int nextA = 0; - for( ; cursor->ok(); cursor->advance() ) { - if ( cursor->indexKeyPattern() == BSON( "a" << 1 ) ) { - // Check that the expected 'a' value is present and in order. - ASSERT_EQUALS( nextA++, cursor->current()[ "a" ].number() ); - } - } - ASSERT_EQUALS( 200, nextA ); - } - }; - - /** Check that an elemMatchKey can be retrieved from MatchDetails using a qo cursor. */ - class ElemMatchKey : public Base { - public: - void run() { - _cli.ensureIndex( ns(), BSON( "a.b" << 1 ) ); - _cli.insert( ns(), fromjson( "{ a:[ { b:1 } ] }" ) ); - - Client::ReadContext ctx( ns() ); - setQueryOptimizerCursor( BSON( "a.b" << 1 ) ); - MatchDetails details; - details.requestElemMatchKey(); - ASSERT( c()->currentMatches( &details ) ); - // The '0' entry of the 'a' array is matched. - ASSERT_EQUALS( string( "0" ), details.elemMatchKey() ); - } - }; - - namespace GetCursor { - - class Base : public QueryOptimizerCursorTests::Base { - public: - Base() { - // create collection - _cli.insert( ns(), BSON( "_id" << 5 ) ); - } - virtual ~Base() {} - void run() { - Lock::GlobalWrite lk; - Client::Context ctx( ns() ); - if ( expectException() ) { - ASSERT_THROWS - ( getOptimizedCursor - ( ns(), query(), order(), planPolicy() ), - MsgAssertionException ); - return; - } - _query = query(); - _parsedQuery.reset( new ParsedQuery( ns(), skip(), limit(), 0, _query, - BSONObj() ) ); - BSONObj extractedQuery = _query; - if ( !_query["$query"].eoo() ) { - extractedQuery = _query["$query"].Obj(); - } - shared_ptr<Cursor> c = getOptimizedCursor( ns(), - extractedQuery, - order(), - planPolicy(), - _parsedQuery, - false ); - string type = c->toString().substr( 0, expectedType().length() ); - ASSERT_EQUALS( expectedType(), type ); - check( c ); - } - protected: - virtual string expectedType() const { return "TESTDUMMY"; } - virtual bool expectException() const { return false; } - virtual BSONObj query() const { return BSONObj(); } - virtual BSONObj order() const { return BSONObj(); } - virtual int skip() const { return 0; } - virtual int limit() const { return 0; } - virtual const QueryPlanSelectionPolicy &planPolicy() const { - return QueryPlanSelectionPolicy::any(); - } - virtual void check( const shared_ptr<Cursor> &c ) { - ASSERT( c->ok() ); - ASSERT( !c->matcher() ); - ASSERT_EQUALS( 5, c->current().getIntField( "_id" ) ); - ASSERT( !c->advance() ); - } - private: - BSONObj _query; - shared_ptr<ParsedQuery> _parsedQuery; - }; - - class NoConstraints : public Base { - string expectedType() const { return "BasicCursor"; } - }; - - class SimpleId : public Base { - public: - SimpleId() { - _cli.insert( ns(), BSON( "_id" << 0 ) ); - _cli.insert( ns(), BSON( "_id" << 10 ) ); - } - string expectedType() const { return "BtreeCursor _id_"; } - BSONObj query() const { return BSON( "_id" << 5 ); } - }; - - class OptimalIndex : public Base { - public: - OptimalIndex() { - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - _cli.insert( ns(), BSON( "a" << 5 ) ); - _cli.insert( ns(), BSON( "a" << 6 ) ); - } - string expectedType() const { return "BtreeCursor a_1"; } - BSONObj query() const { return BSON( "a" << GTE << 5 ); } - void check( const shared_ptr<Cursor> &c ) { - ASSERT( c->ok() ); - ASSERT( c->matcher() ); - ASSERT_EQUALS( 5, c->current().getIntField( "a" ) ); - ASSERT( c->matcher()->matchesCurrent( c.get() ) ); - ASSERT( c->advance() ); - ASSERT_EQUALS( 6, c->current().getIntField( "a" ) ); - ASSERT( c->matcher()->matchesCurrent( c.get() ) ); - ASSERT( !c->advance() ); - } - }; - - class SimpleKeyMatch : public Base { - public: - SimpleKeyMatch() { - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - _cli.update( ns(), BSONObj(), BSON( "$set" << BSON( "a" << true ) ) ); - } - string expectedType() const { return "BtreeCursor a_1"; } - BSONObj query() const { return BSON( "a" << true ); } - virtual void check( const shared_ptr<Cursor> &c ) { - ASSERT( c->ok() ); - ASSERT_EQUALS( 5, c->current().getIntField( "_id" ) ); - ASSERT( !c->advance() ); - } - }; - - class PreventOutOfOrderPlan : public QueryOptimizerCursorTests::Base { - public: - void run() { - _cli.insert( ns(), BSON( "_id" << 5 ) ); - Lock::GlobalWrite lk; - Client::Context ctx( ns() ); - shared_ptr<Cursor> c = getOptimizedCursor( ns(), BSONObj(), BSON( "b" << 1 ) ); - ASSERT( !c ); - } - }; - - class AllowOutOfOrderPlan : public Base { - public: - void run() { - Lock::DBWrite lk(ns()); - Client::Context ctx( ns() ); - shared_ptr<ParsedQuery> parsedQuery - ( new ParsedQuery( ns(), 0, 0, 0, - BSON( "$query" << BSONObj() << - "$orderby" << BSON( "a" << 1 ) ), - BSONObj() ) ); - shared_ptr<Cursor> c = getOptimizedCursor( ns(), - BSONObj(), - BSON( "a" << 1 ), - QueryPlanSelectionPolicy::any(), - parsedQuery, - false ); - ASSERT( c ); - } - }; - - class BestSavedOutOfOrder : public QueryOptimizerCursorTests::Base { - public: - void run() { - _cli.insert( ns(), BSON( "_id" << 5 << "b" << BSON_ARRAY( 1 << 2 << 3 << 4 << 5 ) ) ); - _cli.insert( ns(), BSON( "_id" << 1 << "b" << 6 ) ); - _cli.ensureIndex( ns(), BSON( "b" << 1 ) ); - // record {_id:1} index for this query - ASSERT( _cli.query( ns(), QUERY( "_id" << GT << 0 << "b" << GT << 0 ).sort( "b" ) )->more() ); - Lock::GlobalWrite lk; - Client::Context ctx( ns() ); - shared_ptr<Cursor> c = - getOptimizedCursor( ns(), - BSON( "_id" << GT << 0 << "b" << GT << 0 ), - BSON( "b" << 1 ) ); - // {_id:1} requires scan and order, so {b:1} must be chosen. - ASSERT( c ); - ASSERT_EQUALS( 5, c->current().getIntField( "_id" ) ); - } - }; - - // QUERY MIGRATION - // Cache is turned off - /** - * If an optimal plan is cached, return a Cursor for it rather than a QueryOptimizerCursor. - */ - // class BestSavedOptimal : public QueryOptimizerCursorTests::Base { - // public: - // void run() { - // _cli.insert( ns(), BSON( "_id" << 1 ) ); - // _cli.ensureIndex( ns(), BSON( "_id" << 1 << "q" << 1 ) ); - // ASSERT( _cli.query( ns(), QUERY( "_id" << GT << 0 ) )->more() ); - // Lock::GlobalWrite lk; - // Client::Context ctx( ns() ); - // // Check the plan that was recorded for this query. - // ASSERT_EQUALS( BSON( "_id" << 1 ), cachedIndexForQuery( BSON( "_id" << GT << 0 ) ) ); - // shared_ptr<Cursor> c = getOptimizedCursor( ns(), BSON( "_id" << GT << 0 ) ); - // // No need for query optimizer cursor since the plan is optimal. - // ASSERT_EQUALS( "BtreeCursor _id_", c->toString() ); - // } - // }; - - // QUERY MIGRATIOM - // Cache is turned off - /** If a non optimal plan is a candidate a QueryOptimizerCursor should be returned, even if plan has been recorded. */ - // class BestSavedNotOptimal : public QueryOptimizerCursorTests::Base { - // public: - // void run() { - // _cli.insert( ns(), BSON( "_id" << 1 << "q" << 1 ) ); - // _cli.ensureIndex( ns(), BSON( "q" << 1 ) ); - // // Record {_id:1} index for this query - // ASSERT( _cli.query( ns(), QUERY( "q" << 1 << "_id" << 1 ) )->more() ); - // Lock::GlobalWrite lk; - // Client::Context ctx( ns() ); - // ASSERT_EQUALS( BSON( "_id" << 1 ), - // cachedIndexForQuery( BSON( "q" << 1 << "_id" << 1 ) ) ); - // shared_ptr<Cursor> c = getOptimizedCursor( ns(), BSON( "q" << 1 << "_id" << 1 ) ); - // // Need query optimizer cursor since the cached plan is not optimal. - // ASSERT_EQUALS( "QueryOptimizerCursor", c->toString() ); - // } - // }; - - class MultiIndex : public Base { - public: - MultiIndex() { - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - } - string expectedType() const { return "QueryOptimizerCursor"; } - BSONObj query() const { return BSON( "_id" << GT << 0 << "a" << GT << 0 ); } - void check( const shared_ptr<Cursor> &c ) {} - }; - - class Hint : public Base { - public: - Hint() { - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - } - string expectedType() const { return "BtreeCursor a_1"; } - BSONObj query() const { - return BSON( "$query" << BSON( "_id" << 1 ) << "$hint" << BSON( "a" << 1 ) ); - } - void check( const shared_ptr<Cursor> &c ) {} - }; - - class Snapshot : public Base { - public: - Snapshot() { - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - } - string expectedType() const { return "BtreeCursor _id_"; } - BSONObj query() const { - return BSON( "$query" << BSON( "a" << 1 ) << "$snapshot" << true ); - } - void check( const shared_ptr<Cursor> &c ) {} - }; - - class SnapshotCappedColl : public Base { - public: - SnapshotCappedColl() { - _cli.dropCollection( ns() ); - _cli.createCollection( ns(), 1000, true ); - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - } - string expectedType() const { return "BtreeCursor _id_"; } - BSONObj query() const { - return BSON( "$query" << BSON( "a" << 1 ) << "$snapshot" << true ); - } - void check( const shared_ptr<Cursor> &c ) {} - }; - - class Min : public Base { - public: - Min() { - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - } - string expectedType() const { return "BtreeCursor a_1"; } - BSONObj query() const { - return BSON( "$query" << BSONObj() << "$min" << BSON( "a" << 1 ) ); - } - void check( const shared_ptr<Cursor> &c ) {} - }; - - class Max : public Base { - public: - Max() { - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - } - string expectedType() const { return "BtreeCursor a_1"; } - BSONObj query() const { - return BSON( "$query" << BSONObj() << "$max" << BSON( "a" << 1 ) ); - } - void check( const shared_ptr<Cursor> &c ) {} - }; - - namespace RequireIndex { - - class Base : public GetCursor::Base { - const QueryPlanSelectionPolicy &planPolicy() const { - return QueryPlanSelectionPolicy::indexOnly(); - } - }; - - class NoConstraints : public Base { - bool expectException() const { return true; } - }; - - class SimpleId : public Base { - string expectedType() const { return "BtreeCursor _id_"; } - BSONObj query() const { return BSON( "_id" << 5 ); } - }; - - class UnindexedQuery : public Base { - bool expectException() const { return true; } - BSONObj query() const { return BSON( "a" << GTE << 5 ); } - }; - - class IndexedQuery : public Base { - public: - IndexedQuery() { - _cli.insert( ns(), BSON( "_id" << 6 << "a" << 6 << "c" << 4 ) ); - _cli.ensureIndex( ns(), BSON( "a" << 1 << "b" << 1 << "c" << 1 ) ); - } - string expectedType() const { return "QueryOptimizerCursor"; } - BSONObj query() const { return BSON( "a" << GTE << 5 << "c" << 4 ); } - void check( const shared_ptr<Cursor> &c ) { - ASSERT( c->ok() ); - ASSERT( c->matcher() ); - ASSERT_EQUALS( 6, c->current().getIntField( "_id" ) ); - ASSERT( !c->advance() ); - } - }; - - class SecondOrClauseIndexed : public Base { - public: - SecondOrClauseIndexed() { - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - _cli.ensureIndex( ns(), BSON( "b" << 1 ) ); - _cli.insert( ns(), BSON( "a" << 1 ) ); - _cli.insert( ns(), BSON( "b" << 1 ) ); - } - string expectedType() const { return "QueryOptimizerCursor"; } - BSONObj query() const { return fromjson( "{$or:[{a:1},{b:1}]}" ); } - void check( const shared_ptr<Cursor> &c ) { - ASSERT( c->ok() ); - ASSERT( c->matcher() ); - ASSERT( c->advance() ); - ASSERT( !c->advance() ); // 2 matches exactly - } - }; - - class SecondOrClauseUnindexed : public Base { - public: - SecondOrClauseUnindexed() { - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - _cli.insert( ns(), BSON( "a" << 1 ) ); - } - bool expectException() const { return true; } - BSONObj query() const { return fromjson( "{$or:[{a:1},{b:1}]}" ); } - }; - - class SecondOrClauseUnindexedUndetected : public Base { - public: - SecondOrClauseUnindexedUndetected() { - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - _cli.ensureIndex( ns(), BSON( "a" << 1 << "b" << 1 ) ); - _cli.insert( ns(), BSON( "a" << 1 ) ); - _cli.insert( ns(), BSON( "b" << 1 ) ); - } - string expectedType() const { return "QueryOptimizerCursor"; } - BSONObj query() const { return fromjson( "{$or:[{a:1},{b:1}]}" ); } - void check( const shared_ptr<Cursor> &c ) { - ASSERT( c->ok() ); - ASSERT( c->matcher() ); - // An unindexed cursor is required for the second clause, but is not allowed. - ASSERT_THROWS( c->advance(), MsgAssertionException ); - } - }; - - // QUERY MIGRATION - // Cached is turned off - // class RecordedUnindexedPlan : public Base { - // public: - // RecordedUnindexedPlan() { - // _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - // _cli.insert( ns(), BSON( "a" << BSON_ARRAY( 1 << 2 << 3 ) << "b" << 1 ) ); - // auto_ptr<DBClientCursor> cursor = - // _cli.query( ns(), QUERY( "a" << GT << 0 << "b" << 1 ).explain() ); - // BSONObj explain = cursor->next(); - // ASSERT_EQUALS( "BasicCursor", explain[ "cursor" ].String() ); - // } - // string expectedType() const { return "QueryOptimizerCursor"; } - // BSONObj query() const { return BSON( "a" << GT << 0 << "b" << 1 ); } - // void check( const shared_ptr<Cursor> &c ) { - // ASSERT( c->ok() ); - // ASSERT_EQUALS( BSON( "a" << 1 ), c->indexKeyPattern() ); - // while( c->advance() ) { - // ASSERT_EQUALS( BSON( "a" << 1 ), c->indexKeyPattern() ); - // } - // } - // }; - - } // namespace RequireIndex - - namespace IdElseNatural { - - class Base : public GetCursor::Base { - const QueryPlanSelectionPolicy &planPolicy() const { - return QueryPlanSelectionPolicy::idElseNatural(); - } - }; - - class AllowOptimalNaturalPlan : public Base { - string expectedType() const { return "BasicCursor"; } - void check( const shared_ptr<Cursor> &c ) { - ASSERT( c->ok() ); - ASSERT( !c->matcher() ); - ASSERT_EQUALS( 5, c->current().getIntField( "_id" ) ); - ASSERT( !c->advance() ); - } - }; - - class AllowOptimalIdPlan : public Base { - string expectedType() const { return "BtreeCursor _id_"; } - BSONObj query() const { return BSON( "_id" << 5 ); } - }; - - class HintedIdForQuery : public Base { - public: - HintedIdForQuery( const BSONObj &query ) : _query( query ) { - _cli.remove( ns(), BSONObj() ); - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - _cli.insert( ns(), BSON( "_id" << 1 << "a" << 1 ) ); - } - string expectedType() const { return "BtreeCursor _id_"; } - BSONObj query() const { return _query; } - void check( const shared_ptr<Cursor> &c ) { - ASSERT( c->ok() ); - ASSERT( c->currentMatches() ); - ASSERT_EQUALS( 1, c->current().getIntField( "_id" ) ); - ASSERT( !c->advance() ); - } - private: - BSONObj _query; - }; - - class HintedNaturalForQuery : public Base { - public: - HintedNaturalForQuery( const BSONObj &query ) : _query( query ) { - _cli.dropCollection( ns() ); - _cli.createCollection( ns(), 1024, true ); - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - _cli.insert( ns(), BSON( "_id" << 1 << "a" << 1 ) ); - } - ~HintedNaturalForQuery() { - _cli.dropCollection( ns() ); - } - string expectedType() const { return "ForwardCappedCursor"; } - BSONObj query() const { return _query; } - void check( const shared_ptr<Cursor> &c ) { - ASSERT( c->ok() ); - ASSERT( c->currentMatches() ); - ASSERT_EQUALS( 1, c->current().getIntField( "_id" ) ); - ASSERT( !c->advance() ); - } - private: - BSONObj _query; - }; - - } // namespace IdElseNatural - - /** - * Generating a cursor for an invalid query asserts, even if the collection is empty or - * missing. - */ - class MatcherValidation : public Base { - public: - void run() { - // Matcher validation with an empty collection. - _cli.remove( ns(), BSONObj() ); - checkInvalidQueryAssertions(); - - // Matcher validation with a missing collection. - _cli.dropCollection( ns() ); - checkInvalidQueryAssertions(); - } - private: - static void checkInvalidQueryAssertions() { - Client::ReadContext ctx( ns() ); - - // An invalid query generating a single query plan asserts. - BSONObj invalidQuery = fromjson( "{$and:[{$atomic:true}]}" ); - assertInvalidQueryAssertion( invalidQuery ); - - // An invalid query generating multiple query plans asserts. - BSONObj invalidIdQuery = fromjson( "{_id:0,$and:[{$atomic:true}]}" ); - assertInvalidQueryAssertion( invalidIdQuery ); - } - static void assertInvalidQueryAssertion( const BSONObj &query ) { - ASSERT_THROWS( getOptimizedCursor( ns(), query, BSONObj() ), - UserException ); - } - }; - - class RequestMatcherFalse : public QueryPlanSelectionPolicy { - virtual string name() const { return "RequestMatcherFalse"; } - virtual bool requestMatcher() const { return false; } - } _requestMatcherFalse; - - /** - * A Cursor returned by getOptimizedCursor() may or may not have a - * matcher(). A Matcher will generally exist if required to match the provided query or - * if specifically requested. - */ - class MatcherSet : public Base { - public: - MatcherSet() { - _cli.insert( ns(), BSON( "a" << 2 << "b" << 3 ) ); - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - } - void run() { - // No matcher is set for an empty query. - ASSERT( !hasMatcher( BSONObj(), false ) ); - // No matcher is set for an empty query, even if a matcher is requested. - ASSERT( !hasMatcher( BSONObj(), true ) ); - // No matcher is set for an exact key match indexed query. - ASSERT( !hasMatcher( BSON( "a" << 2 ), false ) ); - // No matcher is set for an exact key match indexed query, unless one is requested. - ASSERT( hasMatcher( BSON( "a" << 2 ), true ) ); - // A matcher is set for a non exact key match indexed query. - ASSERT( hasMatcher( BSON( "a" << 2 << "b" << 3 ), false ) ); - } - private: - bool hasMatcher( const BSONObj& query, bool requestMatcher ) { - Client::ReadContext ctx( ns() ); - shared_ptr<Cursor> cursor = getOptimizedCursor( ns(), - query, - BSONObj(), - requestMatcher ? - QueryPlanSelectionPolicy::any(): - _requestMatcherFalse ); - return cursor->matcher(); - } - }; - - /** - * Even though a Matcher may not be used to perform matching when requestMatcher == false, a - * Matcher must be created because the Matcher's constructor performs query validation. - */ - class MatcherValidate : public Base { - public: - void run() { - Client::ReadContext ctx( ns() ); - // An assertion is triggered because { a:undefined } is an invalid query, even - // though no matcher is required. - ASSERT_THROWS( getOptimizedCursor( ns(), - fromjson( "{a:undefined}" ), - BSONObj(), - _requestMatcherFalse ), - UserException ); - } - }; - - class RequestIntervalCursorTrue : public QueryPlanSelectionPolicy { - virtual string name() const { return "RequestIntervalCursorTrue"; } - virtual bool requestIntervalCursor() const { return true; } - } _requestIntervalCursorTrue; - - /** An IntervalBtreeCursor is selected when requested by a QueryPlanSelectionPolicy. */ - class IntervalCursor : public Base { - public: - IntervalCursor() { - _cli.insert( ns(), BSON( "a" << 2 << "b" << 3 ) ); - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - } - void run() { - Client::ReadContext ctx( ns() ); - shared_ptr<Cursor> cursor = getOptimizedCursor( ns(), - BSON( "a" << 1 ), - BSONObj(), - _requestIntervalCursorTrue ); - ASSERT_EQUALS( "IntervalBtreeCursor", cursor->toString() ); - } - }; - - } // namespace GetCursor - - namespace Explain { - - class ClearRecordedIndex : public QueryOptimizerCursorTests::Base { - public: - void run() { - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - _cli.ensureIndex( ns(), BSON( "b" << 1 ) ); - _cli.insert( ns(), BSON( "a" << 1 << "b" << 1 ) ); - _cli.insert( ns(), BSON( "a" << 1 << "b" << 1 ) ); - - Lock::DBWrite lk(ns()); - Client::Context ctx( ns() ); - BSONObj query = BSON( "a" << 1 << "b" << 1 ); - shared_ptr<Cursor> c = getOptimizedCursor( ns(), query ); - while( c->advance() ); - shared_ptr<ParsedQuery> parsedQuery - ( new ParsedQuery( ns(), 0, 0, 0, - BSON( "$query" << query << "$explain" << true ), - BSONObj() ) ); - c = getOptimizedCursor( ns(), - query, - BSONObj(), - QueryPlanSelectionPolicy::any(), - parsedQuery, - false ); - set<BSONObj> indexKeys; - while( c->ok() ) { - indexKeys.insert( c->indexKeyPattern() ); - c->advance(); - } - ASSERT( indexKeys.size() > 1 ); - } - }; - - class Base : public QueryOptimizerCursorTests::Base { - public: - virtual ~Base() {} - void run() { - setupCollection(); - - Lock::DBWrite lk(ns()); - Client::Context ctx( ns() ); - shared_ptr<ParsedQuery> parsedQuery - ( new ParsedQuery( ns(), 0, 0, 0, - BSON( "$query" << query() << "$explain" << true ), - fields() ) ); - _cursor = - dynamic_pointer_cast<QueryOptimizerCursor> - ( getOptimizedCursor( ns(), - query(), - BSONObj(), - QueryPlanSelectionPolicy::any(), - parsedQuery, - false ) ); - ASSERT( _cursor ); - - handleCursor(); - - _explainInfo = _cursor->explainQueryInfo(); - _explain = _explainInfo->bson(); - - checkExplain(); - } - protected: - virtual void setupCollection() { - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - _cli.insert( ns(), BSON( "a" << 1 << "b" << 1 ) ); - _cli.insert( ns(), BSON( "a" << 2 << "b" << 1 ) ); - } - virtual BSONObj query() const { return BSON( "a" << 1 << "b" << 1 ); } - virtual BSONObj fields() const { return BSONObj(); } - virtual void handleCursor() { - } - virtual void checkExplain() { - } - shared_ptr<QueryOptimizerCursor> _cursor; - shared_ptr<ExplainQueryInfo> _explainInfo; - BSONObj _explain; - }; - - class Initial : public Base { - virtual void checkExplain() { - ASSERT( !_explain[ "cursor" ].eoo() ); - ASSERT( !_explain[ "isMultiKey" ].Bool() ); - ASSERT_EQUALS( 0, _explain[ "n" ].number() ); - ASSERT_EQUALS( 0, _explain[ "nscannedObjectsAllPlans" ].number() ); - ASSERT_EQUALS( 2, _explain[ "nscannedAllPlans" ].number() ); - ASSERT( !_explain[ "scanAndOrder" ].Bool() ); - ASSERT( !_explain[ "indexOnly" ].Bool() ); - ASSERT_EQUALS( 0, _explain[ "nYields" ].Int() ); - ASSERT_EQUALS( 0, _explain[ "nChunkSkips" ].number() ); - ASSERT( !_explain[ "millis" ].eoo() ); - ASSERT( !_explain[ "indexBounds" ].eoo() ); - ASSERT_EQUALS( 2U, _explain[ "allPlans" ].Array().size() ); - - BSONObj plan1 = _explain[ "allPlans" ].Array()[ 0 ].Obj(); - ASSERT_EQUALS( "BtreeCursor a_1", plan1[ "cursor" ].String() ); - ASSERT_EQUALS( 0, plan1[ "n" ].number() ); - ASSERT_EQUALS( 0, plan1[ "nscannedObjects" ].number() ); - ASSERT_EQUALS( 1, plan1[ "nscanned" ].number() ); - ASSERT_EQUALS( fromjson( "{a:[[1,1]]}" ), plan1[ "indexBounds" ].Obj() ); - - BSONObj plan2 = _explain[ "allPlans" ].Array()[ 1 ].Obj(); - ASSERT_EQUALS( "BasicCursor", plan2[ "cursor" ].String() ); - ASSERT_EQUALS( 0, plan2[ "n" ].number() ); - ASSERT_EQUALS( 0, plan2[ "nscannedObjects" ].number() ); - ASSERT_EQUALS( 1, plan2[ "nscanned" ].number() ); - ASSERT_EQUALS( BSONObj(), plan2[ "indexBounds" ].Obj() ); - } - }; - - class Empty : public Base { - virtual void setupCollection() { - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - } - virtual void handleCursor() { - ASSERT( !_cursor->ok() ); - } - virtual void checkExplain() { - ASSERT( !_explain[ "cursor" ].eoo() ); - ASSERT( !_explain[ "isMultiKey" ].Bool() ); - ASSERT_EQUALS( 0, _explain[ "n" ].number() ); - ASSERT_EQUALS( 0, _explain[ "nscannedObjectsAllPlans" ].number() ); - ASSERT_EQUALS( 0, _explain[ "nscannedAllPlans" ].number() ); - ASSERT( !_explain[ "scanAndOrder" ].Bool() ); - ASSERT( !_explain[ "indexOnly" ].Bool() ); - ASSERT_EQUALS( 0, _explain[ "nYields" ].Int() ); - ASSERT_EQUALS( 0, _explain[ "nChunkSkips" ].number() ); - ASSERT( !_explain[ "millis" ].eoo() ); - ASSERT( !_explain[ "indexBounds" ].eoo() ); - ASSERT_EQUALS( 2U, _explain[ "allPlans" ].Array().size() ); - - BSONObj plan1 = _explain[ "allPlans" ].Array()[ 0 ].Obj(); - ASSERT_EQUALS( "BtreeCursor a_1", plan1[ "cursor" ].String() ); - ASSERT_EQUALS( 0, plan1[ "n" ].number() ); - ASSERT_EQUALS( 0, plan1[ "nscannedObjects" ].number() ); - ASSERT_EQUALS( 0, plan1[ "nscanned" ].number() ); - ASSERT_EQUALS( fromjson( "{a:[[1,1]]}" ), plan1[ "indexBounds" ].Obj() ); - - BSONObj plan2 = _explain[ "allPlans" ].Array()[ 1 ].Obj(); - ASSERT_EQUALS( "BasicCursor", plan2[ "cursor" ].String() ); - ASSERT_EQUALS( 0, plan2[ "n" ].number() ); - ASSERT_EQUALS( 0, plan2[ "nscannedObjects" ].number() ); - ASSERT_EQUALS( 0, plan2[ "nscanned" ].number() ); - ASSERT_EQUALS( BSONObj(), plan2[ "indexBounds" ].Obj() ); - } - }; - - class SimpleCount : public Base { - virtual void handleCursor() { - while( _cursor->ok() ) { - MatchDetails matchDetails; - if ( _cursor->currentMatches( &matchDetails ) && - !_cursor->getsetdup( _cursor->currLoc() ) ) { - _cursor->noteIterate( true, true, false ); - } - else { - _cursor->noteIterate( false, matchDetails.hasLoadedRecord(), false ); - } - _cursor->advance(); - } - } - virtual void checkExplain() { - ASSERT_EQUALS( "BtreeCursor a_1", _explain[ "cursor" ].String() ); - ASSERT_EQUALS( 1, _explain[ "n" ].number() ); - ASSERT_EQUALS( 2, _explain[ "nscannedObjectsAllPlans" ].number() ); - ASSERT_EQUALS( 2, _explain[ "nscannedAllPlans" ].number() ); - - BSONObj plan1 = _explain[ "allPlans" ].Array()[ 0 ].Obj(); - ASSERT_EQUALS( 1, plan1[ "n" ].number() ); - ASSERT_EQUALS( 1, plan1[ "nscannedObjects" ].number() ); - ASSERT_EQUALS( 1, plan1[ "nscanned" ].number() ); - - BSONObj plan2 = _explain[ "allPlans" ].Array()[ 1 ].Obj(); - ASSERT_EQUALS( 1, plan2[ "n" ].number() ); - ASSERT_EQUALS( 1, plan2[ "nscannedObjects" ].number() ); - ASSERT_EQUALS( 1, plan2[ "nscanned" ].number() ); - } - }; - - class IterateOnly : public Base { - virtual BSONObj query() const { return BSON( "a" << GT << 0 << "b" << 1 ); } - virtual void handleCursor() { - while( _cursor->ok() ) { - _cursor->advance(); - } - } - virtual void checkExplain() { - ASSERT_EQUALS( "BtreeCursor a_1", _explain[ "cursor" ].String() ); - ASSERT_EQUALS( 0, _explain[ "n" ].number() ); // needs to be set with noteIterate() - ASSERT_EQUALS( 0, _explain[ "nscannedObjectsAllPlans" ].number() ); - ASSERT_EQUALS( 3, _explain[ "nscannedAllPlans" ].number() ); - - BSONObj plan1 = _explain[ "allPlans" ].Array()[ 0 ].Obj(); - ASSERT_EQUALS( 2, plan1[ "n" ].number() ); - ASSERT_EQUALS( 2, plan1[ "nscannedObjects" ].number() ); - ASSERT_EQUALS( 2, plan1[ "nscanned" ].number() ); - - // Not fully incremented without checking for matches. - BSONObj plan2 = _explain[ "allPlans" ].Array()[ 1 ].Obj(); - ASSERT_EQUALS( 1, plan2[ "n" ].number() ); - ASSERT_EQUALS( 1, plan2[ "nscannedObjects" ].number() ); - ASSERT_EQUALS( 1, plan2[ "nscanned" ].number() ); - } - }; - - class ExtraMatchChecks : public Base { - virtual BSONObj query() const { return BSON( "a" << GT << 0 << "b" << 1 ); } - virtual void handleCursor() { - while( _cursor->ok() ) { - _cursor->currentMatches(); - _cursor->currentMatches(); - _cursor->currentMatches(); - _cursor->advance(); - } - } - virtual void checkExplain() { - ASSERT_EQUALS( "BtreeCursor a_1", _explain[ "cursor" ].String() ); - ASSERT_EQUALS( 0, _explain[ "n" ].number() ); // needs to be set with noteIterate() - ASSERT_EQUALS( 0, _explain[ "nscannedObjectsAllPlans" ].number() ); - ASSERT_EQUALS( 4, _explain[ "nscannedAllPlans" ].number() ); - - BSONObj plan1 = _explain[ "allPlans" ].Array()[ 0 ].Obj(); - ASSERT_EQUALS( 2, plan1[ "n" ].number() ); - // nscannedObjects are not deduped. - ASSERT_EQUALS( 6, plan1[ "nscannedObjects" ].number() ); - ASSERT_EQUALS( 2, plan1[ "nscanned" ].number() ); - - BSONObj plan2 = _explain[ "allPlans" ].Array()[ 1 ].Obj(); - ASSERT_EQUALS( 2, plan2[ "n" ].number() ); - ASSERT_EQUALS( 6, plan2[ "nscannedObjects" ].number() ); - ASSERT_EQUALS( 2, plan2[ "nscanned" ].number() ); - } - }; - - class PartialIteration : public Base { - virtual void handleCursor() { - _cursor->currentMatches(); - _cursor->advance(); - _cursor->noteIterate( true, true, false ); - } - virtual void checkExplain() { - ASSERT_EQUALS( "BtreeCursor a_1", _explain[ "cursor" ].String() ); - ASSERT_EQUALS( 1, _explain[ "n" ].number() ); - ASSERT_EQUALS( 1, _explain[ "nscannedObjectsAllPlans" ].number() ); - ASSERT_EQUALS( 2, _explain[ "nscannedAllPlans" ].number() ); - - BSONObj plan1 = _explain[ "allPlans" ].Array()[ 0 ].Obj(); - ASSERT_EQUALS( 1, plan1[ "n" ].number() ); - ASSERT_EQUALS( 1, plan1[ "nscannedObjects" ].number() ); - ASSERT_EQUALS( 1, plan1[ "nscanned" ].number() ); - - BSONObj plan2 = _explain[ "allPlans" ].Array()[ 1 ].Obj(); - ASSERT_EQUALS( 0, plan2[ "n" ].number() ); - ASSERT_EQUALS( 0, plan2[ "nscannedObjects" ].number() ); - ASSERT_EQUALS( 1, plan2[ "nscanned" ].number() ); - } - }; - - class Multikey : public Base { - virtual void setupCollection() { - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - _cli.insert( ns(), BSON( "a" << BSON_ARRAY( 1 << 2 ) ) ); - } - virtual void handleCursor() { - while( _cursor->advance() ); - } - virtual void checkExplain() { - ASSERT( _explain[ "isMultiKey" ].Bool() ); - } - }; - - class MultikeyInitial : public Base { - virtual void setupCollection() { - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - _cli.insert( ns(), BSON( "a" << BSON_ARRAY( 1 << 2 ) ) ); - } - virtual void checkExplain() { - ASSERT( _explain[ "isMultiKey" ].Bool() ); - } - }; - - class BecomesMultikey : public Base { - virtual void setupCollection() { - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - _cli.insert( ns(), BSON( "a" << 1 ) ); - } - virtual void checkExplain() { - ASSERT( !_explain[ "isMultiKey" ].Bool() ); - - _cursor->prepareToYield(); - { - dbtemprelease t; - _cli.insert( ns(), BSON( "a" << BSON_ARRAY( 1 << 2 ) ) ); - } - _cursor->recoverFromYield(); - _cursor->currentMatches(); - ASSERT( _explainInfo->bson()[ "isMultiKey" ].Bool() ); - } - }; - - class CountAndYield : public Base { - virtual void setupCollection() { - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - for( int i = 0; i < 5; ++i ) { - _cli.insert( ns(), BSON( "a" << 1 << "b" << 1 ) ); - } - } - virtual void handleCursor() { - _nYields = 0; - while( _cursor->ok() ) { - _cursor->prepareToYield(); - ++_nYields; - _cursor->recoverFromYield(); - _cursor->noteYield(); - MatchDetails matchDetails; - if ( _cursor->currentMatches( &matchDetails ) && - !_cursor->getsetdup( _cursor->currLoc() ) ) { - _cursor->noteIterate( true, true, false ); - } - else { - _cursor->noteIterate( false, matchDetails.hasLoadedRecord(), false ); - } - _cursor->advance(); - } - } - virtual void checkExplain() { - ASSERT( _nYields > 0 ); - ASSERT_EQUALS( _nYields, _explain[ "nYields" ].Int() ); - - ASSERT_EQUALS( "BtreeCursor a_1", _explain[ "cursor" ].String() ); - ASSERT_EQUALS( 5, _explain[ "n" ].number() ); - ASSERT_EQUALS( 10, _explain[ "nscannedObjectsAllPlans" ].number() ); - ASSERT_EQUALS( 10, _explain[ "nscannedAllPlans" ].number() ); - - BSONObj plan1 = _explain[ "allPlans" ].Array()[ 0 ].Obj(); - ASSERT_EQUALS( 5, plan1[ "n" ].number() ); - ASSERT_EQUALS( 5, plan1[ "nscannedObjects" ].number() ); - ASSERT_EQUALS( 5, plan1[ "nscanned" ].number() ); - - BSONObj plan2 = _explain[ "allPlans" ].Array()[ 1 ].Obj(); - ASSERT_EQUALS( 5, plan2[ "n" ].number() ); - ASSERT_EQUALS( 5, plan2[ "nscannedObjects" ].number() ); - ASSERT_EQUALS( 5, plan2[ "nscanned" ].number() ); - } - protected: - int _nYields; - }; - - class MultipleClauses : public CountAndYield { - virtual void setupCollection() { - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - _cli.ensureIndex( ns(), BSON( "b" << 1 ) ); - for( int i = 0; i < 4; ++i ) { - _cli.insert( ns(), BSON( "a" << 1 << "b" << 1 ) ); - } - _cli.insert( ns(), BSON( "a" << 0 << "b" << 1 ) ); - } - virtual BSONObj query() const { return fromjson( "{$or:[{a:1,c:null},{b:1,c:null}]}"); } - virtual void checkExplain() { - ASSERT_EQUALS( 18, _nYields ); - - ASSERT_EQUALS( 5, _explain[ "n" ].number() ); - ASSERT_EQUALS( 18, _explain[ "nscannedObjectsAllPlans" ].number() ); - ASSERT_EQUALS( 18, _explain[ "nscannedAllPlans" ].number() ); - - BSONObj clause1 = _explain[ "clauses" ].Array()[ 0 ].Obj(); - ASSERT_EQUALS( "BtreeCursor a_1", clause1[ "cursor" ].String() ); - ASSERT_EQUALS( 4, clause1[ "n" ].number() ); - ASSERT_EQUALS( 8, clause1[ "nscannedObjectsAllPlans" ].number() ); - ASSERT_EQUALS( 8, clause1[ "nscannedAllPlans" ].number() ); - ASSERT_EQUALS( 8, clause1[ "nYields" ].Int() ); - - BSONObj c1plan1 = clause1[ "allPlans" ].Array()[ 0 ].Obj(); - ASSERT_EQUALS( "BtreeCursor a_1", c1plan1[ "cursor" ].String() ); - ASSERT_EQUALS( 4, c1plan1[ "n" ].number() ); - ASSERT_EQUALS( 4, c1plan1[ "nscannedObjects" ].number() ); - ASSERT_EQUALS( 4, c1plan1[ "nscanned" ].number() ); - - BSONObj c1plan2 = clause1[ "allPlans" ].Array()[ 1 ].Obj(); - ASSERT_EQUALS( "BasicCursor", c1plan2[ "cursor" ].String() ); - ASSERT_EQUALS( 4, c1plan2[ "n" ].number() ); - ASSERT_EQUALS( 4, c1plan2[ "nscannedObjects" ].number() ); - ASSERT_EQUALS( 4, c1plan2[ "nscanned" ].number() ); - - BSONObj clause2 = _explain[ "clauses" ].Array()[ 1 ].Obj(); - ASSERT_EQUALS( "BtreeCursor b_1", clause2[ "cursor" ].String() ); - ASSERT_EQUALS( 1, clause2[ "n" ].number() ); - ASSERT_EQUALS( 10, clause2[ "nscannedObjectsAllPlans" ].number() ); - ASSERT_EQUALS( 10, clause2[ "nscannedAllPlans" ].number() ); - ASSERT_EQUALS( 10, clause2[ "nYields" ].Int() ); - - BSONObj c2plan1 = clause2[ "allPlans" ].Array()[ 0 ].Obj(); - ASSERT_EQUALS( "BtreeCursor b_1", c2plan1[ "cursor" ].String() ); - ASSERT_EQUALS( 5, c2plan1[ "n" ].number() ); - ASSERT_EQUALS( 5, c2plan1[ "nscannedObjects" ].number() ); - ASSERT_EQUALS( 5, c2plan1[ "nscanned" ].number() ); - - BSONObj c2plan2 = clause2[ "allPlans" ].Array()[ 1 ].Obj(); - ASSERT_EQUALS( "BasicCursor", c2plan2[ "cursor" ].String() ); - ASSERT_EQUALS( 5, c2plan2[ "n" ].number() ); - ASSERT_EQUALS( 5, c2plan2[ "nscannedObjects" ].number() ); - ASSERT_EQUALS( 5, c2plan2[ "nscanned" ].number() ); - } - }; - - class MultiCursorTakeover : public CountAndYield { - virtual void setupCollection() { - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - for( int i = 20; i >= 1; --i ) { - for( int j = 0; j < i; ++j ) { - _cli.insert( ns(), BSON( "a" << i ) ); - } - } - } - virtual BSONObj query() const { - BSONArrayBuilder bab; - for( int i = 20; i >= 1; --i ) { - bab << BSON( "a" << i ); - } - return BSON( "$or" << bab.arr() ); - } - virtual void checkExplain() { - ASSERT_EQUALS( 20U, _explain[ "clauses" ].Array().size() ); - for( int i = 20; i >= 1; --i ) { - BSONObj clause = _explain[ "clauses" ].Array()[ 20-i ].Obj(); - ASSERT_EQUALS( "BtreeCursor a_1", clause[ "cursor" ].String() ); - ASSERT_EQUALS( BSON( "a" << BSON_ARRAY( BSON_ARRAY( i << i ) ) ), - clause[ "indexBounds" ].Obj() ); - ASSERT_EQUALS( i, clause[ "n" ].number() ); - ASSERT_EQUALS( i, clause[ "nscannedObjects" ].number() ); - ASSERT_EQUALS( i, clause[ "nscanned" ].number() ); - ASSERT_EQUALS( i, clause[ "nYields" ].Int() ); - - ASSERT_EQUALS( 1U, clause[ "allPlans" ].Array().size() ); - BSONObj plan = clause[ "allPlans" ].Array()[ 0 ].Obj(); - ASSERT_EQUALS( i, plan[ "n" ].number() ); - ASSERT_EQUALS( i, plan[ "nscannedObjects" ].number() ); - ASSERT_EQUALS( i, plan[ "nscanned" ].number() ); - } - - ASSERT_EQUALS( 210, _explain[ "n" ].number() ); - ASSERT_EQUALS( 210, _explain[ "nscannedObjects" ].number() ); - ASSERT_EQUALS( 210, _explain[ "nscanned" ].number() ); - } - }; - - class NChunkSkipsTakeover : public Base { - virtual void setupCollection() { - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - for( int i = 0; i < 200; ++i ) { - _cli.insert( ns(), BSON( "a" << 1 << "b" << 1 ) ); - } - for( int i = 0; i < 200; ++i ) { - _cli.insert( ns(), BSON( "a" << 2 << "b" << 2 ) ); - } - } - virtual BSONObj query() const { return fromjson( "{$or:[{a:1,b:1},{a:2,b:2}]}" ); } - virtual void handleCursor() { - ASSERT_EQUALS( "QueryOptimizerCursor", _cursor->toString() ); - int i = 0; - while( _cursor->ok() ) { - if ( _cursor->currentMatches() && !_cursor->getsetdup( _cursor->currLoc() ) ) { - _cursor->noteIterate( true, true, i++ %2 == 0 ); - } - _cursor->advance(); - } - } - virtual void checkExplain() { - // Historically, nChunkSkips has been excluded from the query summary. - ASSERT( _explain[ "nChunkSkips" ].eoo() ); - - BSONObj clause0 = _explain[ "clauses" ].Array()[ 0 ].Obj(); - ASSERT_EQUALS( 100, clause0[ "nChunkSkips" ].number() ); - BSONObj plan0 = clause0[ "allPlans" ].Array()[ 0 ].Obj(); - ASSERT( plan0[ "nChunkSkips" ].eoo() ); - - BSONObj clause1 = _explain[ "clauses" ].Array()[ 1 ].Obj(); - ASSERT_EQUALS( 100, clause1[ "nChunkSkips" ].number() ); - BSONObj plan1 = clause1[ "allPlans" ].Array()[ 0 ].Obj(); - ASSERT( plan1[ "nChunkSkips" ].eoo() ); - } - }; - - class CoveredIndex : public Base { - virtual BSONObj query() const { return fromjson( "{$or:[{a:1},{a:2}]}" ); } - virtual BSONObj fields() const { return BSON( "_id" << 0 << "a" << 1 ); } - virtual void handleCursor() { - ASSERT_EQUALS( "QueryOptimizerCursor", _cursor->toString() ); - while( _cursor->advance() ); - } - virtual void checkExplain() { - BSONObj clause0 = _explain[ "clauses" ].Array()[ 0 ].Obj(); - ASSERT( clause0[ "indexOnly" ].Bool() ); - - BSONObj clause1 = _explain[ "clauses" ].Array()[ 1 ].Obj(); - ASSERT( clause1[ "indexOnly" ].Bool() ); - } - }; - - class CoveredIndexTakeover : public Base { - virtual void setupCollection() { - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - for( int i = 0; i < 150; ++i ) { - _cli.insert( ns(), BSON( "a" << 1 ) ); - } - _cli.insert( ns(), BSON( "a" << 2 ) ); - } - virtual BSONObj query() const { return fromjson( "{$or:[{a:1},{a:2}]}" ); } - virtual BSONObj fields() const { return BSON( "_id" << 0 << "a" << 1 ); } - virtual void handleCursor() { - ASSERT_EQUALS( "QueryOptimizerCursor", _cursor->toString() ); - while( _cursor->advance() ); - } - virtual void checkExplain() { - BSONObj clause0 = _explain[ "clauses" ].Array()[ 0 ].Obj(); - ASSERT( clause0[ "indexOnly" ].Bool() ); - - BSONObj clause1 = _explain[ "clauses" ].Array()[ 1 ].Obj(); - ASSERT( clause1[ "indexOnly" ].Bool() ); - } - }; - - /** - * Check that the plan with the most matches is reported at the top of the explain output - * in the absence of a done or picked plan. - */ - class VirtualPickedPlan : public Base { - public: - void run() { - Client::WriteContext ctx( ns() ); - - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - _cli.ensureIndex( ns(), BSON( "b" << 1 ) ); - _cli.ensureIndex( ns(), BSON( "c" << 1 ) ); - - shared_ptr<Cursor> aCursor( getOptimizedCursor( ns(), BSON( "a" << 1 ) ) ); - shared_ptr<Cursor> bCursor( getOptimizedCursor( ns(), BSON( "b" << 1 ) ) ); - shared_ptr<Cursor> cCursor( getOptimizedCursor( ns(), BSON( "c" << 1 ) ) ); - - shared_ptr<ExplainPlanInfo> aPlan( new ExplainPlanInfo() ); - aPlan->notePlan( *aCursor, false, false ); - shared_ptr<ExplainPlanInfo> bPlan( new ExplainPlanInfo() ); - bPlan->notePlan( *bCursor, false, false ); - shared_ptr<ExplainPlanInfo> cPlan( new ExplainPlanInfo() ); - cPlan->notePlan( *cCursor, false, false ); - - aPlan->noteIterate( true, false, *aCursor ); // one match a - bPlan->noteIterate( true, false, *bCursor ); // two matches b - bPlan->noteIterate( true, false, *bCursor ); - cPlan->noteIterate( true, false, *cCursor ); // one match c - - shared_ptr<ExplainClauseInfo> clause( new ExplainClauseInfo() ); - clause->addPlanInfo( aPlan ); - clause->addPlanInfo( bPlan ); - clause->addPlanInfo( cPlan ); - - ASSERT_EQUALS( "BtreeCursor b_1", clause->bson()[ "cursor" ].String() ); - } - }; - - /** Simple check that an explain result can contain a large value of n. */ - class LargeN : public Base { - public: - void run() { - Lock::GlobalWrite lk; - - Client::Context ctx( ns() ); - - shared_ptr<Cursor> cursor( getOptimizedCursor( ns(), BSONObj() ) ); - ExplainSinglePlanQueryInfo explainHelper; - explainHelper.notePlan( *cursor, false, false ); - explainHelper.noteIterate( false, false, false, *cursor ); - - shared_ptr<ExplainQueryInfo> explain = explainHelper.queryInfo(); - explain->reviseN( 3000000000000LL ); - - // large n could be stored either in bson as long or double - // retrieve value using safeNumberLong() so that we don't have to guess - // internal type - BSONObj obj = explain->bson(); - BSONElement e = obj.getField( "n" ); - long long n = e.safeNumberLong(); - ASSERT_EQUALS( 3000000000000LL, n ); - } - }; - - /** - * Base class to test yield count when a cursor yields and its current iterate is deleted. - */ - class NYieldsAdvanceBase : public Base { - protected: - virtual int aValueToDelete() const = 0; - virtual int numDocuments() const { - // Above the takeover threshold at 101 matches. - return 200; - } - private: - virtual void setupCollection() { - // Insert some matching documents. - for( int i = 0; i < numDocuments(); ++i ) { - _cli.insert( ns(), BSON( "a" << i << "b" << 0 ) ); - } - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - } - virtual BSONObj query() const { - // A query that will match all inserted documents and generate a - // QueryOptimizerCursor with a single candidate plan, on the a:1 index. - return BSON( "a" << GTE << 0 << "b" << 0 ); - } - virtual void handleCursor() { - _clientCursor.reset - ( new mongo::ClientCursor( QueryOption_NoCursorTimeout, _cursor, - ns() ) ); - // Advance to the document with the specified 'a' value. - while( _cursor->current()[ "a" ].number() != aValueToDelete() ) { - _cursor->advance(); - } - - // Yield the cursor. - mongo::ClientCursor::YieldData yieldData; - _clientCursor->prepareToYield( yieldData ); - - // Remove the document with the specified 'a' value. - _cli.remove( ns(), BSON( "a" << aValueToDelete() ) ); - - // Recover the cursor and note that a yield occurred. - _clientCursor->recoverFromYield( yieldData ); - _cursor->noteYield(); - - // Exhaust the cursor (not strictly necessary). - while( _cursor->advance() ); - } - virtual void checkExplain() { - // The cursor was yielded once. - ASSERT_EQUALS( 1, _explain[ "nYields" ].number() ); - } - mongo::ClientCursorHolder _clientCursor; - }; - - /** nYields reporting of a QueryOptimizerCursor before it enters takeover mode. */ - class NYieldsAdvanceBasic : public NYieldsAdvanceBase { - virtual int aValueToDelete() const { - // Before the MultiCursor takes over at 101 matches. - return 50; - } - }; - - /** nYields reporting of a QueryOptimizerCursor after it enters takeover mode. */ - class NYieldsAdvanceTakeover : public NYieldsAdvanceBase { - virtual int aValueToDelete() const { return 150; } - }; - - /** nYields reporting when a cursor is exhausted during a yield. */ - class NYieldsAdvanceDeleteLast : public NYieldsAdvanceBase { - virtual int aValueToDelete() const { return 0; } - virtual int numDocuments() const { return 1; } - }; - - /** nYields reporting when a takeover cursor is exhausted during a yield. */ - class NYieldsAdvanceDeleteLastTakeover : public NYieldsAdvanceBase { - virtual int aValueToDelete() const { return 199; } - }; - - /** Explain reporting on yield recovery failure. */ - class YieldRecoveryFailure : public Base { - virtual void setupCollection() { - // Insert some matching documents. - for( int i = 0; i < 10; ++i ) { - _cli.insert( ns(), BSON( "a" << 1 << "b" << 1 ) ); - } - _cli.ensureIndex( ns(), BSON( "a" << 1 ) ); - } - virtual void handleCursor() { - _clientCursor.reset - ( new mongo::ClientCursor( QueryOption_NoCursorTimeout, _cursor, - ns() ) ); - - // Scan 5 matches. - int numMatches = 0; - while( numMatches < 5 ) { - if ( _cursor->currentMatches() && !_cursor->getsetdup( _cursor->currLoc() ) ) { - _cursor->noteIterate( true, true, false ); - ++numMatches; - } - _cursor->noteIterate( false, true, false ); - _cursor->advance(); - } - - // Yield the cursor. - mongo::ClientCursor::YieldData yieldData; - _clientCursor->prepareToYield( yieldData ); - - // Drop the collection and invalidate the ClientCursor. - { - dbtemprelease release; - _cli.dropCollection( ns() ); - } - ASSERT( !_clientCursor->recoverFromYield( yieldData ) ); - - // Note a yield after the ClientCursor was invalidated. - _cursor->noteYield(); - } - virtual void checkExplain() { - // The cursor was yielded once. - ASSERT_EQUALS( 1, _explain[ "nYields" ].number() ); - // Five matches were scanned. - ASSERT_EQUALS( 5, _explain[ "n" ].number() ); - // Matches were identified for each plan. - BSONObjIterator plans( _explain[ "allPlans" ].embeddedObject() ); - while( plans.more() ) { - ASSERT( 0 < plans.next().Obj()[ "n" ].number() ); - } - } - mongo::ClientCursorHolder _clientCursor; - }; - - } // namespace Explain - - class All : public Suite { - public: - All() : Suite( "queryoptimizercursor" ) {} - - void setupTests() { - add<CachedMatchCounter::Count>(); - add<CachedMatchCounter::Accumulate>(); - add<CachedMatchCounter::Dedup>(); - add<CachedMatchCounter::Nscanned>(); - add<SmallDupSet::Upgrade>(); - add<SmallDupSet::UpgradeRead>(); - add<SmallDupSet::UpgradeWrite>(); - add<DurationTimerStop>(); - add<Empty>(); - add<Unindexed>(); - add<Basic>(); - add<NoMatch>(); - add<Interleaved>(); - add<NotMatch>(); - add<StopInterleaving>(); - add<TakeoverWithDup>(); - add<TakeoverWithNonMatches>(); - add<TakeoverWithTakeoverDup>(); - add<BasicOr>(); - add<OrFirstClauseEmpty>(); - add<OrSecondClauseEmpty>(); - add<OrMultipleClausesEmpty>(); - add<TakeoverCountOr>(); - add<TakeoverEndOfOrClause>(); - add<TakeoverBeforeEndOfOrClause>(); - add<TakeoverAfterEndOfOrClause>(); - add<ManualMatchingDeduping>(); - add<ManualMatchingUsingCurrKey>(); - add<ManualMatchingDedupingTakeover>(); - add<Singlekey>(); - add<Multikey>(); - add<AddOtherPlans>(); - add<AddOtherPlansDelete>(); - add<AddOtherPlansContinuousDelete>(); - add<AddOtherPlansWhenOptimalBecomesNonOptimal>(); - add<OrRangeElimination>(); - add<OrDedup>(); - add<EarlyDups>(); - add<OrPopInTakeover>(); - add<OrCollectionScanAbort>(); - add<Yield::NoOp>(); - add<Yield::Delete>(); - add<Yield::DeleteContinue>(); - add<Yield::DeleteContinueFurther>(); - add<Yield::Update>(); - add<Yield::Drop>(); - add<Yield::DropOr>(); - add<Yield::RemoveOr>(); - add<Yield::CappedOverwrite>(); - add<Yield::DropIndex>(); - add<Yield::MultiplePlansNoOp>(); - add<Yield::MultiplePlansAdvanceNoOp>(); - add<Yield::MultiplePlansDelete>(); - add<Yield::MultiplePlansDeleteOr>(); - add<Yield::MultiplePlansDeleteOrAdvance>(); - add<Yield::MultiplePlansCappedOverwrite>(); - add<Yield::MultiplePlansCappedOverwriteManual>(); - add<Yield::MultiplePlansCappedOverwriteManual2>(); - add<Yield::Takeover>(); - add<Yield::TakeoverBasic>(); - add<Yield::InactiveCursorAdvance>(); - add<Yield::TakeoverUpdateKeyAtEndOfIteration>(); - add<Yield::TakeoverUpdateKeyAtEndOfClause>(); - add<Yield::TakeoverUpdateKeyPrecedingEmptyClause>(); - add<OrderId>(); - add<OrderMultiIndex>(); - add<OrderReject>(); - add<OrderNatural>(); - add<OrderUnindexed>(); - add<RecordedOrderInvalid>(); - add<KillOp>(); - add<KillOpFirstClause>(); - add<Nscanned>(); - add<TouchEarlierIterate::Basic>(); - add<TouchEarlierIterate::Delete>(); - add<TouchEarlierIterate::DeleteMultiple>(); - add<TouchEarlierIterate::DeleteAfterYield>(); - add<TouchEarlierIterate::Takeover>(); - add<TouchEarlierIterate::TakeoverDeleteMultiple>(); - add<TouchEarlierIterate::UnindexedTakeoverDeleteMultiple>(); - add<TouchEarlierIterate::TakeoverDeleteMultipleMultiAdvance>(); - add<InitialCappedWrapYieldRecoveryFailure>(); - add<TakeoverCappedWrapYieldRecoveryFailure>(); - add<ClientCursor::Invalidate>(); - add<ClientCursor::Timeout>(); - add<ClientCursor::AboutToDeleteRecoverFromYield>(); - add<ClientCursor::AboutToDeletePrepareToYield>(); - add<ClientCursor::Drop>(); - add<AllowOutOfOrderPlan>(); - add<NoTakeoverByOutOfOrderPlan>(); - add<OutOfOrderOnlyTakeover>(); - add<CoveredIndex>(); - add<CoveredIndexTakeover>(); - add<SaveGoodIndex>(); - add<PossibleInOrderPlans>(); - add<PossibleOutOfOrderPlans>(); - add<PossibleBothPlans>(); - add<AbortOutOfOrderPlans>(); - add<AbortOutOfOrderPlanOnLastMatch>(); - add<AbortOutOfOrderPlansBeforeAddOtherPlans>(); - add<TakeoverOrRangeElimination>(); - add<TakeoverOrDedups>(); - add<TakeoverOrDifferentIndex>(); - add<TakeoverOrderedPlanDupsOutOfOrderPlan>(); - add<ElemMatchKey>(); - add<GetCursor::NoConstraints>(); - add<GetCursor::SimpleId>(); - add<GetCursor::OptimalIndex>(); - add<GetCursor::SimpleKeyMatch>(); - add<GetCursor::PreventOutOfOrderPlan>(); - add<GetCursor::AllowOutOfOrderPlan>(); - add<GetCursor::BestSavedOutOfOrder>(); - //add<GetCursor::BestSavedOptimal>(); - //add<GetCursor::BestSavedNotOptimal>(); - add<GetCursor::MultiIndex>(); - add<GetCursor::Hint>(); - add<GetCursor::Snapshot>(); - add<GetCursor::SnapshotCappedColl>(); - add<GetCursor::Min>(); - add<GetCursor::Max>(); - add<GetCursor::RequireIndex::NoConstraints>(); - add<GetCursor::RequireIndex::SimpleId>(); - add<GetCursor::RequireIndex::UnindexedQuery>(); - add<GetCursor::RequireIndex::IndexedQuery>(); - add<GetCursor::RequireIndex::SecondOrClauseIndexed>(); - add<GetCursor::RequireIndex::SecondOrClauseUnindexed>(); - add<GetCursor::RequireIndex::SecondOrClauseUnindexedUndetected>(); - //add<GetCursor::RequireIndex::RecordedUnindexedPlan>(); - add<GetCursor::IdElseNatural::AllowOptimalNaturalPlan>(); - add<GetCursor::IdElseNatural::AllowOptimalIdPlan>(); - add<GetCursor::IdElseNatural::HintedIdForQuery>( BSON( "_id" << 1 ) ); - add<GetCursor::IdElseNatural::HintedIdForQuery>( BSON( "a" << 1 ) ); - add<GetCursor::IdElseNatural::HintedIdForQuery>( BSON( "_id" << 1 << "a" << 1 ) ); - add<GetCursor::IdElseNatural::HintedNaturalForQuery>( BSONObj() ); - // now capped collections have _id index by default, so skip these - //add<GetCursor::IdElseNatural::HintedNaturalForQuery>( BSON( "_id" << 1 ) ); - //add<GetCursor::IdElseNatural::HintedNaturalForQuery>( BSON( "a" << 1 ) ); - //add<GetCursor::IdElseNatural::HintedNaturalForQuery>( BSON( "_id" << 1 << "a" << 1 ) ); - add<GetCursor::MatcherValidation>(); - add<GetCursor::MatcherSet>(); - add<GetCursor::MatcherValidate>(); - add<GetCursor::IntervalCursor>(); - add<Explain::ClearRecordedIndex>(); - add<Explain::Initial>(); - add<Explain::Empty>(); - add<Explain::SimpleCount>(); - add<Explain::IterateOnly>(); - add<Explain::ExtraMatchChecks>(); - add<Explain::PartialIteration>(); - add<Explain::Multikey>(); - add<Explain::MultikeyInitial>(); - add<Explain::BecomesMultikey>(); - add<Explain::CountAndYield>(); - add<Explain::MultipleClauses>(); - add<Explain::MultiCursorTakeover>(); - add<Explain::NChunkSkipsTakeover>(); - add<Explain::CoveredIndex>(); - add<Explain::CoveredIndexTakeover>(); - add<Explain::VirtualPickedPlan>(); - add<Explain::LargeN>(); - add<Explain::NYieldsAdvanceBasic>(); - add<Explain::NYieldsAdvanceTakeover>(); - add<Explain::NYieldsAdvanceDeleteLast>(); - add<Explain::NYieldsAdvanceDeleteLastTakeover>(); - add<Explain::YieldRecoveryFailure>(); - } - } myall; - -} // namespace QueryOptimizerTests diff --git a/src/mongo/dbtests/queryoptimizertests2.cpp b/src/mongo/dbtests/queryoptimizertests2.cpp deleted file mode 100644 index 45dca25cea9..00000000000 --- a/src/mongo/dbtests/queryoptimizertests2.cpp +++ /dev/null @@ -1,829 +0,0 @@ -/** - * Copyright (C) 2009 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/>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the GNU Affero General Public License in all respects - * for all of the code used other than as permitted herein. If you modify - * file(s) with this exception, you may extend this exception to your - * version of the file(s), but you are not obligated to do so. If you do not - * wish to do so, delete this exception statement from your version. If you - * delete this exception statement from all source files in the program, - * then also delete it in the license file. - */ - -#include "mongo/pch.h" - -#include "mongo/db/query_optimizer_internal.h" - -#include "mongo/db/instance.h" -#include "mongo/db/json.h" -#include "mongo/db/ops/count.h" -#include "mongo/db/ops/delete.h" -#include "mongo/db/query_optimizer.h" -#include "mongo/db/query/new_find.h" -#include "mongo/db/queryutil.h" -#include "mongo/db/structure/collection.h" -#include "mongo/dbtests/dbtests.h" - - -namespace mongo { - extern void runQuery(Message& m, QueryMessage& q, Message &response ); -} // namespace mongo - -namespace { - - using boost::shared_ptr; - - namespace QueryPlanSetTests { - - class Base { - public: - Base() : _context( ns() ) { - string err; - userCreateNS( ns(), BSONObj(), err, false ); - } - virtual ~Base() { - if ( !nsd() ) - return; - cc().database()->dropCollection( ns() ); - } - protected: - static void assembleRequest( const string &ns, BSONObj query, int nToReturn, int nToSkip, BSONObj *fieldsToReturn, int queryOptions, Message &toSend ) { - // see query.h for the protocol we are using here. - BufBuilder b; - int opts = queryOptions; - b.appendNum(opts); - b.appendStr(ns); - b.appendNum(nToSkip); - b.appendNum(nToReturn); - query.appendSelfToBufBuilder(b); - if ( fieldsToReturn ) - fieldsToReturn->appendSelfToBufBuilder(b); - toSend.setData(dbQuery, b.buf(), b.len()); - } - QueryPattern makePattern( const BSONObj &query, const BSONObj &order ) { - FieldRangeSet frs( ns(), query, true, true ); - return QueryPattern( frs, order ); - } - shared_ptr<QueryPlanSet> makeQps( const BSONObj& query = BSONObj(), - const BSONObj& order = BSONObj(), - const BSONObj& hint = BSONObj(), - bool allowSpecial = true ) { - auto_ptr<FieldRangeSetPair> frsp( new FieldRangeSetPair( ns(), query ) ); - auto_ptr<FieldRangeSetPair> frspOrig( new FieldRangeSetPair( *frsp ) ); - return shared_ptr<QueryPlanSet> - ( QueryPlanSet::make( ns(), frsp, frspOrig, query, order, - shared_ptr<const ParsedQuery>(), hint, - QueryPlanGenerator::Use, BSONObj(), BSONObj(), - allowSpecial ) ); - } - static const char *ns() { return "unittests.QueryPlanSetTests"; } - static NamespaceDetails *nsd() { return nsdetails( ns() ); } - DBDirectClient &client() { return _client; } - Collection* collection() { return _context.db()->getCollection( ns() ); } - CollectionInfoCache* infoCache() { return collection()->infoCache(); } - private: - Lock::GlobalWrite lk_; - Client::Context _context; - DBDirectClient _client; - }; - - class ToString : public Base { - public: - void run() { - // Just test that we don't crash. - makeQps( BSON( "a" << 1 ) )->toString(); - } - }; - - class NoIndexes : public Base { - public: - void run() { - ASSERT_EQUALS( 1, makeQps( BSON( "a" << 4 ), BSON( "b" << 1 ) )->nPlans() ); - } - }; - - class Optimal : public Base { - public: - void run() { - Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); - Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "b_2" ); - BSONObj query = BSON( "a" << 4 ); - - // Only one optimal plan is added to the plan set. - ASSERT_EQUALS( 1, makeQps( query )->nPlans() ); - - // The optimal plan is recorded in the plan cache. - FieldRangeSet frs( ns(), query, true, true ); - CachedQueryPlan cachedPlan = - infoCache()->cachedQueryPlanForPattern( QueryPattern( frs, BSONObj() ) ); - ASSERT_EQUALS( BSON( "a" << 1 ), cachedPlan.indexKey() ); - CandidatePlanCharacter planCharacter = cachedPlan.planCharacter(); - ASSERT( planCharacter.mayRunInOrderPlan() ); - ASSERT( !planCharacter.mayRunOutOfOrderPlan() ); - } - }; - - class NoOptimal : public Base { - public: - void run() { - Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); - Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" ); - ASSERT_EQUALS( 3, makeQps( BSON( "a" << 4 ), BSON( "b" << 1 ) )->nPlans() ); - } - }; - - class NoSpec : public Base { - public: - void run() { - Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); - Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" ); - ASSERT_EQUALS( 1, makeQps()->nPlans() ); - } - }; - - class HintSpec : public Base { - public: - void run() { - Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); - Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" ); - ASSERT_EQUALS( 1, makeQps( BSON( "a" << 1 ), BSON( "b" << 1 ), - BSON( "hint" << BSON( "a" << 1 ) ) )->nPlans() ); - } - }; - - class HintName : public Base { - public: - void run() { - Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); - Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" ); - ASSERT_EQUALS( 1, makeQps( BSON( "a" << 1 ), BSON( "b" << 1 ), - BSON( "hint" << "a_1" ) )->nPlans() ); - } - }; - - class NaturalHint : public Base { - public: - void run() { - Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); - Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" ); - ASSERT_EQUALS( 1, makeQps( BSON( "a" << 1 ), BSON( "b" << 1 ), - BSON( "hint" << BSON( "$natural" << 1 ) ) )->nPlans() ); - } - }; - - class NaturalSort : public Base { - public: - void run() { - Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); - Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "b_2" ); - ASSERT_EQUALS( 1, makeQps( BSON( "a" << 1 ), BSON( "$natural" << 1 ) )->nPlans() ); - } - }; - - class BadHint : public Base { - public: - void run() { - ASSERT_THROWS( makeQps( BSON( "a" << 1 ), BSON( "b" << 1 ), - BSON( "hint" << "a_1" ) ), - AssertionException ); - } - }; - - class Count : public Base { - public: - void run() { - Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); - Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" ); - string err; - int errCode; - ASSERT_EQUALS( 0, runCount( ns(), BSON( "query" << BSON( "a" << 4 ) ), err, errCode ) ); - BSONObj one = BSON( "a" << 1 ); - BSONObj fourA = BSON( "a" << 4 ); - BSONObj fourB = BSON( "a" << 4 ); - theDataFileMgr.insertWithObjMod( ns(), one ); - ASSERT_EQUALS( 0, runCount( ns(), BSON( "query" << BSON( "a" << 4 ) ), err, errCode ) ); - theDataFileMgr.insertWithObjMod( ns(), fourA ); - ASSERT_EQUALS( 1, runCount( ns(), BSON( "query" << BSON( "a" << 4 ) ), err, errCode ) ); - theDataFileMgr.insertWithObjMod( ns(), fourB ); - ASSERT_EQUALS( 2, runCount( ns(), BSON( "query" << BSON( "a" << 4 ) ), err, errCode ) ); - ASSERT_EQUALS( 3, runCount( ns(), BSON( "query" << BSONObj() ), err, errCode ) ); - ASSERT_EQUALS( 3, runCount( ns(), BSON( "query" << BSON( "a" << GT << 0 ) ), err, errCode ) ); - // missing ns - ASSERT_EQUALS( -1, runCount( "unittests.missingNS", BSONObj(), err, errCode ) ); - // impossible match - ASSERT_EQUALS( 0, runCount( ns(), BSON( "query" << BSON( "a" << GT << 0 << LT << -1 ) ), err, errCode ) ); - } - }; - - class QueryMissingNs : public Base { - public: - QueryMissingNs() { mongo::unittest::log() << "querymissingns starts" << endl; } - ~QueryMissingNs() { - mongo::unittest::log() << "end QueryMissingNs" << endl; - } - void run() { - Message m; - assembleRequest( "unittests.missingNS", BSONObj(), 0, 0, 0, 0, m ); - DbMessage d(m); - QueryMessage q(d); - Message ret; - CurOp op(&cc()); - op.ensureStarted(); - newRunQuery( m, q, op, ret ); - ASSERT_EQUALS( 0, ((QueryResult*)ret.header())->nReturned ); - } - - }; - - class UnhelpfulIndex : public Base { - public: - void run() { - Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); - Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" ); - ASSERT_EQUALS( 2, makeQps( BSON( "a" << 1 << "c" << 2 ) )->nPlans() ); - } - }; - - class FindOne : public Base { - public: - void run() { - BSONObj one = BSON( "a" << 1 ); - theDataFileMgr.insertWithObjMod( ns(), one ); - BSONObj result; - ASSERT( Helpers::findOne( ns(), BSON( "a" << 1 ), result ) ); - ASSERT_THROWS( Helpers::findOne( ns(), BSON( "a" << 1 ), result, true ), AssertionException ); - Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); - ASSERT( Helpers::findOne( ns(), BSON( "a" << 1 ), result, true ) ); - } - }; - - class Delete : public Base { - public: - void run() { - Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); - for( int i = 0; i < 200; ++i ) { - BSONObj two = BSON( "a" << 2 ); - theDataFileMgr.insertWithObjMod( ns(), two ); - } - BSONObj one = BSON( "a" << 1 ); - theDataFileMgr.insertWithObjMod( ns(), one ); - BSONObj delSpec = BSON( "a" << 1 << "_id" << NE << 0 ); - deleteObjects( ns(), delSpec, false ); - - // QUERY_MIGRATION no plan caching. - //QueryPattern queryPattern = FieldRangeSet( ns(), delSpec, true, true ).pattern(); - //CachedQueryPlan cachedQueryPlan = infoCache()->cachedQueryPlanForPattern( queryPattern ); - //ASSERT_EQUALS( BSON( "a" << 1 ), cachedQueryPlan.indexKey() ); - //ASSERT_EQUALS( 1, cachedQueryPlan.nScanned() ); - } - }; - - class DeleteOneScan : public Base { - public: - void run() { - Helpers::ensureIndex( ns(), BSON( "_id" << 1 ), false, "_id_1" ); - BSONObj one = BSON( "_id" << 3 << "a" << 1 ); - BSONObj two = BSON( "_id" << 2 << "a" << 1 ); - BSONObj three = BSON( "_id" << 1 << "a" << -1 ); - theDataFileMgr.insertWithObjMod( ns(), one ); - theDataFileMgr.insertWithObjMod( ns(), two ); - theDataFileMgr.insertWithObjMod( ns(), three ); - deleteObjects( ns(), BSON( "_id" << GT << 0 << "a" << GT << 0 ), true ); - size_t numDocs = 0; - for( boost::shared_ptr<Cursor> c = theDataFileMgr.findAll( ns() ); c->ok(); c->advance() ) { - ++numDocs; - } - ASSERT_EQUALS(2U, numDocs); - } - }; - - class DeleteOneIndex : public Base { - public: - void run() { - Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a" ); - BSONObj one = BSON( "a" << 2 << "_id" << 0 ); - BSONObj two = BSON( "a" << 1 << "_id" << 1 ); - BSONObj three = BSON( "a" << 0 << "_id" << 2 ); - theDataFileMgr.insertWithObjMod( ns(), one ); - theDataFileMgr.insertWithObjMod( ns(), two ); - theDataFileMgr.insertWithObjMod( ns(), three ); - deleteObjects( ns(), BSON( "a" << GTE << 0 ), true ); - size_t numDocs = 0; - for( boost::shared_ptr<Cursor> c = theDataFileMgr.findAll( ns() ); c->ok(); c->advance() ) { - ++numDocs; - } - ASSERT_EQUALS(2U, numDocs); - } - }; - - class InQueryIntervals : public Base { - public: - void run() { - Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); - for( int i = 0; i < 10; ++i ) { - BSONObj temp = BSON( "a" << i ); - theDataFileMgr.insertWithObjMod( ns(), temp ); - } - BSONObj query = fromjson( "{a:{$in:[2,3,6,9,11]}}" ); - BSONObj order; - BSONObj hint = fromjson( "{$hint:{a:1}}" ); - auto_ptr< FieldRangeSetPair > frsp( new FieldRangeSetPair( ns(), query ) ); - shared_ptr<QueryPlanSet> s = makeQps( query, order, hint ); - scoped_ptr<QueryPlan> qp( QueryPlan::make( nsd(), 1, s->frsp(), frsp.get(), - query, order ) ); - boost::shared_ptr<Cursor> c = qp->newCursor(); - double expected[] = { 2, 3, 6, 9 }; - for( int i = 0; i < 4; ++i, c->advance() ) { - ASSERT_EQUALS( expected[ i ], c->current().getField( "a" ).number() ); - } - ASSERT( !c->ok() ); - - // now check reverse - { - order = BSON( "a" << -1 ); - auto_ptr< FieldRangeSetPair > frsp( new FieldRangeSetPair( ns(), query ) ); - shared_ptr<QueryPlanSet> s = makeQps( query, order, hint ); - scoped_ptr<QueryPlan> qp( QueryPlan::make( nsd(), 1, s->frsp(), frsp.get(), - query, order ) ); - boost::shared_ptr<Cursor> c = qp->newCursor(); - double expected[] = { 9, 6, 3, 2 }; - for( int i = 0; i < 4; ++i, c->advance() ) { - ASSERT_EQUALS( expected[ i ], c->current().getField( "a" ).number() ); - } - ASSERT( !c->ok() ); - } - } - }; - - class EqualityThenIn : public Base { - public: - void run() { - Helpers::ensureIndex( ns(), BSON( "a" << 1 << "b" << 1 ), false, "a_1_b_1" ); - for( int i = 0; i < 10; ++i ) { - BSONObj temp = BSON( "a" << 5 << "b" << i ); - theDataFileMgr.insertWithObjMod( ns(), temp ); - } - auto_ptr< FieldRangeSetPair > frsp( new FieldRangeSetPair( ns(), fromjson( "{a:5,b:{$in:[2,3,6,9,11]}}" ) ) ); - scoped_ptr<QueryPlan> qp( QueryPlan::make( nsd(), 1, *frsp, frsp.get(), - fromjson( "{a:5,b:{$in:[2,3,6,9,11]}}" ), - BSONObj() ) ); - boost::shared_ptr<Cursor> c = qp->newCursor(); - double expected[] = { 2, 3, 6, 9 }; - ASSERT( c->ok() ); - for( int i = 0; i < 4; ++i, c->advance() ) { - ASSERT( c->ok() ); - ASSERT_EQUALS( expected[ i ], c->current().getField( "b" ).number() ); - } - ASSERT( !c->ok() ); - } - }; - - class NotEqualityThenIn : public Base { - public: - void run() { - Helpers::ensureIndex( ns(), BSON( "a" << 1 << "b" << 1 ), false, "a_1_b_1" ); - for( int i = 0; i < 10; ++i ) { - BSONObj temp = BSON( "a" << 5 << "b" << i ); - theDataFileMgr.insertWithObjMod( ns(), temp ); - } - auto_ptr< FieldRangeSetPair > frsp( new FieldRangeSetPair( ns(), fromjson( "{a:{$gte:5},b:{$in:[2,3,6,9,11]}}" ) ) ); - scoped_ptr<QueryPlan> qp - ( QueryPlan::make( nsd(), 1, *frsp, frsp.get(), - fromjson( "{a:{$gte:5},b:{$in:[2,3,6,9,11]}}" ), - BSONObj() ) ); - boost::shared_ptr<Cursor> c = qp->newCursor(); - int matches[] = { 2, 3, 6, 9 }; - for( int i = 0; i < 4; ++i, c->advance() ) { - ASSERT_EQUALS( matches[ i ], c->current().getField( "b" ).number() ); - } - ASSERT( !c->ok() ); - } - }; - - /** Exclude special plan candidate if there are btree plan candidates. SERVER-4531 */ - class ExcludeSpecialPlanWhenBtreePlan : public Base { - public: - void run() { - Helpers::ensureIndex( ns(), BSON( "a" << "2d" ), false, "a_2d" ); - Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); - shared_ptr<QueryPlanSet> s = - makeQps( BSON( "a" << BSON_ARRAY( 0 << 0 ) << "b" << 1 ) ); - // Two query plans, btree and collection scan. - ASSERT_EQUALS( 2, s->nPlans() ); - // Not the geo plan. - ASSERT( s->firstPlan()->special().empty() ); - } - }; - - /** Exclude unindexed plan candidate if there is a special plan candidate. SERVER-4531 */ - class ExcludeUnindexedPlanWhenSpecialPlan : public Base { - public: - void run() { - Helpers::ensureIndex( ns(), BSON( "a" << "2d" ), false, "a_2d" ); - shared_ptr<QueryPlanSet> s = - makeQps( BSON( "a" << BSON_ARRAY( 0 << 0 ) << "b" << 1 ) ); - // Single query plan. - ASSERT_EQUALS( 1, s->nPlans() ); - // It's the geo plan. - ASSERT( !s->firstPlan()->special().empty() ); - } - }; - - class PossiblePlans : public Base { - public: - void run() { - client().ensureIndex( ns(), BSON( "a" << 1 ) ); - client().ensureIndex( ns(), BSON( "b" << 1 ) ); - - { - shared_ptr<QueryPlanSet> qps = makeQps( BSON( "a" << 1 ), BSONObj() ); - ASSERT_EQUALS( 1, qps->nPlans() ); - ASSERT( qps->possibleInOrderPlan() ); - ASSERT( qps->haveInOrderPlan() ); - ASSERT( !qps->possibleOutOfOrderPlan() ); - ASSERT( !qps->hasPossiblyExcludedPlans() ); - ASSERT( !qps->usingCachedPlan() ); - } - - { - shared_ptr<QueryPlanSet> qps = makeQps( BSON( "a" << 1 ), BSON( "b" << 1 ) ); - ASSERT_EQUALS( 3, qps->nPlans() ); - ASSERT( qps->possibleInOrderPlan() ); - ASSERT( qps->haveInOrderPlan() ); - ASSERT( qps->possibleOutOfOrderPlan() ); - ASSERT( !qps->hasPossiblyExcludedPlans() ); - ASSERT( !qps->usingCachedPlan() ); - } - - infoCache()->registerCachedQueryPlanForPattern( makePattern( BSON( "a" << 1 ), BSONObj() ), - CachedQueryPlan( BSON( "a" << 1 ), 1, - CandidatePlanCharacter( true, false ) ) ); - { - shared_ptr<QueryPlanSet> qps = makeQps( BSON( "a" << 1 ), BSONObj() ); - ASSERT_EQUALS( 1, qps->nPlans() ); - ASSERT( qps->possibleInOrderPlan() ); - ASSERT( qps->haveInOrderPlan() ); - ASSERT( !qps->possibleOutOfOrderPlan() ); - ASSERT( !qps->hasPossiblyExcludedPlans() ); - ASSERT( qps->usingCachedPlan() ); - } - - infoCache()->registerCachedQueryPlanForPattern( makePattern( BSON( "a" << 1 ), BSON( "b" << 1 ) ), - CachedQueryPlan( BSON( "a" << 1 ), 1, - CandidatePlanCharacter( true, true ) ) ); - - { - shared_ptr<QueryPlanSet> qps = makeQps( BSON( "a" << 1 ), BSON( "b" << 1 ) ); - ASSERT_EQUALS( 1, qps->nPlans() ); - ASSERT( qps->possibleInOrderPlan() ); - ASSERT( !qps->haveInOrderPlan() ); - ASSERT( qps->possibleOutOfOrderPlan() ); - ASSERT( qps->hasPossiblyExcludedPlans() ); - ASSERT( qps->usingCachedPlan() ); - } - - infoCache()->registerCachedQueryPlanForPattern( makePattern( BSON( "a" << 1 ), BSON( "b" << 1 ) ), - CachedQueryPlan( BSON( "b" << 1 ), 1, - CandidatePlanCharacter( true, true ) ) ); - { - shared_ptr<QueryPlanSet> qps = makeQps( BSON( "a" << 1 ), BSON( "b" << 1 ) ); - ASSERT_EQUALS( 1, qps->nPlans() ); - ASSERT( qps->possibleInOrderPlan() ); - ASSERT( qps->haveInOrderPlan() ); - ASSERT( qps->possibleOutOfOrderPlan() ); - ASSERT( qps->hasPossiblyExcludedPlans() ); - ASSERT( qps->usingCachedPlan() ); - } - - { - shared_ptr<QueryPlanSet> qps = makeQps( BSON( "a" << 1 ), BSON( "c" << 1 ) ); - ASSERT_EQUALS( 2, qps->nPlans() ); - ASSERT( !qps->possibleInOrderPlan() ); - ASSERT( !qps->haveInOrderPlan() ); - ASSERT( qps->possibleOutOfOrderPlan() ); - ASSERT( !qps->hasPossiblyExcludedPlans() ); - ASSERT( !qps->usingCachedPlan() ); - } - } - }; - - /** An unhelpful query plan will not be used if recorded in the query plan cache. */ - class AvoidUnhelpfulRecordedPlan : public Base { - public: - void run() { - client().ensureIndex( ns(), BSON( "a" << 1 ) ); - - // Record the {a:1} index for a {b:1} query. - infoCache()->registerCachedQueryPlanForPattern( makePattern( BSON( "b" << 1 ), BSONObj() ), - CachedQueryPlan( BSON( "a" << 1 ), 1, - CandidatePlanCharacter( true, false ) ) ); - - // The {a:1} index is not used for a {b:1} query because it generates an unhelpful - // plan. - shared_ptr<QueryPlanSet> qps = makeQps( BSON( "b" << 1 ), BSONObj() ); - ASSERT_EQUALS( 1, qps->nPlans() ); - ASSERT_EQUALS( BSON( "$natural" << 1 ), qps->firstPlan()->indexKey() ); - } - }; - - /** An unhelpful query plan will not be used if recorded in the query plan cache. */ - class AvoidDisallowedRecordedPlan : public Base { - public: - void run() { - client().insert( "unittests.system.indexes", - BSON( "ns" << ns() << - "key" << BSON( "a" << 1 ) << - "name" << client().genIndexName( BSON( "a" << 1 ) ) << - "sparse" << true ) ); - - // Record the {a:1} index for a {a:null} query. - infoCache()->registerCachedQueryPlanForPattern( makePattern( BSON( "a" << BSONNULL ), BSONObj() ), - CachedQueryPlan( BSON( "a" << 1 ), 1, - CandidatePlanCharacter( true, false ) ) ); - - // The {a:1} index is not used for an {a:{$exists:false}} query because it generates - // a disallowed plan. - shared_ptr<QueryPlanSet> qps = makeQps( BSON( "a" << BSON( "$exists" << false ) ), - BSONObj() ); - ASSERT_EQUALS( 1, qps->nPlans() ); - ASSERT_EQUALS( BSON( "$natural" << 1 ), qps->firstPlan()->indexKey() ); - } - }; - - /** Special plans are only selected when allowed. */ - class AllowSpecial : public Base { - public: - void run() { - BSONObj naturalIndex = BSON( "$natural" << 1 ); - BSONObj specialIndex = BSON( "a" << "2d" ); - BSONObj query = BSON( "a" << BSON_ARRAY( 0 << 0 ) ); - client().ensureIndex( ns(), specialIndex ); - - // The special plan is chosen if allowed. - assertSingleIndex( specialIndex, makeQps( query ) ); - - // The special plan is not chosen if not allowed - assertSingleIndex( naturalIndex, makeQps( query, BSONObj(), BSONObj(), false ) ); - - // Attempting to hint a special plan when not allowed triggers an assertion. - ASSERT_THROWS( makeQps( query, BSONObj(), BSON( "$hint" << specialIndex ), false ), - UserException ); - - // Attempting to use a geo operator when special plans are not allowed triggers an - // assertion. - ASSERT_THROWS( makeQps( BSON( "a" << BSON( "$near" << BSON_ARRAY( 0 << 0 ) ) ), - BSONObj(), BSONObj(), false ), - UserException ); - - // The special plan is not chosen if not allowed, even if cached. - infoCache()->registerCachedQueryPlanForPattern( makePattern( query, BSONObj() ), - CachedQueryPlan( specialIndex, 1, - CandidatePlanCharacter( true, false ) ) ); - assertSingleIndex( naturalIndex, makeQps( query, BSONObj(), BSONObj(), false ) ); - } - private: - void assertSingleIndex( const BSONObj& index, const shared_ptr<QueryPlanSet>& set ) { - ASSERT_EQUALS( 1, set->nPlans() ); - ASSERT_EQUALS( index, set->firstPlan()->indexKey() ); - } - }; - - } // namespace QueryPlanSetTests - - class Base { - public: - Base() : _ctx( ns() ) { - string err; - userCreateNS( ns(), BSONObj(), err, false ); - } - ~Base() { - if ( !nsd() ) - return; - string s( ns() ); - cc().database()->dropCollection( ns() ); - } - protected: - static const char *ns() { return "unittests.QueryOptimizerTests"; } - static NamespaceDetails *nsd() { return nsdetails( ns() ); } - QueryPattern makePattern( const BSONObj &query, const BSONObj &order ) { - FieldRangeSet frs( ns(), query, true, true ); - return QueryPattern( frs, order ); - } - shared_ptr<MultiPlanScanner> makeMps( const BSONObj &query, const BSONObj &order ) { - shared_ptr<MultiPlanScanner> ret( MultiPlanScanner::make( ns(), query, order ) ); - return ret; - } - DBDirectClient &client() { return _client; } - Collection* collection() { return _ctx.db()->getCollection( ns() ); } - CollectionInfoCache* infoCache() { return collection()->infoCache(); } - private: - Lock::GlobalWrite lk_; - Client::Context _ctx; - DBDirectClient _client; - }; - - namespace MultiPlanScannerTests { - class ToString : public Base { - public: - void run() { - scoped_ptr<MultiPlanScanner> multiPlanScanner - ( MultiPlanScanner::make( ns(), BSON( "a" << 1 ), BSONObj() ) ); - multiPlanScanner->toString(); // Just test that we don't crash. - } - }; - - class PossiblePlans : public Base { - public: - void run() { - client().ensureIndex( ns(), BSON( "a" << 1 ) ); - client().ensureIndex( ns(), BSON( "b" << 1 ) ); - - { - shared_ptr<MultiPlanScanner> mps = makeMps( BSON( "a" << 1 ), BSONObj() ); - ASSERT_EQUALS( 1, mps->currentNPlans() ); - ASSERT( mps->possibleInOrderPlan() ); - ASSERT( mps->haveInOrderPlan() ); - ASSERT( !mps->possibleOutOfOrderPlan() ); - ASSERT( !mps->hasPossiblyExcludedPlans() ); - } - - { - shared_ptr<MultiPlanScanner> mps = - makeMps( BSON( "a" << 1 ), BSON( "b" << 1 ) ); - ASSERT_EQUALS( 3, mps->currentNPlans() ); - ASSERT( mps->possibleInOrderPlan() ); - ASSERT( mps->haveInOrderPlan() ); - ASSERT( mps->possibleOutOfOrderPlan() ); - ASSERT( !mps->hasPossiblyExcludedPlans() ); - } - - infoCache()->registerCachedQueryPlanForPattern( makePattern( BSON( "a" << 1 ), BSONObj() ), - CachedQueryPlan( BSON( "a" << 1 ), 1, - CandidatePlanCharacter( true, false ) ) ); - { - shared_ptr<MultiPlanScanner> mps = makeMps( BSON( "a" << 1 ), BSONObj() ); - ASSERT_EQUALS( 1, mps->currentNPlans() ); - ASSERT( mps->possibleInOrderPlan() ); - ASSERT( mps->haveInOrderPlan() ); - ASSERT( !mps->possibleOutOfOrderPlan() ); - ASSERT( !mps->hasPossiblyExcludedPlans() ); - } - - infoCache()->registerCachedQueryPlanForPattern( makePattern( BSON( "a" << 1 ), BSON( "b" << 1 ) ), - CachedQueryPlan( BSON( "a" << 1 ), 1, - CandidatePlanCharacter( true, true ) ) ); - - { - shared_ptr<MultiPlanScanner> mps = - makeMps( BSON( "a" << 1 ), BSON( "b" << 1 ) ); - ASSERT_EQUALS( 1, mps->currentNPlans() ); - ASSERT( mps->possibleInOrderPlan() ); - ASSERT( !mps->haveInOrderPlan() ); - ASSERT( mps->possibleOutOfOrderPlan() ); - ASSERT( mps->hasPossiblyExcludedPlans() ); - } - - infoCache()->registerCachedQueryPlanForPattern( makePattern( BSON( "a" << 1 ), BSON( "b" << 1 ) ), - CachedQueryPlan( BSON( "b" << 1 ), 1, - CandidatePlanCharacter( true, true ) ) ); - - { - shared_ptr<MultiPlanScanner> mps = - makeMps( BSON( "a" << 1 ), BSON( "b" << 1 ) ); - ASSERT_EQUALS( 1, mps->currentNPlans() ); - ASSERT( mps->possibleInOrderPlan() ); - ASSERT( mps->haveInOrderPlan() ); - ASSERT( mps->possibleOutOfOrderPlan() ); - ASSERT( mps->hasPossiblyExcludedPlans() ); - } - - { - shared_ptr<MultiPlanScanner> mps = - makeMps( BSON( "a" << 1 ), BSON( "c" << 1 ) ); - ASSERT_EQUALS( 2, mps->currentNPlans() ); - ASSERT( !mps->possibleInOrderPlan() ); - ASSERT( !mps->haveInOrderPlan() ); - ASSERT( mps->possibleOutOfOrderPlan() ); - ASSERT( !mps->hasPossiblyExcludedPlans() ); - } - - { - shared_ptr<MultiPlanScanner> mps = - makeMps( fromjson( "{$or:[{a:1},{a:2}]}" ), BSON( "c" << 1 ) ); - ASSERT_EQUALS( 1, mps->currentNPlans() ); - ASSERT( !mps->possibleInOrderPlan() ); - ASSERT( !mps->haveInOrderPlan() ); - ASSERT( mps->possibleOutOfOrderPlan() ); - ASSERT( !mps->hasPossiblyExcludedPlans() ); - } - - { - shared_ptr<MultiPlanScanner> mps = - makeMps( fromjson( "{$or:[{a:1,b:1},{a:2,b:2}]}" ), BSONObj() ); - ASSERT_EQUALS( 3, mps->currentNPlans() ); - ASSERT( mps->possibleInOrderPlan() ); - ASSERT( mps->haveInOrderPlan() ); - ASSERT( !mps->possibleOutOfOrderPlan() ); - ASSERT( !mps->hasPossiblyExcludedPlans() ); - } - } - }; - - } // namespace MultiPlanScannerTests - - class BestGuess : public Base { - public: - void run() { - Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" ); - Helpers::ensureIndex( ns(), BSON( "b" << 1 ), false, "b_1" ); - BSONObj temp = BSON( "a" << 1 ); - theDataFileMgr.insertWithObjMod( ns(), temp ); - temp = BSON( "b" << 1 ); - theDataFileMgr.insertWithObjMod( ns(), temp ); - - boost::shared_ptr< Cursor > c = getBestGuessCursor( ns(), - BSON( "b" << 1 ), - BSON( "a" << 1 ) ); - ASSERT_EQUALS( string( "a" ), c->indexKeyPattern().firstElement().fieldName() ); - - c = getBestGuessCursor( ns(), BSON( "a" << 1 ), BSON( "b" << 1 ) ); - ASSERT_EQUALS( string( "b" ), c->indexKeyPattern().firstElementFieldName() ); - ASSERT( c->matcher() ); - ASSERT( c->currentMatches() ); // { b:1 } document - c->advance(); - ASSERT( !c->currentMatches() ); // { a:1 } document - - c = getBestGuessCursor( ns(), fromjson( "{b:1,$or:[{z:1}]}" ), BSON( "a" << 1 ) ); - ASSERT_EQUALS( string( "a" ), c->indexKeyPattern().firstElement().fieldName() ); - - c = getBestGuessCursor( ns(), fromjson( "{a:1,$or:[{y:1}]}" ), BSON( "b" << 1 ) ); - ASSERT_EQUALS( string( "b" ), c->indexKeyPattern().firstElementFieldName() ); - - FieldRangeSet frs( "ns", BSON( "a" << 1 ), true, true ); - { - infoCache()->registerCachedQueryPlanForPattern( frs.pattern( BSON( "b" << 1 ) ), - CachedQueryPlan( BSON( "a" << 1 ), 0, - CandidatePlanCharacter( true, true ) ) ); - } - - c = getBestGuessCursor( ns(), fromjson( "{a:1,$or:[{y:1}]}" ), BSON( "b" << 1 ) ); - ASSERT_EQUALS( string( "b" ), - c->indexKeyPattern().firstElement().fieldName() ); - } - }; - - class All : public Suite { - public: - All() : Suite( "queryoptimizer2" ) {} - - void setupTests() { - add<QueryPlanSetTests::ToString>(); - add<QueryPlanSetTests::NoIndexes>(); - add<QueryPlanSetTests::Optimal>(); - add<QueryPlanSetTests::NoOptimal>(); - add<QueryPlanSetTests::NoSpec>(); - add<QueryPlanSetTests::HintSpec>(); - add<QueryPlanSetTests::HintName>(); - add<QueryPlanSetTests::NaturalHint>(); - add<QueryPlanSetTests::NaturalSort>(); - add<QueryPlanSetTests::BadHint>(); - add<QueryPlanSetTests::Count>(); - add<QueryPlanSetTests::QueryMissingNs>(); - add<QueryPlanSetTests::UnhelpfulIndex>(); - add<QueryPlanSetTests::FindOne>(); - add<QueryPlanSetTests::Delete>(); - add<QueryPlanSetTests::DeleteOneScan>(); - add<QueryPlanSetTests::DeleteOneIndex>(); - add<QueryPlanSetTests::InQueryIntervals>(); - add<QueryPlanSetTests::EqualityThenIn>(); - add<QueryPlanSetTests::NotEqualityThenIn>(); - add<QueryPlanSetTests::ExcludeSpecialPlanWhenBtreePlan>(); - add<QueryPlanSetTests::ExcludeUnindexedPlanWhenSpecialPlan>(); - add<QueryPlanSetTests::PossiblePlans>(); - add<QueryPlanSetTests::AvoidUnhelpfulRecordedPlan>(); - add<QueryPlanSetTests::AvoidDisallowedRecordedPlan>(); - add<QueryPlanSetTests::AllowSpecial>(); - add<MultiPlanScannerTests::ToString>(); - add<MultiPlanScannerTests::PossiblePlans>(); - add<BestGuess>(); - } - } myall; - -} // namespace QueryOptimizerTests - diff --git a/src/mongo/dbtests/querytests.cpp b/src/mongo/dbtests/querytests.cpp index 6115210b699..caf028698ff 100644 --- a/src/mongo/dbtests/querytests.cpp +++ b/src/mongo/dbtests/querytests.cpp @@ -40,7 +40,6 @@ #include "mongo/db/parsed_query.h" #include "mongo/db/query/new_find.h" #include "mongo/db/query/lite_parsed_query.h" -#include "mongo/db/scanandorder.h" #include "mongo/db/structure/collection.h" #include "mongo/dbtests/dbtests.h" #include "mongo/util/timer.h" diff --git a/src/mongo/dbtests/queryutiltests.cpp b/src/mongo/dbtests/queryutiltests.cpp index ca5a8463593..af1605c1692 100644 --- a/src/mongo/dbtests/queryutiltests.cpp +++ b/src/mongo/dbtests/queryutiltests.cpp @@ -34,8 +34,6 @@ #include "mongo/db/instance.h" #include "mongo/db/json.h" #include "mongo/db/pdfile.h" -#include "mongo/db/query_optimizer_internal.h" -#include "mongo/db/querypattern.h" #include "mongo/db/queryutil.h" #include "mongo/db/structure/collection.h" #include "mongo/dbtests/dbtests.h" @@ -430,68 +428,6 @@ namespace QueryUtilTests { } }; - class QueryPatternBase { - protected: - static QueryPattern p( const BSONObj &query, const BSONObj &sort = BSONObj() ) { - return FieldRangeSet( "", query, true, true ).pattern( sort ); - } - }; - - class QueryPatternTest : public QueryPatternBase { - public: - void run() { - ASSERT( p( BSON( "a" << 1 ) ) == p( BSON( "a" << 1 ) ) ); - ASSERT( p( BSON( "a" << 1 ) ) == p( BSON( "a" << 5 ) ) ); - ASSERT( p( BSON( "a" << 1 ) ) != p( BSON( "b" << 1 ) ) ); - ASSERT( p( BSON( "a" << 1 ) ) != p( BSON( "a" << LTE << 1 ) ) ); - ASSERT( p( BSON( "a" << 1 ) ) != p( BSON( "a" << 1 << "b" << 2 ) ) ); - ASSERT( p( BSON( "a" << 1 << "b" << 3 ) ) != p( BSON( "a" << 1 ) ) ); - ASSERT( p( BSON( "a" << LT << 1 ) ) == p( BSON( "a" << LTE << 5 ) ) ); - ASSERT( p( BSON( "a" << LT << 1 << GTE << 0 ) ) == p( BSON( "a" << LTE << 5 << GTE << 0 ) ) ); - ASSERT( p( BSON( "a" << 1 ) ) < p( BSON( "a" << 1 << "b" << 1 ) ) ); - ASSERT( !( p( BSON( "a" << 1 << "b" << 1 ) ) < p( BSON( "a" << 1 ) ) ) ); - ASSERT( p( BSON( "a" << 1 ), BSON( "b" << 1 ) ) == p( BSON( "a" << 4 ), BSON( "b" << "a" ) ) ); - ASSERT( p( BSON( "a" << 1 ), BSON( "b" << 1 ) ) == p( BSON( "a" << 4 ), BSON( "b" << -1 ) ) ); - ASSERT( p( BSON( "a" << 1 ), BSON( "b" << 1 ) ) != p( BSON( "a" << 4 ), BSON( "c" << 1 ) ) ); - ASSERT( p( BSON( "a" << 1 ), BSON( "b" << 1 << "c" << -1 ) ) == p( BSON( "a" << 4 ), BSON( "b" << -1 << "c" << 1 ) ) ); - ASSERT( p( BSON( "a" << 1 ), BSON( "b" << 1 << "c" << 1 ) ) != p( BSON( "a" << 4 ), BSON( "b" << 1 ) ) ); - ASSERT( p( BSON( "a" << 1 ), BSON( "b" << 1 ) ) != p( BSON( "a" << 4 ), BSON( "b" << 1 << "c" << 1 ) ) ); - } - }; - - class QueryPatternEmpty : public QueryPatternBase { - public: - void run() { - ASSERT( p( BSON( "a" << GT << 5 << LT << 7 ) ) != - p( BSON( "a" << GT << 7 << LT << 5 ) ) ); - } - }; - - class QueryPatternNeConstraint : public QueryPatternBase { - public: - void run() { - ASSERT( p( BSON( "a" << NE << 5 ) ) != p( BSON( "a" << GT << 1 ) ) ); - ASSERT( p( BSON( "a" << NE << 5 ) ) != p( BSONObj() ) ); - ASSERT( p( BSON( "a" << NE << 5 ) ) == p( BSON( "a" << NE << "a" ) ) ); - } - }; - - /** Check QueryPattern categories for optimized bounds. */ - class QueryPatternOptimizedBounds { - public: - void run() { - // With unoptimized bounds, different inequalities yield different query patterns. - ASSERT( p( BSON( "a" << GT << 1 ), false ) != p( BSON( "a" << LT << 1 ), false ) ); - // SERVER-4675 Descriptive test - With optimized bounds, different inequalities - // yield different query patterns. - ASSERT( p( BSON( "a" << GT << 1 ), true ) == p( BSON( "a" << LT << 1 ), true ) ); - } - private: - static QueryPattern p( const BSONObj &query, bool optimize ) { - return FieldRangeSet( "", query, true, optimize ).pattern( BSONObj() ); - } - }; - class NoWhere { public: void run() { @@ -1700,82 +1636,6 @@ namespace QueryUtilTests { } }; - /** Check that clearIndexesForPatterns() clears recorded query plans. */ - class ClearIndexesForPatterns : public IndexBase { - public: - void run() { - index( BSON( "a" << 1 ) ); - BSONObj query = BSON( "a" << GT << 5 << LT << 5 ); - BSONObj sort = BSON( "a" << 1 ); - - Collection* collection = ctx()->db()->getCollection( ns() ); - verify( collection ); - CollectionInfoCache* cache = collection->infoCache(); - - // Record the a:1 index for the query's single and multi key query patterns. - QueryPattern singleKey = FieldRangeSet( ns(), query, true, true ).pattern( sort ); - cache->registerCachedQueryPlanForPattern( singleKey, - CachedQueryPlan( BSON( "a" << 1 ), 1, - CandidatePlanCharacter( true, true ) ) ); - QueryPattern multiKey = FieldRangeSet( ns(), query, false, true ).pattern( sort ); - cache->registerCachedQueryPlanForPattern( multiKey, - CachedQueryPlan( BSON( "a" << 1 ), 5, - CandidatePlanCharacter( true, true ) ) ); - - // The single and multi key fields for this query must differ for the test to be - // valid. - ASSERT( singleKey != multiKey ); - - // Clear the recorded query plans using clearIndexesForPatterns. - FieldRangeSetPair frsp( ns(), query ); - QueryUtilIndexed::clearIndexesForPatterns( frsp, sort ); - - // Check that the recorded query plans were cleared. - ASSERT_EQUALS( BSONObj(), cache->cachedQueryPlanForPattern( singleKey ).indexKey() ); - ASSERT_EQUALS( BSONObj(), cache->cachedQueryPlanForPattern( multiKey ).indexKey() ); - } - }; - - /** Check query plan returned by bestIndexForPatterns(). */ - class BestIndexForPatterns : public IndexBase { - public: - void run() { - index( BSON( "a" << 1 ) ); - index( BSON( "b" << 1 ) ); - BSONObj query = BSON( "a" << GT << 5 << LT << 5 ); - BSONObj sort = BSON( "a" << 1 ); - - Collection* collection = ctx()->db()->getCollection( ns() ); - verify( collection ); - CollectionInfoCache* cache = collection->infoCache(); - - // No query plan is returned when none has been recorded. - FieldRangeSetPair frsp( ns(), query ); - ASSERT_EQUALS( BSONObj(), - QueryUtilIndexed::bestIndexForPatterns( frsp, sort ).indexKey() ); - - // A multikey index query plan is returned if recorded. - QueryPattern multiKey = FieldRangeSet( ns(), query, false, true ).pattern( sort ); - cache->registerCachedQueryPlanForPattern( multiKey, - CachedQueryPlan( BSON( "a" << 1 ), 5, - CandidatePlanCharacter( true, true ) ) ); - ASSERT_EQUALS( BSON( "a" << 1 ), - QueryUtilIndexed::bestIndexForPatterns( frsp, sort ).indexKey() ); - - // A non multikey index query plan is preferentially returned if recorded. - QueryPattern singleKey = FieldRangeSet( ns(), query, true, true ).pattern( sort ); - cache->registerCachedQueryPlanForPattern( singleKey, - CachedQueryPlan( BSON( "b" << 1 ), 5, - CandidatePlanCharacter( true, true ) ) ); - ASSERT_EQUALS( BSON( "b" << 1 ), - QueryUtilIndexed::bestIndexForPatterns( frsp, sort ).indexKey() ); - - // The single and multi key fields for this query must differ for the test to be - // valid. - ASSERT( singleKey != multiKey ); - } - }; - } // namespace FieldRangeSetPairTests namespace FieldRangeVectorTests { @@ -2923,10 +2783,6 @@ namespace QueryUtilTests { add<FieldRangeTests::NonSingletonOr>(); add<FieldRangeTests::Empty>(); add<FieldRangeTests::Equality>(); - add<FieldRangeTests::QueryPatternTest>(); - add<FieldRangeTests::QueryPatternEmpty>(); - add<FieldRangeTests::QueryPatternNeConstraint>(); - add<FieldRangeTests::QueryPatternOptimizedBounds>(); add<FieldRangeTests::NoWhere>(); add<FieldRangeTests::Numeric>(); add<FieldRangeTests::InLowerBound>(); @@ -3060,8 +2916,6 @@ namespace QueryUtilTests { add<FieldRangeSetPairTests::NoNonUniversalRanges>(); add<FieldRangeSetPairTests::MatchPossible>(); add<FieldRangeSetPairTests::MatchPossibleForIndex>(); - add<FieldRangeSetPairTests::ClearIndexesForPatterns>(); - add<FieldRangeSetPairTests::BestIndexForPatterns>(); add<FieldRangeVectorTests::ToString>(); add<FieldRangeVectorTests::HasAllIndexedRanges>(); add<FieldRangeVectorTests::SingleInterval>(); diff --git a/src/mongo/dbtests/repltests.cpp b/src/mongo/dbtests/repltests.cpp index 8ca970c17f2..5da329e645d 100644 --- a/src/mongo/dbtests/repltests.cpp +++ b/src/mongo/dbtests/repltests.cpp @@ -36,7 +36,6 @@ #include "mongo/db/db.h" #include "mongo/db/instance.h" #include "mongo/db/json.h" -#include "mongo/db/query_plan.h" #include "mongo/db/queryutil.h" #include "mongo/db/repl/master_slave.h" #include "mongo/db/repl/oplog.h" |