diff options
-rw-r--r-- | src/mongo/db/clientcursor.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/clientcursor.h | 2 | ||||
-rw-r--r-- | src/mongo/db/exec/projection_exec.cpp | 10 | ||||
-rw-r--r-- | src/mongo/db/exec/projection_exec.h | 9 | ||||
-rw-r--r-- | src/mongo/db/query/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/db/query/canonical_query.cpp | 3 | ||||
-rw-r--r-- | src/mongo/db/query/idhack_runner.cpp | 174 | ||||
-rw-r--r-- | src/mongo/db/query/idhack_runner.h | 100 | ||||
-rw-r--r-- | src/mongo/db/query/new_find.cpp | 46 | ||||
-rw-r--r-- | src/mongo/db/query/query_planner.cpp | 65 | ||||
-rw-r--r-- | src/mongo/db/query/runner_yield_policy.h | 22 |
11 files changed, 359 insertions, 75 deletions
diff --git a/src/mongo/db/clientcursor.cpp b/src/mongo/db/clientcursor.cpp index 85a7819ebc5..6748b70afb5 100644 --- a/src/mongo/db/clientcursor.cpp +++ b/src/mongo/db/clientcursor.cpp @@ -391,7 +391,7 @@ namespace mongo { #endif } - void ClientCursor::staticYield(int micros, const StringData& ns, Record* rec) { + void ClientCursor::staticYield(int micros, const StringData& ns, const Record* rec) { bool haveReadLock = Lock::isReadLocked(); killCurrentOp.checkForInterrupt( false ); diff --git a/src/mongo/db/clientcursor.h b/src/mongo/db/clientcursor.h index 073f883b4b2..20922f100e8 100644 --- a/src/mongo/db/clientcursor.h +++ b/src/mongo/db/clientcursor.h @@ -115,7 +115,7 @@ namespace mongo { // Yielding. // - static void staticYield(int micros, const StringData& ns, Record* rec); + static void staticYield(int micros, const StringData& ns, const Record* rec); // // Static methods about all ClientCursors TODO: Document. diff --git a/src/mongo/db/exec/projection_exec.cpp b/src/mongo/db/exec/projection_exec.cpp index 4b2d39dc155..b7bd0d587df 100644 --- a/src/mongo/db/exec/projection_exec.cpp +++ b/src/mongo/db/exec/projection_exec.cpp @@ -283,6 +283,16 @@ namespace mongo { return Status::OK(); } + Status ProjectionExec::transform(const BSONObj& in, BSONObj* out) const { + BSONObjBuilder bob; + Status s = transform(in, &bob, NULL); + if (!s.isOK()) { + return s; + } + *out = bob.obj(); + return Status::OK(); + } + Status ProjectionExec::transform(const BSONObj& in, BSONObjBuilder* bob, const MatchDetails* details) const { diff --git a/src/mongo/db/exec/projection_exec.h b/src/mongo/db/exec/projection_exec.h index 6a7c1350a80..33c20582f96 100644 --- a/src/mongo/db/exec/projection_exec.h +++ b/src/mongo/db/exec/projection_exec.h @@ -73,6 +73,15 @@ namespace mongo { */ Status transform(WorkingSetMember* member) const; + /** + * Apply this projection to the object 'in'. + * 'this' must be a simple inclusion/exclusion projection. + * + * Upon success, 'out' is set to the new object and Status::OK() is returned. + * Otherwise, returns an error Status and *out is not mutated. + */ + Status transform(const BSONObj& in, BSONObj* out) const; + private: // // Initialization diff --git a/src/mongo/db/query/SConscript b/src/mongo/db/query/SConscript index d772319f81d..7a824adecd5 100644 --- a/src/mongo/db/query/SConscript +++ b/src/mongo/db/query/SConscript @@ -29,6 +29,7 @@ env.Library( "cached_plan_runner.cpp", "eof_runner.cpp", "explain_plan.cpp", + "idhack_runner.cpp", "internal_runner.cpp", "multi_plan_runner.cpp", "new_find.cpp", diff --git a/src/mongo/db/query/canonical_query.cpp b/src/mongo/db/query/canonical_query.cpp index cda8a4eb2a1..97a9064aea3 100644 --- a/src/mongo/db/query/canonical_query.cpp +++ b/src/mongo/db/query/canonical_query.cpp @@ -257,7 +257,8 @@ namespace mongo { string CanonicalQuery::toString() const { stringstream ss; - ss << "ns=" << _pq->ns() << " limit=" << _pq->getNumToReturn() << " skip=" << _pq->getSkip() << endl; + ss << "ns=" << _pq->ns() << " limit=" << _pq->getNumToReturn() + << " skip=" << _pq->getSkip() << endl; // The expression tree puts an endl on for us. ss << "Tree: " << _root->toString(); ss << "Sort: " << _pq->getSort().toString() << endl; diff --git a/src/mongo/db/query/idhack_runner.cpp b/src/mongo/db/query/idhack_runner.cpp new file mode 100644 index 00000000000..fc184617f7a --- /dev/null +++ b/src/mongo/db/query/idhack_runner.cpp @@ -0,0 +1,174 @@ +/** + * Copyright 2013 MongoDB 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/idhack_runner.h" + +#include "mongo/db/btree.h" +#include "mongo/db/catalog/index_catalog.h" +#include "mongo/db/diskloc.h" +#include "mongo/db/exec/projection_exec.h" +#include "mongo/db/index/index_descriptor.h" +#include "mongo/db/jsobj.h" +#include "mongo/db/query/canonical_query.h" +#include "mongo/db/query/type_explain.h" +#include "mongo/db/query/plan_executor.h" +#include "mongo/db/structure/collection.h" +#include "mongo/s/d_logic.h" + +namespace mongo { + + IDHackRunner::IDHackRunner(Collection* collection, CanonicalQuery* query) + : _collection(collection), + _query(query), + _killed(false), + _done(false) { } + + IDHackRunner::~IDHackRunner() { } + + Runner::RunnerState IDHackRunner::getNext(BSONObj* objOut, DiskLoc* dlOut) { + if (_killed) { return Runner::RUNNER_DEAD; } + if (_done) { return Runner::RUNNER_EOF; } + + // Use the index catalog to get the id index. + IndexCatalog* catalog = _collection->getIndexCatalog(); + + // Find the index we use. + IndexDescriptor* idDesc = catalog->findIdIndex(); + if (NULL == idDesc) { + _done = true; + return Runner::RUNNER_EOF; + } + + // Create the key that we need to perform a lookup. + IndexDetails& id = idDesc->getOnDisk(); + BSONObj key = id.getKeyFromQuery(_query->getQueryObj()); + + // Look up the key by going directly to the Btree. + DiskLoc loc; + if (0 == id.version()) { + loc = id.head.btree<V0>()->findSingle(id, id.head, key); + } else { + loc = id.head.btree<V1>()->findSingle(id, id.head, key); + } + + _done = true; + + // Key not found. + if (loc.isNull()) { + return Runner::RUNNER_EOF; + } + + // Set out parameters and note that we're done w/lookup. + if (NULL != objOut) { + Record* record = loc.rec(); + + // If the record isn't in memory... + if (!Record::likelyInPhysicalMemory(record->dataNoThrowing())) { + // And we're allowed to yield ourselves... + if (Runner::YIELD_AUTO == _policy) { + // Note what we're yielding to fetch so that we don't crash if the loc is + // deleted during a yield. + _locFetching = loc; + // Yield. TODO: Do we want to bother yielding if micros < 0? + int micros = ClientCursor::suggestYieldMicros(); + ClientCursor::staticYield(micros, "", record); + // This can happen when we're yielded for various reasons (e.g. db/idx dropped). + if (_killed) { + return Runner::RUNNER_DEAD; + } + } + } + + // Either the data was in memory or we paged it in. + *objOut = loc.obj(); + + // If we're sharded make sure the key belongs to us. We need the object to do this. + if (shardingState.needCollectionMetadata(_query->ns())) { + CollectionMetadataPtr m = shardingState.getCollectionMetadata(_query->ns()); + if (m) { + KeyPattern kp(m->getKeyPattern()); + if (!m->keyBelongsToMe( kp.extractSingleKey(*objOut))) { + // We have something with a matching _id but it doesn't belong to me. + return Runner::RUNNER_EOF; + } + } + } + + // If there is a projection... + if (NULL != _query->getProj()) { + // Create something to execute it. + auto_ptr<ProjectionExec> projExec(new ProjectionExec(_query->getParsed().getProj(), + _query->root())); + projExec->transform(*objOut, objOut); + } + } + + // Return the DiskLoc if the caller wants it. + if (NULL != dlOut) { + *dlOut = loc; + } + + return Runner::RUNNER_ADVANCED; + } + + bool IDHackRunner::isEOF() { + return _killed || _done; + } + + void IDHackRunner::saveState() { } + + bool IDHackRunner::restoreState() { return true; } + + void IDHackRunner::setYieldPolicy(Runner::YieldPolicy policy) { + if (_done || _killed) { return; } + _policy = policy; + } + + // Nothing to do here, holding no state. + void IDHackRunner::invalidate(const DiskLoc& dl) { + if (_done || _killed) { return; } + if (_locFetching == dl) { + _locFetching = DiskLoc(); + _killed = true; + } + } + + const std::string& IDHackRunner::ns() { + return _query->getParsed().ns(); + } + + void IDHackRunner::kill() { + _killed = true; + } + + Status IDHackRunner::getExplainPlan(TypeExplain** explain) const { + // This shouldn't be called anyway as idhack is only used when there is no explain. + return Status(ErrorCodes::InternalError, "no stats available to explain plan"); + } + +} // namespace mongo diff --git a/src/mongo/db/query/idhack_runner.h b/src/mongo/db/query/idhack_runner.h new file mode 100644 index 00000000000..def27173c94 --- /dev/null +++ b/src/mongo/db/query/idhack_runner.h @@ -0,0 +1,100 @@ +/** + * 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 <string> + +#include "mongo/base/status.h" +#include "mongo/db/diskloc.h" +#include "mongo/db/query/runner.h" + +namespace mongo { + + class BSONObj; + class CanonicalQuery; + class Collection; + class DiskLoc; + class PlanStage; + class TypeExplain; + + /** + */ + class IDHackRunner : public Runner { + public: + + /** Takes ownership of all the arguments. */ + IDHackRunner(Collection* collection, CanonicalQuery* query); + + virtual ~IDHackRunner(); + + Runner::RunnerState getNext(BSONObj* objOut, DiskLoc* dlOut); + + virtual bool isEOF(); + + virtual void saveState(); + + virtual bool restoreState(); + + virtual void setYieldPolicy(Runner::YieldPolicy policy); + + virtual void invalidate(const DiskLoc& dl); + + virtual const std::string& ns(); + + virtual void kill(); + + /** + */ + virtual Status getExplainPlan(TypeExplain** explain) const; + + private: + // Not owned here. + Collection* _collection; + + // TODO: When we combine the canonicalize and getRunner steps into one we can get rid of + // this. + boost::scoped_ptr<CanonicalQuery> _query; + + // Are we allowed to release the lock? + Runner::YieldPolicy _policy; + + // Did someone call kill() on us? + bool _killed; + + // Have we returned our one document? + bool _done; + + // If we're yielding to fetch a document, what is it's diskloc? It may be invalidated + // while we're yielded. + DiskLoc _locFetching; + }; + +} // namespace mongo + diff --git a/src/mongo/db/query/new_find.cpp b/src/mongo/db/query/new_find.cpp index c9bbc9d159c..8f8b61e7a72 100644 --- a/src/mongo/db/query/new_find.cpp +++ b/src/mongo/db/query/new_find.cpp @@ -42,6 +42,7 @@ #include "mongo/db/query/cached_plan_runner.h" #include "mongo/db/query/canonical_query.h" #include "mongo/db/query/eof_runner.h" +#include "mongo/db/query/idhack_runner.h" #include "mongo/db/query/internal_plans.h" #include "mongo/db/query/multi_plan_runner.h" #include "mongo/db/query/plan_cache.h" @@ -135,6 +136,39 @@ namespace mongo { return true; } + // Copied verbatim from queryutil.cpp. + static bool isSimpleIdQuery(const BSONObj& query) { + // Just one field name. + BSONObjIterator it(query); + if (!it.more()) { return false; } + + BSONElement elt = it.next(); + if (it.more()) { return false; } + + // Which is _id... + if (strcmp("_id", elt.fieldName()) != 0) { + return false; + } + + // And not something like { _id : { $gt : ... + if (elt.isSimpleType()) { return true; } + + // And if the value is an object... + if (elt.type() == Object) { + // Can't do this. + return elt.Obj().firstElementFieldName()[0] != '$'; + } + + return false; + } + + static bool canUseIDHack(const CanonicalQuery& query) { + return !query.getParsed().isExplain() + && !query.getParsed().showDiskLoc() + && isSimpleIdQuery(query.getParsed().getFilter()) + && !query.getParsed().hasOption(QueryOption_CursorTailable); + } + /** * 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. @@ -176,12 +210,20 @@ namespace mongo { NamespaceDetails* nsd = collection->details(); + // If we have an _id index we can use the idhack runner. + if (canUseIDHack(*canonicalQuery) && (-1 != nsd->findIdIndex())) { + *out = new IDHackRunner(collection, canonicalQuery.release()); + return Status::OK(); + } + // If it's not NULL, we may have indices. Access the catalog and fill out IndexEntry(s) QueryPlannerParams plannerParams; for (int i = 0; i < nsd->getCompletedIndexCount(); ++i) { IndexDescriptor* desc = collection->getIndexCatalog()->getDescriptor( i ); - plannerParams.indices.push_back( - IndexEntry(desc->keyPattern(), desc->isMultikey(), desc->isSparse(), desc->indexName())); + plannerParams.indices.push_back(IndexEntry(desc->keyPattern(), + desc->isMultikey(), + desc->isSparse(), + desc->indexName())); } // Tailable: If the query requests tailable the collection must be capped. diff --git a/src/mongo/db/query/query_planner.cpp b/src/mongo/db/query/query_planner.cpp index 76d67bbf62f..2a384d45ae8 100644 --- a/src/mongo/db/query/query_planner.cpp +++ b/src/mongo/db/query/query_planner.cpp @@ -68,36 +68,6 @@ namespace mongo { return false; } - // Copied verbatim from queryutil.cpp. - static bool isSimpleIdQuery(const BSONObj& query) { - BSONObjIterator it(query); - - if (!it.more()) { - return false; - } - - BSONElement elt = it.next(); - - if (it.more()) { - return false; - } - - if (strcmp("_id", elt.fieldName()) != 0) { - return false; - } - - // e.g. not something like { _id : { $gt : ... - if (elt.isSimpleType()) { - return true; - } - - if (elt.type() == Object) { - return elt.Obj().firstElementFieldName()[0] != '$'; - } - - return false; - } - string optionString(size_t options) { stringstream ss; @@ -167,41 +137,6 @@ namespace mongo { << "=============================" << endl; - // The shortcut formerly known as IDHACK. See if it's a simple _id query. If so we might - // just make an ixscan over the _id index and bypass the rest of planning entirely. - if (!query.getParsed().isExplain() && !query.getParsed().showDiskLoc() - && isSimpleIdQuery(query.getParsed().getFilter()) - && !query.getParsed().hasOption(QueryOption_CursorTailable)) { - - // See if we can find an _id index. - for (size_t i = 0; i < params.indices.size(); ++i) { - if (isIdIndex(params.indices[i].keyPattern)) { - const IndexEntry& index = params.indices[i]; - QLOG() << "IDHACK using index " << index.toString() << endl; - - // If so, we make a simple scan to find the doc. - IndexScanNode* isn = new IndexScanNode(); - isn->indexKeyPattern = index.keyPattern; - isn->indexIsMultiKey = index.multikey; - isn->direction = 1; - isn->bounds.isSimpleRange = true; - BSONObj key = getKeyFromQuery(index.keyPattern, query.getParsed().getFilter()); - isn->bounds.startKey = isn->bounds.endKey = key; - isn->bounds.endKeyInclusive = true; - isn->computeProperties(); - - QuerySolution* soln = QueryPlannerAnalysis::analyzeDataAccess(query, params, isn); - - if (NULL != soln) { - out->push_back(soln); - QLOG() << "IDHACK solution is:\n" << (*out)[0]->toString() << endl; - // And that's it. - return; - } - } - } - } - for (size_t i = 0; i < params.indices.size(); ++i) { QLOG() << "idx " << i << " is " << params.indices[i].toString() << endl; } diff --git a/src/mongo/db/query/runner_yield_policy.h b/src/mongo/db/query/runner_yield_policy.h index 94f04f85bcc..4058108c3bd 100644 --- a/src/mongo/db/query/runner_yield_policy.h +++ b/src/mongo/db/query/runner_yield_policy.h @@ -55,17 +55,19 @@ namespace mongo { * * Provided runner MUST be YIELD_MANUAL. */ - bool yieldAndCheckIfOK(Runner* runner) { + bool yieldAndCheckIfOK(Runner* runner, Record* record = NULL) { verify(runner); int micros = ClientCursor::suggestYieldMicros(); - // No point in yielding. + + // If micros is not positive, no point in yielding, nobody waiting. + // XXX: Do we want to yield anyway if record is not NULL? if (micros <= 0) { return true; } // If micros > 0, we should yield. runner->saveState(); _runnerYielding = runner; ClientCursor::registerRunner(_runnerYielding); - staticYield(micros, NULL); + staticYield(micros, record); ClientCursor::deregisterRunner(_runnerYielding); _runnerYielding = NULL; _elapsedTracker.resetLastTime(); @@ -80,13 +82,23 @@ namespace mongo { */ void yield(Record* rec = NULL) { int micros = ClientCursor::suggestYieldMicros(); - if (micros > 0) { + + // If there is anyone waiting on us or if there's a record to page-in, yield. TODO: Do + // we want to page in the record in the lock even if nobody is waiting for the lock? + if (micros > 0 || (NULL != rec)) { staticYield(micros, rec); + // XXX: when do we really want to reset this? + // + // Currently we reset it when we actually yield. As such we'll keep on trying + // to yield once the tracker has elapsed. + // + // If we reset it even if we don't yield, we'll wait until the time interval + // elapses again to try yielding. _elapsedTracker.resetLastTime(); } } - static void staticYield(int micros, Record* rec = NULL) { + static void staticYield(int micros, const Record* rec = NULL) { ClientCursor::staticYield(micros, "", rec); } |