/** * 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. */ #include "mongo/db/auth/action_set.h" #include "mongo/db/auth/action_type.h" #include "mongo/db/auth/privilege.h" #include "mongo/db/commands.h" #include "mongo/db/exec/and_hash.h" #include "mongo/db/exec/and_sorted.h" #include "mongo/db/exec/collection_scan.h" #include "mongo/db/exec/fetch.h" #include "mongo/db/exec/index_scan.h" #include "mongo/db/exec/limit.h" #include "mongo/db/exec/merge_sort.h" #include "mongo/db/exec/or.h" #include "mongo/db/exec/skip.h" #include "mongo/db/exec/sort.h" #include "mongo/db/index/catalog_hack.h" #include "mongo/db/jsobj.h" #include "mongo/db/matcher/expression_parser.h" #include "mongo/db/namespace_details.h" #include "mongo/db/pdfile.h" #include "mongo/db/query/plan_executor.h" namespace mongo { /** * A command for manually constructing a query tree and running it. * * db.runCommand({stageDebug: rootNode}) * * The value of the filter field is a BSONObj that specifies values that fields must have. What * you'd pass to a matcher. * * Leaf Nodes: * * node -> {ixscan: {filter: {FILTER}, * args: {name: "collectionname", indexKeyPattern: kpObj, start: startObj, * stop: stopObj, endInclusive: true/false, direction: -1/1, * limit: int}}} * node -> {cscan: {filter: {filter}, args: {name: "collectionname", direction: -1/1}}} * * Internal Nodes: * * node -> {andHash: {filter: {filter}, args: { nodes: [node, node]}}} * node -> {andSorted: {filter: {filter}, args: { nodes: [node, node]}}} * node -> {or: {filter: {filter}, args: { dedup:bool, nodes:[node, node]}}} * node -> {fetch: {filter: {filter}, args: {node: node}}} * node -> {limit: {args: {node: node, num: posint}}} * node -> {skip: {args: {node: node, num: posint}}} * node -> {sort: {args: {node: node, pattern: objWithSortCriterion }}} * node -> {mergeSort: {args: {nodes: [node, node], pattern: objWithSortCriterion}}} * node -> {cscan: {filter: {filter}, args: {name: "collectionname" }}} * * Forthcoming Nodes: * * node -> {dedup: {filter: {filter}, args: {node: node, field: field}}} * node -> {unwind: {filter: filter}, args: {node: node, field: field}} */ class StageDebugCmd : public Command { public: StageDebugCmd() : Command("stageDebug") { } // Boilerplate for commands virtual LockType locktype() const { return READ; } bool slaveOk() const { return true; } bool slaveOverrideOk() const { return true; } void help(stringstream& h) const { } virtual void addRequiredPrivileges(const std::string& dbname, const BSONObj& cmdObj, std::vector* out) { ActionSet actions; actions.addAction(ActionType::find); out->push_back(Privilege(parseNs(dbname, cmdObj), actions)); } bool run(const string& dbname, BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result, bool fromRepl) { BSONElement argElt = cmdObj["stageDebug"]; if (argElt.eoo() || !argElt.isABSONObj()) { return false; } BSONObj argObj = argElt.Obj(); OwnedPointerVector exprs; auto_ptr ws(new WorkingSet()); PlanStage* userRoot = parseQuery(dbname, argObj, ws.get(), &exprs); uassert(16911, "Couldn't parse plan from " + argObj.toString(), NULL != userRoot); // Add a fetch at the top for the user so we can get obj back for sure. // TODO: Do we want to do this for the user? I think so. PlanStage* rootFetch = new FetchStage(ws.get(), userRoot, NULL); PlanExecutor runner(ws.release(), rootFetch); BSONArrayBuilder resultBuilder(result.subarrayStart("results")); for (BSONObj obj; Runner::RUNNER_ADVANCED == runner.getNext(&obj, NULL); ) { resultBuilder.append(obj); } resultBuilder.done(); return true; } PlanStage* parseQuery(const string& dbname, BSONObj obj, WorkingSet* workingSet, OwnedPointerVector* exprs) { BSONElement firstElt = obj.firstElement(); if (!firstElt.isABSONObj()) { return NULL; } BSONObj paramObj = firstElt.Obj(); MatchExpression* matcher = NULL; BSONObj nodeArgs; // Every node has these two fields. const string filterTag = "filter"; const string argsTag = "args"; BSONObjIterator it(paramObj); while (it.more()) { BSONElement e = it.next(); if (!e.isABSONObj()) { return NULL; } BSONObj argObj = e.Obj(); if (filterTag == e.fieldName()) { StatusWithMatchExpression swme = MatchExpressionParser::parse(argObj); if (!swme.isOK()) { return NULL; } // exprs is what will wind up deleting this. matcher = swme.getValue(); verify(NULL != matcher); exprs->mutableVector().push_back(matcher); } else if (argsTag == e.fieldName()) { nodeArgs = argObj; } else { uasserted(16910, "Unknown fieldname " + string(e.fieldName()) + " in query node " + obj.toString()); return NULL; } } string nodeName = firstElt.fieldName(); if ("ixscan" == nodeName) { NamespaceDetails* nsd = nsdetails(dbname + "." + nodeArgs["name"].String()); uassert(16913, "Can't find collection " + nodeArgs["name"].String(), nsd); int idxNo = nsd->findIndexByKeyPattern(nodeArgs["keyPattern"].Obj()); uassert(16890, "Can't find index: " + nodeArgs["keyPattern"].Obj().toString(), idxNo != -1); IndexScanParams params; params.descriptor = CatalogHack::getDescriptor(nsd, idxNo); params.bounds.isSimpleRange = true; params.bounds.startKey = nodeArgs["startKey"].Obj(); params.bounds.endKey = nodeArgs["endKey"].Obj(); params.bounds.endKeyInclusive = nodeArgs["endKeyInclusive"].Bool(); params.direction = nodeArgs["direction"].numberInt(); params.limit = nodeArgs["limit"].numberInt(); params.forceBtreeAccessMethod = false; return new IndexScan(params, workingSet, matcher); } else if ("andHash" == nodeName) { uassert(16921, "Nodes argument must be provided to AND", nodeArgs["nodes"].isABSONObj()); auto_ptr andStage(new AndHashStage(workingSet, matcher)); int nodesAdded = 0; BSONObjIterator it(nodeArgs["nodes"].Obj()); while (it.more()) { BSONElement e = it.next(); uassert(16922, "node of AND isn't an obj?: " + e.toString(), e.isABSONObj()); PlanStage* subNode = parseQuery(dbname, e.Obj(), workingSet, exprs); uassert(16923, "Can't parse sub-node of AND: " + e.Obj().toString(), NULL != subNode); // takes ownership andStage->addChild(subNode); ++nodesAdded; } uassert(16927, "AND requires more than one child", nodesAdded >= 2); return andStage.release(); } else if ("andSorted" == nodeName) { uassert(16924, "Nodes argument must be provided to AND", nodeArgs["nodes"].isABSONObj()); auto_ptr andStage(new AndSortedStage(workingSet, matcher)); int nodesAdded = 0; BSONObjIterator it(nodeArgs["nodes"].Obj()); while (it.more()) { BSONElement e = it.next(); uassert(16925, "node of AND isn't an obj?: " + e.toString(), e.isABSONObj()); PlanStage* subNode = parseQuery(dbname, e.Obj(), workingSet, exprs); uassert(16926, "Can't parse sub-node of AND: " + e.Obj().toString(), NULL != subNode); // takes ownership andStage->addChild(subNode); ++nodesAdded; } uassert(16928, "AND requires more than one child", nodesAdded >= 2); return andStage.release(); } else if ("or" == nodeName) { uassert(16934, "Nodes argument must be provided to AND", nodeArgs["nodes"].isABSONObj()); uassert(16935, "Dedup argument must be provided to OR", !nodeArgs["dedup"].eoo()); BSONObjIterator it(nodeArgs["nodes"].Obj()); auto_ptr orStage(new OrStage(workingSet, nodeArgs["dedup"].Bool(), matcher)); while (it.more()) { BSONElement e = it.next(); if (!e.isABSONObj()) { return NULL; } PlanStage* subNode = parseQuery(dbname, e.Obj(), workingSet, exprs); uassert(16936, "Can't parse sub-node of OR: " + e.Obj().toString(), NULL != subNode); // takes ownership orStage->addChild(subNode); } return orStage.release(); } else if ("fetch" == nodeName) { uassert(16929, "Node argument must be provided to fetch", nodeArgs["node"].isABSONObj()); PlanStage* subNode = parseQuery(dbname, nodeArgs["node"].Obj(), workingSet, exprs); return new FetchStage(workingSet, subNode, matcher); } else if ("limit" == nodeName) { uassert(16937, "Limit stage doesn't have a filter (put it on the child)", NULL == matcher); uassert(16930, "Node argument must be provided to limit", nodeArgs["node"].isABSONObj()); uassert(16931, "Num argument must be provided to limit", nodeArgs["num"].isNumber()); PlanStage* subNode = parseQuery(dbname, nodeArgs["node"].Obj(), workingSet, exprs); return new LimitStage(nodeArgs["num"].numberInt(), workingSet, subNode); } else if ("skip" == nodeName) { uassert(16938, "Skip stage doesn't have a filter (put it on the child)", NULL == matcher); uassert(16932, "Node argument must be provided to skip", nodeArgs["node"].isABSONObj()); uassert(16933, "Num argument must be provided to skip", nodeArgs["num"].isNumber()); PlanStage* subNode = parseQuery(dbname, nodeArgs["node"].Obj(), workingSet, exprs); return new SkipStage(nodeArgs["num"].numberInt(), workingSet, subNode); } else if ("cscan" == nodeName) { CollectionScanParams params; // What collection? params.ns = dbname + "." + nodeArgs["name"].String(); uassert(16962, "Can't find collection " + nodeArgs["name"].String(), NULL != nsdetails(params.ns)); // What direction? uassert(16963, "Direction argument must be specified and be a number", nodeArgs["direction"].isNumber()); if (1 == nodeArgs["direction"].numberInt()) { params.direction = CollectionScanParams::FORWARD; } else { params.direction = CollectionScanParams::BACKWARD; } return new CollectionScan(params, workingSet, matcher); } else if ("sort" == nodeName) { uassert(16969, "Node argument must be provided to sort", nodeArgs["node"].isABSONObj()); uassert(16970, "Pattern argument must be provided to sort", nodeArgs["pattern"].isABSONObj()); PlanStage* subNode = parseQuery(dbname, nodeArgs["node"].Obj(), workingSet, exprs); SortStageParams params; params.pattern = nodeArgs["pattern"].Obj(); return new SortStage(params, workingSet, subNode); } else if ("mergeSort" == nodeName) { uassert(16971, "Nodes argument must be provided to sort", nodeArgs["nodes"].isABSONObj()); uassert(16972, "Pattern argument must be provided to sort", nodeArgs["pattern"].isABSONObj()); MergeSortStageParams params; params.pattern = nodeArgs["pattern"].Obj(); // Dedup is true by default. auto_ptr mergeStage(new MergeSortStage(params, workingSet)); BSONObjIterator it(nodeArgs["nodes"].Obj()); while (it.more()) { BSONElement e = it.next(); uassert(16973, "node of mergeSort isn't an obj?: " + e.toString(), e.isABSONObj()); PlanStage* subNode = parseQuery(dbname, e.Obj(), workingSet, exprs); uassert(16974, "Can't parse sub-node of mergeSort: " + e.Obj().toString(), NULL != subNode); // takes ownership mergeStage->addChild(subNode); } return mergeStage.release(); } else { return NULL; } } } stageDebugCmd; } // namespace mongo