diff --git a/src/Interpreters/InterpreterExplainQuery.cpp b/src/Interpreters/InterpreterExplainQuery.cpp index 4eff8b8456d..864bbbb5fe1 100644 --- a/src/Interpreters/InterpreterExplainQuery.cpp +++ b/src/Interpreters/InterpreterExplainQuery.cpp @@ -18,6 +18,9 @@ #include #include +#include +#include + namespace DB { @@ -121,6 +124,7 @@ struct QueryPlanSettings /// Apply query plan optimizations. bool optimize = true; + bool json = false; constexpr static char name[] = "PLAN"; @@ -130,6 +134,7 @@ struct QueryPlanSettings {"description", query_plan_options.description}, {"actions", query_plan_options.actions}, {"optimize", optimize}, + {"json", json} }; }; @@ -255,7 +260,15 @@ BlockInputStreamPtr InterpreterExplainQuery::executeImpl() if (settings.optimize) plan.optimize(QueryPlanOptimizationSettings::fromContext(context)); - plan.explainPlan(buf, settings.query_plan_options); + if (settings.json) + { + auto tree = plan.explainPlan(); + std::stringstream out; + boost::property_tree::json_parser::write_json(out, tree); + buf.str() = out.str(); + } + else + plan.explainPlan(buf, settings.query_plan_options); } else if (ast.getKind() == ASTExplainQuery::QueryPipeline) { diff --git a/src/Processors/QueryPlan/IQueryPlanStep.h b/src/Processors/QueryPlan/IQueryPlanStep.h index 8211b52a6c4..5b235833ad0 100644 --- a/src/Processors/QueryPlan/IQueryPlanStep.h +++ b/src/Processors/QueryPlan/IQueryPlanStep.h @@ -3,6 +3,8 @@ #include #include +#include + namespace DB { @@ -96,6 +98,8 @@ public: const bool write_header = false; }; + virtual void describeActions(boost::property_tree::ptree & /*tree*/) const {} + /// Get detailed description of step actions. This is shown in EXPLAIN query with options `actions = 1`. virtual void describeActions(FormatSettings & /*settings*/) const {} diff --git a/src/Processors/QueryPlan/QueryPlan.cpp b/src/Processors/QueryPlan/QueryPlan.cpp index 974da579d0c..bac4dc232ce 100644 --- a/src/Processors/QueryPlan/QueryPlan.cpp +++ b/src/Processors/QueryPlan/QueryPlan.cpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace DB { @@ -200,6 +201,65 @@ void QueryPlan::addInterpreterContext(std::shared_ptr context) } +static boost::property_tree::ptree explainStep(const IQueryPlanStep & step) +{ + boost::property_tree::ptree tree; + tree.put("Node Type", step.getName()); + + const auto & description = step.getStepDescription(); + if (!description.empty()) + tree.put("Description", description); + + step.describeActions(tree); + + return tree; +} + +boost::property_tree::ptree QueryPlan::explainPlan() +{ + checkInitialized(); + + struct Frame + { + Node * node; + size_t next_child = 0; + boost::property_tree::ptree node_tree = {}; + boost::property_tree::ptree children_trees = {}; + }; + + std::stack stack; + stack.push(Frame{.node = root}); + + boost::property_tree::ptree tree; + + while (!stack.empty()) + { + auto & frame = stack.top(); + + if (frame.next_child == 0) + frame.node_tree = explainStep(*frame.node->step); + + if (frame.next_child < frame.node->children.size()) + { + stack.push(Frame{frame.node->children[frame.next_child]}); + ++frame.next_child; + } + else + { + if (!frame.children_trees.empty()) + frame.node_tree.add_child("Plans", frame.children_trees); + + tree.swap(frame.node_tree); + stack.pop(); + + if (!stack.empty()) + stack.top().children_trees.add_child("", tree); + } + } + + return tree; +} + static void explainStep( const IQueryPlanStep & step, IQueryPlanStep::FormatSettings & settings, diff --git a/src/Processors/QueryPlan/QueryPlan.h b/src/Processors/QueryPlan/QueryPlan.h index d5cc2e8f4e8..d68b57452cb 100644 --- a/src/Processors/QueryPlan/QueryPlan.h +++ b/src/Processors/QueryPlan/QueryPlan.h @@ -3,6 +3,7 @@ #include #include #include +#include #include @@ -73,6 +74,7 @@ public: bool header = false; }; + boost::property_tree::ptree explainPlan(); void explainPlan(WriteBuffer & buffer, const ExplainPlanOptions & options); void explainPipeline(WriteBuffer & buffer, const ExplainPipelineOptions & options);