You've already forked mariadb-columnstore-engine
mirror of
https://github.com/mariadb-corporation/mariadb-columnstore-engine.git
synced 2025-10-31 18:30:33 +03:00
chore(execplan): MCOL: 6144 prerry print filters and havings as a tree (#3712)
* MCOL-6144: print Filters and Having indented for better CSEP visualization
This commit is contained in:
@@ -152,13 +152,49 @@ const string AggregateColumn::toString() const
|
|||||||
return output.str();
|
return output.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const string AggregateColumn::toString(bool compact) const
|
||||||
|
{
|
||||||
|
if (!compact)
|
||||||
|
{
|
||||||
|
return toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
ostringstream output;
|
||||||
|
|
||||||
|
// Compact format for tree display - let tree printer handle indentation
|
||||||
|
output << "AggregateColumn:";
|
||||||
|
output << endl << "Function: " << (int)fAggOp << ", Distinct: " << fDistinct;
|
||||||
|
|
||||||
|
if (fAlias.length() > 0)
|
||||||
|
output << endl << "Alias: " << fAlias;
|
||||||
|
|
||||||
|
if (fAggParms.size() > 0)
|
||||||
|
{
|
||||||
|
for (uint32_t i = 0; i < fAggParms.size(); ++i)
|
||||||
|
{
|
||||||
|
SimpleColumn* sc = dynamic_cast<SimpleColumn*>(fAggParms[i].get());
|
||||||
|
if (sc)
|
||||||
|
{
|
||||||
|
output << endl << sc->toString(true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
output << endl << "Param: " << fAggParms[i]->data();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output.str();
|
||||||
|
}
|
||||||
|
|
||||||
string AggregateColumn::toCppCode(IncludeSet& includes) const
|
string AggregateColumn::toCppCode(IncludeSet& includes) const
|
||||||
{
|
{
|
||||||
includes.insert("aggregatecolumn.h");
|
includes.insert("aggregatecolumn.h");
|
||||||
stringstream ss;
|
stringstream ss;
|
||||||
auto fContent = fData.substr(fFunctionName.size() + 1, fData.size() - fFunctionName.size() - 2);
|
auto fContent = fData.substr(fFunctionName.size() + 1, fData.size() - fFunctionName.size() - 2);
|
||||||
|
|
||||||
ss << "AggregateColumn(" << std::quoted(fFunctionName) << ", " << std::quoted(fContent) << ", " << sessionID() << ")";
|
ss << "AggregateColumn(" << std::quoted(fFunctionName) << ", " << std::quoted(fContent) << ", "
|
||||||
|
<< sessionID() << ")";
|
||||||
|
|
||||||
return ss.str();
|
return ss.str();
|
||||||
}
|
}
|
||||||
@@ -588,15 +624,15 @@ void AggregateColumn::evaluate(Row& row, bool& isNull)
|
|||||||
|
|
||||||
case CalpontSystemCatalog::VARBINARY:
|
case CalpontSystemCatalog::VARBINARY:
|
||||||
case CalpontSystemCatalog::BLOB:
|
case CalpontSystemCatalog::BLOB:
|
||||||
{
|
{
|
||||||
auto const str = row.getConstString(fInputIndex);
|
auto const str = row.getConstString(fInputIndex);
|
||||||
fResult.strVal.dropString();
|
fResult.strVal.dropString();
|
||||||
if (!str.isNull())
|
if (!str.isNull())
|
||||||
fResult.strVal.assign((const uint8_t*)str.str(), str.length());
|
fResult.strVal.assign((const uint8_t*)str.str(), str.length());
|
||||||
|
|
||||||
isNull = isNull || fResult.strVal.isNull();
|
isNull = isNull || fResult.strVal.isNull();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default: // treat as int64
|
default: // treat as int64
|
||||||
if (row.equals<8>(BIGINTNULL, fInputIndex))
|
if (row.equals<8>(BIGINTNULL, fInputIndex))
|
||||||
|
|||||||
@@ -220,6 +220,7 @@ class AggregateColumn : public ReturnedColumn
|
|||||||
* Overloaded stream operator
|
* Overloaded stream operator
|
||||||
*/
|
*/
|
||||||
const std::string toString() const override;
|
const std::string toString() const override;
|
||||||
|
const std::string toString(bool compact) const;
|
||||||
std::string toCppCode(IncludeSet& includes) const override;
|
std::string toCppCode(IncludeSet& includes) const override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
***********************************************************************/
|
***********************************************************************/
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <sstream>
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
#include <boost/uuid/uuid_io.hpp>
|
#include <boost/uuid/uuid_io.hpp>
|
||||||
@@ -35,6 +36,9 @@ using namespace messageqcpp;
|
|||||||
#include "returnedcolumn.h"
|
#include "returnedcolumn.h"
|
||||||
#include "simplecolumn.h"
|
#include "simplecolumn.h"
|
||||||
#include "querystats.h"
|
#include "querystats.h"
|
||||||
|
#include "simplefilter.h"
|
||||||
|
#include "operator.h"
|
||||||
|
#include <boost/core/demangle.hpp>
|
||||||
|
|
||||||
#include "querytele.h"
|
#include "querytele.h"
|
||||||
#include "utils/pron/pron.h"
|
#include "utils/pron/pron.h"
|
||||||
@@ -197,12 +201,140 @@ std::string endlWithIndent(const size_t ident)
|
|||||||
{
|
{
|
||||||
ostringstream output;
|
ostringstream output;
|
||||||
output << endl;
|
output << endl;
|
||||||
output << std::string(ident, ' ');
|
|
||||||
|
for (size_t i = 0; i < ident; i++)
|
||||||
|
output << " ";
|
||||||
|
|
||||||
return output.str();
|
return output.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Iterative tree printer that preserves vertical branches for multi-line nodes
|
||||||
|
static void printIndentedFilterTreeImpl(const ParseTree* root, ostringstream& output, size_t indent,
|
||||||
|
const std::vector<bool>& rootAncestors, bool rootIsLast)
|
||||||
|
{
|
||||||
|
// Stack holds frames: (node, ancestorHasNext, isLastAtLevel)
|
||||||
|
struct Frame
|
||||||
|
{
|
||||||
|
const ParseTree* node;
|
||||||
|
std::vector<bool> ancestors;
|
||||||
|
bool isLast;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<Frame> stack;
|
||||||
|
stack.push_back(Frame{root, rootAncestors, rootIsLast});
|
||||||
|
|
||||||
|
while (!stack.empty())
|
||||||
|
{
|
||||||
|
Frame frame = std::move(stack.back());
|
||||||
|
stack.pop_back();
|
||||||
|
|
||||||
|
const ParseTree* node = frame.node;
|
||||||
|
const std::vector<bool>& ancestorHasNext = frame.ancestors;
|
||||||
|
const bool isLastAtLevel = frame.isLast;
|
||||||
|
|
||||||
|
if (!node)
|
||||||
|
{
|
||||||
|
// Build prefix for a null placeholder
|
||||||
|
std::string base;
|
||||||
|
for (bool hasNext : ancestorHasNext)
|
||||||
|
base += hasNext ? "│ " : " ";
|
||||||
|
std::string nodePrefix = base + (isLastAtLevel ? "└── " : "├── ");
|
||||||
|
output << endlWithIndent(indent) << nodePrefix << "(null)";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gather children in left-to-right order
|
||||||
|
std::vector<const ParseTree*> children;
|
||||||
|
if (node->left())
|
||||||
|
children.push_back(node->left());
|
||||||
|
if (node->right())
|
||||||
|
children.push_back(node->right());
|
||||||
|
|
||||||
|
// Helper to build prefixes
|
||||||
|
auto makePrefixes = [&](bool isLast)
|
||||||
|
{
|
||||||
|
std::string base;
|
||||||
|
for (bool hasNext : ancestorHasNext)
|
||||||
|
base += hasNext ? "│ " : " ";
|
||||||
|
std::string first = base + (isLast ? "└── " : "├── ");
|
||||||
|
std::string cont = base + (isLast ? " " : "│ ");
|
||||||
|
return std::pair<std::string, std::string>(first, cont);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Build node content string
|
||||||
|
TreeNode* data = node->data();
|
||||||
|
std::string nodeContent;
|
||||||
|
if (data)
|
||||||
|
{
|
||||||
|
if (auto sf = dynamic_cast<SimpleFilter*>(data))
|
||||||
|
{
|
||||||
|
nodeContent = sf->toString(true);
|
||||||
|
}
|
||||||
|
else if (auto op = dynamic_cast<Operator*>(data))
|
||||||
|
{
|
||||||
|
nodeContent = op->toString();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
nodeContent = boost::core::demangle(typeid(*data).name()) + ": " + data->toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
nodeContent = "(null data)";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print current node content
|
||||||
|
if (ancestorHasNext.empty())
|
||||||
|
{
|
||||||
|
// Root: print without branch glyphs
|
||||||
|
std::istringstream contentStream(nodeContent);
|
||||||
|
std::string line;
|
||||||
|
while (std::getline(contentStream, line))
|
||||||
|
output << endlWithIndent(indent) << line;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto prefixes = makePrefixes(isLastAtLevel);
|
||||||
|
std::istringstream contentStream(nodeContent);
|
||||||
|
std::string line;
|
||||||
|
bool firstLine = true;
|
||||||
|
while (std::getline(contentStream, line))
|
||||||
|
{
|
||||||
|
if (firstLine)
|
||||||
|
{
|
||||||
|
output << endlWithIndent(indent) << prefixes.first << line;
|
||||||
|
firstLine = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
output << endlWithIndent(indent) << prefixes.second << line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push children onto stack in reverse order to process left child first
|
||||||
|
for (size_t i = children.size(); i-- > 0;)
|
||||||
|
{
|
||||||
|
bool childIsLast = (i == children.size() - 1);
|
||||||
|
std::vector<bool> nextAncestors = ancestorHasNext;
|
||||||
|
// For children, push whether THIS node has a next sibling; this keeps the vertical bar under this node
|
||||||
|
// if we are not the last at our level.
|
||||||
|
nextAncestors.push_back(!isLastAtLevel);
|
||||||
|
stack.push_back(Frame{children[i], std::move(nextAncestors), childIsLast});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void printIndentedFilterTree(const ParseTree* tree, ostringstream& output, size_t indent)
|
||||||
|
{
|
||||||
|
// Start with empty ancestor vector and indicate root is last at its (virtual) level
|
||||||
|
std::vector<bool> ancestors; // empty => root
|
||||||
|
printIndentedFilterTreeImpl(tree, output, indent, ancestors, true);
|
||||||
|
}
|
||||||
|
|
||||||
void CalpontSelectExecutionPlan::printSubCSEP(const size_t& ident, ostringstream& output,
|
void CalpontSelectExecutionPlan::printSubCSEP(const size_t& ident, ostringstream& output,
|
||||||
CalpontSelectExecutionPlan*& plan) const
|
CalpontSelectExecutionPlan*& plan) const
|
||||||
{
|
{
|
||||||
if (plan)
|
if (plan)
|
||||||
{
|
{
|
||||||
@@ -243,7 +375,7 @@ string CalpontSelectExecutionPlan::toString(const size_t ident) const
|
|||||||
|
|
||||||
for (unsigned int i = 0; i < retCols.size(); i++)
|
for (unsigned int i = 0; i < retCols.size(); i++)
|
||||||
{
|
{
|
||||||
output << endlWithIndent(ident+2) << *retCols[i]; // WIP replace with constant
|
output << endlWithIndent(ident + 2) << *retCols[i]; // WIP replace with constant
|
||||||
|
|
||||||
if (retCols[i]->colSource() & SELECT_SUB)
|
if (retCols[i]->colSource() & SELECT_SUB)
|
||||||
{
|
{
|
||||||
@@ -257,7 +389,7 @@ string CalpontSelectExecutionPlan::toString(const size_t ident) const
|
|||||||
|
|
||||||
// From Clause
|
// From Clause
|
||||||
CalpontSelectExecutionPlan::TableList tables = tableList();
|
CalpontSelectExecutionPlan::TableList tables = tableList();
|
||||||
output << endlWithIndent(ident) <<">>From Tables";
|
output << endlWithIndent(ident) << ">>From Tables";
|
||||||
seq = 0;
|
seq = 0;
|
||||||
|
|
||||||
for (unsigned int i = 0; i < tables.size(); i++)
|
for (unsigned int i = 0; i < tables.size(); i++)
|
||||||
@@ -265,7 +397,7 @@ string CalpontSelectExecutionPlan::toString(const size_t ident) const
|
|||||||
// derived table
|
// derived table
|
||||||
if (tables[i].schema.length() == 0 && tables[i].table.length() == 0)
|
if (tables[i].schema.length() == 0 && tables[i].table.length() == 0)
|
||||||
{
|
{
|
||||||
output << endlWithIndent(ident+2) << "derived table - " << tables[i].alias;
|
output << endlWithIndent(ident + 2) << "derived table - " << tables[i].alias;
|
||||||
CalpontSelectExecutionPlan* plan =
|
CalpontSelectExecutionPlan* plan =
|
||||||
dynamic_cast<CalpontSelectExecutionPlan*>(fDerivedTableList[seq++].get());
|
dynamic_cast<CalpontSelectExecutionPlan*>(fDerivedTableList[seq++].get());
|
||||||
|
|
||||||
@@ -273,7 +405,7 @@ string CalpontSelectExecutionPlan::toString(const size_t ident) const
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
output << endlWithIndent(ident+2) << tables[i];
|
output << endlWithIndent(ident + 2) << tables[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,8 +414,8 @@ string CalpontSelectExecutionPlan::toString(const size_t ident) const
|
|||||||
|
|
||||||
if (filters() != nullptr)
|
if (filters() != nullptr)
|
||||||
{
|
{
|
||||||
output << endlWithIndent(ident + 2);
|
output << endlWithIndent(ident + 2) << "Filter Tree:";
|
||||||
filters()->walk(ParseTree::print, output);
|
printIndentedFilterTree(filters(), output, ident + 4);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -308,8 +440,9 @@ string CalpontSelectExecutionPlan::toString(const size_t ident) const
|
|||||||
// Having
|
// Having
|
||||||
if (having() != nullptr)
|
if (having() != nullptr)
|
||||||
{
|
{
|
||||||
output << endlWithIndent(ident) << ">>Having" << endlWithIndent(ident + 2);
|
output << endlWithIndent(ident) << ">>Having";
|
||||||
having()->walk(ParseTree::print, output);
|
output << endlWithIndent(ident + 2) << "Having Tree:";
|
||||||
|
printIndentedFilterTree(having(), output, ident + 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Order by columns
|
// Order by columns
|
||||||
@@ -863,7 +996,8 @@ void CalpontSelectExecutionPlan::pron(std::string&& pron)
|
|||||||
fPron = pron;
|
fPron = pron;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This routine doesn't copy derived table list, union vector, select subqueries, subquery list, and subselects.
|
// This routine doesn't copy derived table list, union vector, select subqueries, subquery list, and
|
||||||
|
// subselects.
|
||||||
execplan::SCSEP CalpontSelectExecutionPlan::cloneWORecursiveSelects()
|
execplan::SCSEP CalpontSelectExecutionPlan::cloneWORecursiveSelects()
|
||||||
{
|
{
|
||||||
execplan::SCSEP newPlan(new CalpontSelectExecutionPlan(fLocation));
|
execplan::SCSEP newPlan(new CalpontSelectExecutionPlan(fLocation));
|
||||||
|
|||||||
@@ -292,6 +292,26 @@ const string SimpleColumn::toString() const
|
|||||||
return output.str();
|
return output.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const string SimpleColumn::toString(bool compact) const
|
||||||
|
{
|
||||||
|
if (!compact)
|
||||||
|
{
|
||||||
|
// Use the original detailed format
|
||||||
|
return toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
ostringstream output;
|
||||||
|
|
||||||
|
// Compact format for tree display - let tree printer handle indentation
|
||||||
|
output << "Column: " << data();
|
||||||
|
datatypes::Charset cs(fResultType.charsetNumber);
|
||||||
|
output << endl
|
||||||
|
<< "Info: " << schemaName() << "." << tableName() << "." << columnName()
|
||||||
|
<< " (Type: " << colDataTypeToString(fResultType.colDataType) << ", OID: " << oid() << ")";
|
||||||
|
|
||||||
|
return output.str();
|
||||||
|
}
|
||||||
|
|
||||||
string SimpleColumn::toCppCode(IncludeSet& includes) const
|
string SimpleColumn::toCppCode(IncludeSet& includes) const
|
||||||
{
|
{
|
||||||
includes.insert("simplecolumn.h");
|
includes.insert("simplecolumn.h");
|
||||||
|
|||||||
@@ -205,6 +205,7 @@ class SimpleColumn : public ReturnedColumn
|
|||||||
void unserialize(messageqcpp::ByteStream&) override;
|
void unserialize(messageqcpp::ByteStream&) override;
|
||||||
|
|
||||||
const std::string toString() const override;
|
const std::string toString() const override;
|
||||||
|
const std::string toString(bool compact) const;
|
||||||
std::string toCppCode(IncludeSet& includes) const override;
|
std::string toCppCode(IncludeSet& includes) const override;
|
||||||
/** @brief Do a deep, strict (as opposed to semantic) equivalence test
|
/** @brief Do a deep, strict (as opposed to semantic) equivalence test
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -29,16 +29,22 @@ using namespace std;
|
|||||||
|
|
||||||
#include "returnedcolumn.h"
|
#include "returnedcolumn.h"
|
||||||
#include "constantcolumn.h"
|
#include "constantcolumn.h"
|
||||||
#include "simplecolumn.h"
|
#include "simplefilter.h"
|
||||||
#include "operator.h"
|
|
||||||
#include "constantfilter.h"
|
|
||||||
#include "bytestream.h"
|
#include "bytestream.h"
|
||||||
#include "objectreader.h"
|
#include "objectreader.h"
|
||||||
#include "functioncolumn.h"
|
#include "simplecolumn.h"
|
||||||
#include "arithmeticcolumn.h"
|
|
||||||
#include "simplefilter.h"
|
|
||||||
#include "aggregatecolumn.h"
|
#include "aggregatecolumn.h"
|
||||||
|
#include "arithmeticcolumn.h"
|
||||||
|
#include "functioncolumn.h"
|
||||||
|
#include "constantcolumn.h"
|
||||||
|
#include "selectfilter.h"
|
||||||
|
#include "operator.h"
|
||||||
#include "windowfunctioncolumn.h"
|
#include "windowfunctioncolumn.h"
|
||||||
|
#include "rowgroup.h"
|
||||||
|
#include "joblisttypes.h"
|
||||||
|
using namespace messageqcpp;
|
||||||
|
using namespace std;
|
||||||
|
using namespace execplan;
|
||||||
|
|
||||||
namespace execplan
|
namespace execplan
|
||||||
{
|
{
|
||||||
@@ -250,6 +256,53 @@ const string SimpleFilter::toString() const
|
|||||||
return output.str();
|
return output.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const string SimpleFilter::toString(bool compact) const
|
||||||
|
{
|
||||||
|
if (!compact)
|
||||||
|
{
|
||||||
|
// Use the original detailed format
|
||||||
|
return toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
ostringstream output;
|
||||||
|
|
||||||
|
// Compact format for tree display
|
||||||
|
output << "SimpleFilter(indexflag=" << fIndexFlag;
|
||||||
|
output << " joinFlag= " << fJoinFlag;
|
||||||
|
output << " card= " << fCardinality << ")";
|
||||||
|
|
||||||
|
// Handle different types of left operands with compact formatting
|
||||||
|
SimpleColumn* sc = dynamic_cast<SimpleColumn*>(fLhs);
|
||||||
|
AggregateColumn* ac = dynamic_cast<AggregateColumn*>(fLhs);
|
||||||
|
|
||||||
|
if (ac)
|
||||||
|
{
|
||||||
|
string acCompact = ac->toString(true);
|
||||||
|
if (!acCompact.empty())
|
||||||
|
{
|
||||||
|
output << endl << acCompact;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (sc)
|
||||||
|
{
|
||||||
|
string scCompact = sc->toString(true);
|
||||||
|
if (!scCompact.empty())
|
||||||
|
{
|
||||||
|
output << endl << scCompact;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// For other operand types, show them compactly
|
||||||
|
output << endl << "LHS: " << fLhs->data();
|
||||||
|
}
|
||||||
|
|
||||||
|
output << endl << "Op: " << fOp->data();
|
||||||
|
output << endl << "RHS: " << fRhs->data();
|
||||||
|
|
||||||
|
return output.str();
|
||||||
|
}
|
||||||
|
|
||||||
string SimpleFilter::toCppCode(IncludeSet& includes) const
|
string SimpleFilter::toCppCode(IncludeSet& includes) const
|
||||||
{
|
{
|
||||||
includes.insert("simplefilter.h");
|
includes.insert("simplefilter.h");
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ class SimpleFilter : public Filter
|
|||||||
void rhs(ReturnedColumn* rhs);
|
void rhs(ReturnedColumn* rhs);
|
||||||
|
|
||||||
const std::string toString() const override;
|
const std::string toString() const override;
|
||||||
|
const std::string toString(bool compact) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The serialization interface
|
* The serialization interface
|
||||||
|
|||||||
Reference in New Issue
Block a user