// static
bool QueryPlannerTestLib::solutionMatches(const BSONObj& testSoln,
        const QuerySolutionNode* trueSoln) {
    //
    // leaf nodes
    //
    if (STAGE_COLLSCAN == trueSoln->getType()) {
        const CollectionScanNode* csn = static_cast<const CollectionScanNode*>(trueSoln);
        BSONElement el = testSoln["cscan"];
        if (el.eoo() || !el.isABSONObj()) {
            return false;
        }
        BSONObj csObj = el.Obj();

        BSONElement dir = csObj["dir"];
        if (dir.eoo() || !dir.isNumber()) {
            return false;
        }
        if (dir.numberInt() != csn->direction) {
            return false;
        }

        BSONElement filter = csObj["filter"];
        if (filter.eoo()) {
            return true;
        } else if (filter.isNull()) {
            return NULL == csn->filter;
        } else if (!filter.isABSONObj()) {
            return false;
        }

        BSONObj collation;
        if (BSONElement collationElt = csObj["collation"]) {
            if (!collationElt.isABSONObj()) {
                return false;
            }
            collation = collationElt.Obj();
        }

        return filterMatches(filter.Obj(), collation, trueSoln);
    } else if (STAGE_IXSCAN == trueSoln->getType()) {
        const IndexScanNode* ixn = static_cast<const IndexScanNode*>(trueSoln);
        BSONElement el = testSoln["ixscan"];
        if (el.eoo() || !el.isABSONObj()) {
            return false;
        }
        BSONObj ixscanObj = el.Obj();

        BSONElement pattern = ixscanObj["pattern"];
        if (pattern.eoo() || !pattern.isABSONObj()) {
            return false;
        }
        if (pattern.Obj() != ixn->indexKeyPattern) {
            return false;
        }

        BSONElement bounds = ixscanObj["bounds"];
        if (!bounds.eoo()) {
            if (!bounds.isABSONObj()) {
                return false;
            } else if (!boundsMatch(bounds.Obj(), ixn->bounds)) {
                return false;
            }
        }

        BSONElement dir = ixscanObj["dir"];
        if (!dir.eoo() && NumberInt == dir.type()) {
            if (dir.numberInt() != ixn->direction) {
                return false;
            }
        }

        BSONElement filter = ixscanObj["filter"];
        if (filter.eoo()) {
            return true;
        } else if (filter.isNull()) {
            return NULL == ixn->filter;
        } else if (!filter.isABSONObj()) {
            return false;
        }

        BSONObj collation;
        if (BSONElement collationElt = ixscanObj["collation"]) {
            if (!collationElt.isABSONObj()) {
                return false;
            }
            collation = collationElt.Obj();
        }

        return filterMatches(filter.Obj(), collation, trueSoln);
    } else if (STAGE_GEO_NEAR_2D == trueSoln->getType()) {
        const GeoNear2DNode* node = static_cast<const GeoNear2DNode*>(trueSoln);
        BSONElement el = testSoln["geoNear2d"];
        if (el.eoo() || !el.isABSONObj()) {
            return false;
        }
        BSONObj geoObj = el.Obj();
        return geoObj == node->indexKeyPattern;
    } else if (STAGE_GEO_NEAR_2DSPHERE == trueSoln->getType()) {
        const GeoNear2DSphereNode* node = static_cast<const GeoNear2DSphereNode*>(trueSoln);
        BSONElement el = testSoln["geoNear2dsphere"];
        if (el.eoo() || !el.isABSONObj()) {
            return false;
        }
        BSONObj geoObj = el.Obj();

        BSONElement pattern = geoObj["pattern"];
        if (pattern.eoo() || !pattern.isABSONObj()) {
            return false;
        }
        if (pattern.Obj() != node->indexKeyPattern) {
            return false;
        }

        BSONElement bounds = geoObj["bounds"];
        if (!bounds.eoo()) {
            if (!bounds.isABSONObj()) {
                return false;
            } else if (!boundsMatch(bounds.Obj(), node->baseBounds)) {
                return false;
            }
        }

        return true;
    } else if (STAGE_TEXT == trueSoln->getType()) {
        // {text: {search: "somestr", language: "something", filter: {blah: 1}}}
        const TextNode* node = static_cast<const TextNode*>(trueSoln);
        BSONElement el = testSoln["text"];
        if (el.eoo() || !el.isABSONObj()) {
            return false;
        }
        BSONObj textObj = el.Obj();

        BSONElement searchElt = textObj["search"];
        if (!searchElt.eoo()) {
            if (searchElt.String() != node->ftsQuery->getQuery()) {
                return false;
            }
        }

        BSONElement languageElt = textObj["language"];
        if (!languageElt.eoo()) {
            if (languageElt.String() != node->ftsQuery->getLanguage()) {
                return false;
            }
        }

        BSONElement caseSensitiveElt = textObj["caseSensitive"];
        if (!caseSensitiveElt.eoo()) {
            if (caseSensitiveElt.trueValue() != node->ftsQuery->getCaseSensitive()) {
                return false;
            }
        }

        BSONElement diacriticSensitiveElt = textObj["diacriticSensitive"];
        if (!diacriticSensitiveElt.eoo()) {
            if (diacriticSensitiveElt.trueValue() != node->ftsQuery->getDiacriticSensitive()) {
                return false;
            }
        }

        BSONElement indexPrefix = textObj["prefix"];
        if (!indexPrefix.eoo()) {
            if (!indexPrefix.isABSONObj()) {
                return false;
            }

            if (0 != indexPrefix.Obj().woCompare(node->indexPrefix)) {
                return false;
            }
        }

        BSONObj collation;
        if (BSONElement collationElt = textObj["collation"]) {
            if (!collationElt.isABSONObj()) {
                return false;
            }
            collation = collationElt.Obj();
        }

        BSONElement filter = textObj["filter"];
        if (!filter.eoo()) {
            if (filter.isNull()) {
                if (NULL != node->filter) {
                    return false;
                }
            } else if (!filter.isABSONObj()) {
                return false;
            } else if (!filterMatches(filter.Obj(), collation, trueSoln)) {
                return false;
            }
        }

        return true;
    }

    //
    // internal nodes
    //
    if (STAGE_FETCH == trueSoln->getType()) {
        const FetchNode* fn = static_cast<const FetchNode*>(trueSoln);

        BSONElement el = testSoln["fetch"];
        if (el.eoo() || !el.isABSONObj()) {
            return false;
        }
        BSONObj fetchObj = el.Obj();

        BSONObj collation;
        if (BSONElement collationElt = fetchObj["collation"]) {
            if (!collationElt.isABSONObj()) {
                return false;
            }
            collation = collationElt.Obj();
        }

        BSONElement filter = fetchObj["filter"];
        if (!filter.eoo()) {
            if (filter.isNull()) {
                if (NULL != fn->filter) {
                    return false;
                }
            } else if (!filter.isABSONObj()) {
                return false;
            } else if (!filterMatches(filter.Obj(), collation, trueSoln)) {
                return false;
            }
        }

        BSONElement child = fetchObj["node"];
        if (child.eoo() || !child.isABSONObj()) {
            return false;
        }
        return solutionMatches(child.Obj(), fn->children[0]);
    } else if (STAGE_OR == trueSoln->getType()) {
        const OrNode* orn = static_cast<const OrNode*>(trueSoln);
        BSONElement el = testSoln["or"];
        if (el.eoo() || !el.isABSONObj()) {
            return false;
        }
        BSONObj orObj = el.Obj();
        return childrenMatch(orObj, orn);
    } else if (STAGE_AND_HASH == trueSoln->getType()) {
        const AndHashNode* ahn = static_cast<const AndHashNode*>(trueSoln);
        BSONElement el = testSoln["andHash"];
        if (el.eoo() || !el.isABSONObj()) {
            return false;
        }
        BSONObj andHashObj = el.Obj();

        BSONObj collation;
        if (BSONElement collationElt = andHashObj["collation"]) {
            if (!collationElt.isABSONObj()) {
                return false;
            }
            collation = collationElt.Obj();
        }

        BSONElement filter = andHashObj["filter"];
        if (!filter.eoo()) {
            if (filter.isNull()) {
                if (NULL != ahn->filter) {
                    return false;
                }
            } else if (!filter.isABSONObj()) {
                return false;
            } else if (!filterMatches(filter.Obj(), collation, trueSoln)) {
                return false;
            }
        }

        return childrenMatch(andHashObj, ahn);
    } else if (STAGE_AND_SORTED == trueSoln->getType()) {
        const AndSortedNode* asn = static_cast<const AndSortedNode*>(trueSoln);
        BSONElement el = testSoln["andSorted"];
        if (el.eoo() || !el.isABSONObj()) {
            return false;
        }
        BSONObj andSortedObj = el.Obj();

        BSONObj collation;
        if (BSONElement collationElt = andSortedObj["collation"]) {
            if (!collationElt.isABSONObj()) {
                return false;
            }
            collation = collationElt.Obj();
        }

        BSONElement filter = andSortedObj["filter"];
        if (!filter.eoo()) {
            if (filter.isNull()) {
                if (NULL != asn->filter) {
                    return false;
                }
            } else if (!filter.isABSONObj()) {
                return false;
            } else if (!filterMatches(filter.Obj(), collation, trueSoln)) {
                return false;
            }
        }

        return childrenMatch(andSortedObj, asn);
    } else if (STAGE_PROJECTION == trueSoln->getType()) {
        const ProjectionNode* pn = static_cast<const ProjectionNode*>(trueSoln);

        BSONElement el = testSoln["proj"];
        if (el.eoo() || !el.isABSONObj()) {
            return false;
        }
        BSONObj projObj = el.Obj();

        BSONElement projType = projObj["type"];
        if (!projType.eoo()) {
            string projTypeStr = projType.str();
            if (!((pn->projType == ProjectionNode::DEFAULT && projTypeStr == "default") ||
                    (pn->projType == ProjectionNode::SIMPLE_DOC && projTypeStr == "simple") ||
                    (pn->projType == ProjectionNode::COVERED_ONE_INDEX &&
                     projTypeStr == "coveredIndex"))) {
                return false;
            }
        }

        BSONElement spec = projObj["spec"];
        if (spec.eoo() || !spec.isABSONObj()) {
            return false;
        }
        BSONElement child = projObj["node"];
        if (child.eoo() || !child.isABSONObj()) {
            return false;
        }

        return (spec.Obj() == pn->projection) && solutionMatches(child.Obj(), pn->children[0]);
    } else if (STAGE_SORT == trueSoln->getType()) {
        const SortNode* sn = static_cast<const SortNode*>(trueSoln);
        BSONElement el = testSoln["sort"];
        if (el.eoo() || !el.isABSONObj()) {
            return false;
        }
        BSONObj sortObj = el.Obj();

        BSONElement patternEl = sortObj["pattern"];
        if (patternEl.eoo() || !patternEl.isABSONObj()) {
            return false;
        }
        BSONElement limitEl = sortObj["limit"];
        if (!limitEl.isNumber()) {
            return false;
        }
        BSONElement child = sortObj["node"];
        if (child.eoo() || !child.isABSONObj()) {
            return false;
        }

        size_t expectedLimit = limitEl.numberInt();
        return (patternEl.Obj() == sn->pattern) && (expectedLimit == sn->limit) &&
               solutionMatches(child.Obj(), sn->children[0]);
    } else if (STAGE_SORT_KEY_GENERATOR == trueSoln->getType()) {
        const SortKeyGeneratorNode* keyGenNode = static_cast<const SortKeyGeneratorNode*>(trueSoln);
        BSONElement el = testSoln["sortKeyGen"];
        if (el.eoo() || !el.isABSONObj()) {
            return false;
        }
        BSONObj keyGenObj = el.Obj();

        BSONElement child = keyGenObj["node"];
        if (child.eoo() || !child.isABSONObj()) {
            return false;
        }

        return solutionMatches(child.Obj(), keyGenNode->children[0]);
    } else if (STAGE_SORT_MERGE == trueSoln->getType()) {
        const MergeSortNode* msn = static_cast<const MergeSortNode*>(trueSoln);
        BSONElement el = testSoln["mergeSort"];
        if (el.eoo() || !el.isABSONObj()) {
            return false;
        }
        BSONObj mergeSortObj = el.Obj();
        return childrenMatch(mergeSortObj, msn);
    } else if (STAGE_SKIP == trueSoln->getType()) {
        const SkipNode* sn = static_cast<const SkipNode*>(trueSoln);
        BSONElement el = testSoln["skip"];
        if (el.eoo() || !el.isABSONObj()) {
            return false;
        }
        BSONObj sortObj = el.Obj();

        BSONElement skipEl = sortObj["n"];
        if (!skipEl.isNumber()) {
            return false;
        }
        BSONElement child = sortObj["node"];
        if (child.eoo() || !child.isABSONObj()) {
            return false;
        }

        return (skipEl.numberInt() == sn->skip) && solutionMatches(child.Obj(), sn->children[0]);
    } else if (STAGE_LIMIT == trueSoln->getType()) {
        const LimitNode* ln = static_cast<const LimitNode*>(trueSoln);
        BSONElement el = testSoln["limit"];
        if (el.eoo() || !el.isABSONObj()) {
            return false;
        }
        BSONObj sortObj = el.Obj();

        BSONElement limitEl = sortObj["n"];
        if (!limitEl.isNumber()) {
            return false;
        }
        BSONElement child = sortObj["node"];
        if (child.eoo() || !child.isABSONObj()) {
            return false;
        }

        return (limitEl.numberInt() == ln->limit) && solutionMatches(child.Obj(), ln->children[0]);
    } else if (STAGE_KEEP_MUTATIONS == trueSoln->getType()) {
        const KeepMutationsNode* kn = static_cast<const KeepMutationsNode*>(trueSoln);

        BSONElement el = testSoln["keep"];
        if (el.eoo() || !el.isABSONObj()) {
            return false;
        }
        BSONObj keepObj = el.Obj();

        // Doesn't have any parameters really.
        BSONElement child = keepObj["node"];
        if (child.eoo() || !child.isABSONObj()) {
            return false;
        }

        return solutionMatches(child.Obj(), kn->children[0]);
    } else if (STAGE_SHARDING_FILTER == trueSoln->getType()) {
        const ShardingFilterNode* fn = static_cast<const ShardingFilterNode*>(trueSoln);

        BSONElement el = testSoln["sharding_filter"];
        if (el.eoo() || !el.isABSONObj()) {
            return false;
        }
        BSONObj keepObj = el.Obj();

        BSONElement child = keepObj["node"];
        if (child.eoo() || !child.isABSONObj()) {
            return false;
        }

        return solutionMatches(child.Obj(), fn->children[0]);
    } else if (STAGE_ENSURE_SORTED == trueSoln->getType()) {
        const EnsureSortedNode* esn = static_cast<const EnsureSortedNode*>(trueSoln);

        BSONElement el = testSoln["ensureSorted"];
        if (el.eoo() || !el.isABSONObj()) {
            return false;
        }
        BSONObj esObj = el.Obj();

        BSONElement patternEl = esObj["pattern"];
        if (patternEl.eoo() || !patternEl.isABSONObj()) {
            return false;
        }
        BSONElement child = esObj["node"];
        if (child.eoo() || !child.isABSONObj()) {
            return false;
        }

        return (patternEl.Obj() == esn->pattern) && solutionMatches(child.Obj(), esn->children[0]);
    }

    return false;
}
示例#2
0
    // static
    bool QueryPlannerTestLib::solutionMatches(const BSONObj& testSoln,
                                              const QuerySolutionNode* trueSoln) {
        //
        // leaf nodes
        //
        if (STAGE_COLLSCAN == trueSoln->getType()) {
            const CollectionScanNode* csn = static_cast<const CollectionScanNode*>(trueSoln);
            BSONElement el = testSoln["cscan"];
            if (el.eoo() || !el.isABSONObj()) { return false; }
            BSONObj csObj = el.Obj();

            BSONElement dir = csObj["dir"];
            if (dir.eoo() || !dir.isNumber()) { return false; }
            if (dir.numberInt() != csn->direction) { return false; }

            BSONElement filter = csObj["filter"];
            if (filter.eoo()) {
                return true;
            }
            else if (filter.isNull()) {
                return NULL == csn->filter;
            }
            else if (!filter.isABSONObj()) {
                return false;
            }
            return filterMatches(filter.Obj(), trueSoln);
        }
        else if (STAGE_IXSCAN == trueSoln->getType()) {
            const IndexScanNode* ixn = static_cast<const IndexScanNode*>(trueSoln);
            BSONElement el = testSoln["ixscan"];
            if (el.eoo() || !el.isABSONObj()) { return false; }
            BSONObj ixscanObj = el.Obj();

            BSONElement pattern = ixscanObj["pattern"];
            if (pattern.eoo() || !pattern.isABSONObj()) { return false; }
            if (pattern.Obj() != ixn->indexKeyPattern) { return false; }

            BSONElement bounds = ixscanObj["bounds"];
            if (!bounds.eoo()) {
                if (!bounds.isABSONObj()) {
                    return false;
                }
                else if (!boundsMatch(bounds.Obj(), ixn->bounds)) {
                    return false;
                }
            }

            BSONElement dir = ixscanObj["dir"];
            if (!dir.eoo() && NumberInt == dir.type()) {
                if (dir.numberInt() != ixn->direction) {
                    return false;
                }
            }

            BSONElement filter = ixscanObj["filter"];
            if (filter.eoo()) {
                return true;
            }
            else if (filter.isNull()) {
                return NULL == ixn->filter;
            }
            else if (!filter.isABSONObj()) {
                return false;
            }
            return filterMatches(filter.Obj(), trueSoln);
        }
        else if (STAGE_GEO_2D == trueSoln->getType()) {
            const Geo2DNode* node = static_cast<const Geo2DNode*>(trueSoln);
            BSONElement el = testSoln["geo2d"];
            if (el.eoo() || !el.isABSONObj()) { return false; }
            BSONObj geoObj = el.Obj();
            return geoObj == node->indexKeyPattern;
        }
        else if (STAGE_GEO_NEAR_2D == trueSoln->getType()) {
            const GeoNear2DNode* node = static_cast<const GeoNear2DNode*>(trueSoln);
            BSONElement el = testSoln["geoNear2d"];
            if (el.eoo() || !el.isABSONObj()) { return false; }
            BSONObj geoObj = el.Obj();
            return geoObj == node->indexKeyPattern;
        }
        else if (STAGE_GEO_NEAR_2DSPHERE == trueSoln->getType()) {
            const GeoNear2DSphereNode* node = static_cast<const GeoNear2DSphereNode*>(trueSoln);
            BSONElement el = testSoln["geoNear2dsphere"];
            if (el.eoo() || !el.isABSONObj()) { return false; }
            BSONObj geoObj = el.Obj();
            return geoObj == node->indexKeyPattern;
        }
        else if (STAGE_TEXT == trueSoln->getType()) {
            // {text: {search: "somestr", language: "something", filter: {blah: 1}}}
            const TextNode* node = static_cast<const TextNode*>(trueSoln);
            BSONElement el = testSoln["text"];
            if (el.eoo() || !el.isABSONObj()) { return false; }
            BSONObj textObj = el.Obj();

            BSONElement searchElt = textObj["search"];
            if (!searchElt.eoo()) {
                if (searchElt.String() != node->query) {
                    return false;
                }
            }

            BSONElement languageElt = textObj["language"];
            if (!languageElt.eoo()) {
                if (languageElt.String() != node->language) {
                    return false;
                }
            }

            BSONElement indexPrefix = textObj["prefix"];
            if (!indexPrefix.eoo()) {
                if (!indexPrefix.isABSONObj()) {
                    return false;
                }

                if (0 != indexPrefix.Obj().woCompare(node->indexPrefix)) {
                    return false;
                }
            }

            BSONElement filter = textObj["filter"];
            if (!filter.eoo()) {
                if (filter.isNull()) {
                    if (NULL != node->filter) { return false; }
                }
                else if (!filter.isABSONObj()) {
                    return false;
                }
                else if (!filterMatches(filter.Obj(), trueSoln)) {
                    return false;
                }
            }

            return true;
        }

        //
        // internal nodes
        //
        if (STAGE_FETCH == trueSoln->getType()) {
            const FetchNode* fn = static_cast<const FetchNode*>(trueSoln);

            BSONElement el = testSoln["fetch"];
            if (el.eoo() || !el.isABSONObj()) { return false; }
            BSONObj fetchObj = el.Obj();

            BSONElement filter = fetchObj["filter"];
            if (!filter.eoo()) {
                if (filter.isNull()) {
                    if (NULL != fn->filter) { return false; }
                }
                else if (!filter.isABSONObj()) {
                    return false;
                }
                else if (!filterMatches(filter.Obj(), trueSoln)) {
                    return false;
                }
            }

            BSONElement child = fetchObj["node"];
            if (child.eoo() || !child.isABSONObj()) { return false; }
            return solutionMatches(child.Obj(), fn->children[0]);
        }
        else if (STAGE_OR == trueSoln->getType()) {
            const OrNode * orn = static_cast<const OrNode*>(trueSoln);
            BSONElement el = testSoln["or"];
            if (el.eoo() || !el.isABSONObj()) { return false; }
            BSONObj orObj = el.Obj();
            return childrenMatch(orObj, orn);
        }
        else if (STAGE_AND_HASH == trueSoln->getType()) {
            const AndHashNode* ahn = static_cast<const AndHashNode*>(trueSoln);
            BSONElement el = testSoln["andHash"];
            if (el.eoo() || !el.isABSONObj()) { return false; }
            BSONObj andHashObj = el.Obj();
            // XXX: andHashObj can have filter
            return childrenMatch(andHashObj, ahn);
        }
        else if (STAGE_AND_SORTED == trueSoln->getType()) {
            const AndSortedNode* asn = static_cast<const AndSortedNode*>(trueSoln);
            BSONElement el = testSoln["andSorted"];
            if (el.eoo() || !el.isABSONObj()) { return false; }
            BSONObj andSortedObj = el.Obj();
            // XXX: anSortedObj can have filter too
            return childrenMatch(andSortedObj, asn);
        }
        else if (STAGE_PROJECTION == trueSoln->getType()) {
            const ProjectionNode* pn = static_cast<const ProjectionNode*>(trueSoln);

            BSONElement el = testSoln["proj"];
            if (el.eoo() || !el.isABSONObj()) { return false; }
            BSONObj projObj = el.Obj();

            BSONElement spec = projObj["spec"];
            if (spec.eoo() || !spec.isABSONObj()) { return false; }
            BSONElement child = projObj["node"];
            if (child.eoo() || !child.isABSONObj()) { return false; }

            return (spec.Obj() == pn->projection)
                   && solutionMatches(child.Obj(), pn->children[0]);
        }
        else if (STAGE_SORT == trueSoln->getType()) {
            const SortNode* sn = static_cast<const SortNode*>(trueSoln);
            BSONElement el = testSoln["sort"];
            if (el.eoo() || !el.isABSONObj()) { return false; }
            BSONObj sortObj = el.Obj();

            BSONElement patternEl = sortObj["pattern"];
            if (patternEl.eoo() || !patternEl.isABSONObj()) { return false; }
            BSONElement limitEl = sortObj["limit"];
            if (!limitEl.isNumber()) { return false; }
            BSONElement child = sortObj["node"];
            if (child.eoo() || !child.isABSONObj()) { return false; }

            return (patternEl.Obj() == sn->pattern)
                   && (limitEl.numberInt() == sn->limit)
                   && solutionMatches(child.Obj(), sn->children[0]);
        }
        else if (STAGE_SORT_MERGE == trueSoln->getType()) {
            const MergeSortNode* msn = static_cast<const MergeSortNode*>(trueSoln);
            BSONElement el = testSoln["mergeSort"];
            if (el.eoo() || !el.isABSONObj()) { return false; }
            BSONObj mergeSortObj = el.Obj();
            return childrenMatch(mergeSortObj, msn);
        }
        else if (STAGE_SKIP == trueSoln->getType()) {
            const SkipNode* sn = static_cast<const SkipNode*>(trueSoln);
            BSONElement el = testSoln["skip"];
            if (el.eoo() || !el.isABSONObj()) { return false; }
            BSONObj sortObj = el.Obj();

            BSONElement skipEl = sortObj["n"];
            if (!skipEl.isNumber()) { return false; }
            BSONElement child = sortObj["node"];
            if (child.eoo() || !child.isABSONObj()) { return false; }

            return (skipEl.numberInt() == sn->skip)
                   && solutionMatches(child.Obj(), sn->children[0]);
        }
        else if (STAGE_LIMIT == trueSoln->getType()) {
            const LimitNode* ln = static_cast<const LimitNode*>(trueSoln);
            BSONElement el = testSoln["limit"];
            if (el.eoo() || !el.isABSONObj()) { return false; }
            BSONObj sortObj = el.Obj();

            BSONElement limitEl = sortObj["n"];
            if (!limitEl.isNumber()) { return false; }
            BSONElement child = sortObj["node"];
            if (child.eoo() || !child.isABSONObj()) { return false; }

            return (limitEl.numberInt() == ln->limit)
                   && solutionMatches(child.Obj(), ln->children[0]);
        }
        else if (STAGE_KEEP_MUTATIONS == trueSoln->getType()) {
            const KeepMutationsNode* kn = static_cast<const KeepMutationsNode*>(trueSoln);

            BSONElement el = testSoln["keep"];
            if (el.eoo() || !el.isABSONObj()) { return false; }
            BSONObj keepObj = el.Obj();

            // Doesn't have any parameters really.
            BSONElement child = keepObj["node"];
            if (child.eoo() || !child.isABSONObj()) { return false; }

            return solutionMatches(child.Obj(), kn->children[0]);
        }

        return false;
    }