// static QuerySolution* QueryPlannerAnalysis::analyzeDataAccess(const CanonicalQuery& query, const QueryPlannerParams& params, QuerySolutionNode* solnRoot) { auto_ptr<QuerySolution> soln(new QuerySolution()); soln->filterData = query.getQueryObj(); verify(soln->filterData.isOwned()); soln->ns = query.ns(); soln->indexFilterApplied = params.indexFiltersApplied; solnRoot->computeProperties(); // solnRoot finds all our results. Let's see what transformations we must perform to the // data. // If we're answering a query on a sharded system, we need to drop documents that aren't // logically part of our shard (XXX GREG elaborate more precisely) if (params.options & QueryPlannerParams::INCLUDE_SHARD_FILTER) { // XXX TODO: use params.shardKey to do fetch analysis instead of always fetching. if (!solnRoot->fetched()) { FetchNode* fetch = new FetchNode(); fetch->children.push_back(solnRoot); solnRoot = fetch; } ShardingFilterNode* sfn = new ShardingFilterNode(); sfn->children.push_back(solnRoot); solnRoot = sfn; } solnRoot = analyzeSort(query, params, solnRoot, &soln->hasSortStage); // This can happen if we need to create a blocking sort stage and we're not allowed to. if (NULL == solnRoot) { return NULL; } // If we can (and should), add the keep mutations stage. // We cannot keep mutated documents if: // // 1. The query requires an index to evaluate the predicate ($text). We can't tell whether // or not the doc actually satisfies the $text predicate since we can't evaluate a // text MatchExpression. // // 2. The query implies a sort ($geoNear). It would be rather expensive and hacky to merge // the document at the right place. // // 3. There is an index-provided sort. Ditto above comment about merging. // XXX; do we want some kind of static init for a set of stages we care about & pass that // set into hasNode? bool cannotKeepFlagged = hasNode(solnRoot, STAGE_TEXT) || hasNode(solnRoot, STAGE_GEO_NEAR_2D) || hasNode(solnRoot, STAGE_GEO_NEAR_2DSPHERE) || (!query.getParsed().getSort().isEmpty() && !soln->hasSortStage); // Only these stages can produce flagged results. A stage has to hold state past one call // to work(...) in order to possibly flag a result. bool couldProduceFlagged = hasNode(solnRoot, STAGE_GEO_2D) || hasNode(solnRoot, STAGE_AND_HASH) || hasNode(solnRoot, STAGE_AND_SORTED) || hasNode(solnRoot, STAGE_FETCH); bool shouldAddMutation = !cannotKeepFlagged && couldProduceFlagged; if (shouldAddMutation && (params.options & QueryPlannerParams::KEEP_MUTATIONS)) { KeepMutationsNode* keep = new KeepMutationsNode(); // We must run the entire expression tree to make sure the document is still valid. keep->filter.reset(query.root()->shallowClone()); if (STAGE_SORT == solnRoot->getType()) { // We want to insert the invalidated results before the sort stage, if there is one. verify(1 == solnRoot->children.size()); keep->children.push_back(solnRoot->children[0]); solnRoot->children[0] = keep; } else { keep->children.push_back(solnRoot); solnRoot = keep; } } // Project the results. if (NULL != query.getProj()) { QLOG() << "PROJECTION: fetched status: " << solnRoot->fetched() << endl; QLOG() << "PROJECTION: Current plan is:\n" << solnRoot->toString() << endl; if (query.getProj()->requiresDocument()) { QLOG() << "PROJECTION: claims to require doc adding fetch.\n"; // If the projection requires the entire document, somebody must fetch. if (!solnRoot->fetched()) { FetchNode* fetch = new FetchNode(); fetch->children.push_back(solnRoot); solnRoot = fetch; } } else { QLOG() << "PROJECTION: requires fields\n"; const vector<string>& fields = query.getProj()->getRequiredFields(); bool covered = true; for (size_t i = 0; i < fields.size(); ++i) { if (!solnRoot->hasField(fields[i])) { QLOG() << "PROJECTION: not covered cuz doesn't have field " << fields[i] << endl; covered = false; break; } } QLOG() << "PROJECTION: is covered?: = " << covered << endl; // If any field is missing from the list of fields the projection wants, // a fetch is required. if (!covered) { FetchNode* fetch = new FetchNode(); fetch->children.push_back(solnRoot); solnRoot = fetch; } } // We now know we have whatever data is required for the projection. ProjectionNode* projNode = new ProjectionNode(); projNode->children.push_back(solnRoot); projNode->fullExpression = query.root(); projNode->projection = query.getParsed().getProj(); solnRoot = projNode; } else { // If there's no projection, we must fetch, as the user wants the entire doc. if (!solnRoot->fetched()) { FetchNode* fetch = new FetchNode(); fetch->children.push_back(solnRoot); solnRoot = fetch; } } if (0 != query.getParsed().getSkip()) { SkipNode* skip = new SkipNode(); skip->skip = query.getParsed().getSkip(); skip->children.push_back(solnRoot); solnRoot = skip; } // When there is both a blocking sort and a limit, the limit will // be enforced by the blocking sort. // Otherwise, we need to limit the results in the case of a hard limit // (ie. limit in raw query is negative) if (0 != query.getParsed().getNumToReturn() && !soln->hasSortStage && !query.getParsed().wantMore()) { LimitNode* limit = new LimitNode(); limit->limit = query.getParsed().getNumToReturn(); limit->children.push_back(solnRoot); solnRoot = limit; } soln->root.reset(solnRoot); return soln.release(); }
// static QuerySolution* QueryPlannerAnalysis::analyzeDataAccess(const CanonicalQuery& query, const QueryPlannerParams& params, QuerySolutionNode* solnRoot) { auto_ptr<QuerySolution> soln(new QuerySolution()); soln->filterData = query.getQueryObj(); verify(soln->filterData.isOwned()); soln->ns = query.ns(); soln->indexFilterApplied = params.indexFiltersApplied; solnRoot->computeProperties(); // solnRoot finds all our results. Let's see what transformations we must perform to the // data. // If we're answering a query on a sharded system, we need to drop documents that aren't // logically part of our shard. if (params.options & QueryPlannerParams::INCLUDE_SHARD_FILTER) { // TODO: We could use params.shardKey to do fetch analysis instead of always fetching. if (!solnRoot->fetched()) { FetchNode* fetch = new FetchNode(); fetch->children.push_back(solnRoot); solnRoot = fetch; } ShardingFilterNode* sfn = new ShardingFilterNode(); sfn->children.push_back(solnRoot); solnRoot = sfn; } bool hasSortStage = false; solnRoot = analyzeSort(query, params, solnRoot, &hasSortStage); // This can happen if we need to create a blocking sort stage and we're not allowed to. if (NULL == solnRoot) { return NULL; } // A solution can be blocking if it has a blocking sort stage or // a hashed AND stage. bool hasAndHashStage = hasNode(solnRoot, STAGE_AND_HASH); soln->hasBlockingStage = hasSortStage || hasAndHashStage; // If we can (and should), add the keep mutations stage. // We cannot keep mutated documents if: // // 1. The query requires an index to evaluate the predicate ($text). We can't tell whether // or not the doc actually satisfies the $text predicate since we can't evaluate a // text MatchExpression. // // 2. The query implies a sort ($geoNear). It would be rather expensive and hacky to merge // the document at the right place. // // 3. There is an index-provided sort. Ditto above comment about merging. // // TODO: do we want some kind of pre-planning step where we look for certain nodes and cache // them? We do lookups in the tree a few times. This may not matter as most trees are // shallow in terms of query nodes. bool cannotKeepFlagged = hasNode(solnRoot, STAGE_TEXT) || hasNode(solnRoot, STAGE_GEO_NEAR_2D) || hasNode(solnRoot, STAGE_GEO_NEAR_2DSPHERE) || (!query.getParsed().getSort().isEmpty() && !hasSortStage); // Only these stages can produce flagged results. A stage has to hold state past one call // to work(...) in order to possibly flag a result. bool couldProduceFlagged = hasNode(solnRoot, STAGE_GEO_2D) || hasAndHashStage || hasNode(solnRoot, STAGE_AND_SORTED) || hasNode(solnRoot, STAGE_FETCH); bool shouldAddMutation = !cannotKeepFlagged && couldProduceFlagged; if (shouldAddMutation && (params.options & QueryPlannerParams::KEEP_MUTATIONS)) { KeepMutationsNode* keep = new KeepMutationsNode(); // We must run the entire expression tree to make sure the document is still valid. keep->filter.reset(query.root()->shallowClone()); if (STAGE_SORT == solnRoot->getType()) { // We want to insert the invalidated results before the sort stage, if there is one. verify(1 == solnRoot->children.size()); keep->children.push_back(solnRoot->children[0]); solnRoot->children[0] = keep; } else { keep->children.push_back(solnRoot); solnRoot = keep; } } // Project the results. if (NULL != query.getProj()) { QLOG() << "PROJECTION: fetched status: " << solnRoot->fetched() << endl; QLOG() << "PROJECTION: Current plan is:\n" << solnRoot->toString() << endl; ProjectionNode::ProjectionType projType = ProjectionNode::DEFAULT; BSONObj coveredKeyObj; if (query.getProj()->requiresDocument()) { QLOG() << "PROJECTION: claims to require doc adding fetch.\n"; // If the projection requires the entire document, somebody must fetch. if (!solnRoot->fetched()) { FetchNode* fetch = new FetchNode(); fetch->children.push_back(solnRoot); solnRoot = fetch; } } else if (!query.getProj()->wantIndexKey()) { // The only way we're here is if it's a simple projection. That is, we can pick out // the fields we want to include and they're not dotted. So we want to execute the // projection in the fast-path simple fashion. Just don't know which fast path yet. QLOG() << "PROJECTION: requires fields\n"; const vector<string>& fields = query.getProj()->getRequiredFields(); bool covered = true; for (size_t i = 0; i < fields.size(); ++i) { if (!solnRoot->hasField(fields[i])) { QLOG() << "PROJECTION: not covered due to field " << fields[i] << endl; covered = false; break; } } QLOG() << "PROJECTION: is covered?: = " << covered << endl; // If any field is missing from the list of fields the projection wants, // a fetch is required. if (!covered) { FetchNode* fetch = new FetchNode(); fetch->children.push_back(solnRoot); solnRoot = fetch; // It's simple but we'll have the full document and we should just iterate // over that. projType = ProjectionNode::SIMPLE_DOC; QLOG() << "PROJECTION: not covered, fetching."; } else { if (solnRoot->fetched()) { // Fetched implies hasObj() so let's run with that. projType = ProjectionNode::SIMPLE_DOC; QLOG() << "PROJECTION: covered via FETCH, using SIMPLE_DOC fast path"; } else { // If we're here we're not fetched so we're covered. Let's see if we can // get out of using the default projType. If there's only one leaf // underneath and it's giving us index data we can use the faster covered // impl. vector<QuerySolutionNode*> leafNodes; getLeafNodes(solnRoot, &leafNodes); if (1 == leafNodes.size()) { // Both the IXSCAN and DISTINCT stages provide covered key data. if (STAGE_IXSCAN == leafNodes[0]->getType()) { projType = ProjectionNode::COVERED_ONE_INDEX; IndexScanNode* ixn = static_cast<IndexScanNode*>(leafNodes[0]); coveredKeyObj = ixn->indexKeyPattern; QLOG() << "PROJECTION: covered via IXSCAN, using COVERED fast path"; } else if (STAGE_DISTINCT == leafNodes[0]->getType()) { projType = ProjectionNode::COVERED_ONE_INDEX; DistinctNode* dn = static_cast<DistinctNode*>(leafNodes[0]); coveredKeyObj = dn->indexKeyPattern; QLOG() << "PROJECTION: covered via DISTINCT, using COVERED fast path"; } } } } } // We now know we have whatever data is required for the projection. ProjectionNode* projNode = new ProjectionNode(); projNode->children.push_back(solnRoot); projNode->fullExpression = query.root(); projNode->projection = query.getParsed().getProj(); projNode->projType = projType; projNode->coveredKeyObj = coveredKeyObj; solnRoot = projNode; } else { // If there's no projection, we must fetch, as the user wants the entire doc. if (!solnRoot->fetched()) { FetchNode* fetch = new FetchNode(); fetch->children.push_back(solnRoot); solnRoot = fetch; } } if (0 != query.getParsed().getSkip()) { SkipNode* skip = new SkipNode(); skip->skip = query.getParsed().getSkip(); skip->children.push_back(solnRoot); solnRoot = skip; } // When there is both a blocking sort and a limit, the limit will // be enforced by the blocking sort. // Otherwise, we need to limit the results in the case of a hard limit // (ie. limit in raw query is negative) if (0 != query.getParsed().getNumToReturn() && !hasSortStage && !query.getParsed().wantMore()) { LimitNode* limit = new LimitNode(); limit->limit = query.getParsed().getNumToReturn(); limit->children.push_back(solnRoot); solnRoot = limit; } soln->root.reset(solnRoot); return soln.release(); }