diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/mongo/db/clientcursor.cpp | 22 | ||||
-rw-r--r-- | src/mongo/db/clientcursor.h | 21 | ||||
-rw-r--r-- | src/mongo/db/ops/query.cpp | 17 | ||||
-rw-r--r-- | src/mongo/db/query/cached_plan_runner.h | 11 | ||||
-rw-r--r-- | src/mongo/db/query/canonical_query.cpp | 7 | ||||
-rw-r--r-- | src/mongo/db/query/canonical_query.h | 1 | ||||
-rw-r--r-- | src/mongo/db/query/multi_plan_runner.cpp | 8 | ||||
-rw-r--r-- | src/mongo/db/query/multi_plan_runner.h | 8 | ||||
-rw-r--r-- | src/mongo/db/query/new_find.cpp | 279 | ||||
-rw-r--r-- | src/mongo/db/query/new_find.h | 3 | ||||
-rw-r--r-- | src/mongo/db/query/query_planner.cpp | 3 | ||||
-rw-r--r-- | src/mongo/db/query/runner.h | 10 | ||||
-rw-r--r-- | src/mongo/db/query/simple_plan_runner.h | 15 | ||||
-rw-r--r-- | src/mongo/db/query/single_solution_runner.h | 76 |
14 files changed, 417 insertions, 64 deletions
diff --git a/src/mongo/db/clientcursor.cpp b/src/mongo/db/clientcursor.cpp index 54bbcf97e84..86e54eff3eb 100644 --- a/src/mongo/db/clientcursor.cpp +++ b/src/mongo/db/clientcursor.cpp @@ -57,21 +57,24 @@ namespace mongo { ClientCursor::ClientCursor(int qopts, const shared_ptr<Cursor>& c, const string& ns, BSONObj query) : _ns(ns), _query(query), _c(c), _yieldSometimesTracker(128, 10) { - init(qopts); + _queryOptions = qopts; _doingDeletes = false; + init(); } - ClientCursor::ClientCursor(int qopts, Runner* runner, const string& ns, BSONObj query) - : _ns(ns), _query(query), _runner(runner), _yieldSometimesTracker(128, 10) { - init(qopts); + ClientCursor::ClientCursor(Runner* runner) : _yieldSometimesTracker(128, 10) { + _runner.reset(runner); + _ns = runner->getQuery().getParsed().ns(); + _query = runner->getQuery().getParsed().getFilter(); + _queryOptions = runner->getQuery().getParsed().getOptions(); + init(); } - void ClientCursor::init(int qopts) { + void ClientCursor::init() { _db = cc().database(); verify( _db ); verify( _db->ownsNS( _ns ) ); - _queryOptions = qopts; _idleAgeMillis = 0; _leftoverMaxTimeMicros = 0; _pinValue = 0; @@ -79,7 +82,7 @@ namespace mongo { Lock::assertAtLeastReadLocked(_ns); - if (qopts & QueryOption_NoCursorTimeout) { + if (_queryOptions & QueryOption_NoCursorTimeout) { // cursors normally timeout after an inactivity period to prevent excess memory use // setting this prevents timeout of the cursor in question. ++_pinValue; @@ -150,10 +153,13 @@ namespace mongo { bool shouldDelete = false; if (NULL != cc->_runner.get()) { + verify(NULL == cc->c()); shouldDelete = true; } // Begin cursor-only else if (cc->c()->shouldDestroyOnNSDeletion()) { + verify(NULL == cc->_runner.get()); + if (isDB) { // already checked that db matched above dassert(str::startsWith(cc->_ns.c_str(), ns)); @@ -419,7 +425,9 @@ namespace mongo { } } + // DEPRECATED only used by Cursor. void ClientCursor::storeOpForSlave( DiskLoc last ) { + verify(NULL == _runner.get()); if ( ! ( _queryOptions & QueryOption_OplogReplay )) return; diff --git a/src/mongo/db/clientcursor.h b/src/mongo/db/clientcursor.h index 4f739a126b3..98eab57add4 100644 --- a/src/mongo/db/clientcursor.h +++ b/src/mongo/db/clientcursor.h @@ -49,7 +49,7 @@ namespace mongo { ClientCursor(int qopts, const shared_ptr<Cursor>& c, const string& ns, BSONObj query = BSONObj()); - ClientCursor(int qopts, Runner* runner, const string& ns, BSONObj query = BSONObj()); + ClientCursor(Runner* runner); ~ClientCursor(); @@ -158,7 +158,6 @@ namespace mongo { // Replication-related stuff. TODO: Document and clean. // - void storeOpForSlave( DiskLoc last ); void updateSlaveLocation( CurOp& curop ); void slaveReadTill( const OpTime& t ) { _slaveReadTill = t; } /** Just for testing. */ @@ -168,7 +167,9 @@ namespace mongo { // Query-specific functionality that may be adapted for the Runner. // - // Used by ops/query.cpp to stash how many results hvae been returned by a query. + Runner* getRunner() const { return _runner.get(); } + + // Used by ops/query.cpp to stash how many results have been returned by a query. int pos() const { return _pos; } void incPos(int n) { _pos += n; } void setPos(int n) { _pos = n; } @@ -214,6 +215,8 @@ namespace mongo { // 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; } @@ -260,7 +263,7 @@ namespace mongo { * Initialization common between Cursor and Runner. * TODO: Remove when we're all-runner. */ - void init(int qopts); + void init(); /** * Allocates a new CursorId. @@ -294,7 +297,7 @@ namespace mongo { unsigned _pinValue; // The namespace we're operating on. - const string _ns; + string _ns; // The database we're operating on. Database* _db; @@ -303,7 +306,7 @@ namespace mongo { int _pos; // The query that prompted this ClientCursor. Only used for debugging. - const BSONObj _query; + BSONObj _query; // See the QueryOptions enum in dbclient.h int _queryOptions; @@ -317,7 +320,11 @@ namespace mongo { // TODO: Document. uint64_t _leftoverMaxTimeMicros; - // TODO: document better. Somehow used in sharding. + // For chunks that are being migrated, there is a period of time when that chunks data is in + // two shards, the donor and the receiver one. That data is picked up by a cursor on the + // receiver side, even before the migration was decided. The CollectionMetadata allow one + // to inquiry if any given document of the collection belongs indeed to this shard or if it + // is coming from (or a vestige of) an ongoing migration. CollectionMetadataPtr _collMetadata; // diff --git a/src/mongo/db/ops/query.cpp b/src/mongo/db/ops/query.cpp index 41a390d397e..19be9499804 100644 --- a/src/mongo/db/ops/query.cpp +++ b/src/mongo/db/ops/query.cpp @@ -115,6 +115,10 @@ namespace mongo { int pass, bool& exhaust, bool* isCursorAuthorized ) { + if (useNewQuerySystem) { + return newGetMore(ns, ntoreturn, cursorid, curop, pass, exhaust, isCursorAuthorized); + } + exhaust = false; int bufSize = 512 + sizeof( QueryResult ) + MaxBytesToReturnToClientAtOnce; @@ -181,10 +185,10 @@ namespace mongo { while ( 1 ) { if ( !c->ok() ) { if ( c->tailable() ) { - /* when a tailable cursor hits "EOF", ok() goes false, and current() is null. however - advance() can still be retries as a reactivation attempt. when there is new data, it will - return true. that's what we are doing here. - */ + // when a tailable cursor hits "EOF", ok() goes false, and current() is + // null. however advance() can still be retries as a reactivation attempt. + // when there is new data, it will return true. that's what we are doing + // here. if ( c->advance() ) continue; @@ -835,7 +839,9 @@ namespace mongo { // we might be missing some data // and its safe to send this as mongos can resend // at this point - throw SendStaleConfigException( ns , "version changed during initial query", shardingVersionAtStart, shardingState.getVersion( ns ) ); + throw SendStaleConfigException(ns, "version changed during initial query", + shardingVersionAtStart, + shardingState.getVersion(ns)); } parentPageFaultSection.reset(0); @@ -1060,6 +1066,7 @@ namespace mongo { } if (useNewQuerySystem) { + // TODO: Copy prequel curop debugging into runNewQuery return newRunQuery(m, q, curop, result); } diff --git a/src/mongo/db/query/cached_plan_runner.h b/src/mongo/db/query/cached_plan_runner.h index c034b3659fa..ce2b87c7980 100644 --- a/src/mongo/db/query/cached_plan_runner.h +++ b/src/mongo/db/query/cached_plan_runner.h @@ -72,16 +72,17 @@ namespace mongo { return false; } - /** - * TODO: Explicit yielding is deprecated pending a ClientCursor rewrite. - */ - virtual void yield() { _runner->yield(); } - virtual void unYield() { _runner->unYield(); } + virtual void saveState() { _runner->saveState(); } + virtual void restoreState() { _runner->restoreState(); } virtual void invalidate(const DiskLoc& dl) { _runner->invalidate(dl); } + virtual const CanonicalQuery& getQuery() { + return *_canonicalQuery; + } + private: scoped_ptr<CanonicalQuery> _canonicalQuery; scoped_ptr<CachedSolution> _cachedQuery; diff --git a/src/mongo/db/query/canonical_query.cpp b/src/mongo/db/query/canonical_query.cpp index 91b0522a408..9caa9bd6c45 100644 --- a/src/mongo/db/query/canonical_query.cpp +++ b/src/mongo/db/query/canonical_query.cpp @@ -28,6 +28,13 @@ namespace mongo { // TODO: ParsedQuery throws. Fix it to return error. cq->_pq.reset(new ParsedQuery(qm)); + // TODO: If pq.hasOption(QueryOption_CursorTailable) make sure it's a capped collection and + // make sure the order(??) is $natural: 1. + + // TODO: Do we want to do this too?: + //if ( pq.getFields() != NULL ) + // pq.getFields()->validateQuery( query ); + StatusWithMatchExpression swme = MatchExpressionParser::parse(cq->_pq->getFilter()); if (!swme.isOK()) { return swme.getStatus(); diff --git a/src/mongo/db/query/canonical_query.h b/src/mongo/db/query/canonical_query.h index 6647360d461..2560df03437 100644 --- a/src/mongo/db/query/canonical_query.h +++ b/src/mongo/db/query/canonical_query.h @@ -40,6 +40,7 @@ namespace mongo { // MatchExpression* root() const { return _root.get(); } BSONObj getQueryObj() const { return _pq->getFilter(); } + const ParsedQuery& getParsed() const { return *_pq; } string toString() const; diff --git a/src/mongo/db/query/multi_plan_runner.cpp b/src/mongo/db/query/multi_plan_runner.cpp index fc4eb33adf7..5ee561dfa84 100644 --- a/src/mongo/db/query/multi_plan_runner.cpp +++ b/src/mongo/db/query/multi_plan_runner.cpp @@ -38,18 +38,18 @@ namespace mongo { _candidates.push_back(CandidatePlan(solution, root, ws)); } - void MultiPlanRunner::yield() { + void MultiPlanRunner::saveState() { if (NULL != _bestPlanRunner) { - _bestPlanRunner->yield(); + _bestPlanRunner->saveState(); } else { yieldAllPlans(); } } - void MultiPlanRunner::unYield() { + void MultiPlanRunner::restoreState() { if (NULL != _bestPlanRunner) { - _bestPlanRunner->yield(); + _bestPlanRunner->restoreState(); } else { unyieldAllPlans(); diff --git a/src/mongo/db/query/multi_plan_runner.h b/src/mongo/db/query/multi_plan_runner.h index c96121987e2..14ad8507bf3 100644 --- a/src/mongo/db/query/multi_plan_runner.h +++ b/src/mongo/db/query/multi_plan_runner.h @@ -67,10 +67,14 @@ namespace mongo { */ bool pickBestPlan(size_t* out); - virtual void yield(); - virtual void unYield(); + virtual void saveState(); + virtual void restoreState(); virtual void invalidate(const DiskLoc& dl); + virtual const CanonicalQuery& getQuery() { + return *_query; + } + private: /** * Have all our candidate plans do something. diff --git a/src/mongo/db/query/new_find.cpp b/src/mongo/db/query/new_find.cpp index 673e5ceb666..e3599cc6b31 100644 --- a/src/mongo/db/query/new_find.cpp +++ b/src/mongo/db/query/new_find.cpp @@ -16,24 +16,33 @@ #include "mongo/db/query/new_find.h" +#include "mongo/client/dbclientinterface.h" +#include "mongo/db/clientcursor.h" #include "mongo/db/commands.h" +#include "mongo/db/keypattern.h" #include "mongo/db/query/cached_plan_runner.h" #include "mongo/db/query/canonical_query.h" #include "mongo/db/query/multi_plan_runner.h" #include "mongo/db/query/plan_cache.h" #include "mongo/db/query/query_planner.h" -#include "mongo/db/query/simple_plan_runner.h" +#include "mongo/db/query/single_solution_runner.h" #include "mongo/db/query/stage_builder.h" +#include "mongo/db/repl/repl_reads_ok.h" +#include "mongo/s/chunk_version.h" +#include "mongo/s/d_logic.h" +#include "mongo/s/stale_exception.h" namespace mongo { + // Copied from db/ops/query.cpp. + static const int32_t MaxBytesToReturnToClientAtOnce = 4 * 1024 * 1024; + // Used in db/ops/query.cpp. bool useNewQuerySystem = false; /** - * For a given query, get a runner. - * The runner could be a SimplePlanRunner, a CachedQueryRunner, or a MultiPlanRunner, depending - * on the cache/query solver/etc. + * For a given query, get a runner. The runner could be a SingleSolutionRunner, a + * CachedQueryRunner, or a MultiPlanRunner, depending on the cache/query solver/etc. */ Status getRunner(QueryMessage& q, Runner** out) { CanonicalQuery* rawCanonicalQuery = NULL; @@ -70,21 +79,13 @@ namespace mongo { } if (1 == solutions.size()) { - // Only one possible plan. Run it. Cache it as well. If we only found one solution - // now, we're only going to find one solution later. - auto_ptr<PlanRankingDecision> why(new PlanRankingDecision()); - why->onlyOneSolution = true; - - // Build the stages from the solution. + // Only one possible plan. Run it. Build the stages from the solution. WorkingSet* ws; PlanStage* root; verify(StageBuilder::build(*solutions[0], &root, &ws)); - // Cache the solution. Takes ownership of all arguments. - localCache->add(canonicalQuery.release(), solutions[0], why.release()); - // And, run the plan. - *out = new SimplePlanRunner(ws, root); + *out = new SingleSolutionRunner(canonicalQuery.release(), solutions[0], root, ws); return Status::OK(); } else { @@ -103,6 +104,123 @@ namespace mongo { } /** + * Also called by db/ops/query.cpp. This is the new getMore entry point. + */ + QueryResult* newGetMore(const char* ns, int ntoreturn, long long cursorid, CurOp& curop, + int pass, bool& exhaust, bool* isCursorAuthorized) { + exhaust = false; + int bufSize = 512 + sizeof(QueryResult) + MaxBytesToReturnToClientAtOnce; + + BufBuilder bb(bufSize); + bb.skip(sizeof(QueryResult)); + + // This is a read lock. TODO: There is a cursor flag for not needing this. Do we care? + Client::ReadContext ctx(ns); + + // TODO: Document. + replVerifyReadsOk(); + + ClientCursorPin ccPin(cursorid); + ClientCursor* cc = ccPin.c(); + + // These are set in the QueryResult msg we return. + int resultFlags = ResultFlag_AwaitCapable; + + int numResults = 0; + int startingResult = 0; + + if (NULL == cc) { + cursorid = 0; + resultFlags = ResultFlag_CursorNotFound; + } + else { + // Quote: check for spoofing of the ns such that it does not match the one originally + // there for the cursor + uassert(17011, "auth error", str::equals(ns, cc->ns().c_str())); + *isCursorAuthorized = true; + + // TODO: fail point? + + // If the operation that spawned this cursor had a time limit set, apply leftover + // time to this getmore. + curop.setMaxTimeMicros(cc->getLeftoverMaxTimeMicros()); + // TODO: + // curop.debug().query = BSONForQuery + // curop.setQuery(curop.debug().query); + + // TODO: What is pass? + if (0 == pass) { cc->updateSlaveLocation(curop); } + + CollectionMetadataPtr collMetadata = cc->getCollMetadata(); + + // If we're replaying the oplog, we save the last time that we read. + OpTime slaveReadTill; + + startingResult = cc->pos(); + + Runner* runner = cc->getRunner(); + const ParsedQuery& pq = runner->getQuery().getParsed(); + + // Get results out of the runner. + // TODO: There may be special handling required for tailable cursors? + runner->restoreState(); + BSONObj obj; + // TODO: Differentiate EOF from error. + while (runner->getNext(&obj)) { + // If we're sharded make sure that we don't return any data that hasn't been + // migrated off of our shard yet. + if (collMetadata) { + KeyPattern kp(collMetadata->getKeyPattern()); + if (!collMetadata->keyBelongsToMe(kp.extractSingleKey(obj))) { continue; } + } + + // Add result to output buffer. + bb.appendBuf((void*)obj.objdata(), obj.objsize()); + + // Count the result. + ++numResults; + + // Possibly note slave's position in the oplog. + if (pq.hasOption(QueryOption_OplogReplay)) { + BSONElement e = obj["ts"]; + if (Date == e.type() || Timestamp == e.type()) { + slaveReadTill = e._opTime(); + } + } + + if ((numResults && numResults >= ntoreturn) + || bb.len() > MaxBytesToReturnToClientAtOnce) { + break; + } + } + + cc->incPos(numResults); + runner->saveState(); + + // Possibly note slave's position in the oplog. + if (pq.hasOption(QueryOption_OplogReplay) && !slaveReadTill.isNull()) { + cc->slaveReadTill(slaveReadTill); + } + + exhaust = pq.hasOption(QueryOption_Exhaust); + + // If the getmore had a time limit, remaining time is "rolled over" back to the + // cursor (for use by future getmore ops). + cc->setLeftoverMaxTimeMicros( curop.getRemainingMaxTimeMicros() ); + } + + QueryResult* qr = reinterpret_cast<QueryResult*>(bb.buf()); + qr->len = bb.len(); + qr->setOperation(opReply); + qr->_resultFlags() = resultFlags; + qr->cursorId = cursorid; + qr->startingFrom = startingResult; + qr->nReturned = numResults; + bb.decouple(); + return qr; + } + + /** * This is called by db/ops/query.cpp. This is the entry point for answering a query. */ string newRunQuery(Message& m, QueryMessage& q, CurOp& curop, Message &result) { @@ -119,17 +237,133 @@ namespace mongo { verify(NULL != rawRunner); auto_ptr<Runner> runner(rawRunner); + // We freak out later if this changes before we're done with the query. + const ChunkVersion shardingVersionAtStart = shardingState.getVersion(q.ns); + + // We use this a lot below. + const ParsedQuery& pq = runner->getQuery().getParsed(); + + // TODO: Document why we do this. + replVerifyReadsOk(&pq); + + // If this exists, the collection is sharded. + // If it doesn't exist, we can assume we're not sharded. + // If we're sharded, we might encounter data that is not consistent with our sharding state. + // We must ignore this data. + CollectionMetadataPtr collMetadata; + if (!shardingState.needCollectionMetadata(pq.ns())) { + collMetadata = CollectionMetadataPtr(); + } + else { + collMetadata = shardingState.getCollectionMetadata(pq.ns()); + } + // Run the query. BufBuilder bb(32768); bb.skip(sizeof(QueryResult)); + + // How many results have we obtained from the runner? int numResults = 0; - // Yielding is handled within the runner, as is page faulting. - // TODO: pay attention to numWanted. + // If we're replaying the oplog, we save the last time that we read. + OpTime slaveReadTill; + + // Do we save the Runner in a ClientCursor for getMore calls later? + bool saveClientCursor = false; + BSONObj obj; + // TODO: Differentiate EOF from error. while (runner->getNext(&obj)) { + // If we're sharded make sure that we don't return any data that hasn't been migrated + // off of our shared yet. + if (collMetadata) { + // This information can change if we yield and as such we must make sure to re-fetch + // it if we yield. + KeyPattern kp(collMetadata->getKeyPattern()); + // This performs excessive BSONObj creation but that's OK for now. + if (!collMetadata->keyBelongsToMe(kp.extractSingleKey(obj))) { continue; } + } + + // Add result to output buffer. bb.appendBuf((void*)obj.objdata(), obj.objsize()); + + // Count the result. ++numResults; + + // Possibly note slave's position in the oplog. + if (pq.hasOption(QueryOption_OplogReplay)) { + BSONElement e = obj["ts"]; + if (Date == e.type() || Timestamp == e.type()) { + slaveReadTill = e._opTime(); + } + } + + // TODO: only one type of 2d search doesn't support this. We need a way to pull it out + // of CanonicalQuery. :( + const bool supportsGetMore = true; + const bool isExplain = pq.isExplain(); + if (isExplain && pq.enoughForExplain(numResults)) { + break; + } + else if (!supportsGetMore && (pq.enough(numResults) + || bb.len() >= MaxBytesToReturnToClientAtOnce)) { + break; + } + else if (pq.enoughForFirstBatch(numResults, bb.len())) { + // If only one result requested assume it's a findOne() and don't save the cursor. + if (pq.wantMore() && 1 != pq.getNumToReturn()) { + saveClientCursor = true; + } + break; + } + } + + // TODO: Stage creation can set tailable depending on what's in the parsed query. We have + // the full parsed query available during planning...set it there. + // + // TODO: If we're tailable we want to save the client cursor. Make sure we do this later. + //if (pq.hasOption(QueryOption_CursorTailable) && pq.getNumToReturn() != 1) { ... } + + // TODO(greg): This will go away soon. + if (!shardingState.getVersion(pq.ns()).isWriteCompatibleWith(shardingVersionAtStart)) { + // if the version changed during the query we might be missing some data and its safe to + // send this as mongos can resend at this point + throw SendStaleConfigException(pq.ns(), "version changed during initial query", + shardingVersionAtStart, + shardingState.getVersion(pq.ns())); + } + + long long ccId = 0; + if (saveClientCursor) { + // Allocate a new ClientCursor. + ClientCursorHolder ccHolder; + ccHolder.reset(new ClientCursor(runner.get())); + ccId = ccHolder->cursorid(); + + // We won't use the runner until it's getMore'd. + runner->saveState(); + + // ClientCursor takes ownership of runner. Release to make sure it's not deleted. + runner.release(); + + if (pq.hasOption(QueryOption_OplogReplay) && !slaveReadTill.isNull()) { + ccHolder->slaveReadTill(slaveReadTill); + } + + if (pq.hasOption(QueryOption_Exhaust)) { + curop.debug().exhaust = true; + } + + // Set attributes for getMore. + ccHolder->setCollMetadata(collMetadata); + ccHolder->setPos(numResults); + + // If the query had a time limit, remaining time is "rolled over" to the cursor (for + // use by future getmore ops). + ccHolder->setLeftoverMaxTimeMicros(curop.getRemainingMaxTimeMicros()); + + // Give up our reference to the CC. + ccHolder.release(); } // Add the results from the query into the output buffer. @@ -138,16 +372,19 @@ namespace mongo { // Fill out the output buffer's header. QueryResult* qr = static_cast<QueryResult*>(result.header()); - // TODO: Figure out how to make this work with a clientcursor - qr->cursorId = 0; + qr->cursorId = ccId; + curop.debug().cursorid = (0 == ccId ? -1 : ccId); qr->setResultFlagsToOk(); qr->setOperation(opReply); qr->startingFrom = 0; qr->nReturned = numResults; - // TODO: set curop.debug() fields. + // TODO: nscanned is bogus. + // curop.debug().nscanned = ( cursor ? cursor->nscanned() : 0LL ); + curop.debug().ntoskip = pq.getSkip(); + curop.debug().nreturned = numResults; - // TODO: what's this? we either return the ns or we return "". - return ""; + // curop.debug().exhaust is set above. + return curop.debug().exhaust ? pq.ns() : ""; } } // namespace mongo diff --git a/src/mongo/db/query/new_find.h b/src/mongo/db/query/new_find.h index 0a4087a4afe..73e9b34721a 100644 --- a/src/mongo/db/query/new_find.h +++ b/src/mongo/db/query/new_find.h @@ -24,6 +24,9 @@ namespace mongo { + QueryResult* newGetMore(const char* ns, int ntoreturn, long long cursorid, CurOp& curop, + int pass, bool& exhaust, bool* isCursorAuthorized); + string newRunQuery(Message& m, QueryMessage& q, CurOp& curop, Message &result); } // namespace mongo diff --git a/src/mongo/db/query/query_planner.cpp b/src/mongo/db/query/query_planner.cpp index ca9af4a1629..e09749e59aa 100644 --- a/src/mongo/db/query/query_planner.cpp +++ b/src/mongo/db/query/query_planner.cpp @@ -25,6 +25,9 @@ namespace mongo { void QueryPlanner::plan(const CanonicalQuery& query, vector<QuerySolution*> *out) { const MatchExpression* root = query.root(); + // TODO: If pq.hasOption(QueryOption_OplogReplay) use FindingStartCursor equivalent which + // must be translated into stages. + // The default plan is always a collection scan with a heavy filter. This is a valid // solution for any query that does not require an index. if (!requiresIndex(root)) { diff --git a/src/mongo/db/query/runner.h b/src/mongo/db/query/runner.h index 43ad7500ac8..2d07361271a 100644 --- a/src/mongo/db/query/runner.h +++ b/src/mongo/db/query/runner.h @@ -16,6 +16,8 @@ #pragma once +#include "mongo/db/query/canonical_query.h" + namespace mongo { /** @@ -39,11 +41,13 @@ namespace mongo { */ virtual void invalidate(const DiskLoc& dl) = 0; + virtual void saveState() = 0; + virtual void restoreState() = 0; + /** - * TODO: Kill these once yielding is controlled inside of a runner. + * Return the query that the runner is running. */ - virtual void yield() = 0; - virtual void unYield() = 0; + virtual const CanonicalQuery& getQuery() = 0; }; } // namespace mongo diff --git a/src/mongo/db/query/simple_plan_runner.h b/src/mongo/db/query/simple_plan_runner.h index d83f286d30b..92d2645777d 100644 --- a/src/mongo/db/query/simple_plan_runner.h +++ b/src/mongo/db/query/simple_plan_runner.h @@ -31,8 +31,9 @@ namespace mongo { * TODO: Yielding policy * TODO: Graceful error handling * TODO: Stats, diagnostics, instrumentation, etc. + * TODO: Rename. It's not a full runner. It just holds the stage/WS and handles yielding. */ - class SimplePlanRunner : public Runner { + class SimplePlanRunner { public: SimplePlanRunner() : _workingSet(new WorkingSet()) { } SimplePlanRunner(WorkingSet* ws, PlanStage* rt) : _workingSet(ws), _root(rt) { } @@ -47,15 +48,9 @@ namespace mongo { _root.reset(root); } - /** - * TODO: Explicit yielding is deprecated pending a ClientCursor rewrite. - */ - virtual void yield() { _root->prepareToYield(); } - virtual void unYield() { _root->recoverFromYield(); } - - virtual void invalidate(const DiskLoc& dl) { - _root->invalidate(dl); - } + void saveState() { _root->prepareToYield(); } + void restoreState() { _root->recoverFromYield(); } + void invalidate(const DiskLoc& dl) { _root->invalidate(dl); } PlanStageStats* getStats() { return _root->getStats(); } diff --git a/src/mongo/db/query/single_solution_runner.h b/src/mongo/db/query/single_solution_runner.h new file mode 100644 index 00000000000..ed7dacbabfe --- /dev/null +++ b/src/mongo/db/query/single_solution_runner.h @@ -0,0 +1,76 @@ +/** + * 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/>. + */ + +#pragma once + +#include "mongo/db/query/canonical_query.h" +#include "mongo/db/query/plan_cache.h" +#include "mongo/db/query/runner.h" +#include "mongo/db/query/simple_plan_runner.h" +#include "mongo/db/query/stage_builder.h" + +namespace mongo { + + /** + * SingleSolutionRunner runs a plan that was the only possible solution to a query. It exists + * only to dump stats into the cache after running. + */ + class SingleSolutionRunner : public Runner { + public: + /** + * Takes ownership of both arguments. + */ + SingleSolutionRunner(CanonicalQuery* canonicalQuery, QuerySolution* soln, + PlanStage* root, WorkingSet* ws) + : _canonicalQuery(canonicalQuery), _solution(soln), + _runner(new SimplePlanRunner(ws, root)) { } + + bool getNext(BSONObj* objOut) { + // Use the underlying runner until it's exhausted. + if (_runner->getNext(objOut)) { + return true; + } + + // TODO: I'm not convinced we want to cache this. What if it's a collscan solution and + // the user adds an index later? We don't want to reach for this. + + // We're done. Update the cache. + //PlanCache* cache = PlanCache::get(_canonicalQuery->ns()); + // TODO: is this a verify? + //if (NULL == cache) { return false; } + // We're done running. Update cache. + //auto_ptr<PlanRankingDecision> why(new PlanRankingDecision()); + //why->onlyOneSolution = true; + //cache->add(canonicalQuery.release(), solutions[0], why.release()); + return false; + } + + virtual void saveState() { _runner->saveState(); } + virtual void restoreState() { _runner->restoreState(); } + virtual void invalidate(const DiskLoc& dl) { _runner->invalidate(dl); } + + virtual const CanonicalQuery& getQuery() { + return *_canonicalQuery; + } + + private: + scoped_ptr<CanonicalQuery> _canonicalQuery; + scoped_ptr<QuerySolution> _solution; + scoped_ptr<SimplePlanRunner> _runner; + }; + +} // namespace mongo + |