summaryrefslogtreecommitdiff
path: root/src/mongo/db/query/idhack_runner.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/db/query/idhack_runner.cpp')
-rw-r--r--src/mongo/db/query/idhack_runner.cpp315
1 files changed, 315 insertions, 0 deletions
diff --git a/src/mongo/db/query/idhack_runner.cpp b/src/mongo/db/query/idhack_runner.cpp
new file mode 100644
index 00000000000..d587323d5f0
--- /dev/null
+++ b/src/mongo/db/query/idhack_runner.cpp
@@ -0,0 +1,315 @@
+/**
+ * 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/client/dbclientinterface.h"
+#include "mongo/db/structure/btree/btree.h"
+#include "mongo/db/catalog/index_catalog.h"
+#include "mongo/db/diskloc.h"
+#include "mongo/db/exec/projection.h"
+#include "mongo/db/index/btree_access_method.h"
+#include "mongo/db/index/index_descriptor.h"
+#include "mongo/db/jsobj.h"
+#include "mongo/db/pdfile.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/catalog/collection.h"
+
+namespace mongo {
+
+namespace {
+
+ CollectionMetadataPtr getMetadata(const std::string& ns) {
+ if (shardingState.needCollectionMetadata(ns)) {
+ return shardingState.getCollectionMetadata(ns);
+ }
+ else {
+ return CollectionMetadataPtr();
+ }
+ }
+
+} // namespace
+
+ IDHackRunner::IDHackRunner(const Collection* collection, CanonicalQuery* query)
+ : _collection(collection),
+ _key(query->getQueryObj()["_id"].wrap()),
+ _query(query),
+ _killed(false),
+ _done(false),
+ _nscanned(0),
+ _nscannedObjects(0),
+ _metadata(getMetadata(collection->ns())) {
+ }
+
+ IDHackRunner::IDHackRunner(Collection* collection, const BSONObj& key)
+ : _collection(collection),
+ _key(key),
+ _query(NULL),
+ _killed(false),
+ _done(false),
+ _nscanned(0),
+ _nscannedObjects(0),
+ _metadata(getMetadata(collection->ns())) {
+ }
+
+ 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.
+ const IndexCatalog* catalog = _collection->getIndexCatalog();
+
+ // Find the index we use.
+ IndexDescriptor* idDesc = catalog->findIdIndex();
+ if (NULL == idDesc) {
+ _done = true;
+ return Runner::RUNNER_EOF;
+ }
+
+ // This may not be valid always. See SERVER-12397.
+ const BtreeBasedAccessMethod* accessMethod =
+ static_cast<const BtreeBasedAccessMethod*>(catalog->getIndex(idDesc));
+
+ // Look up the key by going directly to the Btree.
+ DiskLoc loc = accessMethod->findSingle( _key );
+
+ // Key not found.
+ if (loc.isNull()) {
+ _done = true;
+ return Runner::RUNNER_EOF;
+ }
+
+ _nscanned++;
+
+ // Set out parameters and note that we're done w/lookup.
+ if (NULL == objOut) {
+ // No object requested - nothing to do.
+ }
+ else {
+ // If we're not sharded, consider a covered projection (we can't if we're sharded, since
+ // we require a fetch in order to apply the sharding filter).
+ if (!_metadata && hasCoveredProjection()) {
+ // Covered query on _id field only. Set object to search key. Search key is
+ // retrieved from the canonical query at construction and always contains the _id
+ // field name. It is possible to construct the ID hack runner with just the
+ // collection and the key object (which could be {"": my_obj_id}) but _query would
+ // be null in that case and the query would never be seen as covered.
+ *objOut = _key.getOwned();
+ }
+ // Otherwise, fetch the document.
+ else {
+ Record* record = loc.rec();
+
+ _nscannedObjects++;
+
+ // 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) {
+ _done = true;
+ return Runner::RUNNER_DEAD;
+ }
+ }
+ }
+
+ // If we're here, 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.
+ if (_metadata) {
+ KeyPattern kp(_metadata->getKeyPattern());
+ if (!_metadata->keyBelongsToMe(kp.extractSingleKey(*objOut))) {
+ // We have something with a matching _id but it doesn't belong to me.
+ _done = true;
+ return Runner::RUNNER_EOF;
+ }
+ }
+
+ // Apply the projection if one was requested.
+ if (_query && _query->getProj()) {
+ *objOut = applyProjection(*objOut);
+ }
+ }
+ }
+
+ // Return the DiskLoc if the caller wants it.
+ if (NULL != dlOut) {
+ *dlOut = loc;
+ }
+
+ _done = true;
+ return Runner::RUNNER_ADVANCED;
+ }
+
+ BSONObj IDHackRunner::applyProjection(const BSONObj& docObj) const {
+ invariant(_query && _query->getProj());
+
+ // We have a non-covered projection (covered projections should be handled earlier,
+ // in getNext(..). For simple inclusion projections we use a fast path similar to that
+ // implemented in the ProjectionStage. For non-simple inclusion projections we fallback
+ // to ProjectionExec.
+ const BSONObj& projObj = _query->getParsed().getProj();
+
+ if (_query->getProj()->wantIndexKey()) {
+ // $returnKey is specified. This overrides everything else.
+ BSONObjBuilder bob;
+ const BSONObj& queryObj = _query->getParsed().getFilter();
+ bob.append(queryObj["_id"]);
+ return bob.obj();
+ }
+ else if (_query->getProj()->requiresDocument() || _query->getProj()->wantIndexKey()) {
+ // Not a simple projection, so fallback on the regular projection path.
+ BSONObj projectedObj;
+ ProjectionExec projExec(projObj, _query->root());
+ projExec.transform(docObj, &projectedObj);
+ return projectedObj;
+ }
+ else {
+ // This is a simple inclusion projection. Start by getting the set
+ // of fields to include.
+ unordered_set<StringData, StringData::Hasher> includedFields;
+ ProjectionStage::getSimpleInclusionFields(projObj, &includedFields);
+
+ // Apply the simple inclusion projection.
+ BSONObjBuilder bob;
+ ProjectionStage::transformSimpleInclusion(docObj, includedFields, bob);
+
+ return bob.obj();
+ }
+ }
+
+ 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, InvalidationType type) {
+ if (_done || _killed) { return; }
+ if (_locFetching == dl && (type == INVALIDATION_DELETION)) {
+ _locFetching = DiskLoc();
+ _killed = true;
+ }
+ }
+
+ const std::string& IDHackRunner::ns() {
+ return _collection->ns().ns();
+ }
+
+ void IDHackRunner::kill() {
+ _killed = true;
+ _collection = NULL;
+ }
+
+ Status IDHackRunner::getInfo(TypeExplain** explain,
+ PlanInfo** planInfo) const {
+ // The explain plan simply indicates that the plan is idhack.
+ if (NULL != explain) {
+ *explain = new TypeExplain();
+ // Explain format does not match 2.4 and is intended
+ // to indicate clearly that the ID hack has been applied.
+ (*explain)->setCursor("IDCursor");
+ (*explain)->setIDHack(true);
+ (*explain)->setN(_nscanned);
+ (*explain)->setNScanned(_nscanned);
+ (*explain)->setNScannedObjects(_nscannedObjects);
+ BSONElement keyElt = _key.firstElement();
+ BSONObj indexBounds = BSON("_id" << BSON_ARRAY( BSON_ARRAY( keyElt << keyElt ) ) );
+ (*explain)->setIndexBounds(indexBounds);
+ // ID hack queries are only considered covered if they have the projection {_id: 1}.
+ (*explain)->setIndexOnly(hasCoveredProjection());
+ }
+ else if (NULL != planInfo) {
+ *planInfo = new PlanInfo();
+ (*planInfo)->planSummary = "IDHACK";
+ }
+
+ return Status::OK();
+ }
+
+ // static
+ bool IDHackRunner::supportsQuery(const CanonicalQuery& query) {
+ return !query.getParsed().showDiskLoc()
+ && query.getParsed().getHint().isEmpty()
+ && 0 == query.getParsed().getSkip()
+ && CanonicalQuery::isSimpleIdQuery(query.getParsed().getFilter())
+ && !query.getParsed().hasOption(QueryOption_CursorTailable);
+ }
+
+ // static
+ bool IDHackRunner::hasCoveredProjection() const {
+ // Some update operations use the IDHackRunner without creating a
+ // canonical query. In this case, _query will be NULL. Just return
+ // false, as we won't have to do any projection handling for updates.
+ if (NULL == _query.get()) {
+ return false;
+ }
+
+ const ParsedProjection* proj = _query->getProj();
+ if (!proj) {
+ return false;
+ }
+
+ // If there is a projection, it has to be a covered projection on
+ // the _id field only.
+ if (proj->requiresDocument()) {
+ return false;
+ }
+ const std::vector<std::string>& requiredFields = proj->getRequiredFields();
+ if (1U != requiredFields.size()) {
+ return false;
+ }
+ if ("_id" != requiredFields[0]) {
+ return false;
+ }
+
+ // Can use this projection with ID Hack.
+ return true;
+ }
+
+} // namespace mongo