/** * 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 . * * 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. */ #define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kQuery #include "mongo/db/exec/projection.h" #include "mongo/db/exec/plan_stage.h" #include "mongo/db/exec/scoped_timer.h" #include "mongo/db/exec/working_set_common.h" #include "mongo/db/jsobj.h" #include "mongo/db/matcher/expression.h" #include "mongo/db/record_id.h" #include "mongo/stdx/memory.h" #include "mongo/util/log.h" #include "mongo/util/mongoutils/str.h" namespace mongo { using std::endl; using std::unique_ptr; using std::vector; using stdx::make_unique; static const char* kIdField = "_id"; // static const char* ProjectionStage::kStageType = "PROJECTION"; ProjectionStage::ProjectionStage(OperationContext* opCtx, const ProjectionStageParams& params, WorkingSet* ws, PlanStage* child) : PlanStage(kStageType, opCtx), _ws(ws), _projImpl(params.projImpl) { _children.emplace_back(child); _projObj = params.projObj; if (ProjectionStageParams::NO_FAST_PATH == _projImpl) { _exec.reset( new ProjectionExec(params.projObj, params.fullExpression, *params.whereCallback)); } else { // We shouldn't need the full expression if we're fast-pathing. invariant(NULL == params.fullExpression); // Sanity-check the input. invariant(_projObj.isOwned()); invariant(!_projObj.isEmpty()); // Figure out what fields are in the projection. getSimpleInclusionFields(_projObj, &_includedFields); // If we're pulling data out of one index we can pre-compute the indices of the fields // in the key that we pull data from and avoid looking up the field name each time. if (ProjectionStageParams::COVERED_ONE_INDEX == params.projImpl) { // Sanity-check. _coveredKeyObj = params.coveredKeyObj; invariant(_coveredKeyObj.isOwned()); BSONObjIterator kpIt(_coveredKeyObj); while (kpIt.more()) { BSONElement elt = kpIt.next(); auto fieldIt = _includedFields.find(elt.fieldNameStringData()); if (_includedFields.end() == fieldIt) { // Push an unused value on the back to keep _includeKey and _keyFieldNames // in sync. _keyFieldNames.push_back(StringData()); _includeKey.push_back(false); } else { // If we are including this key field store its field name. _keyFieldNames.push_back(fieldIt->first); _includeKey.push_back(true); } } } else { invariant(ProjectionStageParams::SIMPLE_DOC == params.projImpl); } } } // static void ProjectionStage::getSimpleInclusionFields(const BSONObj& projObj, FieldSet* includedFields) { // The _id is included by default. bool includeId = true; // Figure out what fields are in the projection. TODO: we can get this from the // ParsedProjection...modify that to have this type instead of a vector. BSONObjIterator projObjIt(projObj); while (projObjIt.more()) { BSONElement elt = projObjIt.next(); // Must deal with the _id case separately as there is an implicit _id: 1 in the // projection. if (mongoutils::str::equals(elt.fieldName(), kIdField) && !elt.trueValue()) { includeId = false; continue; } (*includedFields)[elt.fieldNameStringData()] = true; } if (includeId) { (*includedFields)[kIdField] = true; } } // static void ProjectionStage::transformSimpleInclusion(const BSONObj& in, const FieldSet& includedFields, BSONObjBuilder& bob) { // Look at every field in the source document and see if we're including it. BSONObjIterator inputIt(in); while (inputIt.more()) { BSONElement elt = inputIt.next(); auto fieldIt = includedFields.find(elt.fieldNameStringData()); if (includedFields.end() != fieldIt) { // If so, add it to the builder. bob.append(elt); } } } Status ProjectionStage::transform(WorkingSetMember* member) { // The default no-fast-path case. if (ProjectionStageParams::NO_FAST_PATH == _projImpl) { return _exec->transform(member); } BSONObjBuilder bob; // Note that even if our fast path analysis is bug-free something that is // covered might be invalidated and just be an obj. In this case we just go // through the SIMPLE_DOC path which is still correct if the covered data // is not available. // // SIMPLE_DOC implies that we expect an object so it's kind of redundant. if ((ProjectionStageParams::SIMPLE_DOC == _projImpl) || member->hasObj()) { // If we got here because of SIMPLE_DOC the planner shouldn't have messed up. invariant(member->hasObj()); // Apply the SIMPLE_DOC projection. transformSimpleInclusion(member->obj.value(), _includedFields, bob); } else { invariant(ProjectionStageParams::COVERED_ONE_INDEX == _projImpl); // We're pulling data out of the key. invariant(1 == member->keyData.size()); size_t keyIndex = 0; // Look at every key element... BSONObjIterator keyIterator(member->keyData[0].keyData); while (keyIterator.more()) { BSONElement elt = keyIterator.next(); // If we're supposed to include it... if (_includeKey[keyIndex]) { // Do so. bob.appendAs(elt, _keyFieldNames[keyIndex]); } ++keyIndex; } } member->keyData.clear(); member->loc = RecordId(); member->obj = Snapshotted(SnapshotId(), bob.obj()); member->transitionToOwnedObj(); return Status::OK(); } bool ProjectionStage::isEOF() { return child()->isEOF(); } PlanStage::StageState ProjectionStage::work(WorkingSetID* out) { ++_commonStats.works; // Adds the amount of time taken by work() to executionTimeMillis. ScopedTimer timer(&_commonStats.executionTimeMillis); WorkingSetID id = WorkingSet::INVALID_ID; StageState status = child()->work(&id); // Note that we don't do the normal if isEOF() return EOF thing here. Our child might be a // tailable cursor and isEOF() would be true even if it had more data... if (PlanStage::ADVANCED == status) { WorkingSetMember* member = _ws->get(id); // Punt to our specific projection impl. Status projStatus = transform(member); if (!projStatus.isOK()) { warning() << "Couldn't execute projection, status = " << projStatus.toString() << endl; *out = WorkingSetCommon::allocateStatusMember(_ws, projStatus); return PlanStage::FAILURE; } *out = id; ++_commonStats.advanced; } else if (PlanStage::FAILURE == status || PlanStage::DEAD == status) { *out = id; // If a stage fails, it may create a status WSM to indicate why it // failed, in which case 'id' is valid. If ID is invalid, we // create our own error message. if (WorkingSet::INVALID_ID == id) { mongoutils::str::stream ss; ss << "projection stage failed to read in results from child"; Status status(ErrorCodes::InternalError, ss); *out = WorkingSetCommon::allocateStatusMember(_ws, status); } } else if (PlanStage::NEED_TIME == status) { _commonStats.needTime++; } else if (PlanStage::NEED_YIELD == status) { _commonStats.needYield++; *out = id; } return status; } unique_ptr ProjectionStage::getStats() { _commonStats.isEOF = isEOF(); unique_ptr ret = make_unique(_commonStats, STAGE_PROJECTION); unique_ptr projStats = make_unique(_specificStats); projStats->projObj = _projObj; ret->specific = std::move(projStats); ret->children.push_back(child()->getStats().release()); return ret; } const SpecificStats* ProjectionStage::getSpecificStats() const { return &_specificStats; } } // namespace mongo