summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/mongo/db/clientcursor.cpp2
-rw-r--r--src/mongo/db/clientcursor.h2
-rw-r--r--src/mongo/db/exec/projection_exec.cpp10
-rw-r--r--src/mongo/db/exec/projection_exec.h9
-rw-r--r--src/mongo/db/query/SConscript1
-rw-r--r--src/mongo/db/query/canonical_query.cpp3
-rw-r--r--src/mongo/db/query/idhack_runner.cpp174
-rw-r--r--src/mongo/db/query/idhack_runner.h100
-rw-r--r--src/mongo/db/query/new_find.cpp46
-rw-r--r--src/mongo/db/query/query_planner.cpp65
-rw-r--r--src/mongo/db/query/runner_yield_policy.h22
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);
}