From ee9080fba714fbe9ac482b4268ed164579ebda89 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Tue, 20 Aug 2024 15:11:06 +0200 Subject: [PATCH] Revert "Fix unexpected behavior with `FORMAT` and `SETTINGS` parsing" --- programs/client/Client.cpp | 3 - programs/server/Server.cpp | 2 +- src/Access/AccessControl.cpp | 8 +-- src/Access/AccessControl.h | 5 +- src/Access/SettingsConstraints.cpp | 8 +-- src/Client/ClientBase.cpp | 60 ++++++++++++----- src/Interpreters/InterpreterSetQuery.cpp | 34 +++++----- src/Interpreters/InterpreterSetQuery.h | 2 +- src/Parsers/ParserQueryWithOutput.cpp | 67 +++++++------------ ...QueryWithOutputSettingsPushDownVisitor.cpp | 56 ++++++++++++++++ .../QueryWithOutputSettingsPushDownVisitor.h | 39 +++++++++++ .../00857_global_joinsavel_table_alias.sql | 1 + .../01401_FORMAT_SETTINGS.reference | 4 +- .../0_stateless/01401_FORMAT_SETTINGS.sh | 2 +- .../03003_compatibility_setting_bad_value.sql | 3 +- .../03172_format_settings_clauses.reference | 14 ---- .../03172_format_settings_clauses.sql | 30 --------- 17 files changed, 197 insertions(+), 141 deletions(-) create mode 100644 src/Parsers/QueryWithOutputSettingsPushDownVisitor.cpp create mode 100644 src/Parsers/QueryWithOutputSettingsPushDownVisitor.h delete mode 100644 tests/queries/0_stateless/03172_format_settings_clauses.reference delete mode 100644 tests/queries/0_stateless/03172_format_settings_clauses.sql diff --git a/programs/client/Client.cpp b/programs/client/Client.cpp index 39edaf3497e..25c94c56aa6 100644 --- a/programs/client/Client.cpp +++ b/programs/client/Client.cpp @@ -1164,9 +1164,6 @@ void Client::processOptions(const OptionsDescription & options_description, /// (There is no need to copy the context because clickhouse-client has no background tasks so it won't use that context in parallel.) client_context = global_context; initClientContext(); - - /// Allow to pass-through unknown settings to the server. - client_context->getAccessControl().allowAllSettings(); } diff --git a/programs/server/Server.cpp b/programs/server/Server.cpp index 16fee378cf0..74228fae5a3 100644 --- a/programs/server/Server.cpp +++ b/programs/server/Server.cpp @@ -1922,7 +1922,7 @@ try auto & access_control = global_context->getAccessControl(); try { - access_control.setupFromMainConfig(config(), config_path, [&] { return global_context->getZooKeeper(); }); + access_control.setUpFromMainConfig(config(), config_path, [&] { return global_context->getZooKeeper(); }); } catch (...) { diff --git a/src/Access/AccessControl.cpp b/src/Access/AccessControl.cpp index d4f8c7bc859..95a467bbbe5 100644 --- a/src/Access/AccessControl.cpp +++ b/src/Access/AccessControl.cpp @@ -280,7 +280,7 @@ void AccessControl::shutdown() } -void AccessControl::setupFromMainConfig(const Poco::Util::AbstractConfiguration & config_, const String & config_path_, +void AccessControl::setUpFromMainConfig(const Poco::Util::AbstractConfiguration & config_, const String & config_path_, const zkutil::GetZooKeeper & get_zookeeper_function_) { if (config_.has("custom_settings_prefixes")) @@ -868,10 +868,4 @@ const ExternalAuthenticators & AccessControl::getExternalAuthenticators() const return *external_authenticators; } - -void AccessControl::allowAllSettings() -{ - custom_settings_prefixes->registerPrefixes({""}); -} - } diff --git a/src/Access/AccessControl.h b/src/Access/AccessControl.h index 7d8ee1232d0..bfaf256ad48 100644 --- a/src/Access/AccessControl.h +++ b/src/Access/AccessControl.h @@ -57,7 +57,7 @@ public: void shutdown() override; /// Initializes access storage (user directories). - void setupFromMainConfig(const Poco::Util::AbstractConfiguration & config_, const String & config_path_, + void setUpFromMainConfig(const Poco::Util::AbstractConfiguration & config_, const String & config_path_, const zkutil::GetZooKeeper & get_zookeeper_function_); /// Parses access entities from a configuration loaded from users.xml. @@ -238,9 +238,6 @@ public: /// Gets manager of notifications. AccessChangesNotifier & getChangesNotifier(); - /// Allow all setting names - this can be used in clients to pass-through unknown settings to the server. - void allowAllSettings(); - private: class ContextAccessCache; class CustomSettingsPrefixes; diff --git a/src/Access/SettingsConstraints.cpp b/src/Access/SettingsConstraints.cpp index 7506e365035..a274f6b54f2 100644 --- a/src/Access/SettingsConstraints.cpp +++ b/src/Access/SettingsConstraints.cpp @@ -219,8 +219,8 @@ void SettingsConstraints::clamp(const Settings & current_settings, SettingsChang }); } -template -bool getNewValueToCheck(const SettingsT & current_settings, SettingChange & change, Field & new_value, bool throw_on_failure) +template +bool getNewValueToCheck(const T & current_settings, SettingChange & change, Field & new_value, bool throw_on_failure) { Field current_value; bool has_current_value = current_settings.tryGet(change.name, current_value); @@ -230,12 +230,12 @@ bool getNewValueToCheck(const SettingsT & current_settings, SettingChange & chan return false; if (throw_on_failure) - new_value = SettingsT::castValueUtil(change.name, change.value); + new_value = T::castValueUtil(change.name, change.value); else { try { - new_value = SettingsT::castValueUtil(change.name, change.value); + new_value = T::castValueUtil(change.name, change.value); } catch (...) { diff --git a/src/Client/ClientBase.cpp b/src/Client/ClientBase.cpp index e312f2a8158..01d03006eec 100644 --- a/src/Client/ClientBase.cpp +++ b/src/Client/ClientBase.cpp @@ -58,7 +58,6 @@ #include #include #include -#include #include #include #include @@ -1609,14 +1608,14 @@ void ClientBase::sendData(Block & sample, const ColumnsDescription & columns_des auto metadata = storage->getInMemoryMetadataPtr(); QueryPlan plan; storage->read( - plan, - sample.getNames(), - storage->getStorageSnapshot(metadata, client_context), - query_info, - client_context, - {}, - client_context->getSettingsRef().max_block_size, - getNumberOfPhysicalCPUCores()); + plan, + sample.getNames(), + storage->getStorageSnapshot(metadata, client_context), + query_info, + client_context, + {}, + client_context->getSettingsRef().max_block_size, + getNumberOfPhysicalCPUCores()); auto builder = plan.buildQueryPipeline( QueryPlanOptimizationSettings::fromContext(client_context), @@ -1893,19 +1892,48 @@ void ClientBase::processParsedSingleQuery(const String & full_query, const Strin profile_events.watch.restart(); { - /// Temporarily apply query settings to the context. - Settings old_settings = client_context->getSettingsCopy(); - SCOPE_EXIT_SAFE( - { - client_context->setSettings(old_settings); + /// Temporarily apply query settings to context. + std::optional old_settings; + SCOPE_EXIT_SAFE({ + if (old_settings) + client_context->setSettings(*old_settings); }); - InterpreterSetQuery::applySettingsFromQuery(parsed_query, client_context); + + auto apply_query_settings = [&](const IAST & settings_ast) + { + if (!old_settings) + old_settings.emplace(client_context->getSettingsRef()); + client_context->applySettingsChanges(settings_ast.as()->changes); + client_context->resetSettingsToDefaultValue(settings_ast.as()->default_settings); + }; + + const auto * insert = parsed_query->as(); + if (const auto * select = parsed_query->as(); select && select->settings()) + apply_query_settings(*select->settings()); + else if (const auto * select_with_union = parsed_query->as()) + { + const ASTs & children = select_with_union->list_of_selects->children; + if (!children.empty()) + { + // On the client it is enough to apply settings only for the + // last SELECT, since the only thing that is important to apply + // on the client is format settings. + const auto * last_select = children.back()->as(); + if (last_select && last_select->settings()) + { + apply_query_settings(*last_select->settings()); + } + } + } + else if (const auto * query_with_output = parsed_query->as(); query_with_output && query_with_output->settings_ast) + apply_query_settings(*query_with_output->settings_ast); + else if (insert && insert->settings_ast) + apply_query_settings(*insert->settings_ast); if (!connection->checkConnected(connection_parameters.timeouts)) connect(); ASTPtr input_function; - const auto * insert = parsed_query->as(); if (insert && insert->select) insert->tryFindInputFunction(input_function); diff --git a/src/Interpreters/InterpreterSetQuery.cpp b/src/Interpreters/InterpreterSetQuery.cpp index 2ae35c4313b..7e68fc5c4c1 100644 --- a/src/Interpreters/InterpreterSetQuery.cpp +++ b/src/Interpreters/InterpreterSetQuery.cpp @@ -9,7 +9,6 @@ #include #include - namespace DB { @@ -46,7 +45,9 @@ static void applySettingsFromSelectWithUnion(const ASTSelectWithUnionQuery & sel // It is flattened later, when we process UNION ALL/DISTINCT. const auto * last_select = children.back()->as(); if (last_select && last_select->settings()) - InterpreterSetQuery(last_select->settings(), context).executeForCurrentContext(/* ignore_setting_constraints= */ false); + { + InterpreterSetQuery(last_select->settings(), context).executeForCurrentContext(); + } } void InterpreterSetQuery::applySettingsFromQuery(const ASTPtr & ast, ContextMutablePtr context_) @@ -54,20 +55,10 @@ void InterpreterSetQuery::applySettingsFromQuery(const ASTPtr & ast, ContextMuta if (!ast) return; - /// First apply the outermost settings. Then they could be overridden by deeper settings. - if (const auto * query_with_output = dynamic_cast(ast.get())) - { - if (query_with_output->settings_ast) - InterpreterSetQuery(query_with_output->settings_ast, context_).executeForCurrentContext(/* ignore_setting_constraints= */ false); - - if (const auto * create_query = ast->as(); create_query && create_query->select) - applySettingsFromSelectWithUnion(create_query->select->as(), context_); - } - if (const auto * select_query = ast->as()) { if (auto new_settings = select_query->settings()) - InterpreterSetQuery(new_settings, context_).executeForCurrentContext(/* ignore_setting_constraints= */ false); + InterpreterSetQuery(new_settings, context_).executeForCurrentContext(); } else if (const auto * select_with_union_query = ast->as()) { @@ -76,15 +67,28 @@ void InterpreterSetQuery::applySettingsFromQuery(const ASTPtr & ast, ContextMuta else if (const auto * explain_query = ast->as()) { if (explain_query->settings_ast) - InterpreterSetQuery(explain_query->settings_ast, context_).executeForCurrentContext(/* ignore_setting_constraints= */ false); + InterpreterSetQuery(explain_query->settings_ast, context_).executeForCurrentContext(); applySettingsFromQuery(explain_query->getExplainedQuery(), context_); } + else if (const auto * query_with_output = dynamic_cast(ast.get())) + { + if (query_with_output->settings_ast) + InterpreterSetQuery(query_with_output->settings_ast, context_).executeForCurrentContext(); + + if (const auto * create_query = ast->as()) + { + if (create_query->select) + { + applySettingsFromSelectWithUnion(create_query->select->as(), context_); + } + } + } else if (auto * insert_query = ast->as()) { context_->setInsertFormat(insert_query->format); if (insert_query->settings_ast) - InterpreterSetQuery(insert_query->settings_ast, context_).executeForCurrentContext(/* ignore_setting_constraints= */ false); + InterpreterSetQuery(insert_query->settings_ast, context_).executeForCurrentContext(); } } diff --git a/src/Interpreters/InterpreterSetQuery.h b/src/Interpreters/InterpreterSetQuery.h index f50105c39f4..2438762f347 100644 --- a/src/Interpreters/InterpreterSetQuery.h +++ b/src/Interpreters/InterpreterSetQuery.h @@ -23,7 +23,7 @@ public: /** Set setting for current context (query context). * It is used for interpretation of SETTINGS clause in SELECT query. */ - void executeForCurrentContext(bool ignore_setting_constraints); + void executeForCurrentContext(bool ignore_setting_constraints = false); bool supportsTransactions() const override { return true; } diff --git a/src/Parsers/ParserQueryWithOutput.cpp b/src/Parsers/ParserQueryWithOutput.cpp index ac8f7d560e0..cb0c10cd1c9 100644 --- a/src/Parsers/ParserQueryWithOutput.cpp +++ b/src/Parsers/ParserQueryWithOutput.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -151,55 +152,37 @@ bool ParserQueryWithOutput::parseImpl(Pos & pos, ASTPtr & node, Expected & expec } - /// These two sections are allowed in an arbitrary order. ParserKeyword s_format(Keyword::FORMAT); - ParserKeyword s_settings(Keyword::SETTINGS); - /** Why: let's take the following example: - * SELECT 1 UNION ALL SELECT 2 FORMAT TSV - * Each subquery can be put in parentheses and have its own settings: - * (SELECT 1 SETTINGS a=b) UNION ALL (SELECT 2 SETTINGS c=d) FORMAT TSV - * And the whole query can have settings: - * (SELECT 1 SETTINGS a=b) UNION ALL (SELECT 2 SETTINGS c=d) FORMAT TSV SETTINGS e=f - * A single query with output is parsed in the same way as the UNION ALL chain: - * SELECT 1 SETTINGS a=b FORMAT TSV SETTINGS e=f - * So while these forms have a slightly different meaning, they both exist: - * SELECT 1 SETTINGS a=b FORMAT TSV - * SELECT 1 FORMAT TSV SETTINGS e=f - * And due to this effect, the users expect that the FORMAT and SETTINGS may go in an arbitrary order. - * But while this work: - * (SELECT 1) UNION ALL (SELECT 2) FORMAT TSV SETTINGS d=f - * This does not work automatically, unless we explicitly allow different orders: - * (SELECT 1) UNION ALL (SELECT 2) SETTINGS d=f FORMAT TSV - * Inevitably, we also allow this: - * SELECT 1 SETTINGS a=b SETTINGS d=f FORMAT TSV - * ^^^^^^^^^^^^^^^^^^^^^ - * Because this part is consumed into ASTSelectWithUnionQuery - * and the rest into ASTQueryWithOutput. - */ - - for (size_t i = 0; i < 2; ++i) + if (s_format.ignore(pos, expected)) { - if (!query_with_output.format && s_format.ignore(pos, expected)) - { - ParserIdentifier format_p; + ParserIdentifier format_p; - if (!format_p.parse(pos, query_with_output.format, expected)) - return false; - setIdentifierSpecial(query_with_output.format); + if (!format_p.parse(pos, query_with_output.format, expected)) + return false; + setIdentifierSpecial(query_with_output.format); - query_with_output.children.push_back(query_with_output.format); - } - else if (!query_with_output.settings_ast && s_settings.ignore(pos, expected)) + query_with_output.children.push_back(query_with_output.format); + } + + // SETTINGS key1 = value1, key2 = value2, ... + ParserKeyword s_settings(Keyword::SETTINGS); + if (!query_with_output.settings_ast && s_settings.ignore(pos, expected)) + { + ParserSetQuery parser_settings(true); + if (!parser_settings.parse(pos, query_with_output.settings_ast, expected)) + return false; + query_with_output.children.push_back(query_with_output.settings_ast); + + // SETTINGS after FORMAT is not parsed by the SELECT parser (ParserSelectQuery) + // Pass them manually, to apply in InterpreterSelectQuery::initSettings() + if (query->as()) { - // SETTINGS key1 = value1, key2 = value2, ... - ParserSetQuery parser_settings(true); - if (!parser_settings.parse(pos, query_with_output.settings_ast, expected)) - return false; - query_with_output.children.push_back(query_with_output.settings_ast); + auto settings = query_with_output.settings_ast->clone(); + assert_cast(settings.get())->print_in_format = false; + QueryWithOutputSettingsPushDownVisitor::Data data{settings}; + QueryWithOutputSettingsPushDownVisitor(data).visit(query); } - else - break; } node = std::move(query); diff --git a/src/Parsers/QueryWithOutputSettingsPushDownVisitor.cpp b/src/Parsers/QueryWithOutputSettingsPushDownVisitor.cpp new file mode 100644 index 00000000000..8cf0d0063ae --- /dev/null +++ b/src/Parsers/QueryWithOutputSettingsPushDownVisitor.cpp @@ -0,0 +1,56 @@ +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace DB +{ + +bool QueryWithOutputSettingsPushDownMatcher::needChildVisit(ASTPtr & node, const ASTPtr & child) +{ + if (node->as()) + return true; + if (node->as()) + return true; + if (child->as()) + return true; + return false; +} + +void QueryWithOutputSettingsPushDownMatcher::visit(ASTPtr & ast, Data & data) +{ + if (auto * select_query = ast->as()) + visit(*select_query, ast, data); +} + +void QueryWithOutputSettingsPushDownMatcher::visit(ASTSelectQuery & select_query, ASTPtr &, Data & data) +{ + ASTPtr select_settings_ast = select_query.settings(); + if (!select_settings_ast) + { + select_query.setExpression(ASTSelectQuery::Expression::SETTINGS, data.settings_ast->clone()); + return; + } + + SettingsChanges & select_settings = select_settings_ast->as().changes; + SettingsChanges & settings = data.settings_ast->as().changes; + + for (auto & setting : settings) + { + auto it = std::find_if(select_settings.begin(), select_settings.end(), [&](auto & select_setting) + { + return select_setting.name == setting.name; + }); + if (it == select_settings.end()) + select_settings.push_back(setting); + else + it->value = setting.value; + } +} + +} diff --git a/src/Parsers/QueryWithOutputSettingsPushDownVisitor.h b/src/Parsers/QueryWithOutputSettingsPushDownVisitor.h new file mode 100644 index 00000000000..fde8a07b555 --- /dev/null +++ b/src/Parsers/QueryWithOutputSettingsPushDownVisitor.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +namespace DB +{ + +class ASTSelectQuery; +struct SettingChange; +class SettingsChanges; + +/// Pushdown SETTINGS clause that goes after FORMAT to the SELECT query: +/// (since settings after FORMAT parsed separately not in the ParserSelectQuery but in ParserQueryWithOutput) +/// +/// SELECT 1 FORMAT Null SETTINGS max_block_size = 1 -> +/// SELECT 1 SETTINGS max_block_size = 1 FORMAT Null SETTINGS max_block_size = 1 +/// +/// Otherwise settings after FORMAT will not be applied. +class QueryWithOutputSettingsPushDownMatcher +{ +public: + using Visitor = InDepthNodeVisitor; + + struct Data + { + const ASTPtr & settings_ast; + }; + + static bool needChildVisit(ASTPtr & node, const ASTPtr & child); + static void visit(ASTPtr & ast, Data & data); + +private: + static void visit(ASTSelectQuery &, ASTPtr &, Data &); +}; + +using QueryWithOutputSettingsPushDownVisitor = QueryWithOutputSettingsPushDownMatcher::Visitor; + +} diff --git a/tests/queries/0_stateless/00857_global_joinsavel_table_alias.sql b/tests/queries/0_stateless/00857_global_joinsavel_table_alias.sql index 092b071cb48..2044a9b8d22 100644 --- a/tests/queries/0_stateless/00857_global_joinsavel_table_alias.sql +++ b/tests/queries/0_stateless/00857_global_joinsavel_table_alias.sql @@ -1,3 +1,4 @@ + DROP TABLE IF EXISTS local_table; DROP TABLE IF EXISTS other_table; diff --git a/tests/queries/0_stateless/01401_FORMAT_SETTINGS.reference b/tests/queries/0_stateless/01401_FORMAT_SETTINGS.reference index a8b99666654..22405bf1866 100644 --- a/tests/queries/0_stateless/01401_FORMAT_SETTINGS.reference +++ b/tests/queries/0_stateless/01401_FORMAT_SETTINGS.reference @@ -1,7 +1,7 @@ 1 1 1 +1 +1 2 -1 -2 2 diff --git a/tests/queries/0_stateless/01401_FORMAT_SETTINGS.sh b/tests/queries/0_stateless/01401_FORMAT_SETTINGS.sh index 173cc949500..b70c28422c9 100755 --- a/tests/queries/0_stateless/01401_FORMAT_SETTINGS.sh +++ b/tests/queries/0_stateless/01401_FORMAT_SETTINGS.sh @@ -13,7 +13,7 @@ ${CLICKHOUSE_CURL} -sS "${CLICKHOUSE_URL}" -d 'SELECT DISTINCT blockSize() FROM ${CLICKHOUSE_CURL} -sS "${CLICKHOUSE_URL}" -d 'SELECT DISTINCT blockSize() FROM numbers(2) FORMAT CSV SETTINGS max_block_size = 1' # push down append ${CLICKHOUSE_CURL} -sS "${CLICKHOUSE_URL}" -d 'SELECT DISTINCT blockSize() FROM numbers(2) SETTINGS max_compress_block_size = 1 FORMAT CSV SETTINGS max_block_size = 1' -# not overwrite on push down +# overwrite on push down (since these settings goes latest) ${CLICKHOUSE_CURL} -sS "${CLICKHOUSE_URL}" -d 'SELECT DISTINCT blockSize() FROM numbers(2) SETTINGS max_block_size = 2 FORMAT CSV SETTINGS max_block_size = 1' # on push-down ${CLICKHOUSE_CURL} -sS "${CLICKHOUSE_URL}" -d 'SELECT DISTINCT blockSize() FROM numbers(2) SETTINGS max_block_size = 1 FORMAT CSV' diff --git a/tests/queries/0_stateless/03003_compatibility_setting_bad_value.sql b/tests/queries/0_stateless/03003_compatibility_setting_bad_value.sql index 3a09eec7452..48e98798c51 100644 --- a/tests/queries/0_stateless/03003_compatibility_setting_bad_value.sql +++ b/tests/queries/0_stateless/03003_compatibility_setting_bad_value.sql @@ -1 +1,2 @@ -select 42 settings compatibility=NULL; -- {clientError BAD_GET} +select 42 settings compatibility=NULL; -- {clientError BAD_ARGUMENTS} + diff --git a/tests/queries/0_stateless/03172_format_settings_clauses.reference b/tests/queries/0_stateless/03172_format_settings_clauses.reference deleted file mode 100644 index 8a98b137f4b..00000000000 --- a/tests/queries/0_stateless/03172_format_settings_clauses.reference +++ /dev/null @@ -1,14 +0,0 @@ -1 -2 -1 -2 -1 -2 -1 -1 -3 -3 -3 -3 -3 -1 diff --git a/tests/queries/0_stateless/03172_format_settings_clauses.sql b/tests/queries/0_stateless/03172_format_settings_clauses.sql deleted file mode 100644 index 0d1aa4dcfbb..00000000000 --- a/tests/queries/0_stateless/03172_format_settings_clauses.sql +++ /dev/null @@ -1,30 +0,0 @@ -SET max_block_size = 10, max_threads = 1; - --- Take the following example: -SELECT 1 UNION ALL SELECT 2 FORMAT TSV; - --- Each subquery can be put in parentheses and have its own settings: -(SELECT getSetting('max_block_size') SETTINGS max_block_size = 1) UNION ALL (SELECT getSetting('max_block_size') SETTINGS max_block_size = 2) FORMAT TSV; - --- And the whole query can have settings: -(SELECT getSetting('max_block_size') SETTINGS max_block_size = 1) UNION ALL (SELECT getSetting('max_block_size') SETTINGS max_block_size = 2) FORMAT TSV SETTINGS max_block_size = 3; - --- A single query with output is parsed in the same way as the UNION ALL chain: -SELECT getSetting('max_block_size') SETTINGS max_block_size = 1 FORMAT TSV SETTINGS max_block_size = 3; - --- So while these forms have a slightly different meaning, they both exist: -SELECT getSetting('max_block_size') SETTINGS max_block_size = 1 FORMAT TSV; -SELECT getSetting('max_block_size') FORMAT TSV SETTINGS max_block_size = 3; - --- And due to this effect, the users expect that the FORMAT and SETTINGS may go in an arbitrary order. --- But while this work: -(SELECT getSetting('max_block_size')) UNION ALL (SELECT getSetting('max_block_size')) FORMAT TSV SETTINGS max_block_size = 3; - --- This does not work automatically, unless we explicitly allow different orders: -(SELECT getSetting('max_block_size')) UNION ALL (SELECT getSetting('max_block_size')) SETTINGS max_block_size = 3 FORMAT TSV; - --- Inevitably, we allow this: -SELECT getSetting('max_block_size') SETTINGS max_block_size = 1 SETTINGS max_block_size = 3 FORMAT TSV; -/*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/ --- Because this part is consumed into ASTSelectWithUnionQuery --- and the rest into ASTQueryWithOutput.