From 98a301c1c55e8043ba32978adecd3d8e10d7447d Mon Sep 17 00:00:00 2001 From: Leonid Fedorov <79837786+mariadb-LeonidFedorov@users.noreply.github.com> Date: Tue, 19 Aug 2025 16:18:06 +0400 Subject: [PATCH] chore(execplan): MCOL: 6144 prerry print filters and havings as a tree (#3712) * MCOL-6144: print Filters and Having indented for better CSEP visualization --- dbcon/execplan/aggregatecolumn.cpp | 54 +++++- dbcon/execplan/aggregatecolumn.h | 1 + dbcon/execplan/calpontselectexecutionplan.cpp | 178 +++++++++++++++--- dbcon/execplan/simplecolumn.cpp | 20 ++ dbcon/execplan/simplecolumn.h | 1 + dbcon/execplan/simplefilter.cpp | 65 ++++++- dbcon/execplan/simplefilter.h | 1 + 7 files changed, 283 insertions(+), 37 deletions(-) diff --git a/dbcon/execplan/aggregatecolumn.cpp b/dbcon/execplan/aggregatecolumn.cpp index 8dd85727c..e6915b728 100644 --- a/dbcon/execplan/aggregatecolumn.cpp +++ b/dbcon/execplan/aggregatecolumn.cpp @@ -152,13 +152,49 @@ const string AggregateColumn::toString() const 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(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 { includes.insert("aggregatecolumn.h"); stringstream ss; 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(); } @@ -588,15 +624,15 @@ void AggregateColumn::evaluate(Row& row, bool& isNull) case CalpontSystemCatalog::VARBINARY: case CalpontSystemCatalog::BLOB: - { - auto const str = row.getConstString(fInputIndex); - fResult.strVal.dropString(); - if (!str.isNull()) - fResult.strVal.assign((const uint8_t*)str.str(), str.length()); + { + auto const str = row.getConstString(fInputIndex); + fResult.strVal.dropString(); + if (!str.isNull()) + fResult.strVal.assign((const uint8_t*)str.str(), str.length()); - isNull = isNull || fResult.strVal.isNull(); - } - break; + isNull = isNull || fResult.strVal.isNull(); + } + break; default: // treat as int64 if (row.equals<8>(BIGINTNULL, fInputIndex)) diff --git a/dbcon/execplan/aggregatecolumn.h b/dbcon/execplan/aggregatecolumn.h index a0607e814..a17fd9903 100644 --- a/dbcon/execplan/aggregatecolumn.h +++ b/dbcon/execplan/aggregatecolumn.h @@ -220,6 +220,7 @@ class AggregateColumn : public ReturnedColumn * Overloaded stream operator */ const std::string toString() const override; + const std::string toString(bool compact) const; std::string toCppCode(IncludeSet& includes) const override; /** diff --git a/dbcon/execplan/calpontselectexecutionplan.cpp b/dbcon/execplan/calpontselectexecutionplan.cpp index fd48ebb3c..a45436baf 100644 --- a/dbcon/execplan/calpontselectexecutionplan.cpp +++ b/dbcon/execplan/calpontselectexecutionplan.cpp @@ -22,6 +22,7 @@ ***********************************************************************/ #include #include +#include using namespace std; #include @@ -35,6 +36,9 @@ using namespace messageqcpp; #include "returnedcolumn.h" #include "simplecolumn.h" #include "querystats.h" +#include "simplefilter.h" +#include "operator.h" +#include #include "querytele.h" #include "utils/pron/pron.h" @@ -197,12 +201,140 @@ std::string endlWithIndent(const size_t ident) { ostringstream output; output << endl; - output << std::string(ident, ' '); + + for (size_t i = 0; i < ident; i++) + output << " "; + 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& rootAncestors, bool rootIsLast) +{ + // Stack holds frames: (node, ancestorHasNext, isLastAtLevel) + struct Frame + { + const ParseTree* node; + std::vector ancestors; + bool isLast; + }; + + std::vector 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& 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 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(first, cont); + }; + + // Build node content string + TreeNode* data = node->data(); + std::string nodeContent; + if (data) + { + if (auto sf = dynamic_cast(data)) + { + nodeContent = sf->toString(true); + } + else if (auto op = dynamic_cast(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 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 ancestors; // empty => root + printIndentedFilterTreeImpl(tree, output, indent, ancestors, true); +} + void CalpontSelectExecutionPlan::printSubCSEP(const size_t& ident, ostringstream& output, - CalpontSelectExecutionPlan*& plan) const + CalpontSelectExecutionPlan*& plan) const { if (plan) { @@ -243,7 +375,7 @@ string CalpontSelectExecutionPlan::toString(const size_t ident) const 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) { @@ -257,7 +389,7 @@ string CalpontSelectExecutionPlan::toString(const size_t ident) const // From Clause CalpontSelectExecutionPlan::TableList tables = tableList(); - output << endlWithIndent(ident) <<">>From Tables"; + output << endlWithIndent(ident) << ">>From Tables"; seq = 0; for (unsigned int i = 0; i < tables.size(); i++) @@ -265,7 +397,7 @@ string CalpontSelectExecutionPlan::toString(const size_t ident) const // derived table 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 = dynamic_cast(fDerivedTableList[seq++].get()); @@ -273,7 +405,7 @@ string CalpontSelectExecutionPlan::toString(const size_t ident) const } 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) { - output << endlWithIndent(ident + 2); - filters()->walk(ParseTree::print, output); + output << endlWithIndent(ident + 2) << "Filter Tree:"; + printIndentedFilterTree(filters(), output, ident + 4); } else { @@ -308,8 +440,9 @@ string CalpontSelectExecutionPlan::toString(const size_t ident) const // Having if (having() != nullptr) { - output << endlWithIndent(ident) << ">>Having" << endlWithIndent(ident + 2); - having()->walk(ParseTree::print, output); + output << endlWithIndent(ident) << ">>Having"; + output << endlWithIndent(ident + 2) << "Having Tree:"; + printIndentedFilterTree(having(), output, ident + 4); } // Order by columns @@ -863,11 +996,12 @@ void CalpontSelectExecutionPlan::pron(std::string&& 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 newPlan(new CalpontSelectExecutionPlan(fLocation)); - + // Copy simple members newPlan->fLocalQuery = fLocalQuery; newPlan->fTableAlias = fTableAlias; @@ -908,7 +1042,7 @@ execplan::SCSEP CalpontSelectExecutionPlan::cloneWORecursiveSelects() newPlan->fTimeZone = fTimeZone; newPlan->fPron = fPron; newPlan->fWithRollup = fWithRollup; - + // Deep copy of ReturnedColumnList ReturnedColumnList newReturnedCols; for (const auto& col : fReturnedCols) @@ -917,15 +1051,15 @@ execplan::SCSEP CalpontSelectExecutionPlan::cloneWORecursiveSelects() newReturnedCols.push_back(SRCP(col->clone())); } newPlan->returnedCols(newReturnedCols); - + // Deep copy of filters if (fFilters) newPlan->filters(new ParseTree(*fFilters)); - + // Deep copy of filter token list newPlan->filterTokenList(fFilterTokenList); newPlan->havingTokenList(fHavingTokenList); - + // Deep copy of group by columns GroupByColumnList newGroupByCols; for (const auto& col : fGroupByCols) @@ -934,11 +1068,11 @@ execplan::SCSEP CalpontSelectExecutionPlan::cloneWORecursiveSelects() newGroupByCols.push_back(SRCP(col->clone())); } newPlan->groupByCols(newGroupByCols); - + // Deep copy of having clause if (fHaving) newPlan->having(new ParseTree(*fHaving)); - + // Deep copy of order by columns OrderByColumnList newOrderByCols; for (const auto& col : fOrderByCols) @@ -947,7 +1081,7 @@ execplan::SCSEP CalpontSelectExecutionPlan::cloneWORecursiveSelects() newOrderByCols.push_back(SRCP(col->clone())); } newPlan->orderByCols(newOrderByCols); - + // Deep copy of column map ColumnMap newColumnMap; for (const auto& entry : fColumnMap) @@ -956,13 +1090,13 @@ execplan::SCSEP CalpontSelectExecutionPlan::cloneWORecursiveSelects() newColumnMap.insert(ColumnMap::value_type(entry.first, SRCP(entry.second->clone()))); } newPlan->columnMap(newColumnMap); - + // Copy RM parameters newPlan->rmParms(frmParms); - + // Deep copy of table list newPlan->tableList(fTableList); - + return newPlan; } diff --git a/dbcon/execplan/simplecolumn.cpp b/dbcon/execplan/simplecolumn.cpp index a68fd6d02..9f0b25d38 100644 --- a/dbcon/execplan/simplecolumn.cpp +++ b/dbcon/execplan/simplecolumn.cpp @@ -292,6 +292,26 @@ const string SimpleColumn::toString() const 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 { includes.insert("simplecolumn.h"); diff --git a/dbcon/execplan/simplecolumn.h b/dbcon/execplan/simplecolumn.h index 863875013..3e2ff25f0 100644 --- a/dbcon/execplan/simplecolumn.h +++ b/dbcon/execplan/simplecolumn.h @@ -205,6 +205,7 @@ class SimpleColumn : public ReturnedColumn void unserialize(messageqcpp::ByteStream&) override; const std::string toString() const override; + const std::string toString(bool compact) const; std::string toCppCode(IncludeSet& includes) const override; /** @brief Do a deep, strict (as opposed to semantic) equivalence test * diff --git a/dbcon/execplan/simplefilter.cpp b/dbcon/execplan/simplefilter.cpp index 83a73e227..01c6c9248 100644 --- a/dbcon/execplan/simplefilter.cpp +++ b/dbcon/execplan/simplefilter.cpp @@ -29,16 +29,22 @@ using namespace std; #include "returnedcolumn.h" #include "constantcolumn.h" -#include "simplecolumn.h" -#include "operator.h" -#include "constantfilter.h" +#include "simplefilter.h" #include "bytestream.h" #include "objectreader.h" -#include "functioncolumn.h" -#include "arithmeticcolumn.h" -#include "simplefilter.h" +#include "simplecolumn.h" #include "aggregatecolumn.h" +#include "arithmeticcolumn.h" +#include "functioncolumn.h" +#include "constantcolumn.h" +#include "selectfilter.h" +#include "operator.h" #include "windowfunctioncolumn.h" +#include "rowgroup.h" +#include "joblisttypes.h" +using namespace messageqcpp; +using namespace std; +using namespace execplan; namespace execplan { @@ -250,6 +256,53 @@ const string SimpleFilter::toString() const 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(fLhs); + AggregateColumn* ac = dynamic_cast(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 { includes.insert("simplefilter.h"); diff --git a/dbcon/execplan/simplefilter.h b/dbcon/execplan/simplefilter.h index 42123374b..5267e4a76 100644 --- a/dbcon/execplan/simplefilter.h +++ b/dbcon/execplan/simplefilter.h @@ -132,6 +132,7 @@ class SimpleFilter : public Filter void rhs(ReturnedColumn* rhs); const std::string toString() const override; + const std::string toString(bool compact) const; /** * The serialization interface