Query plan visitor with debug logs

This commit is contained in:
Igor Nikonov 2022-12-13 22:46:39 +00:00
parent 1cbdce8eea
commit e25053dec0

View File

@ -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();
}
}