// static QuerySolutionNode* QueryPlannerAnalysis::analyzeSort(const CanonicalQuery& query, const QueryPlannerParams& params, QuerySolutionNode* solnRoot, bool* blockingSortOut) { *blockingSortOut = false; const BSONObj& sortObj = query.getParsed().getSort(); if (sortObj.isEmpty()) { return solnRoot; } // TODO: We could check sortObj for any projections other than :1 and :-1 // and short-cut some of this. // If the sort is $natural, we ignore it, assuming that the caller has detected that and // outputted a collscan to satisfy the desired order. BSONElement natural = sortObj.getFieldDotted("$natural"); if (!natural.eoo()) { return solnRoot; } // See if solnRoot gives us the sort. If so, we're done. BSONObjSet sorts = solnRoot->getSort(); // If the sort we want is in the set of sort orders provided already, bail out. if (sorts.end() != sorts.find(sortObj)) { return solnRoot; } // Sort is not provided. See if we provide the reverse of our sort pattern. // If so, we can reverse the scan direction(s). BSONObj reverseSort = QueryPlannerCommon::reverseSortObj(sortObj); if (sorts.end() != sorts.find(reverseSort)) { QueryPlannerCommon::reverseScans(solnRoot); QLOG() << "Reversing ixscan to provide sort. Result: " << solnRoot->toString() << endl; return solnRoot; } // Sort not provided, can't reverse scans to get the sort. One last trick: We can "explode" // index scans over point intervals to an OR of sub-scans in order to pull out a sort. // Let's try this. if (explodeForSort(query, params, &solnRoot)) { return solnRoot; } // If we're here, we need to add a sort stage. // If we're not allowed to put a blocking sort in, bail out. if (params.options & QueryPlannerParams::NO_BLOCKING_SORT) { delete solnRoot; return NULL; } // Add a fetch stage so we have the full object when we hit the sort stage. XXX TODO: Can // we pull values out of the key and if so in what cases? (covered_index_sort_3.js) if (!solnRoot->fetched()) { FetchNode* fetch = new FetchNode(); fetch->children.push_back(solnRoot); solnRoot = fetch; } // And build the full sort stage. SortNode* sort = new SortNode(); sort->pattern = sortObj; sort->query = query.getParsed().getFilter(); // When setting the limit on the sort, we need to consider both // the limit N and skip count M. The sort should return an ordered list // N + M items so that the skip stage can discard the first M results. if (0 != query.getParsed().getNumToReturn()) { sort->limit = query.getParsed().getNumToReturn() + query.getParsed().getSkip(); } else { sort->limit = 0; } sort->children.push_back(solnRoot); solnRoot = sort; *blockingSortOut = true; return solnRoot; }
// static QuerySolutionNode* QueryPlannerAnalysis::analyzeSort(const CanonicalQuery& query, const QueryPlannerParams& params, QuerySolutionNode* solnRoot, bool* blockingSortOut) { *blockingSortOut = false; const LiteParsedQuery& lpq = query.getParsed(); const BSONObj& sortObj = lpq.getSort(); if (sortObj.isEmpty()) { return solnRoot; } // TODO: We could check sortObj for any projections other than :1 and :-1 // and short-cut some of this. // If the sort is $natural, we ignore it, assuming that the caller has detected that and // outputted a collscan to satisfy the desired order. BSONElement natural = sortObj.getFieldDotted("$natural"); if (!natural.eoo()) { return solnRoot; } // See if solnRoot gives us the sort. If so, we're done. BSONObjSet sorts = solnRoot->getSort(); // If the sort we want is in the set of sort orders provided already, bail out. if (sorts.end() != sorts.find(sortObj)) { return solnRoot; } // Sort is not provided. See if we provide the reverse of our sort pattern. // If so, we can reverse the scan direction(s). BSONObj reverseSort = QueryPlannerCommon::reverseSortObj(sortObj); if (sorts.end() != sorts.find(reverseSort)) { QueryPlannerCommon::reverseScans(solnRoot); LOG(5) << "Reversing ixscan to provide sort. Result: " << solnRoot->toString() << endl; return solnRoot; } // Sort not provided, can't reverse scans to get the sort. One last trick: We can "explode" // index scans over point intervals to an OR of sub-scans in order to pull out a sort. // Let's try this. if (explodeForSort(query, params, &solnRoot)) { return solnRoot; } // If we're here, we need to add a sort stage. // If we're not allowed to put a blocking sort in, bail out. if (params.options & QueryPlannerParams::NO_BLOCKING_SORT) { delete solnRoot; return NULL; } // Add a fetch stage so we have the full object when we hit the sort stage. TODO: Can we // pull the values that we sort by out of the key and if so in what cases? Perhaps we can // avoid a fetch. if (!solnRoot->fetched()) { FetchNode* fetch = new FetchNode(); fetch->children.push_back(solnRoot); solnRoot = fetch; } // And build the full sort stage. The sort stage has to have a sort key generating stage // as its child, supplying it with the appropriate sort keys. SortKeyGeneratorNode* keyGenNode = new SortKeyGeneratorNode(); keyGenNode->queryObj = lpq.getFilter(); keyGenNode->sortSpec = sortObj; keyGenNode->children.push_back(solnRoot); solnRoot = keyGenNode; SortNode* sort = new SortNode(); sort->pattern = sortObj; sort->children.push_back(solnRoot); solnRoot = sort; // When setting the limit on the sort, we need to consider both // the limit N and skip count M. The sort should return an ordered list // N + M items so that the skip stage can discard the first M results. if (lpq.getLimit()) { // We have a true limit. The limit can be combined with the SORT stage. sort->limit = static_cast<size_t>(*lpq.getLimit()) + static_cast<size_t>(lpq.getSkip().value_or(0)); } else if (lpq.getNToReturn()) { // We have an ntoreturn specified by an OP_QUERY style find. This is used // by clients to mean both batchSize and limit. // // Overflow here would be bad and could cause a nonsense limit. Cast // skip and limit values to unsigned ints to make sure that the // sum is never stored as signed. (See SERVER-13537). sort->limit = static_cast<size_t>(*lpq.getNToReturn()) + static_cast<size_t>(lpq.getSkip().value_or(0)); // This is a SORT with a limit. The wire protocol has a single quantity // called "numToReturn" which could mean either limit or batchSize. // We have no idea what the client intended. One way to handle the ambiguity // of a limited OR stage is to use the SPLIT_LIMITED_SORT hack. // // If wantMore is false (meaning that 'ntoreturn' was initially passed to // the server as a negative value), then we treat numToReturn as a limit. // Since there is no limit-batchSize ambiguity in this case, we do not use the // SPLIT_LIMITED_SORT hack. // // If numToReturn is really a limit, then we want to add a limit to this // SORT stage, and hence perform a topK. // // If numToReturn is really a batchSize, then we want to perform a regular // blocking sort. // // Since we don't know which to use, just join the two options with an OR, // with the topK first. If the client wants a limit, they'll get the efficiency // of topK. If they want a batchSize, the other OR branch will deliver the missing // results. The OR stage handles deduping. // // We must also add an ENSURE_SORTED node above the OR to ensure that the final results are // in correct sorted order, which may not be true if the data is concurrently modified. if (lpq.wantMore() && params.options & QueryPlannerParams::SPLIT_LIMITED_SORT && !QueryPlannerCommon::hasNode(query.root(), MatchExpression::TEXT) && !QueryPlannerCommon::hasNode(query.root(), MatchExpression::GEO) && !QueryPlannerCommon::hasNode(query.root(), MatchExpression::GEO_NEAR)) { // If we're here then the SPLIT_LIMITED_SORT hack is turned on, // and the query is of a type that allows the hack. // // Not allowed for geo or text, because we assume elsewhere that those // stages appear just once. OrNode* orn = new OrNode(); orn->children.push_back(sort); SortNode* sortClone = static_cast<SortNode*>(sort->clone()); sortClone->limit = 0; orn->children.push_back(sortClone); // Add ENSURE_SORTED above the OR. EnsureSortedNode* esn = new EnsureSortedNode(); esn->pattern = sort->pattern; esn->children.push_back(orn); solnRoot = esn; } } else { sort->limit = 0; } *blockingSortOut = true; return solnRoot; }