/** * Copyright (C) 2013-2014 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. */ #define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kQuery #include "mongo/platform/basic.h" #include "mongo/db/query/get_executor.h" #include #include "mongo/base/error_codes.h" #include "mongo/base/parse_number.h" #include "mongo/client/dbclientinterface.h" #include "mongo/db/exec/cached_plan.h" #include "mongo/db/exec/count.h" #include "mongo/db/exec/delete.h" #include "mongo/db/exec/eof.h" #include "mongo/db/exec/group.h" #include "mongo/db/exec/idhack.h" #include "mongo/db/exec/multi_plan.h" #include "mongo/db/exec/projection.h" #include "mongo/db/exec/shard_filter.h" #include "mongo/db/exec/subplan.h" #include "mongo/db/exec/update.h" #include "mongo/db/global_environment_experiment.h" #include "mongo/db/ops/update_lifecycle.h" #include "mongo/db/query/canonical_query.h" #include "mongo/db/query/explain.h" #include "mongo/db/query/query_settings.h" #include "mongo/db/query/index_bounds_builder.h" #include "mongo/db/query/internal_plans.h" #include "mongo/db/query/plan_cache.h" #include "mongo/db/query/plan_executor.h" #include "mongo/db/query/planner_analysis.h" #include "mongo/db/query/planner_access.h" #include "mongo/db/query/qlog.h" #include "mongo/db/query/query_knobs.h" #include "mongo/db/query/query_planner.h" #include "mongo/db/query/query_planner_common.h" #include "mongo/db/query/stage_builder.h" #include "mongo/db/repl/replication_coordinator_global.h" #include "mongo/db/index_names.h" #include "mongo/db/server_options.h" #include "mongo/db/server_parameters.h" #include "mongo/s/d_state.h" #include "mongo/scripting/engine.h" #include "mongo/util/log.h" namespace mongo { using std::auto_ptr; using std::endl; using std::string; using std::vector; // static void filterAllowedIndexEntries(const AllowedIndices& allowedIndices, std::vector* indexEntries) { invariant(indexEntries); // Filter index entries // Check BSON objects in AllowedIndices::_indexKeyPatterns against IndexEntry::keyPattern. // Removes IndexEntrys that do not match _indexKeyPatterns. std::vector temp; for (std::vector::const_iterator i = indexEntries->begin(); i != indexEntries->end(); ++i) { const IndexEntry& indexEntry = *i; for (std::vector::const_iterator j = allowedIndices.indexKeyPatterns.begin(); j != allowedIndices.indexKeyPatterns.end(); ++j) { const BSONObj& index = *j; // Copy index entry to temp vector if found in query settings. if (0 == indexEntry.keyPattern.woCompare(index)) { temp.push_back(indexEntry); break; } } } // Update results. temp.swap(*indexEntries); } namespace { // The body is below in the "count hack" section but getExecutor calls it. bool turnIxscanIntoCount(QuerySolution* soln); } // namespace void fillOutPlannerParams(OperationContext* txn, Collection* collection, CanonicalQuery* canonicalQuery, QueryPlannerParams* plannerParams) { // If it's not NULL, we may have indices. Access the catalog and fill out IndexEntry(s) IndexCatalog::IndexIterator ii = collection->getIndexCatalog()->getIndexIterator(txn, false); while (ii.more()) { const IndexDescriptor* desc = ii.next(); plannerParams->indices.push_back(IndexEntry(desc->keyPattern(), desc->getAccessMethodName(), desc->isMultikey(txn), desc->isSparse(), desc->indexName(), desc->infoObj())); } // If query supports index filters, filter params.indices by indices in query settings. QuerySettings* querySettings = collection->infoCache()->getQuerySettings(); AllowedIndices* allowedIndicesRaw; // Filter index catalog if index filters are specified for query. // Also, signal to planner that application hint should be ignored. if (querySettings->getAllowedIndices(*canonicalQuery, &allowedIndicesRaw)) { boost::scoped_ptr allowedIndices(allowedIndicesRaw); filterAllowedIndexEntries(*allowedIndices, &plannerParams->indices); plannerParams->indexFiltersApplied = true; } // We will not output collection scans unless there are no indexed solutions. NO_TABLE_SCAN // overrides this behavior by not outputting a collscan even if there are no indexed // solutions. if (storageGlobalParams.noTableScan) { const string& ns = canonicalQuery->ns(); // There are certain cases where we ignore this restriction: bool ignore = canonicalQuery->getQueryObj().isEmpty() || (string::npos != ns.find(".system.")) || (0 == ns.find("local.")); if (!ignore) { plannerParams->options |= QueryPlannerParams::NO_TABLE_SCAN; } } // If the caller wants a shard filter, make sure we're actually sharded. if (plannerParams->options & QueryPlannerParams::INCLUDE_SHARD_FILTER) { CollectionMetadataPtr collMetadata = shardingState.getCollectionMetadata(canonicalQuery->ns()); if (collMetadata) { plannerParams->shardKey = collMetadata->getKeyPattern(); } else { // If there's no metadata don't bother w/the shard filter since we won't know what // the key pattern is anyway... plannerParams->options &= ~QueryPlannerParams::INCLUDE_SHARD_FILTER; } } if (internalQueryPlannerEnableIndexIntersection) { plannerParams->options |= QueryPlannerParams::INDEX_INTERSECTION; } plannerParams->options |= QueryPlannerParams::SPLIT_LIMITED_SORT; // Doc-level locking storage engines do not use the invalidation framework, and therefore // have no need for KEEP_MUTATIONS. if (!supportsDocLocking()) { plannerParams->options |= QueryPlannerParams::KEEP_MUTATIONS; } } namespace { /** * Build an execution tree for the query described in 'canonicalQuery'. Does not take * ownership of arguments. * * If an execution tree could be created, then returns Status::OK() and sets 'rootOut' to * the root of the constructed execution tree, and sets 'querySolutionOut' to the associated * query solution (if applicable) or NULL. * * If an execution tree could not be created, returns a Status indicating why and sets both * 'rootOut' and 'querySolutionOut' to NULL. */ Status prepareExecution(OperationContext* opCtx, Collection* collection, WorkingSet* ws, CanonicalQuery* canonicalQuery, size_t plannerOptions, PlanStage** rootOut, QuerySolution** querySolutionOut) { invariant(canonicalQuery); *rootOut = NULL; *querySolutionOut = NULL; // This can happen as we're called by internal clients as well. if (NULL == collection) { const string& ns = canonicalQuery->ns(); LOG(2) << "Collection " << ns << " does not exist." << " Using EOF plan: " << canonicalQuery->toStringShort(); *rootOut = new EOFStage(); return Status::OK(); } // Fill out the planning params. We use these for both cached solutions and non-cached. QueryPlannerParams plannerParams; plannerParams.options = plannerOptions; fillOutPlannerParams(opCtx, collection, canonicalQuery, &plannerParams); // If we have an _id index we can use an idhack plan. if (IDHackStage::supportsQuery(*canonicalQuery) && collection->getIndexCatalog()->findIdIndex(opCtx)) { LOG(2) << "Using idhack: " << canonicalQuery->toStringShort(); *rootOut = new IDHackStage(opCtx, collection, canonicalQuery, ws); // Might have to filter out orphaned docs. if (plannerParams.options & QueryPlannerParams::INCLUDE_SHARD_FILTER) { *rootOut = new ShardFilterStage(shardingState.getCollectionMetadata(collection->ns()), ws, *rootOut); } // There might be a projection. The idhack stage will always fetch the full // document, so we don't support covered projections. However, we might use the // simple inclusion fast path. if (NULL != canonicalQuery->getProj()) { ProjectionStageParams params(WhereCallbackReal(opCtx, collection->ns().db())); params.projObj = canonicalQuery->getProj()->getProjObj(); // Stuff the right data into the params depending on what proj impl we use. if (canonicalQuery->getProj()->requiresDocument() || canonicalQuery->getProj()->wantIndexKey()) { params.fullExpression = canonicalQuery->root(); params.projImpl = ProjectionStageParams::NO_FAST_PATH; } else { params.projImpl = ProjectionStageParams::SIMPLE_DOC; } *rootOut = new ProjectionStage(params, ws, *rootOut); } return Status::OK(); } // Tailable: If the query requests tailable the collection must be capped. if (canonicalQuery->getParsed().getOptions().tailable) { if (!collection->isCapped()) { return Status(ErrorCodes::BadValue, "error processing query: " + canonicalQuery->toString() + " tailable cursor requested on non capped collection"); } // If a sort is specified it must be equal to expectedSort. const BSONObj expectedSort = BSON("$natural" << 1); const BSONObj& actualSort = canonicalQuery->getParsed().getSort(); if (!actualSort.isEmpty() && !(actualSort == expectedSort)) { return Status(ErrorCodes::BadValue, "error processing query: " + canonicalQuery->toString() + " invalid sort specified for tailable cursor: " + actualSort.toString()); } } // Try to look up a cached solution for the query. CachedSolution* rawCS; if (PlanCache::shouldCacheQuery(*canonicalQuery) && collection->infoCache()->getPlanCache()->get(*canonicalQuery, &rawCS).isOK()) { // We have a CachedSolution. Have the planner turn it into a QuerySolution. boost::scoped_ptr cs(rawCS); QuerySolution *qs, *backupQs; Status status = QueryPlanner::planFromCache(*canonicalQuery, plannerParams, *cs, &qs, &backupQs); if (status.isOK()) { PlanStage *backupRoot = NULL; // The working set is shared by the root and backupRoot plans. verify(StageBuilder::build(opCtx, collection, *qs, ws, rootOut)); if ((plannerParams.options & QueryPlannerParams::PRIVATE_IS_COUNT) && turnIxscanIntoCount(qs)) { LOG(2) << "Using fast count: " << canonicalQuery->toStringShort() << ", planSummary: " << Explain::getPlanSummary(*rootOut); } else if (NULL != backupQs) { verify(StageBuilder::build(opCtx, collection, *backupQs, ws, &backupRoot)); } // Add a CachedPlanStage on top of the previous root. Takes ownership of // '*rootOut', 'backupRoot', 'qs', and 'backupQs'. *rootOut = new CachedPlanStage(collection, canonicalQuery, *rootOut, qs, backupRoot, backupQs); return Status::OK(); } } if (internalQueryPlanOrChildrenIndependently && SubplanStage::canUseSubplanning(*canonicalQuery)) { QLOG() << "Running query as sub-queries: " << canonicalQuery->toStringShort(); LOG(2) << "Running query as sub-queries: " << canonicalQuery->toStringShort(); *rootOut = new SubplanStage(opCtx, collection, ws, plannerParams, canonicalQuery); return Status::OK(); } vector solutions; Status status = QueryPlanner::plan(*canonicalQuery, plannerParams, &solutions); if (!status.isOK()) { return Status(ErrorCodes::BadValue, "error processing query: " + canonicalQuery->toString() + " planner returned error: " + status.reason()); } // We cannot figure out how to answer the query. Perhaps it requires an index // we do not have? if (0 == solutions.size()) { return Status(ErrorCodes::BadValue, str::stream() << "error processing query: " << canonicalQuery->toString() << " No query solutions"); } // See if one of our solutions is a fast count hack in disguise. if (plannerParams.options & QueryPlannerParams::PRIVATE_IS_COUNT) { for (size_t i = 0; i < solutions.size(); ++i) { if (turnIxscanIntoCount(solutions[i])) { // Great, we can use solutions[i]. Clean up the other QuerySolution(s). for (size_t j = 0; j < solutions.size(); ++j) { if (j != i) { delete solutions[j]; } } // We're not going to cache anything that's fast count. verify(StageBuilder::build(opCtx, collection, *solutions[i], ws, rootOut)); LOG(2) << "Using fast count: " << canonicalQuery->toStringShort() << ", planSummary: " << Explain::getPlanSummary(*rootOut); *querySolutionOut = solutions[i]; return Status::OK(); } } } if (1 == solutions.size()) { // Only one possible plan. Run it. Build the stages from the solution. verify(StageBuilder::build(opCtx, collection, *solutions[0], ws, rootOut)); LOG(2) << "Only one plan is available; it will be run but will not be cached. " << canonicalQuery->toStringShort() << ", planSummary: " << Explain::getPlanSummary(*rootOut); *querySolutionOut = solutions[0]; return Status::OK(); } else { // Many solutions. Create a MultiPlanStage to pick the best, update the cache, // and so on. The working set will be shared by all candidate plans. MultiPlanStage* multiPlanStage = new MultiPlanStage(opCtx, collection, canonicalQuery); for (size_t ix = 0; ix < solutions.size(); ++ix) { if (solutions[ix]->cacheData.get()) { solutions[ix]->cacheData->indexFilterApplied = plannerParams.indexFiltersApplied; } // version of StageBuild::build when WorkingSet is shared PlanStage* nextPlanRoot; verify(StageBuilder::build(opCtx, collection, *solutions[ix], ws, &nextPlanRoot)); // Owns none of the arguments multiPlanStage->addPlan(solutions[ix], nextPlanRoot, ws); } *rootOut = multiPlanStage; return Status::OK(); } } } // namespace Status getExecutor(OperationContext* txn, Collection* collection, CanonicalQuery* rawCanonicalQuery, PlanExecutor::YieldPolicy yieldPolicy, PlanExecutor** out, size_t plannerOptions) { auto_ptr canonicalQuery(rawCanonicalQuery); auto_ptr ws(new WorkingSet()); PlanStage* root; QuerySolution* querySolution; Status status = prepareExecution(txn, collection, ws.get(), canonicalQuery.get(), plannerOptions, &root, &querySolution); if (!status.isOK()) { return status; } invariant(root); // We must have a tree of stages in order to have a valid plan executor, but the query // solution may be null. return PlanExecutor::make(txn, ws.release(), root, querySolution, canonicalQuery.release(), collection, yieldPolicy, out); } Status getExecutor(OperationContext* txn, Collection* collection, const std::string& ns, const BSONObj& unparsedQuery, PlanExecutor::YieldPolicy yieldPolicy, PlanExecutor** out, size_t plannerOptions) { if (!collection) { LOG(2) << "Collection " << ns << " does not exist." << " Using EOF stage: " << unparsedQuery.toString(); EOFStage* eofStage = new EOFStage(); WorkingSet* ws = new WorkingSet(); return PlanExecutor::make(txn, ws, eofStage, ns, yieldPolicy, out); } if (!CanonicalQuery::isSimpleIdQuery(unparsedQuery) || !collection->getIndexCatalog()->findIdIndex(txn)) { const WhereCallbackReal whereCallback(txn, collection->ns().db()); CanonicalQuery* cq; Status status = CanonicalQuery::canonicalize(collection->ns(), unparsedQuery, &cq, whereCallback); if (!status.isOK()) return status; // Takes ownership of 'cq'. return getExecutor(txn, collection, cq, yieldPolicy, out, plannerOptions); } LOG(2) << "Using idhack: " << unparsedQuery.toString(); WorkingSet* ws = new WorkingSet(); PlanStage* root = new IDHackStage(txn, collection, unparsedQuery["_id"].wrap(), ws); // Might have to filter out orphaned docs. if (plannerOptions & QueryPlannerParams::INCLUDE_SHARD_FILTER) { root = new ShardFilterStage(shardingState.getCollectionMetadata(collection->ns()), ws, root); } return PlanExecutor::make(txn, ws, root, collection, yieldPolicy, out); } // // Delete // Status getExecutorDelete(OperationContext* txn, Collection* collection, ParsedDelete* parsedDelete, PlanExecutor** execOut) { const DeleteRequest* request = parsedDelete->getRequest(); const NamespaceString& nss(request->getNamespaceString()); if (!request->isGod()) { if (nss.isSystem()) { uassert(12050, "cannot delete from system namespace", legalClientSystemNS(nss.ns(), true)); } if (nss.ns().find('$') != string::npos) { log() << "cannot delete from collection with reserved $ in name: " << nss << endl; uasserted(10100, "cannot delete from collection with reserved $ in name"); } } if (collection && collection->isCapped()) { return Status(ErrorCodes::IllegalOperation, str::stream() << "cannot remove from a capped collection: " << nss.ns()); } if (request->shouldCallLogOp() && !repl::getGlobalReplicationCoordinator()->canAcceptWritesForDatabase(nss.db())) { return Status(ErrorCodes::NotMaster, str::stream() << "Not primary while removing from " << nss.ns()); } DeleteStageParams deleteStageParams; deleteStageParams.isMulti = request->isMulti(); deleteStageParams.shouldCallLogOp = request->shouldCallLogOp(); deleteStageParams.fromMigrate = request->isFromMigrate(); deleteStageParams.isExplain = request->isExplain(); auto_ptr ws(new WorkingSet()); PlanExecutor::YieldPolicy policy = parsedDelete->canYield() ? PlanExecutor::YIELD_AUTO : PlanExecutor::YIELD_MANUAL; if (!parsedDelete->hasParsedQuery()) { // This is the idhack fast-path for getting a PlanExecutor without doing the work // to create a CanonicalQuery. const BSONObj& unparsedQuery = request->getQuery(); if (!collection) { // Treat collections that do not exist as empty collections. Note that the explain // reporting machinery always assumes that the root stage for a delete operation is // a DeleteStage, so in this case we put a DeleteStage on top of an EOFStage. LOG(2) << "Collection " << nss.ns() << " does not exist." << " Using EOF stage: " << unparsedQuery.toString(); DeleteStage* deleteStage = new DeleteStage(txn, deleteStageParams, ws.get(), NULL, new EOFStage()); return PlanExecutor::make(txn, ws.release(), deleteStage, nss.ns(), policy, execOut); } if (CanonicalQuery::isSimpleIdQuery(unparsedQuery) && collection->getIndexCatalog()->findIdIndex(txn)) { LOG(2) << "Using idhack: " << unparsedQuery.toString(); PlanStage* idHackStage = new IDHackStage(txn, collection, unparsedQuery["_id"].wrap(), ws.get()); DeleteStage* root = new DeleteStage(txn, deleteStageParams, ws.get(), collection, idHackStage); return PlanExecutor::make(txn, ws.release(), root, collection, policy, execOut); } // If we're here then we don't have a parsed query, but we're also not eligible for // the idhack fast path. We need to force canonicalization now. Status cqStatus = parsedDelete->parseQueryToCQ(); if (!cqStatus.isOK()) { return cqStatus; } } // This is the regular path for when we have a CanonicalQuery. std::auto_ptr cq(parsedDelete->releaseParsedQuery()); PlanStage* root; QuerySolution* querySolution; const size_t defaultPlannerOptions = 0; Status status = prepareExecution(txn, collection, ws.get(), cq.get(), defaultPlannerOptions, &root, &querySolution); if (!status.isOK()) { return status; } invariant(root); root = new DeleteStage(txn, deleteStageParams, ws.get(), collection, root); // We must have a tree of stages in order to have a valid plan executor, but the query // solution may be null. return PlanExecutor::make(txn, ws.release(), root, querySolution, cq.release(), collection, policy, execOut); } // // Update // namespace { // TODO: Make this a function on NamespaceString, or make it cleaner. inline void validateUpdate(const char* ns , const BSONObj& updateobj, const BSONObj& patternOrig) { uassert(10155 , "cannot update reserved $ collection", strchr(ns, '$') == 0); if (strstr(ns, ".system.")) { /* dm: it's very important that system.indexes is never updated as IndexDetails has pointers into it */ uassert(10156, str::stream() << "cannot update system collection: " << ns << " q: " << patternOrig << " u: " << updateobj, legalClientSystemNS(ns , true)); } } } // namespace Status getExecutorUpdate(OperationContext* txn, Collection* collection, ParsedUpdate* parsedUpdate, OpDebug* opDebug, PlanExecutor** execOut) { const UpdateRequest* request = parsedUpdate->getRequest(); UpdateDriver* driver = parsedUpdate->getDriver(); const NamespaceString& nsString = request->getNamespaceString(); UpdateLifecycle* lifecycle = request->getLifecycle(); validateUpdate(nsString.ns().c_str(), request->getUpdates(), request->getQuery()); // If there is no collection and this is an upsert, callers are supposed to create // the collection prior to calling this method. Explain, however, will never do // collection or database creation. if (!collection && request->isUpsert()) { invariant(request->isExplain()); } // TODO: This seems a bit circuitious. opDebug->updateobj = request->getUpdates(); // If this is a user-issued update, then we want to return an error: you cannot perform // writes on a secondary. If this is an update to a secondary from the replication system, // however, then we make an exception and let the write proceed. In this case, // shouldCallLogOp() will be false. if (request->shouldCallLogOp() && !repl::getGlobalReplicationCoordinator()->canAcceptWritesForDatabase(nsString.db())) { return Status(ErrorCodes::NotMaster, str::stream() << "Not primary while performing update on " << nsString.ns()); } if (lifecycle) { lifecycle->setCollection(collection); driver->refreshIndexKeys(lifecycle->getIndexKeys(txn)); } PlanExecutor::YieldPolicy policy = parsedUpdate->canYield() ? PlanExecutor::YIELD_AUTO : PlanExecutor::YIELD_MANUAL; auto_ptr ws(new WorkingSet()); UpdateStageParams updateStageParams(request, driver, opDebug); if (!parsedUpdate->hasParsedQuery()) { // This is the idhack fast-path for getting a PlanExecutor without doing the work // to create a CanonicalQuery. const BSONObj& unparsedQuery = request->getQuery(); if (!collection) { // Treat collections that do not exist as empty collections. Note that the explain // reporting machinery always assumes that the root stage for an update operation is // an UpdateStage, so in this case we put an UpdateStage on top of an EOFStage. LOG(2) << "Collection " << nsString.ns() << " does not exist." << " Using EOF stage: " << unparsedQuery.toString(); UpdateStage* updateStage = new UpdateStage(txn, updateStageParams, ws.get(), collection, new EOFStage()); return PlanExecutor::make(txn, ws.release(), updateStage, nsString.ns(), policy, execOut); } if (CanonicalQuery::isSimpleIdQuery(unparsedQuery) && collection->getIndexCatalog()->findIdIndex(txn)) { LOG(2) << "Using idhack: " << unparsedQuery.toString(); PlanStage* idHackStage = new IDHackStage(txn, collection, unparsedQuery["_id"].wrap(), ws.get()); UpdateStage* root = new UpdateStage(txn, updateStageParams, ws.get(), collection, idHackStage); return PlanExecutor::make(txn, ws.release(), root, collection, policy, execOut); } // If we're here then we don't have a parsed query, but we're also not eligible for // the idhack fast path. We need to force canonicalization now. Status cqStatus = parsedUpdate->parseQueryToCQ(); if (!cqStatus.isOK()) { return cqStatus; } } // This is the regular path for when we have a CanonicalQuery. std::auto_ptr cq(parsedUpdate->releaseParsedQuery()); PlanStage* root; QuerySolution* querySolution; const size_t defaultPlannerOptions = 0; Status status = prepareExecution(txn, collection, ws.get(), cq.get(), defaultPlannerOptions, &root, &querySolution); if (!status.isOK()) { return status; } invariant(root); updateStageParams.canonicalQuery = cq.get(); root = new UpdateStage(txn, updateStageParams, ws.get(), collection, root); // We must have a tree of stages in order to have a valid plan executor, but the query // solution may be null. Takes ownership of all args other than 'collection' and 'txn' return PlanExecutor::make(txn, ws.release(), root, querySolution, cq.release(), collection, policy, execOut); } // // Group // Status getExecutorGroup(OperationContext* txn, Collection* collection, const GroupRequest& request, PlanExecutor::YieldPolicy yieldPolicy, PlanExecutor** execOut) { if (!globalScriptEngine) { return Status(ErrorCodes::BadValue, "server-side JavaScript execution is disabled"); } auto_ptr ws(new WorkingSet()); PlanStage* root; QuerySolution* querySolution; if (!collection) { // Treat collections that do not exist as empty collections. Note that the explain // reporting machinery always assumes that the root stage for a group operation is a // GroupStage, so in this case we put a GroupStage on top of an EOFStage. root = new GroupStage(txn, request, ws.get(), new EOFStage()); return PlanExecutor::make(txn, ws.release(), root, request.ns, yieldPolicy, execOut); } const NamespaceString nss(request.ns); const WhereCallbackReal whereCallback(txn, nss.db()); CanonicalQuery* rawCanonicalQuery; Status canonicalizeStatus = CanonicalQuery::canonicalize(request.ns, request.query, request.explain, &rawCanonicalQuery, whereCallback); if (!canonicalizeStatus.isOK()) { return canonicalizeStatus; } auto_ptr canonicalQuery(rawCanonicalQuery); const size_t defaultPlannerOptions = 0; Status status = prepareExecution(txn, collection, ws.get(), canonicalQuery.get(), defaultPlannerOptions, &root, &querySolution); if (!status.isOK()) { return status; } invariant(root); root = new GroupStage(txn, request, ws.get(), root); // We must have a tree of stages in order to have a valid plan executor, but the query // solution may be null. Takes ownership of all args other than 'collection'. return PlanExecutor::make(txn, ws.release(), root, querySolution, canonicalQuery.release(), collection, yieldPolicy, execOut); } // // Count hack // namespace { /** * Returns 'true' if the provided solution 'soln' can be rewritten to use * a fast counting stage. Mutates the tree in 'soln->root'. * * Otherwise, returns 'false'. */ bool turnIxscanIntoCount(QuerySolution* soln) { QuerySolutionNode* root = soln->root.get(); // Root should be a fetch w/o any filters. if (STAGE_FETCH != root->getType()) { return false; } if (NULL != root->filter.get()) { return false; } // Child should be an ixscan. if (STAGE_IXSCAN != root->children[0]->getType()) { return false; } IndexScanNode* isn = static_cast(root->children[0]); // No filters allowed and side-stepping isSimpleRange for now. TODO: do we ever see // isSimpleRange here? because we could well use it. I just don't think we ever do see // it. if (NULL != isn->filter.get() || isn->bounds.isSimpleRange) { return false; } // Make sure the bounds are OK. BSONObj startKey; bool startKeyInclusive; BSONObj endKey; bool endKeyInclusive; if (!IndexBoundsBuilder::isSingleInterval( isn->bounds, &startKey, &startKeyInclusive, &endKey, &endKeyInclusive )) { return false; } // Make the count node that we replace the fetch + ixscan with. CountNode* cn = new CountNode(); cn->indexKeyPattern = isn->indexKeyPattern; cn->startKey = startKey; cn->startKeyInclusive = startKeyInclusive; cn->endKey = endKey; cn->endKeyInclusive = endKeyInclusive; // Takes ownership of 'cn' and deletes the old root. soln->root.reset(cn); return true; } /** * Returns true if indices contains an index that can be * used with DistinctNode. Sets indexOut to the array index * of PlannerParams::indices. * Look for the index for the fewest fields. * Criteria for suitable index is that the index cannot be special * (geo, hashed, text, ...). * * Multikey indices are not suitable for DistinctNode when the projection * is on an array element. Arrays are flattened in a multikey index which * makes it impossible for the distinct scan stage (plan stage generated from * DistinctNode) to select the requested element by array index. * * Multikey indices cannot be used for the fast distinct hack if the field is dotted. * Currently the solution generated for the distinct hack includes a projection stage and * the projection stage cannot be covered with a dotted field. */ bool getDistinctNodeIndex(const std::vector& indices, const std::string& field, size_t* indexOut) { invariant(indexOut); bool isDottedField = str::contains(field, '.'); int minFields = std::numeric_limits::max(); for (size_t i = 0; i < indices.size(); ++i) { // Skip special indices. if (!IndexNames::findPluginName(indices[i].keyPattern).empty()) { continue; } // Skip multikey indices if we are projecting on a dotted field. if (indices[i].multikey && isDottedField) { continue; } int nFields = indices[i].keyPattern.nFields(); // Pick the index with the lowest number of fields. if (nFields < minFields) { minFields = nFields; *indexOut = i; } } return minFields != std::numeric_limits::max(); } /** * Checks dotted field for a projection and truncates the * field name if we could be projecting on an array element. * Sets 'isIDOut' to true if the projection is on a sub document of _id. * For example, _id.a.2, _id.b.c. */ std::string getProjectedDottedField(const std::string& field, bool* isIDOut) { // Check if field contains an array index. std::vector res; mongo::splitStringDelim(field, &res, '.'); // Since we could exit early from the loop, // we should check _id here and set '*isIDOut' accordingly. *isIDOut = ("_id" == res[0]); // Skip the first dotted component. If the field starts // with a number, the number cannot be an array index. int arrayIndex = 0; for (size_t i = 1; i < res.size(); ++i) { if (mongo::parseNumberFromStringWithBase(res[i], 10, &arrayIndex).isOK()) { // Array indices cannot be negative numbers (this is not $slice). // Negative numbers are allowed as field names. if (arrayIndex >= 0) { // Generate prefix of field up to (but not including) array index. std::vector prefixStrings(res); prefixStrings.resize(i); // Reset projectedField. Instead of overwriting, joinStringDelim() appends joined string // to the end of projectedField. std::string projectedField; mongo::joinStringDelim(prefixStrings, &projectedField, '.'); return projectedField; } } } return field; } /** * Creates a projection spec for a distinct command from the requested field. * In most cases, the projection spec will be {_id: 0, key: 1}. * The exceptions are: * 1) When the requested field is '_id', the projection spec will {_id: 1}. * 2) When the requested field could be an array element (eg. a.0), * the projected field will be the prefix of the field up to the array element. * For example, a.b.2 => {_id: 0, 'a.b': 1} * Note that we can't use a $slice projection because the distinct command filters * the results from the executor using the dotted field name. Using $slice will * re-order the documents in the array in the results. */ BSONObj getDistinctProjection(const std::string& field) { std::string projectedField(field); bool isID = false; if ("_id" == field) { isID = true; } else if (str::contains(field, '.')) { projectedField = getProjectedDottedField(field, &isID); } BSONObjBuilder bob; if (!isID) { bob.append("_id", 0); } bob.append(projectedField, 1); return bob.obj(); } } // namespace Status getExecutorCount(OperationContext* txn, Collection* collection, const CountRequest& request, PlanExecutor::YieldPolicy yieldPolicy, PlanExecutor** execOut) { auto_ptr ws(new WorkingSet()); PlanStage* root; QuerySolution* querySolution; // If collection exists and the query is empty, no additional canonicalization is needed. if (collection && request.query.isEmpty()) { // If the query is empty, then we can determine the count by just asking the collection // for its number of records. This is implemented by the CountStage, and we don't need // to create a child for the count stage in this case. root = new CountStage(txn, collection, request, ws.get(), NULL); return PlanExecutor::make(txn, ws.release(), root, request.ns, yieldPolicy, execOut); } auto_ptr cq; if (!request.query.isEmpty()) { // If query is not empty, canonicalize it before working with collection. typedef MatchExpressionParser::WhereCallback WhereCallback; CanonicalQuery* rawCq = NULL; Status canonStatus = CanonicalQuery::canonicalize( request.ns, request.query, BSONObj(), // sort BSONObj(), // projection 0, // skip 0, // limit request.hint, BSONObj(), // min BSONObj(), // max false, // snapshot request.explain, &rawCq, collection ? static_cast(WhereCallbackReal(txn, collection->ns().db())) : static_cast(WhereCallbackNoop())); if (!canonStatus.isOK()) { return canonStatus; } cq.reset(rawCq); } if (!collection) { // Treat collections that do not exist as empty collections. Note that the explain // reporting machinery always assumes that the root stage for a count operation is // a CountStage, so in this case we put a CountStage on top of an EOFStage. root = new CountStage(txn, collection, request, ws.get(), new EOFStage()); return PlanExecutor::make(txn, ws.release(), root, request.ns, yieldPolicy, execOut); } invariant(cq.get()); const size_t plannerOptions = QueryPlannerParams::PRIVATE_IS_COUNT; Status prepStatus = prepareExecution(txn, collection, ws.get(), cq.get(), plannerOptions, &root, &querySolution); if (!prepStatus.isOK()) { return prepStatus; } invariant(root); // Make a CountStage to be the new root. root = new CountStage(txn, collection, request, ws.get(), root); // We must have a tree of stages in order to have a valid plan executor, but the query // solution may be NULL. Takes ownership of all args other than 'collection' and 'txn' return PlanExecutor::make(txn, ws.release(), root, querySolution, cq.release(), collection, yieldPolicy, execOut); } // // Distinct hack // bool turnIxscanIntoDistinctIxscan(QuerySolution* soln, const string& field) { QuerySolutionNode* root = soln->root.get(); // We're looking for a project on top of an ixscan. if (STAGE_PROJECTION == root->getType() && (STAGE_IXSCAN == root->children[0]->getType())) { IndexScanNode* isn = static_cast(root->children[0]); // An additional filter must be applied to the data in the key, so we can't just skip // all the keys with a given value; we must examine every one to find the one that (may) // pass the filter. if (NULL != isn->filter.get()) { return false; } // We only set this when we have special query modifiers (.max() or .min()) or other // special cases. Don't want to handle the interactions between those and distinct. // Don't think this will ever really be true but if it somehow is, just ignore this // soln. if (isn->bounds.isSimpleRange) { return false; } // Make a new DistinctNode. We swap this for the ixscan in the provided solution. DistinctNode* dn = new DistinctNode(); dn->indexKeyPattern = isn->indexKeyPattern; dn->direction = isn->direction; dn->bounds = isn->bounds; // Figure out which field we're skipping to the next value of. TODO: We currently only // try to distinct-hack when there is an index prefixed by the field we're distinct-ing // over. Consider removing this code if we stick with that policy. dn->fieldNo = 0; BSONObjIterator it(isn->indexKeyPattern); while (it.more()) { if (field == it.next().fieldName()) { break; } dn->fieldNo++; } // Delete the old index scan, set the child of project to the fast distinct scan. delete root->children[0]; root->children[0] = dn; return true; } return false; } Status getExecutorDistinct(OperationContext* txn, Collection* collection, const BSONObj& query, const std::string& field, PlanExecutor::YieldPolicy yieldPolicy, PlanExecutor** out) { // This should'a been checked by the distinct command. invariant(collection); // TODO: check for idhack here? // When can we do a fast distinct hack? // 1. There is a plan with just one leaf and that leaf is an ixscan. // 2. The ixscan indexes the field we're interested in. // 2a: We are correct if the index contains the field but for now we look for prefix. // 3. The query is covered/no fetch. // // We go through normal planning (with limited parameters) to see if we can produce // a soln with the above properties. QueryPlannerParams plannerParams; plannerParams.options = QueryPlannerParams::NO_TABLE_SCAN; IndexCatalog::IndexIterator ii = collection->getIndexCatalog()->getIndexIterator(txn,false); while (ii.more()) { const IndexDescriptor* desc = ii.next(); // The distinct hack can work if any field is in the index but it's not always clear // if it's a win unless it's the first field. if (desc->keyPattern().firstElement().fieldName() == field) { plannerParams.indices.push_back(IndexEntry(desc->keyPattern(), desc->getAccessMethodName(), desc->isMultikey(txn), desc->isSparse(), desc->indexName(), desc->infoObj())); } } const WhereCallbackReal whereCallback(txn, collection->ns().db()); // If there are no suitable indices for the distinct hack bail out now into regular planning // with no projection. if (plannerParams.indices.empty()) { CanonicalQuery* cq; Status status = CanonicalQuery::canonicalize( collection->ns().ns(), query, &cq, whereCallback); if (!status.isOK()) { return status; } // Takes ownership of 'cq'. return getExecutor(txn, collection, cq, yieldPolicy, out); } // // If we're here, we have an index prefixed by the field we're distinct-ing over. // // Applying a projection allows the planner to try to give us covered plans that we can turn // into the projection hack. getDistinctProjection deals with .find() projection semantics // (ie _id:1 being implied by default). BSONObj projection = getDistinctProjection(field); // Apply a projection of the key. Empty BSONObj() is for the sort. CanonicalQuery* cq; Status status = CanonicalQuery::canonicalize(collection->ns().ns(), query, BSONObj(), projection, &cq, whereCallback); if (!status.isOK()) { return status; } auto_ptr autoCq(cq); // If there's no query, we can just distinct-scan one of the indices. // Not every index in plannerParams.indices may be suitable. Refer to // getDistinctNodeIndex(). size_t distinctNodeIndex = 0; if (query.isEmpty() && getDistinctNodeIndex(plannerParams.indices, field, &distinctNodeIndex)) { DistinctNode* dn = new DistinctNode(); dn->indexKeyPattern = plannerParams.indices[distinctNodeIndex].keyPattern; dn->direction = 1; IndexBoundsBuilder::allValuesBounds(dn->indexKeyPattern, &dn->bounds); dn->fieldNo = 0; QueryPlannerParams params; // Takes ownership of 'dn'. QuerySolution* soln = QueryPlannerAnalysis::analyzeDataAccess(*cq, params, dn); invariant(soln); WorkingSet* ws = new WorkingSet(); PlanStage* root; verify(StageBuilder::build(txn, collection, *soln, ws, &root)); LOG(2) << "Using fast distinct: " << cq->toStringShort() << ", planSummary: " << Explain::getPlanSummary(root); // Takes ownership of its arguments (except for 'collection'). return PlanExecutor::make(txn, ws, root, soln, autoCq.release(), collection, yieldPolicy, out); } // See if we can answer the query in a fast-distinct compatible fashion. vector solutions; status = QueryPlanner::plan(*cq, plannerParams, &solutions); if (!status.isOK()) { return getExecutor(txn, collection, autoCq.release(), yieldPolicy, out); } // We look for a solution that has an ixscan we can turn into a distinctixscan for (size_t i = 0; i < solutions.size(); ++i) { if (turnIxscanIntoDistinctIxscan(solutions[i], field)) { // Great, we can use solutions[i]. Clean up the other QuerySolution(s). for (size_t j = 0; j < solutions.size(); ++j) { if (j != i) { delete solutions[j]; } } // Build and return the SSR over solutions[i]. WorkingSet* ws = new WorkingSet(); PlanStage* root; verify(StageBuilder::build(txn, collection, *solutions[i], ws, &root)); LOG(2) << "Using fast distinct: " << cq->toStringShort() << ", planSummary: " << Explain::getPlanSummary(root); // Takes ownership of 'ws', 'root', 'solutions[i]', and 'autoCq'. return PlanExecutor::make(txn, ws, root, solutions[i], autoCq.release(), collection, yieldPolicy, out); } } // If we're here, the planner made a soln with the restricted index set but we couldn't // translate any of them into a distinct-compatible soln. So, delete the solutions and just // go through normal planning. for (size_t i = 0; i < solutions.size(); ++i) { delete solutions[i]; } // We drop the projection from the 'cq'. Unfortunately this is not trivial. status = CanonicalQuery::canonicalize(collection->ns().ns(), query, &cq, whereCallback); if (!status.isOK()) { return status; } autoCq.reset(cq); // Takes ownership of 'autoCq'. return getExecutor(txn, collection, autoCq.release(), yieldPolicy, out); } } // namespace mongo