diff --git a/src/Interpreters/InterpreterExplainQuery.cpp b/src/Interpreters/InterpreterExplainQuery.cpp index 2e4fd50cd01..17a6b695088 100644 --- a/src/Interpreters/InterpreterExplainQuery.cpp +++ b/src/Interpreters/InterpreterExplainQuery.cpp @@ -288,6 +288,20 @@ struct ExplainSettings : public Settings } }; +struct QuerySyntaxSettings +{ + bool oneline = false; + + constexpr static char name[] = "SYNTAX"; + + std::unordered_map> boolean_settings = + { + {"oneline", oneline}, + }; + + std::unordered_map> integer_settings; +}; + template ExplainSettings checkAndGetSettings(const ASTPtr & ast_settings) { @@ -362,13 +376,12 @@ QueryPipeline InterpreterExplainQuery::executeImpl() } case ASTExplainQuery::AnalyzedSyntax: { - if (ast.getSettings()) - throw Exception("Settings are not supported for EXPLAIN SYNTAX query.", ErrorCodes::UNKNOWN_SETTING); + auto settings = checkAndGetSettings(ast.getSettings()); ExplainAnalyzedSyntaxVisitor::Data data(getContext()); ExplainAnalyzedSyntaxVisitor(data).visit(query); - ast.getExplainedQuery()->format(IAST::FormatSettings(buf, false)); + ast.getExplainedQuery()->format(IAST::FormatSettings(buf, settings.oneline)); break; } case ASTExplainQuery::QueryTree: diff --git a/src/Parsers/ASTExplainQuery.h b/src/Parsers/ASTExplainQuery.h index 156ffdeacb9..cb8b3199c81 100644 --- a/src/Parsers/ASTExplainQuery.h +++ b/src/Parsers/ASTExplainQuery.h @@ -6,6 +6,10 @@ namespace DB { +namespace ErrorCodes +{ + extern const int BAD_ARGUMENTS; +} /// AST, EXPLAIN or other query with meaning of explanation query instead of execution class ASTExplainQuery : public ASTQueryWithOutput @@ -23,6 +27,45 @@ public: CurrentTransaction, /// 'EXPLAIN CURRENT TRANSACTION' }; + static String toString(ExplainKind kind) + { + switch (kind) + { + case ParsedAST: return "EXPLAIN AST"; + case AnalyzedSyntax: return "EXPLAIN SYNTAX"; + case QueryTree: return "EXPLAIN QUERY TREE"; + case QueryPlan: return "EXPLAIN"; + case QueryPipeline: return "EXPLAIN PIPELINE"; + case QueryEstimates: return "EXPLAIN ESTIMATE"; + case TableOverride: return "EXPLAIN TABLE OVERRIDE"; + case CurrentTransaction: return "EXPLAIN CURRENT TRANSACTION"; + } + + UNREACHABLE(); + } + + static ExplainKind fromString(const String & str) + { + if (str == "EXPLAIN AST") + return ParsedAST; + if (str == "EXPLAIN SYNTAX") + return AnalyzedSyntax; + if (str == "EXPLAIN QUERY TREE") + return QueryTree; + if (str == "EXPLAIN" || str == "EXPLAIN PLAN") + return QueryPlan; + if (str == "EXPLAIN PIPELINE") + return QueryPipeline; + if (str == "EXPLAIN ESTIMATE") + return QueryEstimates; + if (str == "EXPLAIN TABLE OVERRIDE") + return TableOverride; + if (str == "EXPLAIN CURRENT TRANSACTION") + return CurrentTransaction; + + throw Exception(ErrorCodes::BAD_ARGUMENTS, "Unknown explain kind '{}'", str); + } + explicit ASTExplainQuery(ExplainKind kind_) : kind(kind_) {} String getID(char delim) const override { return "Explain" + (delim + toString(kind)); } @@ -103,23 +146,6 @@ private: /// Used by EXPLAIN TABLE OVERRIDE ASTPtr table_function; ASTPtr table_override; - - static String toString(ExplainKind kind) - { - switch (kind) - { - case ParsedAST: return "EXPLAIN AST"; - case AnalyzedSyntax: return "EXPLAIN SYNTAX"; - case QueryTree: return "EXPLAIN QUERY TREE"; - case QueryPlan: return "EXPLAIN"; - case QueryPipeline: return "EXPLAIN PIPELINE"; - case QueryEstimates: return "EXPLAIN ESTIMATE"; - case TableOverride: return "EXPLAIN TABLE OVERRIDE"; - case CurrentTransaction: return "EXPLAIN CURRENT TRANSACTION"; - } - - UNREACHABLE(); - } }; } diff --git a/src/Parsers/ExpressionElementParsers.cpp b/src/Parsers/ExpressionElementParsers.cpp index 5951128c285..790cb128415 100644 --- a/src/Parsers/ExpressionElementParsers.cpp +++ b/src/Parsers/ExpressionElementParsers.cpp @@ -28,6 +28,8 @@ #include #include #include +#include +#include #include #include #include @@ -116,8 +118,40 @@ bool ParserSubquery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected) } else if (ASTPtr explain_node; explain.parse(pos, explain_node, expected)) { - /// Replace SELECT * FROM (EXPLAIN SELECT ...) with SELECT * FROM viewExplain(EXPLAIN SELECT ...) - result_node = buildSelectFromTableFunction(makeASTFunction("viewExplain", explain_node)); + const auto & explain_query = explain_node->as(); + + if (explain_query.getTableFunction() || explain_query.getTableOverride()) + throw Exception("EXPLAIN in a subquery cannot have a table function or table override", ErrorCodes::BAD_ARGUMENTS); + + /// Replace subquery `(EXPLAIN SELECT ...)` + /// with `(SELECT * FROM viewExplain("", "", SELECT ...))` + + String kind_str = ASTExplainQuery::toString(explain_query.getKind()); + + String settings_str; + if (ASTPtr settings_ast = explain_query.getSettings()) + { + if (!settings_ast->as()) + throw Exception("EXPLAIN settings must be a SET query", ErrorCodes::BAD_ARGUMENTS); + settings_str = queryToString(settings_ast); + } + + const ASTPtr & explained_ast = explain_query.getExplainedQuery(); + if (explained_ast) + { + auto view_explain = makeASTFunction("viewExplain", + std::make_shared(kind_str), + std::make_shared(settings_str), + explained_ast); + result_node = buildSelectFromTableFunction(view_explain); + } + else + { + auto view_explain = makeASTFunction("viewExplain", + std::make_shared(kind_str), + std::make_shared(settings_str)); + result_node = buildSelectFromTableFunction(view_explain); + } } else { diff --git a/src/TableFunctions/TableFunctionExplain.cpp b/src/TableFunctions/TableFunctionExplain.cpp index 02b9308ed22..5b2d0e7b78f 100644 --- a/src/TableFunctions/TableFunctionExplain.cpp +++ b/src/TableFunctions/TableFunctionExplain.cpp @@ -1,6 +1,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -20,22 +23,58 @@ namespace ErrorCodes void TableFunctionExplain::parseArguments(const ASTPtr & ast_function, ContextPtr /*context*/) { const auto * function = ast_function->as(); - if (function && function->arguments && function->arguments->children.size() == 1) - { - const auto & query_arg = function->arguments->children[0]; - - if (!query_arg->as()) - throw Exception(ErrorCodes::BAD_ARGUMENTS, - "Table function '{}' requires a explain query argument, got '{}'", - getName(), queryToString(query_arg)); - - query = query_arg; - } - else - { + if (!function || !function->arguments) throw Exception(ErrorCodes::BAD_ARGUMENTS, "Table function '{}' cannot be called directly, use `SELECT * FROM (EXPLAIN ...)` syntax", getName()); + + size_t num_args = function->arguments->children.size(); + if (num_args != 2 && num_args != 3) + throw Exception(ErrorCodes::BAD_ARGUMENTS, + "Table function '{}' requires 2 or 3 arguments, got {}", getName(), num_args); + + const auto & kind_arg = function->arguments->children[0]; + const auto * kind_literal = kind_arg->as(); + if (!kind_literal || kind_literal->value.getType() != Field::Types::String) + throw Exception(ErrorCodes::BAD_ARGUMENTS, + "Table function '{}' requires a String argument for EXPLAIN kind, got '{}'", + getName(), queryToString(kind_arg)); + + ASTExplainQuery::ExplainKind kind = ASTExplainQuery::fromString(kind_literal->value.get()); + auto explain_query = std::make_shared(kind); + + const auto * settings_arg = function->arguments->children[1]->as(); + if (!settings_arg || settings_arg->value.getType() != Field::Types::String) + throw Exception(ErrorCodes::BAD_ARGUMENTS, + "Table function '{}' requires a serialized string settings argument, got '{}'", + getName(), queryToString(function->arguments->children[1])); + + const auto & settings_str = settings_arg->value.get(); + if (!settings_str.empty()) + { + constexpr UInt64 max_size = 4096; + constexpr UInt64 max_depth = 16; + + /// parse_only_internals_ = true - we don't want to parse `SET` keyword + ParserSetQuery settings_parser(/* parse_only_internals_ = */ true); + ASTPtr settings_ast = parseQuery(settings_parser, settings_str, max_size, max_depth); + explain_query->setSettings(std::move(settings_ast)); } + + if (function->arguments->children.size() > 2) + { + const auto & query_arg = function->arguments->children[2]; + if (!query_arg->as()) + throw Exception(ErrorCodes::BAD_ARGUMENTS, + "Table function '{}' requires a EXPLAIN SELECT query argument, got EXPLAIN '{}'", + getName(), queryToString(query_arg)); + explain_query->setExplainedQuery(query_arg); + } + else if (kind != ASTExplainQuery::ExplainKind::CurrentTransaction) + { + throw Exception(ErrorCodes::BAD_ARGUMENTS, "Table function '{}' requires a query argument", getName()); + } + + query = std::move(explain_query); } ColumnsDescription TableFunctionExplain::getActualTableStructure(ContextPtr context) const diff --git a/tests/queries/0_stateless/02421_explain_subquery.reference b/tests/queries/0_stateless/02421_explain_subquery.reference index c18b4e9b082..58501cbd0fc 100644 --- a/tests/queries/0_stateless/02421_explain_subquery.reference +++ b/tests/queries/0_stateless/02421_explain_subquery.reference @@ -6,3 +6,11 @@ 1 1 1 +1 +1 +1 +1 +1 +1 +1 +1 diff --git a/tests/queries/0_stateless/02421_explain_subquery.sql b/tests/queries/0_stateless/02421_explain_subquery.sql index af80e51bca3..9f83b2f56c7 100644 --- a/tests/queries/0_stateless/02421_explain_subquery.sql +++ b/tests/queries/0_stateless/02421_explain_subquery.sql @@ -1,3 +1,5 @@ +SET allow_experimental_analyzer = 0; + SELECT count() > 3 FROM (EXPLAIN PIPELINE header = 1 SELECT * FROM system.numbers ORDER BY number DESC) WHERE explain LIKE '%Header: number UInt64%'; SELECT count() > 0 FROM (EXPLAIN PLAN SELECT * FROM system.numbers ORDER BY number DESC) WHERE explain ILIKE '%Sort%'; SELECT count() > 0 FROM (EXPLAIN SELECT * FROM system.numbers ORDER BY number DESC) WHERE explain ILIKE '%Sort%'; @@ -15,9 +17,69 @@ SELECT * FROM ( ) ) FORMAT Null; +SELECT (EXPLAIN SYNTAX oneline = 1 SELECT 1) == 'SELECT 1'; + +SELECT * FROM viewExplain('', ''); -- { serverError BAD_ARGUMENTS } +SELECT * FROM viewExplain('EXPLAIN AST', ''); -- { serverError BAD_ARGUMENTS } +SELECT * FROM viewExplain('EXPLAIN AST', '', 1); -- { serverError BAD_ARGUMENTS } +SELECT * FROM viewExplain('EXPLAIN AST', '', ''); -- { serverError BAD_ARGUMENTS } + CREATE TABLE t1 ( a UInt64 ) Engine = MergeTree ORDER BY tuple() AS SELECT number AS a FROM system.numbers LIMIT 100000; SELECT rows > 1000 FROM (EXPLAIN ESTIMATE SELECT sum(a) FROM t1); SELECT count() == 1 FROM (EXPLAIN ESTIMATE SELECT sum(a) FROM t1); DROP TABLE IF EXISTS t1; + +SET allow_experimental_analyzer = 1; + +SELECT count() > 3 FROM (EXPLAIN PIPELINE header = 1 SELECT * FROM system.numbers ORDER BY number DESC) WHERE explain LIKE '%Header: system.numbers.number__ UInt64%'; +SELECT count() > 0 FROM (EXPLAIN PLAN SELECT * FROM system.numbers ORDER BY number DESC) WHERE explain ILIKE '%Sort%'; +SELECT count() > 0 FROM (EXPLAIN SELECT * FROM system.numbers ORDER BY number DESC) WHERE explain ILIKE '%Sort%'; +SELECT count() > 0 FROM (EXPLAIN CURRENT TRANSACTION); +SELECT count() == 1 FROM (EXPLAIN SYNTAX SELECT number FROM system.numbers ORDER BY number DESC) WHERE explain ILIKE 'SELECT%'; + +-- We have `Identifier number` instead of `Asterisk` because query argument of `viewExplain` table function was analyzed. +-- Compare: +-- :) EXPLAIN AST SELECT *; +-- ┌─explain───────────────────────────┐ +-- │ SelectWithUnionQuery (children 1) │ +-- │ ExpressionList (children 1) │ +-- │ SelectQuery (children 1) │ +-- │ ExpressionList (children 1) │ +-- │ Asterisk │ +-- └───────────────────────────────────┘ +-- :) SELECT * FROM (EXPLAIN AST SELECT *); +-- ┌─explain─────────────────────────────────────┐ +-- │ SelectWithUnionQuery (children 1) │ +-- │ ExpressionList (children 1) │ +-- │ SelectQuery (children 2) │ +-- │ ExpressionList (children 1) │ +-- │ Identifier dummy │ +-- │ TablesInSelectQuery (children 1) │ +-- │ TablesInSelectQueryElement (children 1) │ +-- │ TableExpression (children 1) │ +-- │ TableIdentifier system.one │ +-- └─────────────────────────────────────────────┘ +-- TODO: argument of `viewExplain` (and subquery in `EXAPLAN ...`) should not be analyzed. +-- See _Support query tree in table functions_ in https://github.com/ClickHouse/ClickHouse/issues/42648 +SELECT trim(explain) == 'Identifier number' FROM (EXPLAIN AST SELECT * FROM system.numbers LIMIT 10) WHERE explain LIKE '%Identifier number%'; + +SELECT * FROM ( + EXPLAIN AST SELECT * FROM ( + EXPLAIN PLAN SELECT * FROM ( + EXPLAIN SYNTAX SELECT trim(explain) == 'Asterisk' FROM ( + EXPLAIN AST SELECT * FROM system.numbers LIMIT 10 + ) WHERE explain LIKE '%Asterisk%' + ) + ) +) FORMAT Null; + +SELECT (EXPLAIN SYNTAX oneline = 1 SELECT 1) == 'SELECT 1 FROM system.one'; + +SELECT * FROM viewExplain('', ''); -- { serverError BAD_ARGUMENTS } +SELECT * FROM viewExplain('EXPLAIN AST', ''); -- { serverError BAD_ARGUMENTS } +SELECT * FROM viewExplain('EXPLAIN AST', '', 1); -- { serverError BAD_ARGUMENTS } +SELECT * FROM viewExplain('EXPLAIN AST', '', ''); -- { serverError BAD_ARGUMENTS } + +-- EXPLAIN ESTIMATE is not supported in experimental analyzer