/** * Copyright (C) 2013 10gen Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * * As a special exception, the copyright holders give permission to link the * code of portions of this program with the OpenSSL library under certain * conditions as described in each individual source file and distribute * linked combinations including the program with the OpenSSL library. You * must comply with the GNU Affero General Public License in all respects for * all of the code used other than as permitted herein. If you modify file(s) * with this exception, you may extend this exception to your version of the * file(s), but you are not obligated to do so. If you do not wish to do so, * delete this exception statement from your version. If you delete this * exception statement from all source files in the program, then also delete * it in the license file. */ /** * This file contains tests for mongo/db/query/query_planner.cpp */ #include "mongo/db/query/query_planner_test_lib.h" #include #include "mongo/db/jsobj.h" #include "mongo/db/json.h" #include "mongo/db/matcher/expression_parser.h" #include "mongo/db/query/query_planner.h" #include "mongo/db/query/query_solution.h" #include "mongo/unittest/unittest.h" #include "mongo/util/assert_util.h" namespace { using namespace mongo; using std::string; bool filterMatches(const BSONObj& testFilter, const QuerySolutionNode* trueFilterNode) { if (NULL == trueFilterNode->filter) { return false; } StatusWithMatchExpression swme = MatchExpressionParser::parse(testFilter); if (!swme.isOK()) { return false; } const std::unique_ptr root(swme.getValue()); CanonicalQuery::sortTree(root.get()); std::unique_ptr trueFilter(trueFilterNode->filter->shallowClone()); CanonicalQuery::sortTree(trueFilter.get()); return trueFilter->equivalent(root.get()); } void appendIntervalBound(BSONObjBuilder& bob, BSONElement& el) { if (el.type() == String) { std::string data = el.String(); if (data == "MaxKey") { bob.appendMaxKey(""); } else if (data == "MinKey") { bob.appendMinKey(""); } else { bob.appendAs(el, ""); } } else { bob.appendAs(el, ""); } } bool intervalMatches(const BSONObj& testInt, const Interval trueInt) { BSONObjIterator it(testInt); if (!it.more()) { return false; } BSONElement low = it.next(); if (!it.more()) { return false; } BSONElement high = it.next(); if (!it.more()) { return false; } bool startInclusive = it.next().Bool(); if (!it.more()) { return false; } bool endInclusive = it.next().Bool(); if (it.more()) { return false; } BSONObjBuilder bob; appendIntervalBound(bob, low); appendIntervalBound(bob, high); Interval toCompare(bob.obj(), startInclusive, endInclusive); return Interval::INTERVAL_EQUALS == trueInt.compare(toCompare); } /** * Returns whether the BSON representation of the index bounds in * 'testBounds' matches 'trueBounds'. * * 'testBounds' should be of the following format: * {: , : , ...} * Each ordered interval list (e.g. ) is an array of arrays of * the format: * [[,,,], ...] * * For example, * {a: [[1,2,true,false], [3,4,false,true]], b: [[-Infinity, Infinity]]} * Means that the index bounds on field 'a' consist of the two intervals * [1, 2) and (3, 4] and the index bounds on field 'b' are [-Infinity, Infinity]. */ bool boundsMatch(const BSONObj& testBounds, const IndexBounds trueBounds) { // Iterate over the fields on which we have index bounds. BSONObjIterator fieldIt(testBounds); int fieldItCount = 0; while (fieldIt.more()) { BSONElement arrEl = fieldIt.next(); if (arrEl.type() != Array) { return false; } // Iterate over an ordered interval list for // a particular field. BSONObjIterator oilIt(arrEl.Obj()); int oilItCount = 0; while (oilIt.more()) { BSONElement intervalEl = oilIt.next(); if (intervalEl.type() != Array) { return false; } Interval trueInt = trueBounds.getInterval(fieldItCount, oilItCount); if (!intervalMatches(intervalEl.Obj(), trueInt)) { return false; } ++oilItCount; } ++fieldItCount; } return true; } } // namespace namespace mongo { /** * Looks in the children stored in the 'nodes' field of 'testSoln' * to see if thet match the 'children' field of 'trueSoln'. * * This does an unordered comparison, i.e. childrenMatch returns * true as long as the set of subtrees in testSoln's 'nodes' matches * the set of subtrees in trueSoln's 'children' vector. */ static bool childrenMatch(const BSONObj& testSoln, const QuerySolutionNode* trueSoln) { BSONElement children = testSoln["nodes"]; if (children.eoo() || !children.isABSONObj()) { return false; } // The order of the children array in testSoln might not match // the order in trueSoln, so we have to check all combos with // these nested loops. BSONObjIterator i(children.Obj()); while (i.more()) { BSONElement child = i.next(); if (child.eoo() || !child.isABSONObj()) { return false; } // try to match against one of the QuerySolutionNode's children bool found = false; for (size_t j = 0; j < trueSoln->children.size(); ++j) { if (QueryPlannerTestLib::solutionMatches(child.Obj(), trueSoln->children[j])) { found = true; break; } } // we couldn't match child if (!found) { return false; } } return true; } // static bool QueryPlannerTestLib::solutionMatches(const BSONObj& testSoln, const QuerySolutionNode* trueSoln) { // // leaf nodes // if (STAGE_COLLSCAN == trueSoln->getType()) { const CollectionScanNode* csn = static_cast(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(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_NEAR_2D == trueSoln->getType()) { const GeoNear2DNode* node = static_cast(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(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(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 caseSensitiveElt = textObj["caseSensitive"]; if (!caseSensitiveElt.eoo()) { if (caseSensitiveElt.trueValue() != node->caseSensitive) { 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(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(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(trueSoln); BSONElement el = testSoln["andHash"]; if (el.eoo() || !el.isABSONObj()) { return false; } BSONObj andHashObj = el.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(), trueSoln)) { return false; } } return childrenMatch(andHashObj, ahn); } else if (STAGE_AND_SORTED == trueSoln->getType()) { const AndSortedNode* asn = static_cast(trueSoln); BSONElement el = testSoln["andSorted"]; if (el.eoo() || !el.isABSONObj()) { return false; } BSONObj andSortedObj = el.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(), trueSoln)) { return false; } } return childrenMatch(andSortedObj, asn); } else if (STAGE_PROJECTION == trueSoln->getType()) { const ProjectionNode* pn = static_cast(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(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_MERGE == trueSoln->getType()) { const MergeSortNode* msn = static_cast(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(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(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(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(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]); } return false; } } // namespace mongo