diff --git a/src/Core/SortDescription.cpp b/src/Core/SortDescription.cpp index cb7378cf096..314b6624623 100644 --- a/src/Core/SortDescription.cpp +++ b/src/Core/SortDescription.cpp @@ -1,6 +1,7 @@ #include #include #include +#include namespace DB { @@ -37,6 +38,22 @@ void dumpSortDescription(const SortDescription & description, const Block & head } } +void SortColumnDescription::explain(JSONBuilder::JSONMap & map, const Block & header) const +{ + if (!column_name.empty()) + map.add("Column", column_name); + else + { + if (column_number < header.columns()) + map.add("Column", header.getByPosition(column_number).name); + + map.add("Position", column_number); + } + + map.add("Ascending", direction > 0); + map.add("With Fill", with_fill); +} + std::string dumpSortDescription(const SortDescription & description) { WriteBufferFromOwnString wb; @@ -44,5 +61,17 @@ std::string dumpSortDescription(const SortDescription & description) return wb.str(); } +JSONBuilder::ItemPtr explainSortDescription(const SortDescription & description, const Block & header) +{ + auto json_array = std::make_unique(); + for (const auto & descr : description) + { + auto json_map = std::make_unique(); + descr.explain(*json_map, header); + json_array->add(std::move(json_map)); + } + + return json_array; } +} diff --git a/src/Core/SortDescription.h b/src/Core/SortDescription.h index 1450393ebd8..41b4e5b6b32 100644 --- a/src/Core/SortDescription.h +++ b/src/Core/SortDescription.h @@ -12,6 +12,15 @@ class Collator; namespace DB { +namespace JSONBuilder +{ + class JSONMap; + class IItem; + using ItemPtr = std::unique_ptr; +} + +class Block; + struct FillColumnDescription { /// All missed values in range [FROM, TO) will be filled @@ -62,16 +71,18 @@ struct SortColumnDescription { return fmt::format("{}:{}:dir {}nulls ", column_name, column_number, direction, nulls_direction); } + + void explain(JSONBuilder::JSONMap & map, const Block & header) const; }; /// Description of the sorting rule for several columns. using SortDescription = std::vector; -class Block; - /// Outputs user-readable description into `out`. void dumpSortDescription(const SortDescription & description, const Block & header, WriteBuffer & out); std::string dumpSortDescription(const SortDescription & description); +JSONBuilder::ItemPtr explainSortDescription(const SortDescription & description, const Block & header); + } diff --git a/src/Interpreters/InterpreterExplainQuery.cpp b/src/Interpreters/InterpreterExplainQuery.cpp index 8dd33a0d139..0860e3eba69 100644 --- a/src/Interpreters/InterpreterExplainQuery.cpp +++ b/src/Interpreters/InterpreterExplainQuery.cpp @@ -228,6 +228,7 @@ BlockInputStreamPtr InterpreterExplainQuery::executeImpl() MutableColumns res_columns = sample_block.cloneEmptyColumns(); WriteBufferFromOwnString buf; + bool single_line = false; if (ast.getKind() == ASTExplainQuery::ParsedAST) { @@ -275,6 +276,8 @@ BlockInputStreamPtr InterpreterExplainQuery::executeImpl() JSONBuilder::FormatContext format_context{.out = buf}; plan_array->format(json_format_settings, format_context); + + single_line = true; } else plan.explainPlan(buf, settings.query_plan_options); @@ -310,7 +313,10 @@ BlockInputStreamPtr InterpreterExplainQuery::executeImpl() } } - fillColumn(*res_columns[0], buf.str()); + if (single_line) + res_columns[0]->insertData(buf.str().data(), buf.str().size()); + else + fillColumn(*res_columns[0], buf.str()); return std::make_shared(sample_block.cloneWithColumns(std::move(res_columns))); } diff --git a/src/Interpreters/addMissingDefaults.cpp b/src/Interpreters/addMissingDefaults.cpp index bb444103d8e..fbf4db2f603 100644 --- a/src/Interpreters/addMissingDefaults.cpp +++ b/src/Interpreters/addMissingDefaults.cpp @@ -21,6 +21,9 @@ ActionsDAGPtr addMissingDefaults( const ColumnsDescription & columns, const Context & context) { + + std::cerr << "====== addMissingDefaults " << header.dumpStructure() << std::endl; + auto actions = std::make_shared(header.getColumnsWithTypeAndName()); auto & index = actions->getIndex(); diff --git a/src/Processors/QueryPlan/CreatingSetsStep.cpp b/src/Processors/QueryPlan/CreatingSetsStep.cpp index ec710d493ed..0e04a1ab11a 100644 --- a/src/Processors/QueryPlan/CreatingSetsStep.cpp +++ b/src/Processors/QueryPlan/CreatingSetsStep.cpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace DB { @@ -60,6 +61,15 @@ void CreatingSetStep::describeActions(FormatSettings & settings) const settings.out << description << '\n'; } +void CreatingSetStep::describeActions(JSONBuilder::JSONMap & map) const +{ + if (subquery_for_set.set) + map.add("Set", description); + else if (subquery_for_set.join) + map.add("Join", description); +} + + CreatingSetsStep::CreatingSetsStep(DataStreams input_streams_) { if (input_streams_.empty()) diff --git a/src/Processors/QueryPlan/CreatingSetsStep.h b/src/Processors/QueryPlan/CreatingSetsStep.h index 14006a1a384..fca24535203 100644 --- a/src/Processors/QueryPlan/CreatingSetsStep.h +++ b/src/Processors/QueryPlan/CreatingSetsStep.h @@ -21,6 +21,7 @@ public: void transformPipeline(QueryPipeline & pipeline, const BuildQueryPipelineSettings &) override; + void describeActions(JSONBuilder::JSONMap & map) const override; void describeActions(FormatSettings & settings) const override; private: diff --git a/src/Processors/QueryPlan/DistinctStep.cpp b/src/Processors/QueryPlan/DistinctStep.cpp index ecd7918118b..5edd2f52f47 100644 --- a/src/Processors/QueryPlan/DistinctStep.cpp +++ b/src/Processors/QueryPlan/DistinctStep.cpp @@ -2,6 +2,7 @@ #include #include #include +#include namespace DB { @@ -102,4 +103,13 @@ void DistinctStep::describeActions(FormatSettings & settings) const settings.out << '\n'; } +void DistinctStep::describeActions(JSONBuilder::JSONMap & map) const +{ + auto columns_array = std::make_unique(); + for (const auto & column : columns) + columns_array->add(column); + + map.add("Columns", std::move(columns_array)); +} + } diff --git a/src/Processors/QueryPlan/DistinctStep.h b/src/Processors/QueryPlan/DistinctStep.h index f4a5647fbc7..815601d6253 100644 --- a/src/Processors/QueryPlan/DistinctStep.h +++ b/src/Processors/QueryPlan/DistinctStep.h @@ -20,6 +20,7 @@ public: void transformPipeline(QueryPipeline & pipeline, const BuildQueryPipelineSettings &) override; + void describeActions(JSONBuilder::JSONMap & map) const override; void describeActions(FormatSettings & settings) const override; private: diff --git a/src/Processors/QueryPlan/FillingStep.cpp b/src/Processors/QueryPlan/FillingStep.cpp index 1bdd4fc468c..a4306ffed2b 100644 --- a/src/Processors/QueryPlan/FillingStep.cpp +++ b/src/Processors/QueryPlan/FillingStep.cpp @@ -2,6 +2,7 @@ #include #include #include +#include namespace DB { @@ -50,4 +51,9 @@ void FillingStep::describeActions(FormatSettings & settings) const settings.out << '\n'; } +void FillingStep::describeActions(JSONBuilder::JSONMap & map) const +{ + map.add("Sort Description", explainSortDescription(sort_description, input_streams.front().header)); +} + } diff --git a/src/Processors/QueryPlan/FillingStep.h b/src/Processors/QueryPlan/FillingStep.h index 20eb132b7a6..f4c6782e9df 100644 --- a/src/Processors/QueryPlan/FillingStep.h +++ b/src/Processors/QueryPlan/FillingStep.h @@ -15,6 +15,7 @@ public: void transformPipeline(QueryPipeline & pipeline, const BuildQueryPipelineSettings &) override; + void describeActions(JSONBuilder::JSONMap & map) const override; void describeActions(FormatSettings & settings) const override; const SortDescription & getSortDescription() const { return sort_description; } diff --git a/src/Processors/QueryPlan/FilterStep.cpp b/src/Processors/QueryPlan/FilterStep.cpp index 6930d9563f9..522e7dabba8 100644 --- a/src/Processors/QueryPlan/FilterStep.cpp +++ b/src/Processors/QueryPlan/FilterStep.cpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace DB { @@ -113,4 +114,13 @@ void FilterStep::describeActions(FormatSettings & settings) const settings.out << '\n'; } +void FilterStep::describeActions(JSONBuilder::JSONMap & map) const +{ + map.add("Filter Column", filter_column_name); + map.add("Removes Filter", remove_filter_column); + + auto expression = std::make_shared(actions_dag, ExpressionActionsSettings{}); + map.add("Expression", expression->toTree()); +} + } diff --git a/src/Processors/QueryPlan/FilterStep.h b/src/Processors/QueryPlan/FilterStep.h index 0cd48691261..d01d128a08c 100644 --- a/src/Processors/QueryPlan/FilterStep.h +++ b/src/Processors/QueryPlan/FilterStep.h @@ -22,6 +22,7 @@ public: void updateInputStream(DataStream input_stream, bool keep_header); + void describeActions(JSONBuilder::JSONMap & map) const override; void describeActions(FormatSettings & settings) const override; const ActionsDAGPtr & getExpression() const { return actions_dag; } diff --git a/src/Processors/QueryPlan/FinishSortingStep.cpp b/src/Processors/QueryPlan/FinishSortingStep.cpp index d30e0bf9145..f8346281d10 100644 --- a/src/Processors/QueryPlan/FinishSortingStep.cpp +++ b/src/Processors/QueryPlan/FinishSortingStep.cpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace DB { @@ -101,4 +102,13 @@ void FinishSortingStep::describeActions(FormatSettings & settings) const settings.out << prefix << "Limit " << limit << '\n'; } +void FinishSortingStep::describeActions(JSONBuilder::JSONMap & map) const +{ + map.add("Prefix Sort Description", explainSortDescription(prefix_description, input_streams.front().header)); + map.add("Result Sort Description", explainSortDescription(result_description, input_streams.front().header)); + + if (limit) + map.add("Limit", limit); +} + } diff --git a/src/Processors/QueryPlan/FinishSortingStep.h b/src/Processors/QueryPlan/FinishSortingStep.h index c89f8fee40d..9fe031e792d 100644 --- a/src/Processors/QueryPlan/FinishSortingStep.h +++ b/src/Processors/QueryPlan/FinishSortingStep.h @@ -20,6 +20,7 @@ public: void transformPipeline(QueryPipeline & pipeline, const BuildQueryPipelineSettings &) override; + void describeActions(JSONBuilder::JSONMap & map) const override; void describeActions(FormatSettings & settings) const override; /// Add limit or change it to lower value. diff --git a/src/Processors/QueryPlan/LimitByStep.cpp b/src/Processors/QueryPlan/LimitByStep.cpp index 78f96d75949..8ded0784b41 100644 --- a/src/Processors/QueryPlan/LimitByStep.cpp +++ b/src/Processors/QueryPlan/LimitByStep.cpp @@ -2,6 +2,7 @@ #include #include #include +#include namespace DB { @@ -72,4 +73,15 @@ void LimitByStep::describeActions(FormatSettings & settings) const settings.out << prefix << "Offset " << group_offset << '\n'; } +void LimitByStep::describeActions(JSONBuilder::JSONMap & map) const +{ + auto columns_array = std::make_unique(); + for (const auto & column : columns) + columns_array->add(column); + + map.add("Columns", std::move(columns_array)); + map.add("Length", group_length); + map.add("Offset", group_offset); +} + } diff --git a/src/Processors/QueryPlan/LimitByStep.h b/src/Processors/QueryPlan/LimitByStep.h index 7b978286e48..1b574cd02a1 100644 --- a/src/Processors/QueryPlan/LimitByStep.h +++ b/src/Processors/QueryPlan/LimitByStep.h @@ -16,6 +16,7 @@ public: void transformPipeline(QueryPipeline & pipeline, const BuildQueryPipelineSettings &) override; + void describeActions(JSONBuilder::JSONMap & map) const override; void describeActions(FormatSettings & settings) const override; private: diff --git a/src/Processors/QueryPlan/LimitStep.cpp b/src/Processors/QueryPlan/LimitStep.cpp index 4c6b4523a54..5f5a0bd0d64 100644 --- a/src/Processors/QueryPlan/LimitStep.cpp +++ b/src/Processors/QueryPlan/LimitStep.cpp @@ -2,6 +2,7 @@ #include #include #include +#include namespace DB { @@ -76,4 +77,12 @@ void LimitStep::describeActions(FormatSettings & settings) const } } +void LimitStep::describeActions(JSONBuilder::JSONMap & map) const +{ + map.add("Limit", limit); + map.add("Offset", offset); + map.add("With Ties", with_ties); + map.add("Reads All Data", always_read_till_end); +} + } diff --git a/src/Processors/QueryPlan/LimitStep.h b/src/Processors/QueryPlan/LimitStep.h index 4428acb9ef0..772ba0722a7 100644 --- a/src/Processors/QueryPlan/LimitStep.h +++ b/src/Processors/QueryPlan/LimitStep.h @@ -20,6 +20,7 @@ public: void transformPipeline(QueryPipeline & pipeline, const BuildQueryPipelineSettings &) override; + void describeActions(JSONBuilder::JSONMap & map) const override; void describeActions(FormatSettings & settings) const override; size_t getLimitForSorting() const diff --git a/src/Processors/QueryPlan/MergeSortingStep.cpp b/src/Processors/QueryPlan/MergeSortingStep.cpp index 0c5c5cec4e9..f0ef514ee9e 100644 --- a/src/Processors/QueryPlan/MergeSortingStep.cpp +++ b/src/Processors/QueryPlan/MergeSortingStep.cpp @@ -2,6 +2,7 @@ #include #include #include +#include namespace DB { @@ -84,4 +85,12 @@ void MergeSortingStep::describeActions(FormatSettings & settings) const settings.out << prefix << "Limit " << limit << '\n'; } +void MergeSortingStep::describeActions(JSONBuilder::JSONMap & map) const +{ + map.add("Sort Description", explainSortDescription(description, input_streams.front().header)); + + if (limit) + map.add("Limit", limit); +} + } diff --git a/src/Processors/QueryPlan/MergeSortingStep.h b/src/Processors/QueryPlan/MergeSortingStep.h index 9802d1a760e..dcecdffd122 100644 --- a/src/Processors/QueryPlan/MergeSortingStep.h +++ b/src/Processors/QueryPlan/MergeSortingStep.h @@ -26,6 +26,7 @@ public: void transformPipeline(QueryPipeline & pipeline, const BuildQueryPipelineSettings &) override; + void describeActions(JSONBuilder::JSONMap & map) const override; void describeActions(FormatSettings & settings) const override; /// Add limit or change it to lower value. diff --git a/src/Processors/QueryPlan/MergingAggregatedStep.cpp b/src/Processors/QueryPlan/MergingAggregatedStep.cpp index 56e91dcbd71..71efb37b363 100644 --- a/src/Processors/QueryPlan/MergingAggregatedStep.cpp +++ b/src/Processors/QueryPlan/MergingAggregatedStep.cpp @@ -68,4 +68,9 @@ void MergingAggregatedStep::describeActions(FormatSettings & settings) const return params->params.explain(settings.out, settings.offset); } +void MergingAggregatedStep::describeActions(JSONBuilder::JSONMap & map) const +{ + params->params.explain(map); +} + } diff --git a/src/Processors/QueryPlan/MergingAggregatedStep.h b/src/Processors/QueryPlan/MergingAggregatedStep.h index 2742a040708..2e94d536a8c 100644 --- a/src/Processors/QueryPlan/MergingAggregatedStep.h +++ b/src/Processors/QueryPlan/MergingAggregatedStep.h @@ -23,6 +23,7 @@ public: void transformPipeline(QueryPipeline & pipeline, const BuildQueryPipelineSettings &) override; + void describeActions(JSONBuilder::JSONMap & map) const override; void describeActions(FormatSettings & settings) const override; private: diff --git a/src/Processors/QueryPlan/MergingFinal.cpp b/src/Processors/QueryPlan/MergingFinal.cpp index 4109c5e7274..c564a28d377 100644 --- a/src/Processors/QueryPlan/MergingFinal.cpp +++ b/src/Processors/QueryPlan/MergingFinal.cpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace DB { @@ -161,4 +162,9 @@ void MergingFinal::describeActions(FormatSettings & settings) const settings.out << '\n'; } +void MergingFinal::describeActions(JSONBuilder::JSONMap & map) const +{ + map.add("Sort Description", explainSortDescription(sort_description, input_streams.front().header)); +} + } diff --git a/src/Processors/QueryPlan/MergingFinal.h b/src/Processors/QueryPlan/MergingFinal.h index 5b621e9f4b2..ed0394a62f4 100644 --- a/src/Processors/QueryPlan/MergingFinal.h +++ b/src/Processors/QueryPlan/MergingFinal.h @@ -22,6 +22,7 @@ public: void transformPipeline(QueryPipeline & pipeline, const BuildQueryPipelineSettings &) override; + void describeActions(JSONBuilder::JSONMap & map) const override; void describeActions(FormatSettings & settings) const override; private: diff --git a/src/Processors/QueryPlan/MergingSortedStep.cpp b/src/Processors/QueryPlan/MergingSortedStep.cpp index 2c3006dc637..e0c6004c96b 100644 --- a/src/Processors/QueryPlan/MergingSortedStep.cpp +++ b/src/Processors/QueryPlan/MergingSortedStep.cpp @@ -2,6 +2,7 @@ #include #include #include +#include namespace DB { @@ -73,4 +74,12 @@ void MergingSortedStep::describeActions(FormatSettings & settings) const settings.out << prefix << "Limit " << limit << '\n'; } +void MergingSortedStep::describeActions(JSONBuilder::JSONMap & map) const +{ + map.add("Sort Description", explainSortDescription(sort_description, input_streams.front().header)); + + if (limit) + map.add("Limit", limit); +} + } diff --git a/src/Processors/QueryPlan/MergingSortedStep.h b/src/Processors/QueryPlan/MergingSortedStep.h index ac5a7f666c8..4f82e3830d0 100644 --- a/src/Processors/QueryPlan/MergingSortedStep.h +++ b/src/Processors/QueryPlan/MergingSortedStep.h @@ -21,6 +21,7 @@ public: void transformPipeline(QueryPipeline & pipeline, const BuildQueryPipelineSettings &) override; + void describeActions(JSONBuilder::JSONMap & map) const override; void describeActions(FormatSettings & settings) const override; /// Add limit or change it to lower value. diff --git a/src/Processors/QueryPlan/OffsetStep.cpp b/src/Processors/QueryPlan/OffsetStep.cpp index b455a32d5af..34ddb687ddd 100644 --- a/src/Processors/QueryPlan/OffsetStep.cpp +++ b/src/Processors/QueryPlan/OffsetStep.cpp @@ -2,6 +2,7 @@ #include #include #include +#include namespace DB { @@ -41,4 +42,9 @@ void OffsetStep::describeActions(FormatSettings & settings) const settings.out << String(settings.offset, ' ') << "Offset " << offset << '\n'; } +void OffsetStep::describeActions(JSONBuilder::JSONMap & map) const +{ + map.add("Offset", offset); +} + } diff --git a/src/Processors/QueryPlan/OffsetStep.h b/src/Processors/QueryPlan/OffsetStep.h index c278744ff50..a10fcc7baec 100644 --- a/src/Processors/QueryPlan/OffsetStep.h +++ b/src/Processors/QueryPlan/OffsetStep.h @@ -15,6 +15,7 @@ public: void transformPipeline(QueryPipeline & pipeline, const BuildQueryPipelineSettings &) override; + void describeActions(JSONBuilder::JSONMap & map) const override; void describeActions(FormatSettings & settings) const override; private: diff --git a/src/Processors/QueryPlan/PartialSortingStep.cpp b/src/Processors/QueryPlan/PartialSortingStep.cpp index bb8b2c8a26b..5746a692ae9 100644 --- a/src/Processors/QueryPlan/PartialSortingStep.cpp +++ b/src/Processors/QueryPlan/PartialSortingStep.cpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace DB { @@ -81,4 +82,12 @@ void PartialSortingStep::describeActions(FormatSettings & settings) const settings.out << prefix << "Limit " << limit << '\n'; } +void PartialSortingStep::describeActions(JSONBuilder::JSONMap & map) const +{ + map.add("Sort Description", explainSortDescription(sort_description, input_streams.front().header)); + + if (limit) + map.add("Limit", limit); +} + } diff --git a/src/Processors/QueryPlan/PartialSortingStep.h b/src/Processors/QueryPlan/PartialSortingStep.h index e93e9e8ef93..aeca42f7096 100644 --- a/src/Processors/QueryPlan/PartialSortingStep.h +++ b/src/Processors/QueryPlan/PartialSortingStep.h @@ -20,6 +20,7 @@ public: void transformPipeline(QueryPipeline & pipeline, const BuildQueryPipelineSettings &) override; + void describeActions(JSONBuilder::JSONMap & map) const override; void describeActions(FormatSettings & settings) const override; /// Add limit or change it to lower value. diff --git a/src/Processors/QueryPlan/TotalsHavingStep.cpp b/src/Processors/QueryPlan/TotalsHavingStep.cpp index 7a60f0a6f36..4966c04dee7 100644 --- a/src/Processors/QueryPlan/TotalsHavingStep.cpp +++ b/src/Processors/QueryPlan/TotalsHavingStep.cpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace DB { @@ -95,4 +96,15 @@ void TotalsHavingStep::describeActions(FormatSettings & settings) const } } +void TotalsHavingStep::describeActions(JSONBuilder::JSONMap & map) const +{ + map.add("Mode", totalsModeToString(totals_mode, auto_include_threshold)); + if (actions_dag) + { + map.add("Filter column", filter_column_name); + auto expression = std::make_shared(actions_dag, ExpressionActionsSettings{}); + map.add("Expression", expression->toTree()); + } +} + } diff --git a/src/Processors/QueryPlan/TotalsHavingStep.h b/src/Processors/QueryPlan/TotalsHavingStep.h index 4041960de28..bc053c96970 100644 --- a/src/Processors/QueryPlan/TotalsHavingStep.h +++ b/src/Processors/QueryPlan/TotalsHavingStep.h @@ -26,6 +26,7 @@ public: void transformPipeline(QueryPipeline & pipeline, const BuildQueryPipelineSettings & settings) override; + void describeActions(JSONBuilder::JSONMap & map) const override; void describeActions(FormatSettings & settings) const override; const ActionsDAGPtr & getActions() const { return actions_dag; } diff --git a/src/Processors/QueryPlan/WindowStep.cpp b/src/Processors/QueryPlan/WindowStep.cpp index 66c329acb4b..29f2999ec83 100644 --- a/src/Processors/QueryPlan/WindowStep.cpp +++ b/src/Processors/QueryPlan/WindowStep.cpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace DB { @@ -116,4 +117,25 @@ void WindowStep::describeActions(FormatSettings & settings) const } } +void WindowStep::describeActions(JSONBuilder::JSONMap & map) const +{ + if (!window_description.partition_by.empty()) + { + auto partion_columns_array = std::make_unique(); + for (const auto & descr : window_description.partition_by) + partion_columns_array->add(descr.column_name); + + map.add("Partition By", std::move(partion_columns_array)); + } + + if (!window_description.order_by.empty()) + map.add("Sort Description", explainSortDescription(window_description.order_by, {})); + + auto functions_array = std::make_unique(); + for (const auto & func : window_functions) + functions_array->add(func.column_name); + + map.add("Functions", std::move(functions_array)); +} + } diff --git a/src/Processors/QueryPlan/WindowStep.h b/src/Processors/QueryPlan/WindowStep.h index 9e3c18c0b16..b5018b1d5a7 100644 --- a/src/Processors/QueryPlan/WindowStep.h +++ b/src/Processors/QueryPlan/WindowStep.h @@ -22,6 +22,7 @@ public: void transformPipeline(QueryPipeline & pipeline, const BuildQueryPipelineSettings &) override; + void describeActions(JSONBuilder::JSONMap & map) const override; void describeActions(FormatSettings & settings) const override; private: