Exemplo n.º 1
0
    // 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;
    }
Exemplo n.º 2
0
// 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;
}