/** * 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 . * * 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/catalog/collection.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/s/d_logic.h" namespace mongo { IDHackRunner::IDHackRunner(const Collection* collection, CanonicalQuery* query) : _collection(collection), _key(query->getQueryObj()["_id"].wrap()), _query(query), _killed(false), _done(false), _nscanned(0), _nscannedObjects(0) { } IDHackRunner::IDHackRunner(Collection* collection, const BSONObj& key) : _collection(collection), _key(key), _query(NULL), _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. 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(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 (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(); } else { _nscannedObjects++; if (!applyProjection(loc, objOut)) { // No projection. Just return the object inside the diskloc. *objOut = _collection->docFor(loc); } // If we're sharded make sure the key belongs to us. We need the object to do this. if (shardingState.needCollectionMetadata(_collection->ns().ns())) { CollectionMetadataPtr m = shardingState.getCollectionMetadata(_collection->ns().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. _done = true; return Runner::RUNNER_EOF; } } } } // Return the DiskLoc if the caller wants it. if (NULL != dlOut) { *dlOut = loc; } _done = true; return Runner::RUNNER_ADVANCED; } bool IDHackRunner::applyProjection(const DiskLoc& loc, BSONObj* objOut) const { if (NULL == _query.get() || NULL == _query->getProj()) { // This idhack query does not have a projection. return false; } const BSONObj& docAtLoc = _collection->docFor(loc); // 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"]); *objOut = bob.obj(); } else if (_query->getProj()->requiresDocument() || _query->getProj()->wantIndexKey()) { // Not a simple projection, so fallback on the regular projection path. ProjectionExec projExec(projObj, _query->root(), WhereCallbackReal(_collection->ns().db())); projExec.transform(docAtLoc, objOut); } else { // This is a simple inclusion projection. Start by getting the set // of fields to include. unordered_set includedFields; ProjectionStage::getSimpleInclusionFields(projObj, &includedFields); // Apply the simple inclusion projection. BSONObjBuilder bob; ProjectionStage::transformSimpleInclusion(docAtLoc, includedFields, bob); *objOut = bob.obj(); } return true; } bool IDHackRunner::isEOF() { return _killed || _done; } void IDHackRunner::saveState() { } bool IDHackRunner::restoreState() { return true; } // 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& 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