mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-12-12 01:12:12 +00:00
Query plan visitor with debug logs
This commit is contained in:
parent
1cbdce8eea
commit
e25053dec0
@ -14,17 +14,10 @@
|
||||
|
||||
namespace DB::QueryPlanOptimizations
|
||||
{
|
||||
const char * stepName(const QueryPlan::Node * node)
|
||||
template <typename Derived, bool debug_logging = false>
|
||||
class QueryPlanVisitor
|
||||
{
|
||||
IQueryPlanStep * current_step = node->step.get();
|
||||
return typeid(*current_step).name();
|
||||
}
|
||||
|
||||
void printStepName(const char * prefix, const QueryPlan::Node * node)
|
||||
{
|
||||
LOG_DEBUG(&Poco::Logger::get("RedundantOrderBy"), "{}: {}: {}", prefix, stepName(node), reinterpret_cast<void *>(node->step.get()));
|
||||
}
|
||||
|
||||
protected:
|
||||
struct FrameWithParent
|
||||
{
|
||||
QueryPlan::Node * node = nullptr;
|
||||
@ -34,50 +27,15 @@ struct FrameWithParent
|
||||
|
||||
using StackWithParent = std::vector<FrameWithParent>;
|
||||
|
||||
bool checkIfCanDeleteSorting(const StackWithParent & stack, const QueryPlan::Node * node_affect_order)
|
||||
{
|
||||
chassert(!stack.empty());
|
||||
chassert(typeid_cast<const SortingStep *>(stack.back().node->step.get()));
|
||||
|
||||
/// skip element on top of stack since it's sorting
|
||||
for (StackWithParent::const_reverse_iterator it = stack.rbegin() + 1; it != stack.rend(); ++it)
|
||||
{
|
||||
const auto * node = it->node;
|
||||
/// walking though stack until reach node which affects order
|
||||
if (node == node_affect_order)
|
||||
break;
|
||||
|
||||
const auto * step = node->step.get();
|
||||
|
||||
const auto * expr = typeid_cast<const ExpressionStep *>(step);
|
||||
if (expr)
|
||||
{
|
||||
if (expr->getExpression()->hasStatefulFunctions())
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto * window = typeid_cast<const WindowStep *>(step);
|
||||
if (window)
|
||||
return true;
|
||||
|
||||
const auto * trans = typeid_cast<const ITransformingStep *>(step);
|
||||
if (!trans)
|
||||
break;
|
||||
|
||||
if (!trans->getDataStreamTraits().preserves_sorting)
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void tryRemoveRedundantOrderBy(QueryPlan::Node * root)
|
||||
{
|
||||
QueryPlan::Node * root = nullptr;
|
||||
StackWithParent stack;
|
||||
stack.push_back({.node = root});
|
||||
|
||||
std::vector<QueryPlan::Node *> nodes_affect_order;
|
||||
public:
|
||||
explicit QueryPlanVisitor(QueryPlan::Node * root_) : root(root_) { }
|
||||
|
||||
void visit()
|
||||
{
|
||||
stack.push_back({.node = root});
|
||||
|
||||
while (!stack.empty())
|
||||
{
|
||||
@ -85,13 +43,76 @@ void tryRemoveRedundantOrderBy(QueryPlan::Node * root)
|
||||
|
||||
QueryPlan::Node * current_node = frame.node;
|
||||
QueryPlan::Node * parent_node = frame.parent_node;
|
||||
IQueryPlanStep * current_step = current_node->step.get();
|
||||
printStepName("back", current_node);
|
||||
|
||||
logStep("back", current_node);
|
||||
|
||||
/// top-down visit
|
||||
if (0 == frame.next_child)
|
||||
{
|
||||
printStepName("visit", current_node);
|
||||
logStep("top-down", current_node);
|
||||
if (! visitTopDown(current_node, parent_node))
|
||||
continue;
|
||||
}
|
||||
/// Traverse all children
|
||||
if (frame.next_child < frame.node->children.size())
|
||||
{
|
||||
auto next_frame = FrameWithParent{.node = current_node->children[frame.next_child], .parent_node = current_node};
|
||||
++frame.next_child;
|
||||
logStep("push", next_frame.node);
|
||||
stack.push_back(next_frame);
|
||||
continue;
|
||||
}
|
||||
|
||||
/// bottom-up visit
|
||||
logStep("bottom-up", current_node);
|
||||
visitBottomUp(current_node, parent_node);
|
||||
|
||||
logStep("pop", current_node);
|
||||
stack.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
bool visitTopDown(QueryPlan::Node * current_node, QueryPlan::Node * parent_node)
|
||||
{
|
||||
return getDerived().visitTopDown(current_node, parent_node);
|
||||
}
|
||||
void visitBottomUp(QueryPlan::Node * current_node, QueryPlan::Node * parent_node)
|
||||
{
|
||||
getDerived().visitBottomUp(current_node, parent_node);
|
||||
}
|
||||
|
||||
private:
|
||||
Derived & getDerived() { return *static_cast<Derived *>(this); }
|
||||
|
||||
const Derived & getDerived() const { return *static_cast<Derived *>(this); }
|
||||
|
||||
protected:
|
||||
void logStep(const char * prefix, const QueryPlan::Node * node)
|
||||
{
|
||||
if constexpr (debug_logging)
|
||||
{
|
||||
IQueryPlanStep * current_step = node->step.get();
|
||||
LOG_DEBUG(
|
||||
&Poco::Logger::get("QueryPlanVisitor"),
|
||||
"{}: {}: {}",
|
||||
prefix,
|
||||
current_step->getName(),
|
||||
reinterpret_cast<void *>(current_step));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class RemoveRedundantOrderBy : public QueryPlanVisitor<RemoveRedundantOrderBy, true>
|
||||
{
|
||||
std::vector<QueryPlan::Node *> nodes_affect_order;
|
||||
|
||||
public:
|
||||
explicit RemoveRedundantOrderBy(QueryPlan::Node * root_) : QueryPlanVisitor<RemoveRedundantOrderBy, true>(root_) { }
|
||||
|
||||
bool visitTopDown(QueryPlan::Node * current_node, QueryPlan::Node * parent_node)
|
||||
{
|
||||
IQueryPlanStep * current_step = current_node->step.get();
|
||||
|
||||
/// if there is parent node which can affect order and current step is sorting
|
||||
/// then check if we can remove the sorting step (and corresponding expression step)
|
||||
if (!nodes_affect_order.empty() && typeid_cast<SortingStep *>(current_step))
|
||||
@ -134,7 +155,7 @@ void tryRemoveRedundantOrderBy(QueryPlan::Node * root)
|
||||
/// stateful function output can depend on order
|
||||
/// (2) for window function we do ORDER BY in 2 Sorting steps, so do not delete Sorting
|
||||
/// if window function step is on top
|
||||
if (checkIfCanDeleteSorting(stack, node_affect_order))
|
||||
if (checkIfCanDeleteSorting(node_affect_order))
|
||||
return false;
|
||||
|
||||
chassert(typeid_cast<ExpressionStep *>(current_node->children.front()->step.get()));
|
||||
@ -147,17 +168,17 @@ void tryRemoveRedundantOrderBy(QueryPlan::Node * root)
|
||||
};
|
||||
if (try_to_remove_sorting_step())
|
||||
{
|
||||
LOG_DEBUG(&Poco::Logger::get("RedundantOrderBy"), "Sorting removed");
|
||||
logStep("removed from plan", current_node);
|
||||
|
||||
auto & frame = stack.back();
|
||||
/// mark removed node as visited
|
||||
frame.next_child = frame.node->children.size();
|
||||
|
||||
/// current sorting step has been removed from plan, its parent has new children, need to visit them
|
||||
auto next_frame = FrameWithParent{.node = parent_node->children[0], .parent_node = parent_node};
|
||||
++frame.next_child;
|
||||
printStepName("push", next_frame.node);
|
||||
stack.push_back(next_frame);
|
||||
continue;
|
||||
logStep("push", next_frame.node);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -167,32 +188,68 @@ void tryRemoveRedundantOrderBy(QueryPlan::Node * root)
|
||||
|| typeid_cast<SortingStep *>(current_step) /// (3) ORDER BY will change order of previous sorting
|
||||
|| typeid_cast<AggregatingStep *>(current_step)) /// (4) aggregation change order
|
||||
{
|
||||
printStepName("steps_affect_order/push", current_node);
|
||||
logStep("steps_affect_order/push", current_node);
|
||||
nodes_affect_order.push_back(current_node);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Traverse all children
|
||||
if (frame.next_child < frame.node->children.size())
|
||||
void visitBottomUp(QueryPlan::Node * current_node, QueryPlan::Node *)
|
||||
{
|
||||
auto next_frame = FrameWithParent{.node = current_node->children[frame.next_child], .parent_node = current_node};
|
||||
++frame.next_child;
|
||||
printStepName("push", next_frame.node);
|
||||
stack.push_back(next_frame);
|
||||
continue;
|
||||
}
|
||||
|
||||
/// bottom-up visit
|
||||
/// we come here when all children of current_node are visited,
|
||||
/// so it's a node which affect order, remove it from the corresponding stack
|
||||
if (!nodes_affect_order.empty() && nodes_affect_order.back() == current_node)
|
||||
{
|
||||
printStepName("node_affect_order/pop", current_node);
|
||||
logStep("node_affect_order/pop", current_node);
|
||||
nodes_affect_order.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
printStepName("pop", current_node);
|
||||
stack.pop_back();
|
||||
private:
|
||||
bool checkIfCanDeleteSorting(const QueryPlan::Node * node_affect_order)
|
||||
{
|
||||
chassert(!stack.empty());
|
||||
chassert(typeid_cast<const SortingStep *>(stack.back().node->step.get()));
|
||||
|
||||
/// skip element on top of stack since it's sorting
|
||||
for (StackWithParent::const_reverse_iterator it = stack.rbegin() + 1; it != stack.rend(); ++it)
|
||||
{
|
||||
const auto * node = it->node;
|
||||
/// walking though stack until reach node which affects order
|
||||
if (node == node_affect_order)
|
||||
break;
|
||||
|
||||
const auto * step = node->step.get();
|
||||
|
||||
const auto * expr = typeid_cast<const ExpressionStep *>(step);
|
||||
if (expr)
|
||||
{
|
||||
if (expr->getExpression()->hasStatefulFunctions())
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto * window = typeid_cast<const WindowStep *>(step);
|
||||
if (window)
|
||||
return true;
|
||||
|
||||
const auto * trans = typeid_cast<const ITransformingStep *>(step);
|
||||
if (!trans)
|
||||
break;
|
||||
|
||||
if (!trans->getDataStreamTraits().preserves_sorting)
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
void tryRemoveRedundantOrderBy(QueryPlan::Node * root)
|
||||
{
|
||||
RemoveRedundantOrderBy(root).visit();
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user