Exemplo n.º 1
0
        void run() {
            // We insert lots of copies of {a:1, b:1, c: 20}.  We have the indices {a:1} and {b:1},
            // and the query is {a:1, b:1, c: 999}.  No data that matches the query but we won't
            // know that during plan ranking.  We don't want to choose an intersection plan here.
            for (int i = 0; i < N; ++i) {
                insert(BSON("a" << 1 << "b" << 1 << "c" << 20));
            }

            addIndex(BSON("a" << 1));
            addIndex(BSON("b" << 1));

            // There is no data that matches this query but we don't know that until EOF.
            CanonicalQuery* cq;
            BSONObj queryObj = BSON("a" << 1 << "b" << 1 << "c" << 99);
            ASSERT(CanonicalQuery::canonicalize(ns, queryObj, &cq).isOK());
            ASSERT(NULL != cq);

            // Takes ownership of cq.
            QuerySolution* soln = pickBestPlan(cq);

            // Anti-prefer the intersection plan.
            bool bestIsScanOverA = QueryPlannerTestLib::solutionMatches(
                        "{fetch: {node: {ixscan: {pattern: {a: 1}}}}}",
                        soln->root.get());
            bool bestIsScanOverB = QueryPlannerTestLib::solutionMatches(
                        "{fetch: {node: {ixscan: {pattern: {b: 1}}}}}",
                        soln->root.get());
            ASSERT(bestIsScanOverA || bestIsScanOverB);
        }
Exemplo n.º 2
0
        void run() {
            for (int i = 0; i < N; ++i) {
                insert(BSON("a" << 1));
                insert(BSON("a" << 1 << "b" << 1 << "c" << i));
            }

            // Indices on 'a' and 'b'.
            addIndex(BSON("a" << 1));
            addIndex(BSON("b" << 1));

            // Solutions using either 'a' or 'b' will take a long time to start producing
            // results. However, an index scan on 'b' will start producing results sooner
            // than an index scan on 'a'.
            CanonicalQuery* cq;
            ASSERT(CanonicalQuery::canonicalize(ns,
                                                fromjson("{a: 1, b: 1, c: {$gte: 5000}}"),
                                                &cq).isOK());
            ASSERT(NULL != cq);

            // Use index on 'b'.
            QuerySolution* soln = pickBestPlan(cq);
            ASSERT(QueryPlannerTestLib::solutionMatches(
                        "{fetch: {node: {ixscan: {pattern: {b: 1}}}}}",
                        soln->root.get()));
        }
Exemplo n.º 3
0
        void run() {
            // Set up the data so that for the query {a: 1, b: 1, c: 1}, the intersection
            // between 'b' and 'c' is small, and the other intersections are larger.
            for (int i = 0; i < 10; ++i) {
                insert(BSON("a" << 1 << "b" << 1 << "c" << 1));
            }
            for (int i = 0; i < 10; ++i) {
                insert(BSON("a" << 2 << "b" << 1 << "c" << 1));
            }
            for (int i = 0; i < N/2; ++i) {
                insert(BSON("a" << 1 << "b" << 1 << "c" << 2));
                insert(BSON("a" << 1 << "b" << 2 << "c" << 1));
            }

            // Add indices on 'a', 'b', and 'c'.
            addIndex(BSON("a" << 1));
            addIndex(BSON("b" << 1));
            addIndex(BSON("c" << 1));

            CanonicalQuery* cq;
            ASSERT(CanonicalQuery::canonicalize(ns,
                                                fromjson("{a: 1, b: 1, c: 1}"),
                                                &cq).isOK());
            ASSERT(NULL != cq);

            // Intersection between 'b' and 'c' should hit EOF while the
            // other plans are busy fetching documents.
            QuerySolution* soln = pickBestPlan(cq);
            ASSERT(QueryPlannerTestLib::solutionMatches(
                "{fetch: {filter: {a:1}, node: {andSorted: {nodes: ["
                    "{ixscan: {filter: null, pattern: {b:1}}},"
                    "{ixscan: {filter: null, pattern: {c:1}}}]}}}}",
                soln->root.get()));
        }
Exemplo n.º 4
0
        void run() {
            // We insert lots of copies of {a:1, b:1}.  We have the indices {a:1} and {a:1, b:1},
            // the query is for a doc that doesn't exist, but there is a projection over 'a' and
            // 'b'.  We should prefer the index that provides a covered query.
            for (int i = 0; i < N; ++i) {
                insert(BSON("a" << 1 << "b" << 1));
            }

            addIndex(BSON("a" << 1));
            addIndex(BSON("a" << 1 << "b" << 1));

            // There is no data that matches this query ({a:2}).  Both scans will hit EOF before
            // returning any data.

            CanonicalQuery* cq;
            ASSERT(CanonicalQuery::canonicalize(ns,
                                                BSON("a" << 2),
                                                BSONObj(),
                                                BSON("_id" << 0 << "a" << 1 << "b" << 1),
                                                &cq).isOK());
            ASSERT(NULL != cq);

            // Takes ownership of cq.
            QuerySolution* soln = pickBestPlan(cq);
            // Prefer the fully covered plan.
            ASSERT(QueryPlannerTestLib::solutionMatches(
                        "{proj: {spec: {_id:0, a:1, b:1}, node: {ixscan: {pattern: {a: 1, b:1}}}}}",
                        soln->root.get()));
        }
Exemplo n.º 5
0
    Runner::RunnerState MultiPlanRunner::getNext(BSONObj* objOut) {
        if (_failure || _killed) {
            return Runner::RUNNER_DEAD;
        }

        // If we haven't picked the best plan yet...
        if (NULL == _bestPlan) {
            // TODO: Consider rewriting pickBestPlan to return results as it iterates.
            if (!pickBestPlan(NULL)) {
                verify(_failure);
                return Runner::RUNNER_DEAD;
            }
        }

        if (!_alreadyProduced.empty()) {
            WorkingSetID id = _alreadyProduced.front();
            _alreadyProduced.pop();

            WorkingSetMember* member = _bestPlan->getWorkingSet()->get(id);
            // TODO: getOwned?
            verify(WorkingSetCommon::fetch(member));
            *objOut = member->obj;
            _bestPlan->getWorkingSet()->free(id);
            return Runner::RUNNER_ADVANCED;
        }

        return _bestPlan->getNext(objOut);
    }
Exemplo n.º 6
0
        void run() {
            // Simulate needing lots of FETCH's.
            turnOnAlwaysFetch();

            // Neither 'a' nor 'b' is selective.
            for (int i = 0; i < N; ++i) {
                insert(BSON("a" << 1 << "b" << 1));
            }

            // Add indices on 'a' and 'b'.
            addIndex(BSON("a" << 1));
            addIndex(BSON("b" << 1));

            // Query {a:1, b:1}, and project out all fields other than 'a' and 'b'.
            CanonicalQuery* cq;
            ASSERT(CanonicalQuery::canonicalize(ns,
                                                BSON("a" << 1 << "b" << 1),
                                                BSONObj(),
                                                BSON("_id" << 0 << "a" << 1 << "b" << 1),
                                                &cq).isOK());
            ASSERT(NULL != cq);

            // We should choose an ixisect plan because it requires fewer fetches.
            // Takes ownership of cq.
            QuerySolution* soln = pickBestPlan(cq);
            ASSERT(QueryPlannerTestLib::solutionMatches(
                "{proj: {spec: {_id:0,a:1,b:1}, node: {andSorted: {nodes: ["
                    "{ixscan: {filter: null, pattern: {a:1}}},"
                    "{ixscan: {filter: null, pattern: {b:1}}}]}}}}",
                soln->root.get()));

            turnOffAlwaysFetch();
        }
Exemplo n.º 7
0
        void run() {
            for (int i = 0; i < N; ++i) {
                insert(BSON("a" << 1 << "d" << i));
            }

            // The index {d: 1, e: 1} provides the desired sort order,
            // while index {a: 1, b: 1} can be used to answer the
            // query predicate, but does not provide the sort.
            addIndex(BSON("a" << 1 << "b" << 1));
            addIndex(BSON("d" << 1 << "e" << 1));

            // Query: find({a: 1}).sort({d: 1})
            CanonicalQuery* cq;
            ASSERT(CanonicalQuery::canonicalize(ns,
                                                BSON("a" << 1),
                                                BSON("d" << 1), // sort
                                                BSONObj(), // projection
                                                &cq).isOK());
            ASSERT(NULL != cq);

            // No results will be returned during the trial period,
            // so we expect to choose {d: 1, e: 1}, as it allows us
            // to avoid the sort stage.
            QuerySolution* soln = pickBestPlan(cq);
            ASSERT(QueryPlannerTestLib::solutionMatches(
                        "{fetch: {filter: {a:1}, node: "
                            "{ixscan: {filter: null, pattern: {d:1,e:1}}}}}",
                        soln->root.get()));
        }
Exemplo n.º 8
0
        void run() {
            // Simulate needing lots of FETCH's.
            turnOnAlwaysFetch();

            // Neither 'a' nor 'b' is selective.
            for (int i = 0; i < N; ++i) {
                insert(BSON("a" << 1 << "b" << 1));
            }

            // Add indices on 'a' and 'b'.
            addIndex(BSON("a" << 1));
            addIndex(BSON("b" << 1));

            // Query {a:1, b:1}.
            CanonicalQuery* cq;
            ASSERT(CanonicalQuery::canonicalize(ns,
                                                BSON("a" << 1 << "b" << 1),
                                                &cq).isOK());
            ASSERT(NULL != cq);

            // The intersection is large, and ixisect does not make the
            // query covered. We should NOT choose an intersection plan.
            QuerySolution* soln = pickBestPlan(cq);
            bool bestIsScanOverA = QueryPlannerTestLib::solutionMatches(
                        "{fetch: {node: {ixscan: {pattern: {a: 1}}}}}",
                        soln->root.get());
            bool bestIsScanOverB = QueryPlannerTestLib::solutionMatches(
                        "{fetch: {node: {ixscan: {pattern: {b: 1}}}}}",
                        soln->root.get());
            ASSERT(bestIsScanOverA || bestIsScanOverB);

            turnOffAlwaysFetch();
        }
Exemplo n.º 9
0
        void run() {
            for (int i = 0; i < N; ++i) {
                insert(BSON("_id" << i));
            }

            addIndex(BSON("_id" << 1));

            // Run a query with a sort.  The blocking sort won't produce any data during the
            // evaluation period.
            CanonicalQuery* cq;
            BSONObj queryObj = BSON("_id" << BSON("$gte" << 20 << "$lte" << 200));
            BSONObj sortObj = BSON("c" << 1);
            BSONObj projObj = BSONObj();
            ASSERT(CanonicalQuery::canonicalize(ns,
                                                queryObj,
                                                sortObj,
                                                projObj,
                                                &cq).isOK());

            // Takes ownership of cq.
            QuerySolution* soln = pickBestPlan(cq);

            // The best must not be a collscan.
            ASSERT(QueryPlannerTestLib::solutionMatches(
                        "{sort: {pattern: {c: 1}, limit: 0, node: {"
                            "fetch: {filter: null, node: "
                                "{ixscan: {filter: null, pattern: {_id: 1}}}}}}}}",
                        soln->root.get()));
        }
Exemplo n.º 10
0
        void run() {
            // Set up the data so that for the query {a: 1, b: 1}, the
            // intersection is empty. The single index plans have to do
            // more fetching from disk in order to determine that the result
            // set is empty. As a result, the intersection plan hits EOF first.
            for (int i = 0; i < 30; ++i) {
                insert(BSON("a" << 1 << "b" << 2));
            }
            for (int i = 0; i < 30; ++i) {
                insert(BSON("a" << 2 << "b" << 1));
            }
            for (int i = 0; i < N; ++i) {
                insert(BSON("a" << 2 << "b" << 2));
            }

            // Add indices on 'a' and 'b'.
            addIndex(BSON("a" << 1));
            addIndex(BSON("b" << 1));

            CanonicalQuery* cq;
            ASSERT(CanonicalQuery::canonicalize(ns,
                                                fromjson("{a: 1, b: 1}"),
                                                &cq).isOK());
            ASSERT(NULL != cq);

            // Choose the index intersection plan.
            QuerySolution* soln = pickBestPlan(cq);
            ASSERT(QueryPlannerTestLib::solutionMatches(
                "{fetch: {filter: null, node: {andSorted: {nodes: ["
                    "{ixscan: {filter: null, pattern: {a:1}}},"
                    "{ixscan: {filter: null, pattern: {b:1}}}]}}}}",
                soln->root.get()));
        }
Exemplo n.º 11
0
        void run() {
            // Insert data {a:i, b:i}.  Index {a:1} and {a:1, b:1}, query on 'a', projection on 'a'
            // and 'b'.  Should prefer the second index as we can pull the 'b' data out.
            for (int i = 0; i < N; ++i) {
                insert(BSON("a" << i << "b" << i));
            }

            addIndex(BSON("a" << 1));
            addIndex(BSON("a" << 1 << "b" << 1));

            // Query for a==27 with projection that wants 'a' and 'b'.  BSONObj() is for sort.
            CanonicalQuery* cq;
            ASSERT(CanonicalQuery::canonicalize(ns,
                                                BSON("a" << 27),
                                                BSONObj(),
                                                BSON("_id" << 0 << "a" << 1 << "b" << 1),
                                                &cq).isOK());
            ASSERT(NULL != cq);

            // Takes ownership of cq.
            QuerySolution* soln = pickBestPlan(cq);

            // Prefer the fully covered plan.
            ASSERT(QueryPlannerTestLib::solutionMatches(
                        "{proj: {spec: {_id:0, a:1, b:1}, node: {ixscan: {pattern: {a: 1, b:1}}}}}",
                        soln->root.get()));
        }
Exemplo n.º 12
0
        void run() {
            // 'a' is very selective, 'b' is not.
            for (int i = 0; i < N; ++i) {
                insert(BSON("a" << i << "b" << 1));
            }

            // Add indices on 'a' and 'b'.
            addIndex(BSON("a" << 1));
            addIndex(BSON("b" << 1));

            // Run the query {a:1, b:{$gt:1}.
            CanonicalQuery* cq;
            verify(CanonicalQuery::canonicalize(ns, BSON("a" << 1 << "b" << BSON("$gt" << 1)),
                                                &cq).isOK());
            ASSERT(NULL != cq);

            // Turn on the "force intersect" option.
            // This will be reverted by PlanRankingTestBase's destructor when the test completes.
            internalQueryForceIntersectionPlans = true;

            // Takes ownership of cq.
            QuerySolution* soln = pickBestPlan(cq);
            ASSERT(QueryPlannerTestLib::solutionMatches(
                             "{fetch: {filter: null, node: {andHash: {nodes: ["
                                     "{ixscan: {filter: null, pattern: {a:1}}},"
                                     "{ixscan: {filter: null, pattern: {b:1}}}]}}}}",
                             soln->root.get()));

            // Confirm that a backup plan is available.
            ASSERT(hasBackupPlan());
        }
Exemplo n.º 13
0
        void run() {
            // 'a' is very selective, 'b' is not.
            for (int i = 0; i < N; ++i) {
                insert(BSON("a" << i << "b" << 1));
            }

            // Add indices on 'a' and 'b'.
            addIndex(BSON("a" << 1));
            addIndex(BSON("b" << 1));

            // Run the query {a:4, b:1}.
            CanonicalQuery* cq;
            verify(CanonicalQuery::canonicalize(ns, BSON("a" << 100 << "b" << 1), &cq).isOK());
            ASSERT(NULL != cq);

            // {a:100} is super selective so choose that.
            // Takes ownership of cq.
            QuerySolution* soln = pickBestPlan(cq);
            ASSERT(QueryPlannerTestLib::solutionMatches(
                        "{fetch: {filter: {b:1}, node: {ixscan: {pattern: {a: 1}}}}}",
                        soln->root.get()));

            // Turn on the "force intersect" option.
            // This will be reverted by PlanRankingTestBase's destructor when the test completes.
            internalQueryForceIntersectionPlans = true;

            // And run the same query again.
            ASSERT(CanonicalQuery::canonicalize(ns, BSON("a" << 100 << "b" << 1), &cq).isOK());

            // With the "ranking picks ixisect always" option we pick an intersection plan that uses
            // both the {a:1} and {b:1} indices even though it performs poorly.

            // Takes ownership of cq.
            soln = pickBestPlan(cq);
            ASSERT(QueryPlannerTestLib::solutionMatches(
                             "{fetch: {filter: null, node: {andSorted: {nodes: ["
                                     "{ixscan: {filter: null, pattern: {a:1}}},"
                                     "{ixscan: {filter: null, pattern: {b:1}}}]}}}}",
                             soln->root.get()));
        }
Exemplo n.º 14
0
    Runner::RunnerState MultiPlanRunner::getNext(BSONObj* objOut, DiskLoc* dlOut) {
        if (_killed) { return Runner::RUNNER_DEAD; }
        if (_failure) { return Runner::RUNNER_ERROR; }

        // If we haven't picked the best plan yet...
        if (NULL == _bestPlan) {
            if (!pickBestPlan(NULL)) {
                verify(_failure || _killed);
                if (_killed) { return Runner::RUNNER_DEAD; }
                if (_failure) { return Runner::RUNNER_ERROR; }
            }
        }

        if (!_alreadyProduced.empty()) {
            WorkingSetID id = _alreadyProduced.front();
            _alreadyProduced.pop_front();

            WorkingSetMember* member = _bestPlan->getWorkingSet()->get(id);
            // Note that this copies code from PlanExecutor.
            if (NULL != objOut) {
                if (WorkingSetMember::LOC_AND_IDX == member->state) {
                    if (1 != member->keyData.size()) {
                        _bestPlan->getWorkingSet()->free(id);
                        return Runner::RUNNER_ERROR;
                    }
                    *objOut = member->keyData[0].keyData;
                }
                else if (member->hasObj()) {
                    *objOut = member->obj;
                }
                else {
                    // TODO: Checking the WSM for covered fields goes here.
                    _bestPlan->getWorkingSet()->free(id);
                    return Runner::RUNNER_ERROR;
                }
            }

            if (NULL != dlOut) {
                if (member->hasLoc()) {
                    *dlOut = member->loc;
                }
                else {
                    _bestPlan->getWorkingSet()->free(id);
                    return Runner::RUNNER_ERROR;
                }
            }
            _bestPlan->getWorkingSet()->free(id);
            return Runner::RUNNER_ADVANCED;
        }

        return _bestPlan->getNext(objOut, dlOut);
    }
Exemplo n.º 15
0
        void run() {
            // Insert data for which we have no index.
            for (int i = 0; i < N; ++i) {
                insert(BSON("foo" << i));
            }

            // Look for A Space Odyssey.
            CanonicalQuery* cq;
            verify(CanonicalQuery::canonicalize(ns, BSON("foo" << 2001), &cq).isOK());
            ASSERT(NULL != cq);

            // Takes ownership of cq.
            QuerySolution* soln = pickBestPlan(cq);

            // The best must be a collscan.
            ASSERT(QueryPlannerTestLib::solutionMatches(
                        "{cscan: {dir: 1, filter: {foo: 2001}}}",
                        soln->root.get()));
        }
Exemplo n.º 16
0
        void run() {
            // Simulate needing lots of FETCH's.
            turnOnAlwaysFetch();

            // Set up data so that the following conditions hold:
            //  1) Documents matching {a: 1} are of high cardinality.
            //  2) Documents matching {b: 1} are of high cardinality.
            //  3) Documents matching {a: 1, b: 1} are of low cardinality---
            //  the intersection is small.
            //  4) At least one of the documents in the intersection is
            //  returned during the trial period.
            insert(BSON("a" << 1 << "b" << 1));
            for (int i = 0; i < N/2; ++i) {
                insert(BSON("a" << 1 << "b" << 2));
            }
            for (int i = 0; i < N/2; ++i) {
                insert(BSON("a" << 2 << "b" << 1));
            }

            // Add indices on 'a' and 'b'.
            addIndex(BSON("a" << 1));
            addIndex(BSON("b" << 1));

            // Neither the predicate on 'b' nor the predicate on 'a' is
            // very selective: both retrieve about half the documents.
            // However, the intersection is very small, which makes
            // the intersection plan desirable.
            CanonicalQuery* cq;
            ASSERT(CanonicalQuery::canonicalize(ns,
                                                fromjson("{a: 1, b: 1}"),
                                                &cq).isOK());
            ASSERT(NULL != cq);

            QuerySolution* soln = pickBestPlan(cq);
            ASSERT(QueryPlannerTestLib::solutionMatches(
                "{fetch: {filter: null, node: {andSorted: {nodes: ["
                    "{ixscan: {filter: null, pattern: {a:1}}},"
                    "{ixscan: {filter: null, pattern: {b:1}}}]}}}}",
                soln->root.get()));

            turnOffAlwaysFetch();
        }
Exemplo n.º 17
0
        void run() {
            // 'a' is very selective, 'b' is not.
            for (int i = 0; i < N; ++i) {
                insert(BSON("a" << i << "b" << 1));
            }

            // Add indices on 'a' and 'b'.
            addIndex(BSON("a" << 1));
            addIndex(BSON("b" << 1));

            // Run the query {a:N+1, b:1}.  (No such document.)
            CanonicalQuery* cq;
            verify(CanonicalQuery::canonicalize(ns, BSON("a" << N + 1 << "b" << 1), &cq).isOK());
            ASSERT(NULL != cq);

            // {a: 100} is super selective so choose that.
            // Takes ownership of cq.
            QuerySolution* soln = pickBestPlan(cq);
            ASSERT(QueryPlannerTestLib::solutionMatches(
                        "{fetch: {filter: {b:1}, node: {ixscan: {pattern: {a: 1}}}}}",
                        soln->root.get()));
        }
Exemplo n.º 18
0
        void run() {
            for (int i = 0; i < 100; ++i) {
                insert(BSON("a" << i << "b" << i << "c" << i));
            }

            // These indices look equivalent to the ranker for the query below unless we account
            // for key skipping. We should pick index {a: 1} if we account for key skipping
            // properly.
            addIndex(BSON("b" << 1 << "c" << 1));
            addIndex(BSON("a" << 1));

            CanonicalQuery* cq;
            ASSERT(CanonicalQuery::canonicalize(ns,
                                                fromjson("{a: 9, b: {$ne: 10}, c: 9}"),
                                                &cq).isOK());
            ASSERT(NULL != cq);

            // Expect to use index {a: 1, b: 1}.
            QuerySolution* soln = pickBestPlan(cq);
            ASSERT(QueryPlannerTestLib::solutionMatches(
                        "{fetch: {node: {ixscan: {pattern: {a: 1}}}}}",
                        soln->root.get()));
        }
Exemplo n.º 19
0
    Runner::RunnerState MultiPlanRunner::getNext(BSONObj* objOut, DiskLoc* dlOut) {
        if (_killed) { return Runner::RUNNER_DEAD; }
        if (_failure) { return Runner::RUNNER_ERROR; }

        // If we haven't picked the best plan yet...
        if (NULL == _bestPlan) {
            if (!pickBestPlan(NULL, objOut)) {
                verify(_failure || _killed);
                if (_killed) { return Runner::RUNNER_DEAD; }
                if (_failure) { return Runner::RUNNER_ERROR; }
            }
        }

        // Look for an already produced result that provides the data the caller wants.
        while (!_alreadyProduced.empty()) {
            WorkingSetID id = _alreadyProduced.front();
            _alreadyProduced.pop_front();

            WorkingSetMember* member = _bestPlan->getWorkingSet()->get(id);

            // Note that this copies code from PlanExecutor.
            if (NULL != objOut) {
                if (WorkingSetMember::LOC_AND_IDX == member->state) {
                    if (1 != member->keyData.size()) {
                        _bestPlan->getWorkingSet()->free(id);
                        // If the caller needs the key data and the WSM doesn't have it, drop the
                        // result and carry on.
                        continue;
                    }
                    *objOut = member->keyData[0].keyData;
                }
                else if (member->hasObj()) {
                    *objOut = member->obj;
                }
                else {
                    // If the caller needs an object and the WSM doesn't have it, drop and
                    // try the next result.
                    _bestPlan->getWorkingSet()->free(id);
                    continue;
                }
            }

            if (NULL != dlOut) {
                if (member->hasLoc()) {
                    *dlOut = member->loc;
                }
                else {
                    // If the caller needs a DiskLoc and the WSM doesn't have it, drop and carry on.
                    _bestPlan->getWorkingSet()->free(id);
                    continue;
                }
            }

            // If we're here, the caller has all the data needed and we've set the out
            // parameters.  Remove the result from the WorkingSet.
            _bestPlan->getWorkingSet()->free(id);
            return Runner::RUNNER_ADVANCED;
        }

        RunnerState state = _bestPlan->getNext(objOut, dlOut);

        if (Runner::RUNNER_ERROR == state && (NULL != _backupSolution)) {
            QLOG() << "Best plan errored out switching to backup\n";
            // Uncache the bad solution if we fall back
            // on the backup solution.
            //
            // XXX: Instead of uncaching we should find a way for the
            // cached plan runner to fall back on a different solution
            // if the best solution fails. Alternatively we could try to
            // defer cache insertion to be after the first produced result.
            Database* db = cc().database();
            verify(NULL != db);
            Collection* collection = db->getCollection(_query->ns());
            verify(NULL != collection);
            PlanCache* cache = collection->infoCache()->getPlanCache();
            cache->remove(*_query);

            _bestPlan.reset(_backupPlan);
            _backupPlan = NULL;
            _bestSolution.reset(_backupSolution);
            _backupSolution = NULL;
            _alreadyProduced = _backupAlreadyProduced;
            return getNext(objOut, dlOut);
        }

        if (NULL != _backupSolution && Runner::RUNNER_ADVANCED == state) {
            QLOG() << "Best plan had a blocking sort, became unblocked, deleting backup plan\n";
            delete _backupSolution;
            delete _backupPlan;
            _backupSolution = NULL;
            _backupPlan = NULL;
            // TODO: free from WS?
            _backupAlreadyProduced.clear();
        }

        return state;
    }