diff --git a/src/Analyzer/Utils.cpp b/src/Analyzer/Utils.cpp index fe5ddbdd6d7f..eb44eff6376c 100644 --- a/src/Analyzer/Utils.cpp +++ b/src/Analyzer/Utils.cpp @@ -31,6 +31,7 @@ #include #include +#include #include @@ -55,6 +56,7 @@ #include #include +#include namespace DB { @@ -1017,6 +1019,81 @@ void resolveAggregateFunctionNodeByName(FunctionNode & function_node, const Stri function_node.resolveAsAggregateFunction(std::move(aggregate_function)); } +namespace +{ + +class FinalizeAliasMarkersVisitor : public InDepthQueryTreeVisitor +{ +public: + explicit FinalizeAliasMarkersVisitor(ContextPtr context_) : context(std::move(context_)) {} + + /// Visit children first, so a nested marker chain is materialized from the inside out. + bool shouldTraverseTopToBottom() const { return false; } + + void visitImpl(QueryTreeNodePtr & node) + { + auto * function_node = node->as(); + if (!function_node || function_node->getFunctionName() != "__aliasMarker") + return; + + auto & arguments = function_node->getArguments().getNodes(); + if (arguments.size() != 3) + return; + + if (!arguments[0] || !arguments[1] || !arguments[2]) + throw Exception(ErrorCodes::BAD_ARGUMENTS, "Invalid internal pending __aliasMarker arguments"); + + const auto * token = arguments[2]->as(); + if (!token || !isString(token->getResultType()) || token->getValue().safeGet() != AliasMarkerName::pending_token) + throw Exception(ErrorCodes::BAD_ARGUMENTS, "Invalid internal pending __aliasMarker token"); + + const auto * column_node = arguments[1]->as(); + if (!column_node) + throw Exception(ErrorCodes::BAD_ARGUMENTS, "Internal pending __aliasMarker requires a column id"); + + const auto & column_source = column_node->getColumnSourceOrNull(); + if (!column_source || column_source->getNodeType() == QueryTreeNodeType::LAMBDA || !column_source->hasAlias()) + throw Exception(ErrorCodes::BAD_ARGUMENTS, "Internal pending __aliasMarker id has no table source alias"); + + auto alias_id = column_source->getAlias() + "." + column_node->getColumnName(); + arguments[1] = std::make_shared(std::move(alias_id), std::make_shared()); + arguments.pop_back(); + resolveOrdinaryFunctionNodeByName(*function_node, "__aliasMarker", context); + } + +private: + ContextPtr context; +}; + +} + +void finalizeAliasMarkersForDistributedSerialization(QueryTreeNodePtr & node, const ContextPtr & context) +{ + FinalizeAliasMarkersVisitor visitor(context); + visitor.visit(node); +} + +void assertNoPendingAliasMarkersForDistributedSerialization(const QueryTreeNodePtr & node) +{ + if (!node) + return; + + std::vector nodes_to_visit{node.get()}; + while (!nodes_to_visit.empty()) + { + const auto * current = nodes_to_visit.back(); + nodes_to_visit.pop_back(); + + if (const auto * function_node = current->as(); + function_node && function_node->getFunctionName() == "__aliasMarker" && function_node->getArguments().getNodes().size() == 3) + throw Exception(ErrorCodes::BAD_ARGUMENTS, "Internal pending __aliasMarker cannot be shipped before finalization"); + + for (const auto & child : current->getChildren()) + if (child) + nodes_to_visit.push_back(child.get()); + } +} + std::pair getExpressionSource(const QueryTreeNodePtr & node) { if (const auto * column = node->as()) diff --git a/src/Analyzer/Utils.h b/src/Analyzer/Utils.h index 1e1182c76649..843d0992d646 100644 --- a/src/Analyzer/Utils.h +++ b/src/Analyzer/Utils.h @@ -157,6 +157,22 @@ void resolveOrdinaryFunctionNodeByName(FunctionNode & function_node, const Strin /// Arguments and parameters are taken from the node. void resolveAggregateFunctionNodeByName(FunctionNode & function_node, const String & function_name); +/** Materialize the id of each injected, three-argument pending `__aliasMarker` in the tree. Replace its `ColumnNode` + * with the `String` constant the shard will read, and remove its pending-form token. Leave two-argument calls alone. + * + * Call this immediately before the tree is rendered as SQL for a shard, and no earlier. The id is the column's + * analyzer identifier, and `createUniqueAliasesIfNecessary` -- which runs inside `buildQueryTreeForShard` -- is what + * settles the `__tableN` part of it. An id frozen before that point names a table alias that no longer exists by the + * time the shard header is built. + * + * A marker already materialized on an earlier hop has two arguments. A hand-written two-argument marker is left + * alone too, even when its id is a `ColumnNode`. + */ +void finalizeAliasMarkersForDistributedSerialization(QueryTreeNodePtr & node, const ContextPtr & context); + +/// Reject a pending `__aliasMarker` before converting a query tree to SQL or building a plan to ship. +void assertNoPendingAliasMarkersForDistributedSerialization(const QueryTreeNodePtr & node); + /// Returns single source of expression node. /// First element of pair is source node, can be nullptr if there are no sources or multiple sources. /// Second element of pair is true if there is at most one source, false if there are multiple sources. diff --git a/src/Functions/identity.cpp b/src/Functions/identity.cpp index 05d2ef870601..8aeed5bbacd8 100644 --- a/src/Functions/identity.cpp +++ b/src/Functions/identity.cpp @@ -38,12 +38,14 @@ REGISTER_FUNCTION(AliasMarker) { factory.registerFunction(FunctionDocumentation{ .description = R"( -Internal function that marks ALIAS column expressions for the analyzer. Not intended for direct use. +Internal function. Returns its first argument unchanged. The second argument records which ALIAS column the +expression was inlined from, so that a shard names the result the way the initiator expects. Not intended for +direct use: an explicitly supplied string id controls the planner's action name. )", - .syntax = {"__aliasMarker(expr, alias_name)"}, + .syntax = {"__aliasMarker(expr, alias_id)"}, .arguments = { {"expr", "Expression to mark.", {"Any"}}, - {"alias_name", "Alias name attached to the expression.", {"String"}}, + {"alias_id", "Identity of the ALIAS column the expression came from.", {"Any"}}, }, .returned_value = {"Returns expr unchanged.", {"Any"}}, .introduced_in = {25, 8}, diff --git a/src/Functions/identity.h b/src/Functions/identity.h index 9c2ae607de1d..54f63889ffa2 100644 --- a/src/Functions/identity.h +++ b/src/Functions/identity.h @@ -1,4 +1,5 @@ #pragma once +#include #include #include #include @@ -106,8 +107,36 @@ class FunctionActionName final : public FunctionIdentityBase struct AliasMarkerName { static constexpr auto name = "__aliasMarker"; + static constexpr auto pending_token = "__aliasMarker_pending_v1"; }; +/** `__aliasMarker(expr, id)` is an internal pass-through identity. It returns `expr` untouched; `id` exists only to + * give the expression a stable name in the planner's `ActionsDAG`. + * + * It is injected when an `ALIAS` column is inlined into its defining expression for transport to a shard. The + * initiator still sees the un-inlined column, so without the marker the shard would name the output after the + * expression (`multiply(__table1.value, 2)`) while the initiator expects the column (`__table1.computed`), and the + * two headers could not be matched by name. + * + * The marker carries the low-level column identity, not the user's SQL alias. A SQL alias cannot do this job: it + * participates in user-visible query semantics, it can collide with names the user chose, and in the + * mergeable-state path the projection step that would normally apply it is skipped. + * + * This is also why the marker is not `__actionName`. `__actionName` survives into the `ActionsDAG` as a function node + * with a forced result name; `__aliasMarker` is consumed into an alias on top of its child, which is what keeps the + * expression behaving like a distinct logical column. + * + * An injected marker has a third, constant token argument until it is shipped. The second argument is a `ColumnNode` + * so analyzer passes, in particular `createUniqueAliasesIfNecessary`, can assign the final `__tableN` alias. + * `finalizeAliasMarkersForDistributedSerialization` replaces it with a `String` id and removes the token before shipping. + * The token distinguishes this pending form from a hand-written two-argument call; it is not authentication. + * + * A hand-written two-argument `__aliasMarker(x, x)` is not finalized. An explicitly supplied `String` id still + * controls action naming, so direct use of this internal function is not guaranteed to be harmless. + * + * This is a bridge for as long as distributed transport still goes through SQL text. Once query plan serialization + * replaces that boundary, the marker should become unnecessary. + */ class FunctionAliasMarker : public IFunction { public: @@ -115,20 +144,31 @@ class FunctionAliasMarker : public IFunction static FunctionPtr create(ContextPtr) { return std::make_shared(); } String getName() const override { return name; } - size_t getNumberOfArguments() const override { return 2; } - ColumnNumbers getArgumentsThatAreAlwaysConstant() const override { return {1}; } + size_t getNumberOfArguments() const override { return 0; } + bool isVariadic() const override { return true; } + /// Index 2 exists only on the pending form; the executable and DAG builder ignore absent constant arguments. + ColumnNumbers getArgumentsThatAreAlwaysConstant() const override { return {2}; } bool isSuitableForConstantFolding() const override { return false; } bool isSuitableForShortCircuitArgumentsExecution(const DataTypesWithConstInfo & /*arguments*/) const override { return false; } + /// Validate the token even if an argument is `NULL` or has type `Nothing`. + bool useDefaultImplementationForNulls() const override { return false; } + bool useDefaultImplementationForNothing() const override { return false; } + /// Preserve the payload type: the planner replaces the marker with its child without conversion. + bool useDefaultImplementationForLowCardinalityColumns() const override { return false; } - DataTypePtr getReturnTypeImpl(const DataTypes & arguments) const override + DataTypePtr getReturnTypeImpl(const ColumnsWithTypeAndName & arguments) const override { - if (arguments.size() != 2) - throw Exception(ErrorCodes::BAD_ARGUMENTS, "Function __aliasMarker expects 2 arguments"); + if (arguments.size() != 2 && arguments.size() != 3) + throw Exception(ErrorCodes::BAD_ARGUMENTS, "Function __aliasMarker expects 2 arguments, or 3 for its internal pending form"); - if (!WhichDataType(arguments[1]).isString()) - throw Exception(ErrorCodes::BAD_ARGUMENTS, "Function __aliasMarker is internal and should not be used directly"); + if (arguments.size() == 3) + { + const auto * token = arguments[2].column ? checkAndGetColumn(arguments[2].column.get()) : nullptr; + if (!WhichDataType(arguments[2].type).isString() || !token || token->getValue() != AliasMarkerName::pending_token) + throw Exception(ErrorCodes::BAD_ARGUMENTS, "Invalid internal pending __aliasMarker token"); + } - return arguments.front(); + return arguments.front().type; } ColumnPtr executeImpl(const ColumnsWithTypeAndName & arguments, const DataTypePtr &, size_t /*input_rows_count*/) const override diff --git a/src/Interpreters/ClusterProxy/executeQuery.cpp b/src/Interpreters/ClusterProxy/executeQuery.cpp index 89e80bb9cb7a..03fa1f1a1b75 100644 --- a/src/Interpreters/ClusterProxy/executeQuery.cpp +++ b/src/Interpreters/ClusterProxy/executeQuery.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -1169,6 +1170,8 @@ std::optional executeInsertSelectWithParallelReplicas( { InterpreterSelectQueryAnalyzer analyzer(query_ast.select, new_context, {}); const auto & query_tree = analyzer.getQueryTree(); + /// This parallel `INSERT SELECT` path renders directly, bypassing `queryNodeToDistributedSelectQuery`. + assertNoPendingAliasMarkersForDistributedSerialization(query_tree); auto select_ast = query_tree->toAST(); auto new_query_ast = query_ast.clone(); diff --git a/src/Planner/PlannerActionsVisitor.cpp b/src/Planner/PlannerActionsVisitor.cpp index cf347f8df6c4..48121345da44 100644 --- a/src/Planner/PlannerActionsVisitor.cpp +++ b/src/Planner/PlannerActionsVisitor.cpp @@ -95,6 +95,16 @@ String calculateActionNodeNameWithCastIfNeeded(const ConstantNode & constant_nod return buffer.str(); } +/// Return a two-argument `__aliasMarker`'s string id, or an empty string when it has none. +/// A three-argument pending marker always resolves to its payload in an intermediate plan. +String tryExtractAliasMarkerId(const QueryTreeNodePtr & id_argument) +{ + if (const auto * id_node = id_argument->as(); id_node && isString(id_node->getResultType())) + return id_node->getValue().safeGet(); + + return {}; +} + class ActionNodeNameHelper { public: @@ -198,18 +208,19 @@ class ActionNodeNameHelper const auto & function_node = node->as(); if (function_node.getFunctionName() == "__aliasMarker") { - /// Perform sanity check, because user may call this function with unexpected arguments const auto & function_argument_nodes = function_node.getArguments().getNodes(); + if (function_argument_nodes.size() != 2 && function_argument_nodes.size() != 3) + throw Exception(ErrorCodes::BAD_ARGUMENTS, "Function __aliasMarker expects 2 or 3 arguments"); + if (function_argument_nodes.size() == 2) - { - if (const auto * second_argument = function_argument_nodes.at(1)->as()) - { - if (isString(second_argument->getResultType())) - result = second_argument->getValue().safeGet(); - } - } + result = tryExtractAliasMarkerId(function_argument_nodes.at(1)); - /// Empty node name is not allowed and leads to logical errors + /// A pending marker, or a hand-written one without a string id, is named after its payload. + /// The pending form can reach an intermediate planner, but must be finalized before shipping. + if (result.empty()) + result = calculateActionNodeName(function_argument_nodes.at(0)); + + /// An empty node name is not allowed and leads to logical errors. if (result.empty()) throw Exception(ErrorCodes::BAD_ARGUMENTS, "Function __aliasMarker is internal and should not be used directly"); break; @@ -1256,18 +1267,18 @@ PlannerActionsVisitorImpl::NodeNameAndNodeMinLevel PlannerActionsVisitorImpl::vi if (function_node.getFunctionName() == "__aliasMarker") { const auto & function_arguments = function_node.getArguments().getNodes(); - if (function_arguments.size() != 2) - throw Exception(ErrorCodes::BAD_ARGUMENTS, "Function __aliasMarker expects 2 arguments"); + if (function_arguments.size() != 2 && function_arguments.size() != 3) + throw Exception(ErrorCodes::BAD_ARGUMENTS, "Function __aliasMarker expects 2 or 3 arguments"); - const auto * alias_id_node = function_arguments.at(1)->as(); - if (!alias_id_node || !isString(alias_id_node->getResultType())) - throw Exception(ErrorCodes::BAD_ARGUMENTS, "Function __aliasMarker is internal and should not be used directly"); + auto [child_name, levels] = visitImpl(function_arguments.at(0)); - const auto & alias_id = alias_id_node->getValue().safeGet(); + /// An intermediate pending marker and a hand-written marker without a string id resolve to their payload. + String alias_id; + if (function_arguments.size() == 2) + alias_id = tryExtractAliasMarkerId(function_arguments.at(1)); if (alias_id.empty()) - throw Exception(ErrorCodes::BAD_ARGUMENTS, "Function __aliasMarker is internal and should not be used directly"); + alias_id = child_name; - auto [child_name, levels] = visitImpl(function_arguments.at(0)); if (alias_id == child_name) return {child_name, levels}; diff --git a/src/Planner/Utils.cpp b/src/Planner/Utils.cpp index f48515204da2..dcfe1e5476fe 100644 --- a/src/Planner/Utils.cpp +++ b/src/Planner/Utils.cpp @@ -317,6 +317,9 @@ void normalizeAliasMarkersInQueryTree(QueryTreeNodePtr & node) ASTPtr queryNodeToDistributedSelectQuery(const QueryTreeNodePtr & query_node) { + /// Check before normalization: it can discard a nested marker and hide an unfinalized pending id. + assertNoPendingAliasMarkersForDistributedSerialization(query_node); + /// Remove CTEs information from distributed queries. /// Now, if cte_name is set for subquery node, AST -> String serialization will only print cte name. /// But CTE is defined only for top-level query part, so may not be sent. diff --git a/src/Processors/QueryPlan/ParallelReplicasLocalPlan.cpp b/src/Processors/QueryPlan/ParallelReplicasLocalPlan.cpp index d63fd813be14..a88baa30a992 100644 --- a/src/Processors/QueryPlan/ParallelReplicasLocalPlan.cpp +++ b/src/Processors/QueryPlan/ParallelReplicasLocalPlan.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -116,6 +117,9 @@ std::shared_ptr createRemotePlanForParallelReplicas( { checkStackSize(); + /// A pending marker planned here would resolve to its payload and disappear before the plan was shipped. + assertNoPendingAliasMarkersForDistributedSerialization(query_tree); + auto new_context = Context::createCopy(context); auto select_query_options = SelectQueryOptions(processed_stage); diff --git a/src/Storages/IStorageCluster.cpp b/src/Storages/IStorageCluster.cpp index fe1a51a861c0..83c756ff39c3 100644 --- a/src/Storages/IStorageCluster.cpp +++ b/src/Storages/IStorageCluster.cpp @@ -437,6 +437,11 @@ void IStorageCluster::read( auto cluster_name_from_settings = getClusterName(context); const auto & settings = context->getSettingsRef(); + /// The ALLOW path can forward the original AST without calling `queryNodeToDistributedSelectQuery`. + if (settings[Setting::allow_experimental_analyzer] + && (!cluster_name_from_settings.empty() || settings[Setting::object_storage_remote_initiator])) + assertNoPendingAliasMarkersForDistributedSerialization(query_info.query_tree); + ASTPtr query_to_send = query_info.query; if (cluster_name_from_settings.empty()) diff --git a/src/Storages/StorageDistributed.cpp b/src/Storages/StorageDistributed.cpp index a51044cfe76d..2bae73f364a1 100644 --- a/src/Storages/StorageDistributed.cpp +++ b/src/Storages/StorageDistributed.cpp @@ -10,7 +10,6 @@ #include #include #include -#include #include #include @@ -55,7 +54,6 @@ #include #include -#include #include #include #include @@ -213,7 +211,6 @@ namespace Setting extern const SettingsBool skip_unavailable_shards; extern const SettingsBool enable_global_with_statement; extern const SettingsBool allow_experimental_hybrid_table; - extern const SettingsBool enable_alias_marker; } namespace DistributedSetting @@ -849,73 +846,6 @@ StorageSnapshotPtr StorageDistributed::getStorageSnapshot(const StorageMetadataP namespace { -class ReplaseAliasColumnsVisitor : public InDepthQueryTreeVisitor -{ - QueryTreeNodePtr getColumnNodeAliasExpression(const QueryTreeNodePtr & node) const - { - const auto * column_node = node->as(); - if (!column_node || !column_node->hasExpression()) - return nullptr; - - const auto & column_source = column_node->getColumnSourceOrNull(); - if (!column_source || column_source->getNodeType() == QueryTreeNodeType::JOIN - || column_source->getNodeType() == QueryTreeNodeType::CROSS_JOIN - || column_source->getNodeType() == QueryTreeNodeType::ARRAY_JOIN) - return nullptr; - - auto column_expression = column_node->getExpression(); - const auto & column_name = column_node->getColumnName(); - - if (!context->getSettingsRef()[Setting::enable_alias_marker]) - { - column_expression->setAlias(column_name); - return column_expression; - } - - String alias_id; - const auto & source_alias = column_source->getAlias(); - if (!source_alias.empty()) - alias_id = source_alias + "." + column_name; - else - alias_id = column_name; - - if (auto * function_node = column_expression->as(); - function_node && function_node->getFunctionName() == "__aliasMarker") - { - auto & arguments = function_node->getArguments().getNodes(); - if (arguments.size() == 2) - arguments[1] = std::make_shared(alias_id, std::make_shared()); - - column_expression->setAlias(column_name); - return column_expression; - } - - QueryTreeNodes arguments; - arguments.reserve(2); - arguments.emplace_back(std::move(column_expression)); - arguments.emplace_back(std::make_shared(alias_id, std::make_shared())); - - auto alias_marker_node = std::make_shared("__aliasMarker"); - alias_marker_node->getArguments().getNodes() = std::move(arguments); - alias_marker_node->setAlias(column_name); - resolveOrdinaryFunctionNodeByName(*alias_marker_node, "__aliasMarker", context); - - return alias_marker_node; - } - -public: - explicit ReplaseAliasColumnsVisitor(ContextPtr context_) : context(std::move(context_)) {} - - void visitImpl(QueryTreeNodePtr & node) - { - if (auto column_expression = getColumnNodeAliasExpression(node)) - node = column_expression; - } - -private: - ContextPtr context; -}; - using ColumnNameToColumnNodeMap = std::unordered_map; ColumnNameToColumnNodeMap buildColumnNodesForTableExpression(const QueryTreeNodePtr & table_expression_node, const ContextPtr & context) @@ -1151,7 +1081,7 @@ QueryTreeNodePtr buildQueryTreeDistributed(SelectQueryInfo & query_info, * (including fully-resolved ALIAS expressions) and rewrite the whole query tree * so all references to the replaced table share the same column source and * the same alias semantics. This keeps SELECT and WHERE consistent before - * ReplaseAliasColumnsVisitor performs final alias expansion. + * inlineAliasColumns performs final alias expansion. */ ReplaceColumnNodesForTableExpressionVisitor replace_query_columns_visitor( replacement_table_expression, @@ -1160,8 +1090,7 @@ QueryTreeNodePtr buildQueryTreeDistributed(SelectQueryInfo & query_info, replace_query_columns_visitor.visit(query_tree_to_modify); } - ReplaseAliasColumnsVisitor replace_alias_columns_visitor(query_context); - replace_alias_columns_visitor.visit(query_tree_to_modify); + inlineAliasColumns(query_tree_to_modify, query_context); const auto & settings = query_context->getSettingsRef(); @@ -1174,8 +1103,9 @@ QueryTreeNodePtr buildQueryTreeDistributed(SelectQueryInfo & query_info, rewriteJoinToGlobalJoinIfNeeded(query_node.getJoinTree()); } + /// `buildQueryTreeForShard` materializes the marker ids on the way out, after the `__tableN` renumbering they are + /// built from. return buildQueryTreeForShard(query_info.planner_context, query_tree_to_modify, /*allow_global_join_for_right_table*/ false); - } std::optional> tryGetParamTypeAndName(const ASTPtr & node) diff --git a/src/Storages/StorageMerge.cpp b/src/Storages/StorageMerge.cpp index 62e0d04487f1..9a57de12e3c3 100644 --- a/src/Storages/StorageMerge.cpp +++ b/src/Storages/StorageMerge.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -942,7 +943,16 @@ std::vector ReadFromMerge::createChildrenPlans(SelectQ { /// Source tables could have different but convertible types, like numeric types of different width. /// We must return streams with structure equals to structure of Merge table. - convertAndFilterSourceStream(*common_header, modified_query_info, nested_storage_snapshot, aliases, row_policy_data_opt, context, child, is_smallest_column_requested); + convertAndFilterSourceStream( + *common_header, + modified_query_info, + nested_storage_snapshot, + merge_storage_snapshot->metadata->getColumns(), + aliases, + row_policy_data_opt, + context, + child, + is_smallest_column_requested); for (const auto & filter_info : pushed_down_filters) { @@ -1412,14 +1422,23 @@ ReadFromMerge::RowPolicyData::RowPolicyData(RowPolicyFilterPtr row_policy_filter NamesAndTypesList added; NamesAndTypesList deleted; sample_block_columns.getDifference(required_columns, added, deleted); - if (!deleted.empty() || added.size() != 1) + if (deleted.empty() && added.empty() && expr->as() + && actions_dag.findInOutputs(expr->getColumnName()).type == ActionsDAG::ActionType::INPUT) + { + /// A bare column policy filters on an existing input, which must remain available after filtering. + filter_column_name = expr->getColumnName(); + remove_filter_column = false; + } + else if (deleted.empty() && added.size() == 1) + { + filter_column_name = added.getNames().front(); + } + else { throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot determine row level filter; {} columns deleted, {} columns added", deleted.size(), added.size()); } - - filter_column_name = added.getNames().front(); } void ReadFromMerge::RowPolicyData::extendNames(Names & names) const @@ -1446,10 +1465,49 @@ void ReadFromMerge::RowPolicyData::addStorageFilter(SourceStepWithFilter * step) step->addFilter(actions_dag.clone(), filter_column_name); } -void ReadFromMerge::RowPolicyData::addFilterTransform(QueryPlan & plan) const +void ReadFromMerge::RowPolicyData::addFilterTransform(QueryPlan & plan, const String & table_alias) const { - auto filter_step = std::make_unique(plan.getCurrentHeader(), actions_dag.clone(), filter_column_name, true /* remove filter column */); + const auto original_header = plan.getCurrentHeader(); + const auto & header = *original_header; + ActionsDAG naming_actions(header.getColumnsWithTypeAndName()); + bool added_aliases = false; + /// The policy was analyzed against the child's plain column names. Expose the same values + /// under those names when the analyzer qualified them in the child stream. + for (const auto & column_name : filter_actions->getRequiredColumns()) + { + if (header.has(column_name)) + continue; + + const String identifier = table_alias.empty() ? column_name : backQuoteIfNeed(table_alias) + "." + backQuoteIfNeed(column_name); + if (!header.has(identifier)) + continue; + + naming_actions.addOrReplaceInOutputs(naming_actions.addAlias(naming_actions.findInOutputs(identifier), column_name)); + added_aliases = true; + } + + if (added_aliases) + { + auto naming_step = std::make_unique(plan.getCurrentHeader(), std::move(naming_actions)); + plan.addStep(std::move(naming_step)); + } + + auto filter_step = std::make_unique(plan.getCurrentHeader(), actions_dag.clone(), filter_column_name, remove_filter_column); plan.addStep(std::move(filter_step)); + + if (added_aliases) + { + /// Do not let policy-only names change the column count or order used for the later + /// positional reconciliation with the Merge table's expected header. + ActionsDAG projection_actions(plan.getCurrentHeader()->getColumnsWithTypeAndName()); + NamesWithAliases original_names; + original_names.reserve(header.columns()); + for (const auto & column : header) + original_names.emplace_back(column.name, ""); + projection_actions.project(original_names); + auto projection_step = std::make_unique(plan.getCurrentHeader(), std::move(projection_actions)); + plan.addStep(std::move(projection_step)); + } } StorageMerge::StorageListWithLocks ReadFromMerge::getSelectedTables( @@ -1632,6 +1690,7 @@ void ReadFromMerge::convertAndFilterSourceStream( const Block & header, SelectQueryInfo & modified_query_info, const StorageSnapshotPtr & snapshot, + const ColumnsDescription & merge_columns, const Aliases & aliases, const RowPolicyDataOpt & row_policy_data_opt, ContextPtr local_context, @@ -1642,12 +1701,112 @@ void ReadFromMerge::convertAndFilterSourceStream( auto pipe_columns = before_block_header->getNamesAndTypesList(); + /// TODO(storage-merge-alias): the analyzer branch below recomputes by hand what the analyzer's own column-alias + /// resolution would produce if it ran end to end on the child plan. It exists because the work is split in two: + /// + /// Step 1, in `getModifiedQueryInfo`: rewrite the query going to the child storage. `replaceColumns` swaps every + /// reference to a Merge-level ALIAS column for its resolved expression, so the child storage is asked for the + /// PHYSICAL columns those expressions need and never sees the alias names. + /// + /// Step 2, here: recompute the alias VALUES at the Merge level from those physical columns, by building a fresh + /// `ActionsDAG`, running `QueryAnalysisPass` over each alias expression and visiting it with + /// `PlannerActionsVisitor`. Each alias output is emitted under its analyzer identifier so the Merge target + /// header, which also uses analyzer identifiers, can pick it up by name. + /// + /// The awkward part is that alias values are computed AFTER the child's read, not inside it. A predicate over an + /// ALIAS column can still use the underlying physical column for index analysis, but only because Step 1 happens + /// to inline the alias expression into the predicate as well, so `KeyCondition` sees `col * 2 > 10` rather than + /// `alias > 10`. Output-side aliases are recomputed here even when the child already produced the same value -- + /// a `Distributed` child, for instance, evaluates alias expressions on the shard and returns them as + /// expression-named columns, which this then computes a second time. + /// + /// The unification would be to have Step 1 emit `__aliasMarker(, '')` instead + /// of a bare resolved expression. `PlannerActionsVisitor` resolves the marker at plan-build time and the call + /// disappears from the resulting `ActionsDAG`, leaving an ordinary action node that computes `` + /// under ``. Predicate and `KeyCondition` analysis would be unaffected, because the marker is a + /// planner-time naming device rather than a runtime expression. Step 2 would then be deletable outright: + /// `pipe_columns` would already carry the alias values under the right names. + /// + /// Left as future work. What is here is correct -- Step 1 and Step 2 together produce the right values -- just not + /// minimal. if (local_context->getSettingsRef()[Setting::allow_experimental_analyzer]) { + /// The Merge table wants its columns under analyzer identifiers (`__table1.a`, ``__table1.`n.a` ``), while alias + /// expressions and `alias.name` speak in plain logical names (`a`, `n.a`). + /// + /// The planner's `TableExpressionData` for the Merge node is not populated yet at this point -- column + /// collection happens later, in `CollectTableExpressionData` -- so the mapping cannot be looked up through + /// `PlannerContext`. For inputs, match each Merge-declared column name against the suffixes of + /// the identifier names in `pipe_columns`: a column named + /// `.` belongs to the Merge column `C`. + /// + /// Matching declared Merge names with the planner's quoting rule also handles dotted names, + /// `Nested` subcolumns, and names requiring backticks. + + /// `getAll` builds a fresh list on every call, so take it once rather than per candidate column. + const auto all_merge_columns = merge_columns.getAll(); + + auto build_plain_to_identifier = [&merge_columns, &all_merge_columns](const auto & candidate_names) + { + std::unordered_map plain_to_identifier; + std::unordered_set ambiguous; + for (const auto & column : candidate_names) + { + /// An exact match means the column carries no analyzer prefix at all. + if (merge_columns.has(column.name)) + { + if (!plain_to_identifier.emplace(column.name, column.name).second) + ambiguous.insert(column.name); + continue; + } + + /// Otherwise match the quoted identifier component emitted by `buildColumnIdentifier`. + /// Columns matching no declared name are intermediate outputs of the child plan. + for (const auto & merge_column : all_merge_columns) + { + const String qualified_suffix = "." + backQuoteIfNeed(merge_column.name); + if (column.name.ends_with(qualified_suffix)) + { + if (!plain_to_identifier.emplace(merge_column.name, column.name).second) + ambiguous.insert(merge_column.name); + break; + } + } + } + for (const auto & ambiguous_name : ambiguous) + plain_to_identifier.erase(ambiguous_name); + return plain_to_identifier; + }; + + const auto pipe_plain_to_identifier = build_plain_to_identifier(pipe_columns); + for (const auto & alias : aliases) { ActionsDAG actions_dag(pipe_columns); + /// Alias expressions name their inputs in plain logical names, but the child stream's inputs carry analyzer + /// identifiers. Expose every mapped column under its plain name as well, so that `buildQueryTree` below + /// resolves a reference like `a` or `n.a` against an input named `__table1.a` or ``__table1.`n.a` ``. An + /// alias defined over another alias needs this. + for (const auto & [plain, identifier] : pipe_plain_to_identifier) + { + if (plain == identifier) + continue; + + const ActionsDAG::Node * input_node = nullptr; + for (const auto * candidate : actions_dag.getInputs()) + { + if (candidate->result_name == identifier) + { + input_node = candidate; + break; + } + } + + if (input_node) + actions_dag.addAlias(*input_node, plain); + } + QueryTreeNodePtr query_tree = buildQueryTree(alias.expression, local_context); query_tree->setAlias(alias.name); @@ -1664,7 +1823,13 @@ void ReadFromMerge::convertAndFilterSourceStream( if (nodes.size() != 1) throw Exception(ErrorCodes::LOGICAL_ERROR, "Expected to have 1 output but got {}", nodes.size()); - actions_dag.addOrReplaceInOutputs(actions_dag.addAlias(*nodes.front(), alias.name)); + /// Emit an alias needed by the output header under its analyzer identifier. An alias + /// needed only as an input to another alias keeps its plain logical name. + const auto & source_alias = modified_query_info.table_expression->getAlias(); + const String expected_identifier = source_alias.empty() + ? alias.name : backQuoteIfNeed(source_alias) + "." + backQuoteIfNeed(alias.name); + const String & output_name = header.has(expected_identifier) ? expected_identifier : alias.name; + actions_dag.addOrReplaceInOutputs(actions_dag.addAlias(*nodes.front(), output_name)); auto expression_step = std::make_unique(child.plan.getCurrentHeader(), std::move(actions_dag)); child.plan.addStep(std::move(expression_step)); } @@ -1687,7 +1852,8 @@ void ReadFromMerge::convertAndFilterSourceStream( /// This is the filter for the individual source table, that's why filtering has to be done before all structure adaptations. if (row_policy_data_opt) - row_policy_data_opt->addFilterTransform(child.plan); + row_policy_data_opt->addFilterTransform( + child.plan, modified_query_info.table_expression ? modified_query_info.table_expression->getAlias() : ""); /** Output headers may differ from what StorageMerge expects in some cases. * When the child table engine produces a query plan for the stage after FetchColumns, @@ -1738,6 +1904,8 @@ void ReadFromMerge::convertAndFilterSourceStream( } } + /// Position, because the loop above builds `converted_columns[i]` out of `current_step_columns[i]`: the pairing is + /// fixed by that construction rather than by the names, and one of the branches deliberately renames a column. auto convert_actions_dag = ActionsDAG::makeConvertingActions( current_step_columns, converted_columns, diff --git a/src/Storages/StorageMerge.h b/src/Storages/StorageMerge.h index 99d815791fea..7d08bea8e7c1 100644 --- a/src/Storages/StorageMerge.h +++ b/src/Storages/StorageMerge.h @@ -258,10 +258,11 @@ class ReadFromMerge final : public SourceStepWithFilter /// Create explicit filter transform to exclude /// rows that are not conform to row level policy - void addFilterTransform(QueryPlan &) const; + void addFilterTransform(QueryPlan &, const String & table_alias) const; private: std::string filter_column_name; // complex filter, may contain logic operations + bool remove_filter_column = true; ActionsDAG actions_dag; ExpressionActionsPtr filter_actions; StorageMetadataPtr storage_metadata_snapshot; @@ -306,6 +307,7 @@ class ReadFromMerge final : public SourceStepWithFilter const Block & header, SelectQueryInfo & modified_query_info, const StorageSnapshotPtr & snapshot, + const ColumnsDescription & merge_columns, const Aliases & aliases, const RowPolicyDataOpt & row_policy_data_opt, ContextPtr context, diff --git a/src/Storages/buildQueryTreeForShard.cpp b/src/Storages/buildQueryTreeForShard.cpp index 9669cd40ad7e..1b4c5a82e052 100644 --- a/src/Storages/buildQueryTreeForShard.cpp +++ b/src/Storages/buildQueryTreeForShard.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -62,6 +63,7 @@ namespace Setting extern const SettingsBool parallel_replicas_prefer_local_join; extern const SettingsBool prefer_global_in_and_join; extern const SettingsBool enable_add_distinct_to_in_subqueries; + extern const SettingsBool enable_alias_marker; extern const SettingsInt64 optimize_const_name_size; extern const SettingsOverflowMode transfer_overflow_mode; extern const SettingsObjectStorageClusterJoinMode object_storage_cluster_join_mode; @@ -78,6 +80,224 @@ namespace ErrorCodes namespace { +/// Return a clone of the defining expression of an inlineable `ALIAS` column node, or nullptr otherwise. +/// A JOIN / CROSS_JOIN / ARRAY_JOIN source puts a `ListNode` of the joined sides in the expression child, which is not +/// an alias body. The expression is cloned so each occurrence gets its own copy: that lets one occurrence be aliased +/// (a projection output) without mutating another (a reference in `ORDER BY`). +QueryTreeNodePtr getInlineableAliasColumnExpression(const QueryTreeNodePtr & node) +{ + const auto * column_node = node->as(); + if (!column_node || !column_node->hasExpression()) + return nullptr; + + const auto & column_source = column_node->getColumnSourceOrNull(); + if (!column_source || column_source->getNodeType() == QueryTreeNodeType::JOIN + || column_source->getNodeType() == QueryTreeNodeType::CROSS_JOIN + || column_source->getNodeType() == QueryTreeNodeType::ARRAY_JOIN) + return nullptr; + + return column_node->getExpression()->clone(); +} + +/// Wrap an inlined `ALIAS` expression in `__aliasMarker` so it carries the identity of the column it came from. +/// +/// The id goes in as a live `ColumnNode`, not as a finished string. Later analyzer passes still have to run -- +/// `createUniqueAliasesIfNecessary` in particular, which assigns the `__tableN` aliases the id is built from -- and a +/// `ColumnNode` is transformed by those passes exactly as the rest of the tree is. +/// `finalizeAliasMarkersForDistributedSerialization` turns it into a `String` constant at the serialization boundary. +/// +/// An expression that is already a marker has its id replaced rather than gaining a second wrapper. +QueryTreeNodePtr wrapInAliasMarker(QueryTreeNodePtr expression, const ColumnNode & column_node, const ContextPtr & context) +{ + auto marker_id = std::make_shared(column_node.getColumn(), column_node.getColumnSourceOrNull()); + auto token = std::make_shared(String(AliasMarkerName::pending_token), std::make_shared()); + + if (auto * function_node = expression->as(); + function_node && function_node->getFunctionName() == "__aliasMarker") + { + auto & marker_arguments = function_node->getArguments().getNodes(); + if (marker_arguments.size() == 2 || marker_arguments.size() == 3) + { + marker_arguments[1] = std::move(marker_id); + marker_arguments.resize(2); + marker_arguments.emplace_back(std::move(token)); + resolveOrdinaryFunctionNodeByName(*function_node, "__aliasMarker", context); + return expression; + } + } + + QueryTreeNodes arguments; + arguments.reserve(3); + arguments.emplace_back(std::move(expression)); + arguments.emplace_back(std::move(marker_id)); + arguments.emplace_back(std::move(token)); + + auto marker_node = std::make_shared("__aliasMarker"); + marker_node->getArguments().getNodes() = std::move(arguments); + resolveOrdinaryFunctionNodeByName(*marker_node, "__aliasMarker", context); + + return marker_node; +} + +/// Inlines `ALIAS` columns into their defining expressions across a query tree that is about to be shipped. +/// See `inlineAliasColumns` in the header for what the pass guarantees and why. +struct AliasColumnInliner +{ + ContextPtr context; + bool use_alias_marker; + + /// Replace one inlineable `ALIAS` column node with its body, marking the body when markers are on. + /// Returns false when `node` is not an inlineable `ALIAS` column, leaving it untouched. + bool inlineOnce(QueryTreeNodePtr & node) const + { + auto expression = getInlineableAliasColumnExpression(node); + if (!expression) + return false; + + if (use_alias_marker) + expression = wrapInAliasMarker(std::move(expression), node->as(), context); + + node = std::move(expression); + return true; + } + + /// Inline `ALIAS` columns inside an expression subtree without assigning any alias. A nested subquery goes back + /// through `inlineQuery` so its own projection columns keep their names. + void inlineExpression(QueryTreeNodePtr & node) const + { + if (node->as() || node->as()) + { + inlineQuery(node); + return; + } + + /// An `ALIAS` column may be defined over another one, so keep unwrapping. With markers on the loop stops after + /// the first step, because the wrapper is a function node; the column it wrapped is reached through the + /// children below and gets its own marker there. + /// + /// A marker that ends up nested inside the payload expression -- `computed ALIAS inner * 2` -- is kept as it + /// is. One that ends up as the payload itself -- `computed ALIAS inner` -- is collapsed by + /// `NormalizeAliasMarkerVisitor` when the tree is rendered to SQL, and nothing is lost by that: the inner id + /// names an intermediate action node, while the name the initiator matches against comes from the outer id. + while (inlineOnce(node)) + { + } + + if (auto * marker_node = node->as(); + marker_node && marker_node->getFunctionName() == "__aliasMarker") + { + auto & marker_arguments = marker_node->getArguments().getNodes(); + if (marker_arguments.size() == 3) + { + /// The pending form's second argument is an injected column identity, not a column read by the query. + /// A hand-written two-argument marker is traversed normally: its id must resolve in the shipped SQL. + inlineExpression(marker_arguments[0]); + return; + } + } + + auto * join_node = node->as(); + const bool using_join = join_node && join_node->isUsingJoinExpression(); + + for (auto & child : node->getChildren()) + { + if (!child) + continue; + + if (using_join && child == join_node->getJoinExpression()) + inlineJoinUsingKeys(child); + else + inlineExpression(child); + } + } + + /// A `JOIN USING` key is a `ColumnNode` whose expression is a `ListNode` recording how the key resolves on each + /// side, and a side's entry can itself be an `ALIAS` column. Such an entry keeps the key's name as an alias when + /// inlined, because the shipped SQL renders the entry rather than the key: `USING (x AS a)` is what lets a remote + /// server resolve a key that exists only as an `ALIAS` column of the initiator's table. `rejectUnshippableJoinUsingKeys` + /// reads the same shape to decide which keys no remote server can resolve. + void inlineJoinUsingKeys(QueryTreeNodePtr & join_expression) const + { + auto * using_list = join_expression->as(); + if (!using_list) + return; + + for (auto & using_node : using_list->getNodes()) + { + auto * using_column = using_node->as(); + if (!using_column || !using_column->hasExpression()) + continue; + + auto * key_sides = using_column->getExpression()->as(); + if (!key_sides) + continue; + + for (auto & side : key_sides->getNodes()) + { + const auto * side_column = side->as(); + auto expression = getInlineableAliasColumnExpression(side); + if (!expression) + continue; + + const String key_name = side_column->getColumnName(); + if (use_alias_marker) + expression = wrapInAliasMarker(std::move(expression), *side_column, context); + + inlineExpression(expression); + expression->setAlias(key_name); + side = std::move(expression); + } + } + } + + /// Inline `ALIAS` columns into their defining expressions, so the expression is evaluated on the shard that reads + /// the real table instead of the column being resolved there as if it were physical. + /// + /// A top-level projection item keeps the column's logical name as an alias, so the mergeable-state output column + /// keeps its name. Inside expression clauses no alias is set: otherwise two same-named `ALIAS` columns from + /// different `JOIN` sources land in one scope carrying different bodies, and the shard throws + /// `MULTIPLE_EXPRESSIONS_FOR_ALIAS` (https://github.com/ClickHouse/ClickHouse/issues/107990). + void inlineQuery(QueryTreeNodePtr & node) const + { + if (auto * union_node = node->as()) + { + for (auto & query : union_node->getQueries().getNodes()) + inlineQuery(query); + return; + } + + auto * query_node = node->as(); + if (!query_node) + { + inlineExpression(node); + return; + } + + for (auto & projection_item : query_node->getProjection().getNodes()) + { + const auto * column_node = projection_item->as(); + auto expression = getInlineableAliasColumnExpression(projection_item); + if (!expression) + { + inlineExpression(projection_item); + continue; + } + + const String output_alias = column_node->getColumnName(); + if (use_alias_marker) + expression = wrapInAliasMarker(std::move(expression), *column_node, context); + + inlineExpression(expression); + expression->setAlias(output_alias); + projection_item = std::move(expression); + } + + for (auto & child : query_node->getChildren()) + if (child && child != query_node->getProjectionNode()) + inlineExpression(child); + } +}; + /// Visitor that collect column source to columns mapping from query and all subqueries class CollectColumnSourceToColumnsVisitor : public InDepthQueryTreeVisitor { @@ -104,8 +324,22 @@ class CollectColumnSourceToColumnsVisitor : public InDepthQueryTreeVisitoras(); + function_node && function_node->getFunctionName() == "__aliasMarker") + { + const auto & marker_arguments = function_node->getArguments().getNodes(); + if (marker_arguments.size() == 3 && marker_arguments[1]) + marker_id_nodes.insert(marker_arguments[1].get()); + return; + } + auto * column_node = node->as(); - if (!column_node) + if (!column_node || marker_id_nodes.contains(node.get())) return; auto column_source = column_node->getColumnSourceOrNull(); @@ -124,6 +358,7 @@ class CollectColumnSourceToColumnsVisitor : public InDepthQueryTreeVisitor column_source_to_columns; + std::unordered_set marker_id_nodes; }; /** Visitor that rewrites IN and JOINs in query and all subqueries according to distributed_product_mode and @@ -345,6 +580,14 @@ class ReplaceLongConstWithScalarVisitor : public InDepthQueryTreeVisitorWithCont void enterImpl(QueryTreeNodePtr & node) { + if (const auto * function_node = node->as(); + function_node && function_node->getFunctionName() == "__aliasMarker") + { + const auto & arguments = function_node->getArguments().getNodes(); + if (arguments.size() == 3 && arguments[2]) + pending_marker_tokens.insert(arguments[2].get()); + } + // Do not visit second argument of "in" functions if (!in_second_argument.empty() && in_second_argument.top() == node) { @@ -360,7 +603,8 @@ class ReplaceLongConstWithScalarVisitor : public InDepthQueryTreeVisitorWithCont auto * constant_node = node->as(); - if (!constant_node) + /// Keep pending tokens literal until the finalizer validates and removes them. + if (!constant_node || pending_marker_tokens.contains(node.get())) return; const auto * col_const = typeid_cast(constant_node->getColumn().get()); @@ -402,6 +646,7 @@ class ReplaceLongConstWithScalarVisitor : public InDepthQueryTreeVisitorWithCont private: Int64 max_size = 0; std::stack in_second_argument; + std::unordered_set pending_marker_tokens; }; // Helper function to add DISTINCT to all QueryNode objects inside a query/union subtree @@ -434,7 +679,14 @@ TableNodePtr executeSubqueryNode(const QueryTreeNodePtr & subquery_node, ContextMutablePtr & mutable_context, size_t subquery_depth) { - const auto subquery_hash = subquery_node->getTreeHash(); + /// A `GLOBAL IN` / `GLOBAL JOIN` subquery is materialized here and shipped as a temporary table, so this is a + /// serialization boundary too and the marker ids have to be finalized before the subquery runs. Finalizing also + /// makes the tree hash below stable: an unmaterialized marker hashes its `ColumnNode`, whose identifier can still + /// change, which would key the same subquery under two different temporary table names. + auto subquery_node_to_execute = subquery_node->clone(); + finalizeAliasMarkersForDistributedSerialization(subquery_node_to_execute, mutable_context); + + const auto subquery_hash = subquery_node_to_execute->getTreeHash(); const auto temporary_table_name = fmt::format("_data_{}", toString(subquery_hash)); const auto & external_tables = mutable_context->getExternalTables(); @@ -452,7 +704,7 @@ TableNodePtr executeSubqueryNode(const QueryTreeNodePtr & subquery_node, auto context_copy = Context::createCopy(mutable_context); updateContextForSubqueryExecution(context_copy); - InterpreterSelectQueryAnalyzer interpreter(subquery_node, context_copy, subquery_options); + InterpreterSelectQueryAnalyzer interpreter(subquery_node_to_execute, context_copy, subquery_options); auto & query_plan = interpreter.getQueryPlan(); auto sample_block_with_unique_names = *query_plan.getCurrentHeader(); @@ -738,6 +990,12 @@ void rejectUnshippableJoinUsingKeys(const QueryTreeNodePtr & root) } +void inlineAliasColumns(QueryTreeNodePtr & query_tree_to_modify, const ContextPtr & context) +{ + const AliasColumnInliner inliner{context, context->getSettingsRef()[Setting::enable_alias_marker]}; + inliner.inlineQuery(query_tree_to_modify); +} + QueryTreeNodePtr buildQueryTreeForShard( const PlannerContextPtr & planner_context, QueryTreeNodePtr query_tree_to_modify, @@ -913,6 +1171,12 @@ QueryTreeNodePtr buildQueryTreeForShard( scalar_visitor.visit(query_tree_to_modify); } + /// Last, because `createUniqueAliasesIfNecessary` above is what settles the `__tableN` aliases the marker ids are + /// built from: an id materialized any earlier would name the table alias the initiator happened to assign before + /// the renumbering rather than the one the shard will use. Here rather than in the callers, so that every path + /// that ships a query tree finalizes whatever markers it carries. + finalizeAliasMarkersForDistributedSerialization(query_tree_to_modify, planner_context->getQueryContext()); + return query_tree_to_modify; } diff --git a/src/Storages/buildQueryTreeForShard.h b/src/Storages/buildQueryTreeForShard.h index 5e3ae678dcf6..7730c43adc6b 100644 --- a/src/Storages/buildQueryTreeForShard.h +++ b/src/Storages/buildQueryTreeForShard.h @@ -27,6 +27,23 @@ QueryTreeNodePtr buildQueryTreeForShard( bool allow_global_join_for_right_table, bool find_cross_join = false); +/** Replace every `ALIAS` column node with its defining expression, so the expression is evaluated on the shard that reads + * the real table instead of the column being resolved there as if it were physical. + * + * Apply this to any query tree that is about to be shipped, before `buildQueryTreeForShard`: that function rebuilds a + * shipped table expression from column names and types only, which drops an `ALIAS` column's resolved expression and + * leaves the remote side asking storage for a column it does not have. + * + * When `enable_alias_marker` is on, each inlined expression is also wrapped in `__aliasMarker(expr, '')`. The + * marker preserves the identity of the logical column the expression was expanded from, so the initiator -- which + * still sees the un-inlined column -- can match the shard header by name instead of by position. + * + * The column's logical name is kept as an alias on top-level projection items and on `JOIN USING` key sides only. + * Aliasing an occurrence inside `WHERE` / `GROUP BY` / `ORDER BY` / `HAVING` / `JOIN ON` makes two same-named `ALIAS` + * columns from different sources collide in one scope, and the shard then throws `MULTIPLE_EXPRESSIONS_FOR_ALIAS`. + */ +void inlineAliasColumns(QueryTreeNodePtr & query_tree_to_modify, const ContextPtr & context); + void rewriteJoinToGlobalJoin(QueryTreeNodePtr query_tree_to_modify, ContextPtr context, bool force_prefer_global_join = false); void rewriteInToGlobalIn(QueryTreeNodePtr & query_tree_to_modify, ContextPtr context, bool rewrite_for_distributed = false); diff --git a/src/Storages/tests/gtest_alias_marker_pending.cpp b/src/Storages/tests/gtest_alias_marker_pending.cpp new file mode 100644 index 000000000000..f96fc2ea5c0c --- /dev/null +++ b/src/Storages/tests/gtest_alias_marker_pending.cpp @@ -0,0 +1,131 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace DB +{ +namespace ErrorCodes +{ + extern const int BAD_ARGUMENTS; +} +} + +using namespace DB; + +namespace +{ + +class MarkerTestTableNode final : public IQueryTreeNode +{ +public: + MarkerTestTableNode() : IQueryTreeNode(0) {} + + QueryTreeNodeType getNodeType() const override { return QueryTreeNodeType::TABLE; } + void dumpTreeImpl(WriteBuffer &, FormatState &, size_t) const override {} + bool isEqualImpl(const IQueryTreeNode &, CompareOptions) const override { return true; } + void updateTreeHashImpl(HashState &, CompareOptions) const override {} + QueryTreeNodePtr cloneImpl() const override { return std::make_shared(); } + ASTPtr toASTImpl(const ConvertToASTOptions &) const override { return nullptr; } +}; + +FunctionNodePtr makeMarker(QueryTreeNodes arguments) +{ + auto marker = std::make_shared(AliasMarkerName::name); + marker->getArguments().getNodes() = std::move(arguments); + return marker; +} + +} + +TEST(AliasMarkerPending, RejectNestedMarkerBeforeShipping) +{ + auto pending = makeMarker({std::make_shared(1), std::make_shared(1), + std::make_shared(String(AliasMarkerName::pending_token), std::make_shared())}); + auto outer = makeMarker({pending, std::make_shared(String("outer"), std::make_shared())}); + auto query = std::make_shared(Context::createCopy(getContext().context)); + query->getProjection().getNodes().push_back(outer); + QueryTreeNodePtr root = query; + + auto expect_pending_rejected = [](auto && ship) + { + try + { + ship(); + FAIL() << "A nested pending marker must not cross the shipping boundary"; + } + catch (const Exception & e) + { + EXPECT_EQ(e.code(), ErrorCodes::BAD_ARGUMENTS); + EXPECT_NE(e.message().find("cannot be shipped before finalization"), String::npos); + } + }; + + /// Both shipping entry points check before normalization or planning can discard the inner marker. + expect_pending_rejected([&] { assertNoPendingAliasMarkersForDistributedSerialization(root); }); + expect_pending_rejected([&] { (void)queryNodeToDistributedSelectQuery(root); }); + expect_pending_rejected([&] + { + (void)createRemotePlanForParallelReplicas(root, Block{}, query->getContext(), QueryProcessingStage::FetchColumns); + }); +} + +TEST(AliasMarkerPending, ProjectionAliasOnJoinUsingSide) +{ + tryRegisterFunctions(); + auto context = Context::createCopy(getContext().context); + context->setSetting("enable_alias_marker", Field(1)); + context->setSetting("analyzer_compatibility_join_using_top_level_identifier", Field(1)); + + auto left = std::make_shared(); + left->setAlias("__table1"); + auto right = std::make_shared(); + right->setAlias("__table2"); + + const auto type = std::make_shared(); + auto column = std::make_shared(NameAndTypePair("x", type), left); + /// Model the expression-bearing column that projection-alias resolution places on the left `USING` side. + auto projection_alias = std::make_shared(NameAndTypePair("k", type), column, left); + auto right_key = std::make_shared(NameAndTypePair("k", type), right); + auto key_sides = std::make_shared(QueryTreeNodes{projection_alias, right_key}); + auto using_key = std::make_shared(NameAndTypePair("k", type), key_sides, left); + auto using_list = std::make_shared(QueryTreeNodes{using_key}); + + auto join = std::make_shared(left, right, using_list, + JoinLocality::Unspecified, JoinStrictness::All, JoinKind::Inner, true); + auto query = std::make_shared(context); + query->getJoinTree() = join; + QueryTreeNodePtr root = query; + + inlineAliasColumns(root, context); + + const auto & side = key_sides->getNodes()[0]; + const auto * pending = side->as(); + ASSERT_NE(pending, nullptr); + ASSERT_EQ(pending->getArguments().getNodes().size(), 3); + EXPECT_EQ(pending->getArguments().getNodes()[1]->as().getColumnName(), "k"); + EXPECT_THROW(assertNoPendingAliasMarkersForDistributedSerialization(root), Exception); + + finalizeAliasMarkersForDistributedSerialization(root, context); + + const auto & finalized_arguments = pending->getArguments().getNodes(); + ASSERT_EQ(finalized_arguments.size(), 2); + EXPECT_EQ(finalized_arguments[1]->as().getValue().safeGet(), "__table1.k"); + EXPECT_NO_THROW(assertNoPendingAliasMarkersForDistributedSerialization(root)); +} diff --git a/tests/queries/0_stateless/03648_alias_marker_with_mergeable_state.sh b/tests/queries/0_stateless/03648_alias_marker_with_mergeable_state.sh index 66974be38517..79409806c073 100755 --- a/tests/queries/0_stateless/03648_alias_marker_with_mergeable_state.sh +++ b/tests/queries/0_stateless/03648_alias_marker_with_mergeable_state.sh @@ -4,8 +4,13 @@ CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh . "$CUR_DIR"/../shell_config.sh +# `--query_kind secondary_query` goes with every `--stage` below, because the point is to print the +# header a shard leg produces. A query marked as secondary skips AST-level optimizations +# (`PlannerContext.cpp`, `is_ast_level_optimization_allowed`) and changes how aggregation is split, +# neither of which a real shard leg would have applied. Inherited unchanged from Altinity#1844. + echo "---- stage: with_mergeable_state (analyzer=1, setting=enable_alias_marker=1) ----" -$CLICKHOUSE_CLIENT --enable_analyzer=1 --stage with_mergeable_state --multiquery 2>&1 <<'EOF' | sed -n '/^Header:/,/^ [^ ]/p' | sed '$d' +$CLICKHOUSE_CLIENT --enable_analyzer=1 --query_kind secondary_query --stage with_mergeable_state --multiquery 2>&1 <<'EOF' | sed -n '/^Header:/,/^ [^ ]/p' | sed '$d' SET enable_alias_marker=1; EXPLAIN header=1 SELECT sum(__aliasMarker(number*2-3,'foo')) AS x @@ -23,26 +28,26 @@ else fi echo "---- stage: complete (analyzer=1) ----" -$CLICKHOUSE_CLIENT --enable_analyzer=1 --stage complete --query \ +$CLICKHOUSE_CLIENT --enable_analyzer=1 --query_kind secondary_query --stage complete --query \ "EXPLAIN header=1 SELECT sum(__aliasMarker(number*2-3,'foo')) AS x FROM numbers(10)" \ 2>&1 | sed -n '/^Header:/,/^ [^ ]/p' | sed '$d' echo "---- stage: fetch_columns (analyzer=1) ----" -$CLICKHOUSE_CLIENT --enable_analyzer=1 --stage fetch_columns --query \ +$CLICKHOUSE_CLIENT --enable_analyzer=1 --query_kind secondary_query --stage fetch_columns --query \ "EXPLAIN header=1 SELECT sum(__aliasMarker(number*2-3,'foo')) AS x FROM numbers(10)" \ 2>&1 | sed -n '/^Header:/,/^ [^ ]/p' | sed '$d' echo "---- stage: with_mergeable_state (analyzer=1) ----" -$CLICKHOUSE_CLIENT --enable_analyzer=1 --stage with_mergeable_state --query \ +$CLICKHOUSE_CLIENT --enable_analyzer=1 --query_kind secondary_query --stage with_mergeable_state --query \ "EXPLAIN header=1 SELECT sum(__aliasMarker(number*2-3,'foo')) AS x FROM numbers(10)" \ 2>&1 | sed -n '/^Header:/,/^ [^ ]/p' | sed '$d' echo "---- stage: with_mergeable_state_after_aggregation (analyzer=1) ----" -$CLICKHOUSE_CLIENT --enable_analyzer=1 --stage with_mergeable_state_after_aggregation --query \ +$CLICKHOUSE_CLIENT --enable_analyzer=1 --query_kind secondary_query --stage with_mergeable_state_after_aggregation --query \ "EXPLAIN header=1 SELECT sum(__aliasMarker(number*2-3,'foo')) AS x FROM numbers(10)" \ 2>&1 | sed -n '/^Header:/,/^ [^ ]/p' | sed '$d' echo "---- stage: with_mergeable_state_after_aggregation_and_limit (analyzer=1) ----" -$CLICKHOUSE_CLIENT --enable_analyzer=1 --stage with_mergeable_state_after_aggregation_and_limit --query \ +$CLICKHOUSE_CLIENT --enable_analyzer=1 --query_kind secondary_query --stage with_mergeable_state_after_aggregation_and_limit --query \ "EXPLAIN header=1 SELECT sum(__aliasMarker(number*2-3,'foo')) AS x FROM numbers(10) GROUP BY intDiv(number,10) AS y ORDER BY y LIMIT 10" \ 2>&1 | sed -n '/^Header:/,/^ [^ ]/p' | sed '$d' diff --git a/tests/queries/0_stateless/03842_hybrid_alias_issue_1424.reference b/tests/queries/0_stateless/03842_hybrid_alias_issue_1424.reference new file mode 100644 index 000000000000..e6fabb5220b6 --- /dev/null +++ b/tests/queries/0_stateless/03842_hybrid_alias_issue_1424.reference @@ -0,0 +1,31 @@ +max in subquery +4294967294 +sum in subquery +-4921211434 +cte min with predicate +679772422 +cte with limit +-2147483648 -4294967296 +-1762862292 -574613778 +-1329695183 -1573638336 +cte without limit +-2147483648 -4294967296 +-1762862292 -574613778 +-1329695183 -1573638336 +-221724287 679772422 +0 0 +550067609 -3048000734 +1084637461 3417479706 +1169291374 -3082049462 +1899628504 -740161250 +2147483647 4294967294 +group by in subquery +10 10 +intersect with order by +-221724287 679772422 +1084637461 3417479706 +2147483647 4294967294 +constant alias in subquery +9 7 32 +constant alias predicate +2 diff --git a/tests/queries/0_stateless/03842_hybrid_alias_issue_1424.sql b/tests/queries/0_stateless/03842_hybrid_alias_issue_1424.sql new file mode 100644 index 000000000000..27f29faa1943 --- /dev/null +++ b/tests/queries/0_stateless/03842_hybrid_alias_issue_1424.sql @@ -0,0 +1,189 @@ +SET allow_experimental_hybrid_table = 1, enable_analyzer = 1, enable_alias_marker = 1; + +DROP TABLE IF EXISTS test_hybrid_issue_1424; +DROP TABLE IF EXISTS test_hybrid_issue_1424_left; +DROP TABLE IF EXISTS test_hybrid_issue_1424_right; +DROP TABLE IF EXISTS test_hybrid_issue_1424_const; +DROP TABLE IF EXISTS test_hybrid_issue_1424_const_left; +DROP TABLE IF EXISTS test_hybrid_issue_1424_const_right; + +CREATE TABLE test_hybrid_issue_1424_left +( + id Int32, + value Int32, + date_col Date, + computed ALIAS value * 2 +) +ENGINE = MergeTree +PARTITION BY toYYYYMM(date_col) +ORDER BY (date_col, id); + +INSERT INTO test_hybrid_issue_1424_left VALUES + (toInt32(2147483647), toInt32(2147483647), toDate('2149-06-06')), + (toInt32(-2147483648), toInt32(-2147483648), toDate('1970-01-01')), + (toInt32(0), toInt32(0), '1970-01-01'), + (toInt32(1084637461), toInt32(1708739853), toDate(1335613783)), + (toInt32(-221724287), toInt32(339886211), toDate(1294089763)), + (toInt32(-1762862292), toInt32(-287306889), toDate(1375707465)), + (toInt32(1169291374), toInt32(-1541024731), toDate(1082126480)), + (toInt32(-1329695183), toInt32(-786819168), toDate(1226000164)), + (toInt32(1899628504), toInt32(-370080625), toDate(1179050966)), + (toInt32(550067609), toInt32(-1524000367), toDate(1410654931)); + +CREATE TABLE test_hybrid_issue_1424_right +( + id Int32, + value Int32, + date_col Date, + computed ALIAS value * 2 +) +ENGINE = MergeTree +PARTITION BY toYYYYMM(date_col) +ORDER BY (date_col, id); + +INSERT INTO test_hybrid_issue_1424_right VALUES + (toInt32(2147483647), toInt32(2147483647), toDate('2149-06-06')), + (toInt32(-2147483648), toInt32(-2147483648), toDate('1970-01-01')), + (toInt32(0), toInt32(0), '1970-01-01'), + (toInt32(1084637461), toInt32(1708739853), toDate(1335613783)), + (toInt32(-221724287), toInt32(339886211), toDate(1294089763)), + (toInt32(-1762862292), toInt32(-287306889), toDate(1375707465)), + (toInt32(1169291374), toInt32(-1541024731), toDate(1082126480)), + (toInt32(-1329695183), toInt32(-786819168), toDate(1226000164)), + (toInt32(1899628504), toInt32(-370080625), toDate(1179050966)), + (toInt32(550067609), toInt32(-1524000367), toDate(1410654931)); + +CREATE TABLE test_hybrid_issue_1424 +( + id Int32, + value Int32, + date_col Date, + computed Int64 +) +ENGINE = Hybrid( + remote('127.0.0.1:9000', currentDatabase(), 'test_hybrid_issue_1424_left'), date_col >= '2025-01-15', + remote('127.0.0.1:9000', currentDatabase(), 'test_hybrid_issue_1424_right'), date_col < '2025-01-15' +); + +SELECT 'max in subquery'; +SELECT max_computed FROM (SELECT max(computed) AS max_computed FROM test_hybrid_issue_1424); + +SELECT 'sum in subquery'; +SELECT sum_computed FROM (SELECT sum(computed) AS sum_computed FROM test_hybrid_issue_1424); + +SELECT 'cte min with predicate'; +WITH cte AS +( + SELECT min(computed) AS min_computed + FROM test_hybrid_issue_1424 + WHERE computed > 50 +) +SELECT * FROM cte; + +SELECT 'cte with limit'; +WITH ranked AS +( + SELECT id, computed + FROM test_hybrid_issue_1424 + ORDER BY id + LIMIT 3 +) +SELECT * +FROM ranked +ORDER BY id ASC; + +SELECT 'cte without limit'; +WITH ranked AS +( + SELECT id, computed + FROM test_hybrid_issue_1424 +) +SELECT * +FROM ranked +ORDER BY id ASC; + +SELECT 'group by in subquery'; +WITH monthly AS +( + SELECT count() AS cnt + FROM test_hybrid_issue_1424 + GROUP BY computed +) +SELECT sum(cnt), count() FROM monthly; + +SELECT 'intersect with order by'; +SELECT * +FROM +( + SELECT id, computed + FROM test_hybrid_issue_1424 + WHERE computed > 100 + INTERSECT + SELECT id, computed + FROM test_hybrid_issue_1424 + WHERE value > 50 +) +ORDER BY id; + +CREATE TABLE test_hybrid_issue_1424_const_left +( + id Int32, + value Int32, + date_col Date, + computed ALIAS toInt64(7) +) +ENGINE = MergeTree +PARTITION BY toYYYYMM(date_col) +ORDER BY (date_col, id); + +INSERT INTO test_hybrid_issue_1424_const_left VALUES + (1, 1, toDate('2025-01-15')), + (2, 2, toDate('2025-02-01')); + +CREATE TABLE test_hybrid_issue_1424_const_right +( + id Int32, + value Int32, + date_col Date, + computed ALIAS toInt64(9) +) +ENGINE = MergeTree +PARTITION BY toYYYYMM(date_col) +ORDER BY (date_col, id); + +INSERT INTO test_hybrid_issue_1424_const_right VALUES + (3, 3, toDate('2024-12-31')), + (4, 4, toDate('2020-01-01')); + +CREATE TABLE test_hybrid_issue_1424_const +( + id Int32, + value Int32, + date_col Date, + computed Int64 +) +ENGINE = Hybrid( + remote('127.0.0.1:9000', currentDatabase(), 'test_hybrid_issue_1424_const_left'), date_col >= '2025-01-15', + remote('127.0.0.1:9000', currentDatabase(), 'test_hybrid_issue_1424_const_right'), date_col < '2025-01-15' +); + +SELECT 'constant alias in subquery'; +SELECT max_computed, min_computed, sum_computed +FROM +( + SELECT + max(computed) AS max_computed, + min(computed) AS min_computed, + sum(computed) AS sum_computed + FROM test_hybrid_issue_1424_const +); + +SELECT 'constant alias predicate'; +SELECT count() FROM test_hybrid_issue_1424_const WHERE computed = 9; + +DROP TABLE test_hybrid_issue_1424; +DROP TABLE test_hybrid_issue_1424_left; +DROP TABLE test_hybrid_issue_1424_right; +DROP TABLE test_hybrid_issue_1424_const; +DROP TABLE test_hybrid_issue_1424_const_left; +DROP TABLE test_hybrid_issue_1424_const_right; diff --git a/tests/queries/0_stateless/03844_distributed_nested_alias_marker.reference b/tests/queries/0_stateless/03844_distributed_nested_alias_marker.reference new file mode 100644 index 000000000000..7b05cb1e81a0 --- /dev/null +++ b/tests/queries/0_stateless/03844_distributed_nested_alias_marker.reference @@ -0,0 +1,4 @@ +analyzer +x x +legacy +x x diff --git a/tests/queries/0_stateless/03844_distributed_nested_alias_marker.sql b/tests/queries/0_stateless/03844_distributed_nested_alias_marker.sql new file mode 100644 index 000000000000..b725acf38949 --- /dev/null +++ b/tests/queries/0_stateless/03844_distributed_nested_alias_marker.sql @@ -0,0 +1,34 @@ +DROP TABLE IF EXISTS test_nested_alias_dist; +DROP TABLE IF EXISTS test_nested_alias_local; + +CREATE TABLE test_nested_alias_local +( + dt DateTime64(3), + base String, + a String ALIAS base, + b String ALIAS a +) +ENGINE = MergeTree() +ORDER BY dt; + +INSERT INTO test_nested_alias_local VALUES ('1999-03-29T01:15:33', 'x'); + +CREATE TABLE test_nested_alias_dist AS test_nested_alias_local +ENGINE = Distributed('test_shard_localhost', currentDatabase(), test_nested_alias_local, rand()); + +SELECT 'analyzer'; +SELECT a, b +FROM test_nested_alias_dist +ORDER BY dt +LIMIT 1 +SETTINGS enable_analyzer = 1; + +SELECT 'legacy'; +SELECT a, b +FROM test_nested_alias_dist +ORDER BY dt +LIMIT 1 +SETTINGS enable_analyzer = 0; + +DROP TABLE test_nested_alias_dist; +DROP TABLE test_nested_alias_local; diff --git a/tests/queries/0_stateless/03845_distributed_global_in_join_alias_chain.reference b/tests/queries/0_stateless/03845_distributed_global_in_join_alias_chain.reference new file mode 100644 index 000000000000..325078d71cc1 --- /dev/null +++ b/tests/queries/0_stateless/03845_distributed_global_in_join_alias_chain.reference @@ -0,0 +1,8 @@ +rewrite_in +1 +1 +rewrite_join +1 +1 +1 +1 diff --git a/tests/queries/0_stateless/03845_distributed_global_in_join_alias_chain.sql b/tests/queries/0_stateless/03845_distributed_global_in_join_alias_chain.sql new file mode 100644 index 000000000000..9bd95d72fd20 --- /dev/null +++ b/tests/queries/0_stateless/03845_distributed_global_in_join_alias_chain.sql @@ -0,0 +1,34 @@ +DROP TABLE IF EXISTS test_global_alias_chain_dist; +DROP TABLE IF EXISTS test_global_alias_chain_local; + +CREATE TABLE test_global_alias_chain_local +( + id UInt64, + base UInt64, + a UInt64 ALIAS base, + b UInt64 ALIAS a +) +ENGINE = MergeTree() +ORDER BY id; + +INSERT INTO test_global_alias_chain_local VALUES (1, 1); + +CREATE TABLE test_global_alias_chain_dist AS test_global_alias_chain_local +ENGINE = Distributed('test_cluster_two_shards', currentDatabase(), test_global_alias_chain_local, rand()); + +SELECT 'rewrite_in'; +SELECT id +FROM test_global_alias_chain_dist +WHERE id IN (SELECT b FROM test_global_alias_chain_dist) +ORDER BY id +SETTINGS enable_analyzer = 1, distributed_product_mode = 'global'; + +SELECT 'rewrite_join'; +SELECT l.id +FROM test_global_alias_chain_dist AS l +INNER JOIN (SELECT b FROM test_global_alias_chain_dist) AS r ON l.id = r.b +ORDER BY l.id +SETTINGS enable_analyzer = 1, distributed_product_mode = 'global'; + +DROP TABLE test_global_alias_chain_dist; +DROP TABLE test_global_alias_chain_local; diff --git a/tests/queries/0_stateless/03846_distributed_global_in_alias_marker_collision.reference b/tests/queries/0_stateless/03846_distributed_global_in_alias_marker_collision.reference new file mode 100644 index 000000000000..9a3a29a69ce8 --- /dev/null +++ b/tests/queries/0_stateless/03846_distributed_global_in_alias_marker_collision.reference @@ -0,0 +1,2 @@ +global_in_collision_check +1 diff --git a/tests/queries/0_stateless/03846_distributed_global_in_alias_marker_collision.sql b/tests/queries/0_stateless/03846_distributed_global_in_alias_marker_collision.sql new file mode 100644 index 000000000000..d47e6a304ba1 --- /dev/null +++ b/tests/queries/0_stateless/03846_distributed_global_in_alias_marker_collision.sql @@ -0,0 +1,56 @@ +DROP TABLE IF EXISTS test_marker_collision_dist; +DROP TABLE IF EXISTS test_marker_collision_main; +DROP TABLE IF EXISTS test_marker_collision_left; +DROP TABLE IF EXISTS test_marker_collision_right; + +CREATE TABLE test_marker_collision_main +( + id UInt64 +) +ENGINE = MergeTree() +ORDER BY id; + +INSERT INTO test_marker_collision_main VALUES (1); + +CREATE TABLE test_marker_collision_left +( + id UInt64, + x UInt64, + b UInt64 ALIAS x +) +ENGINE = MergeTree() +ORDER BY id; + +CREATE TABLE test_marker_collision_right +( + id UInt64, + y UInt64, + b UInt64 ALIAS y +) +ENGINE = MergeTree() +ORDER BY id; + +INSERT INTO test_marker_collision_left VALUES (1, 1); +INSERT INTO test_marker_collision_right VALUES (1, 20); + +CREATE TABLE test_marker_collision_dist AS test_marker_collision_main +ENGINE = Distributed('test_shard_localhost', currentDatabase(), test_marker_collision_main, rand()); + +SELECT 'global_in_collision_check'; +SELECT id +FROM test_marker_collision_dist +WHERE id GLOBAL IN +( + SELECT test_marker_collision_left.id + FROM test_marker_collision_left + INNER JOIN test_marker_collision_right + ON test_marker_collision_left.id = test_marker_collision_right.id + WHERE test_marker_collision_left.b + test_marker_collision_right.b = 21 +) +ORDER BY id +SETTINGS enable_analyzer = 1, enable_alias_marker = 1; + +DROP TABLE test_marker_collision_dist; +DROP TABLE test_marker_collision_main; +DROP TABLE test_marker_collision_left; +DROP TABLE test_marker_collision_right; diff --git a/tests/queries/0_stateless/03847_remote_over_distributed_alias_marker.reference b/tests/queries/0_stateless/03847_remote_over_distributed_alias_marker.reference new file mode 100644 index 000000000000..9ae0b7c40d76 --- /dev/null +++ b/tests/queries/0_stateless/03847_remote_over_distributed_alias_marker.reference @@ -0,0 +1,6 @@ +ast_second_hop +1999-03-29 01:15:33.000 x x +1999-03-29 01:15:34.000 y y +plan_second_hop +1999-03-29 01:15:33.000 x x +1999-03-29 01:15:34.000 y y diff --git a/tests/queries/0_stateless/03847_remote_over_distributed_alias_marker.sql b/tests/queries/0_stateless/03847_remote_over_distributed_alias_marker.sql new file mode 100644 index 000000000000..eac350656a2e --- /dev/null +++ b/tests/queries/0_stateless/03847_remote_over_distributed_alias_marker.sql @@ -0,0 +1,36 @@ +-- Regression coverage for nested `ALIAS` columns across `remote` -> `Distributed` -> `MergeTree`. + +DROP TABLE IF EXISTS test_alias_pr_second_hop_dist; +DROP TABLE IF EXISTS test_alias_pr_second_hop_local; + +CREATE TABLE test_alias_pr_second_hop_local +( + dt DateTime64(3), + base String, + alias_base_0 String ALIAS base, + alias_base_1 String ALIAS base +) +ENGINE = MergeTree() +ORDER BY dt; + +INSERT INTO test_alias_pr_second_hop_local VALUES + ('1999-03-29T01:15:33', 'x'), + ('1999-03-29T01:15:34', 'y'); + +CREATE TABLE test_alias_pr_second_hop_dist AS test_alias_pr_second_hop_local +ENGINE = Distributed(test_cluster_one_shard_three_replicas_localhost, currentDatabase(), test_alias_pr_second_hop_local); + +SELECT 'ast_second_hop'; +SELECT dt, alias_base_0, alias_base_1 +FROM remote('127.0.0.2', currentDatabase(), test_alias_pr_second_hop_dist) +ORDER BY dt +SETTINGS enable_analyzer = 1, enable_alias_marker = 1, serialize_query_plan = 0; + +SELECT 'plan_second_hop'; +SELECT dt, alias_base_0, alias_base_1 +FROM remote('127.0.0.2', currentDatabase(), test_alias_pr_second_hop_dist) +ORDER BY dt +SETTINGS enable_analyzer = 1, enable_alias_marker = 1, serialize_query_plan = 1; + +DROP TABLE test_alias_pr_second_hop_dist; +DROP TABLE test_alias_pr_second_hop_local; diff --git a/tests/queries/0_stateless/03931_parallel_replicas_alias_swap.reference b/tests/queries/0_stateless/03931_parallel_replicas_alias_swap.reference new file mode 100644 index 000000000000..3efdf9330f4c --- /dev/null +++ b/tests/queries/0_stateless/03931_parallel_replicas_alias_swap.reference @@ -0,0 +1,20 @@ +local +10 11 12 +20 21 22 +dist_plan +10 11 12 +20 21 22 +pr_ast +10 11 12 +20 21 22 +pr_query_ast +1 1 +10 11 12 +20 21 22 +pr_query_plan +1 1 +10 11 12 +20 21 22 +03931_dist_ast 1 1 +03931_query_ast 1 1 +03931_query_plan 1 1 diff --git a/tests/queries/0_stateless/03931_parallel_replicas_alias_swap.sql b/tests/queries/0_stateless/03931_parallel_replicas_alias_swap.sql new file mode 100644 index 000000000000..440320d4a4cc --- /dev/null +++ b/tests/queries/0_stateless/03931_parallel_replicas_alias_swap.sql @@ -0,0 +1,82 @@ +-- The `Distributed` queries check nested `ALIAS` columns in AST and serialized-plan transport. +-- The nested queries over `MergeTree` exercise `buildQueryPlanForParallelReplicas` and its +-- `Convert distributed names` header reconciliation in both transports. A serialized +-- `Distributed` plan is built before the shard can enable parallel replicas. +-- +-- Parallel replicas over a small non-replicated table can read the same rows on several replicas. +-- `GROUP BY x, a1, a2` deduplicates them and keeps x required for the `ALIAS` expansion; +-- `ORDER BY x` over distinct values gives a total order. Swapped or wrong aliases still fail. +DROP TABLE IF EXISTS t_local_03931; +DROP TABLE IF EXISTS t_dist_03931; + +CREATE TABLE t_local_03931 (x UInt32, a1 UInt32 ALIAS x + 1, a2 UInt32 ALIAS a1 + 1) +ENGINE = MergeTree ORDER BY x; +INSERT INTO t_local_03931 VALUES (10), (20); + +CREATE TABLE t_dist_03931 AS t_local_03931 +ENGINE = Distributed(test_cluster_one_shard_three_replicas_localhost, currentDatabase(), t_local_03931); + +SELECT 'local'; +SELECT x, a1, a2 FROM t_local_03931 GROUP BY x, a1, a2 ORDER BY x; + +SET enable_analyzer = 1, enable_alias_marker = 1; +SELECT 'dist_plan'; +SELECT x, a1, a2 FROM t_dist_03931 GROUP BY x, a1, a2 ORDER BY x +SETTINGS serialize_query_plan = 1; + +SET allow_experimental_parallel_reading_from_replicas = 2, max_parallel_replicas = 3, + parallel_replicas_for_non_replicated_merge_tree = 1, + parallel_replicas_min_number_of_rows_per_replica = 0, + automatic_parallel_replicas_mode = 0, + cluster_for_parallel_replicas = 'test_cluster_one_shard_three_replicas_localhost', + serialize_query_plan = 0; + +SELECT 'pr_ast'; +SELECT x, a1, a2 FROM t_dist_03931 GROUP BY x, a1, a2 ORDER BY x +SETTINGS log_comment = '03931_dist_ast'; + +-- The subquery makes the query-level parallel-replica candidate eligible. Check the +-- distinctive reconciliation and remote-reading steps, not just the resulting values. +SELECT 'pr_query_ast'; +SELECT countIf(explain LIKE '%Convert distributed names%') > 0, + countIf(explain LIKE '%ReadFromRemoteParallelReplicas%') > 0 +FROM (EXPLAIN PLAN SELECT x, a1, a2 FROM (SELECT x, a1, a2 FROM t_local_03931) + GROUP BY x, a1, a2 ORDER BY x); +SELECT x, a1, a2 FROM (SELECT x, a1, a2 FROM t_local_03931) +GROUP BY x, a1, a2 ORDER BY x +SETTINGS log_comment = '03931_query_ast'; + +SET serialize_query_plan = 1; +SELECT 'pr_query_plan'; +SELECT countIf(explain LIKE '%Convert distributed names%') > 0, + countIf(explain LIKE '%ReadFromRemoteParallelReplicas%') > 0 +FROM (EXPLAIN PLAN SELECT x, a1, a2 FROM (SELECT x, a1, a2 FROM t_local_03931) + GROUP BY x, a1, a2 ORDER BY x); +SELECT x, a1, a2 FROM (SELECT x, a1, a2 FROM t_local_03931) +GROUP BY x, a1, a2 ORDER BY x +SETTINGS log_comment = '03931_query_plan'; + +SET allow_experimental_parallel_reading_from_replicas = 0, serialize_query_plan = 0; +SYSTEM FLUSH LOGS query_log; +-- The `Distributed` initiator does not itself count the replica reads. Correlate its +-- `initial_query_id` with secondary queries instead of checking only the initial row. +SELECT '03931_dist_ast', countIf(ProfileEvents['ParallelReplicasQueryCount'] > 0) > 0, + countIf(ProfileEvents['ParallelReplicasUsedCount'] > 0) > 0 +FROM system.query_log +WHERE event_date >= yesterday() AND type = 'QueryFinish' + AND initial_query_id IN + ( + SELECT query_id FROM system.query_log + WHERE event_date >= yesterday() AND type = 'QueryFinish' AND is_initial_query + AND current_database = currentDatabase() AND log_comment = '03931_dist_ast' + ); +SELECT log_comment, ProfileEvents['ParallelReplicasQueryCount'] > 0, + ProfileEvents['ParallelReplicasUsedCount'] > 0 +FROM system.query_log +WHERE event_date >= yesterday() AND type = 'QueryFinish' + AND is_initial_query AND current_database = currentDatabase() + AND log_comment IN ('03931_query_ast', '03931_query_plan') +ORDER BY log_comment; + +DROP TABLE t_dist_03931; +DROP TABLE t_local_03931; diff --git a/tests/queries/0_stateless/03933_alias_marker_direct_use_no_logical_error.reference b/tests/queries/0_stateless/03933_alias_marker_direct_use_no_logical_error.reference new file mode 100644 index 000000000000..f3f736b7dea2 --- /dev/null +++ b/tests/queries/0_stateless/03933_alias_marker_direct_use_no_logical_error.reference @@ -0,0 +1,14 @@ +2arg_identity +42 +lambda_local +[1] +[2] +[3] +lambda_over_distributed +[1] +[2] +[3] +lambda_over_distributed_plan +[1] +[2] +[3] diff --git a/tests/queries/0_stateless/03933_alias_marker_direct_use_no_logical_error.sql b/tests/queries/0_stateless/03933_alias_marker_direct_use_no_logical_error.sql new file mode 100644 index 000000000000..e327c442397d --- /dev/null +++ b/tests/queries/0_stateless/03933_alias_marker_direct_use_no_logical_error.sql @@ -0,0 +1,31 @@ +-- __aliasMarker is an internal pass-through identity function. Direct use from SQL must not +-- raise a server-side LOGICAL_ERROR (which would abort under abort_on_logical_error / sanitizers), +-- in particular inside a lambda over a Distributed table where the marker's column argument +-- resolves to a lambda parameter with no table source. +DROP TABLE IF EXISTS t_local_03933; +DROP TABLE IF EXISTS t_dist_03933; + +CREATE TABLE t_local_03933 (x UInt64) ENGINE = MergeTree ORDER BY x; +INSERT INTO t_local_03933 VALUES (1), (2), (3); + +CREATE TABLE t_dist_03933 AS t_local_03933 +ENGINE = Distributed(test_shard_localhost, currentDatabase(), t_local_03933); + +SELECT '2arg_identity'; +SELECT __aliasMarker(42, 'anything'); + +SELECT 'lambda_local'; +SELECT arrayMap(lx -> __aliasMarker(lx, lx), [x]) AS arr FROM t_local_03933 ORDER BY x; + +SELECT 'lambda_over_distributed'; +SELECT arrayMap(lx -> __aliasMarker(lx, lx), [x]) AS arr +FROM t_dist_03933 ORDER BY x +SETTINGS enable_analyzer = 1, enable_alias_marker = 1, prefer_localhost_replica = 0; + +SELECT 'lambda_over_distributed_plan'; +SELECT arrayMap(lx -> __aliasMarker(lx, lx), [x]) AS arr +FROM t_dist_03933 ORDER BY x +SETTINGS enable_analyzer = 1, enable_alias_marker = 1, prefer_localhost_replica = 0, serialize_query_plan = 1; + +DROP TABLE t_dist_03933; +DROP TABLE t_local_03933; diff --git a/tests/queries/0_stateless/05053_distributed_alias_same_expression.reference b/tests/queries/0_stateless/05053_distributed_alias_same_expression.reference new file mode 100644 index 000000000000..25d3e1f10881 --- /dev/null +++ b/tests/queries/0_stateless/05053_distributed_alias_same_expression.reference @@ -0,0 +1,16 @@ +first +1999-03-29 01:15:33.000 nonempty nonempty +second +1999-03-29 01:15:33.000 nonempty nonempty +third +1999-03-29 01:15:33.000 nonempty nonempty +fourth +1999-03-29 01:15:33.000 nonempty nonempty +fifth +1999-03-29 01:15:33.000 nonempty nonempty +sixth +query_alias_0 query_alias_1 +nonempty nonempty +seventh +alias_String_7_0 alias_String_7_1 +nonempty nonempty diff --git a/tests/queries/0_stateless/05053_distributed_alias_same_expression.sql b/tests/queries/0_stateless/05053_distributed_alias_same_expression.sql new file mode 100644 index 000000000000..d6589354bd75 --- /dev/null +++ b/tests/queries/0_stateless/05053_distributed_alias_same_expression.sql @@ -0,0 +1,76 @@ +-- Two ALIAS columns over the same expression, read through `remote` with ORDER BY. The shard's +-- ActionsDAG deduplicates the two identical expressions into one output column, so its header is a +-- column short of what the initiator expects. +-- +-- Every variant below must return the same single row. `enable_alias_marker = 0` is covered too: +-- the marker keeps the two columns distinct in transport, but it is not what makes this shape work. +-- `buildShardCollapseFanOut` reconstructs the missing column either way, so turning the marker off +-- must not change the answer. +-- +-- Related issue: https://github.com/ClickHouse/ClickHouse/issues/79916 +-- Fixed upstream by: https://github.com/ClickHouse/ClickHouse/pull/107913 + +DROP TABLE IF EXISTS test_alias_same_expr_remote; + +CREATE TABLE test_alias_same_expr_remote +( + dt DateTime64(3), + String_7 String, + alias_String_7_0 String ALIAS String_7, + alias_String_7_1 String ALIAS String_7 +) +ENGINE = MergeTree() +ORDER BY dt; + +INSERT INTO test_alias_same_expr_remote VALUES ('1999-03-29T01:15:33', 'nonempty'); + +SELECT 'first'; +SELECT dt, alias_String_7_0, alias_String_7_1 +FROM remote('127.0.0.{1,2}', currentDatabase(), test_alias_same_expr_remote) +LIMIT 1; + +SELECT 'second'; +SELECT dt, alias_String_7_0, alias_String_7_1 +FROM remote('127.0.0.{1,2}', currentDatabase(), test_alias_same_expr_remote) +ORDER BY dt +LIMIT 1 +SETTINGS enable_analyzer = 0; + +SELECT 'third'; +SELECT dt, alias_String_7_0, alias_String_7_1 +FROM remote('127.0.0.{1,2}', currentDatabase(), test_alias_same_expr_remote) +ORDER BY dt +LIMIT 1 +SETTINGS enable_analyzer = 1; + +SELECT 'fourth'; +SELECT dt, alias_String_7_0, alias_String_7_1 +FROM remote('127.0.0.{1,2}', currentDatabase(), test_alias_same_expr_remote) +ORDER BY dt +LIMIT 1 +SETTINGS enable_analyzer = 1, enable_alias_marker = 0; + +SELECT 'fifth'; +SELECT dt, alias_String_7_0, alias_String_7_1 +FROM remote('127.0.0.{1,2}', currentDatabase(), test_alias_same_expr_remote) +ORDER BY dt +LIMIT 1 +SETTINGS enable_analyzer = 1, enable_alias_marker = 1, serialize_query_plan = 1; + +SELECT 'sixth'; +SELECT alias_String_7_0 AS query_alias_0, alias_String_7_1 AS query_alias_1 +FROM remote('127.0.0.{1,2}', currentDatabase(), test_alias_same_expr_remote) +ORDER BY dt +LIMIT 1 +SETTINGS enable_analyzer = 1, enable_alias_marker = 1 +FORMAT TSVWithNames; + +SELECT 'seventh'; +SELECT alias_String_7_0, alias_String_7_1 +FROM remote('127.0.0.{1,2}', currentDatabase(), test_alias_same_expr_remote) +ORDER BY dt +LIMIT 1 +SETTINGS enable_analyzer = 1, enable_alias_marker = 1 +FORMAT TSVWithNames; + +DROP TABLE test_alias_same_expr_remote; diff --git a/tests/queries/0_stateless/05054_distributed_global_alias_marker_matrix.reference b/tests/queries/0_stateless/05054_distributed_global_alias_marker_matrix.reference new file mode 100644 index 000000000000..8c2ebec53ff8 --- /dev/null +++ b/tests/queries/0_stateless/05054_distributed_global_alias_marker_matrix.reference @@ -0,0 +1,35 @@ +case1_global_in_unnamed_identical_derived_subqueries +1 +case2_global_join_unnamed_identical_derived_subqueries +id left_b0 right_b0 +1 10 20 +case3_global_join_unnamed_identical_derived_subqueries_serialize_query_plan +id left_b0 right_b0 +1 10 20 +case4_global_join_unnamed_remote_over_distributed_subqueries +id left_b0 right_b0 +1 10 20 +case5_global_join_unnamed_identical_dual_alias_columns +id left_b0 right_b1 +1 10 20 +case6_local_join_unnamed_identical_derived_subqueries +id left_b0 right_b0 +1 10 20 +case7_local_join_unnamed_identical_derived_subqueries_serialize_query_plan +id left_b0 right_b0 +1 10 20 +case8_global_join_direct_distributed_serialize_query_plan +id b0 b1 +1 10 10 +2 20 20 +case9_global_join_direct_remote_over_distributed_serialize_query_plan +id b0 b1 +1 10 10 +2 20 20 +case10_wrapper_alias_subquery_serialize_query_plan +id left_foo right_foo +1 1 20 +case11_wrapper_constant_alias_subquery_serialize_query_plan +id left_foo right_foo +1 foo foo +2 foo foo diff --git a/tests/queries/0_stateless/05054_distributed_global_alias_marker_matrix.sql b/tests/queries/0_stateless/05054_distributed_global_alias_marker_matrix.sql new file mode 100644 index 000000000000..2ac9a8a65fe3 --- /dev/null +++ b/tests/queries/0_stateless/05054_distributed_global_alias_marker_matrix.sql @@ -0,0 +1,297 @@ +DROP TABLE IF EXISTS test_marker_suite_main_dist; +DROP TABLE IF EXISTS test_marker_suite_side_dist; +DROP TABLE IF EXISTS test_marker_suite_main; +DROP TABLE IF EXISTS test_marker_suite_side; +DROP TABLE IF EXISTS test_wrapper_alias_a_dist; +DROP TABLE IF EXISTS test_wrapper_alias_b_dist; +DROP TABLE IF EXISTS test_wrapper_alias_a_local; +DROP TABLE IF EXISTS test_wrapper_alias_b_local; +DROP TABLE IF EXISTS test_wrapper_const_alias_a_dist; +DROP TABLE IF EXISTS test_wrapper_const_alias_b_dist; +DROP TABLE IF EXISTS test_wrapper_const_alias_a_local; +DROP TABLE IF EXISTS test_wrapper_const_alias_b_local; + +CREATE TABLE test_marker_suite_main +( + id UInt64 +) +ENGINE = MergeTree() +ORDER BY id; + +INSERT INTO test_marker_suite_main VALUES (1), (2); + +CREATE TABLE test_marker_suite_side +( + id UInt64, + x UInt64, + b0 UInt64 ALIAS x, + b1 UInt64 ALIAS x +) +ENGINE = MergeTree() +ORDER BY id; + +INSERT INTO test_marker_suite_side VALUES (1, 10), (2, 20); + +CREATE TABLE test_marker_suite_main_dist AS test_marker_suite_main +ENGINE = Distributed('test_shard_localhost', currentDatabase(), test_marker_suite_main, rand()); + +CREATE TABLE test_marker_suite_side_dist AS test_marker_suite_side +ENGINE = Distributed('test_shard_localhost', currentDatabase(), test_marker_suite_side, rand()); + +SELECT 'case1_global_in_unnamed_identical_derived_subqueries'; +SELECT id +FROM test_marker_suite_main_dist +WHERE id GLOBAL IN +( + SELECT left_id + FROM + (SELECT id AS left_id, b0 AS left_b0 FROM test_marker_suite_side_dist) + INNER JOIN + (SELECT id AS right_id, b0 AS right_b0 FROM test_marker_suite_side_dist) + ON left_id < right_id + WHERE left_b0 + right_b0 = 30 + SETTINGS joined_subquery_requires_alias = 0 +) +ORDER BY id +SETTINGS enable_analyzer = 1, enable_alias_marker = 1; + +SELECT 'case2_global_join_unnamed_identical_derived_subqueries'; +SELECT m.id, j.left_b0, j.right_b0 +FROM test_marker_suite_main_dist AS m +GLOBAL INNER JOIN +( + SELECT left_id AS id, left_b0, right_b0 + FROM + (SELECT id AS left_id, b0 AS left_b0 FROM test_marker_suite_side_dist) + INNER JOIN + (SELECT id AS right_id, b0 AS right_b0 FROM test_marker_suite_side_dist) + ON left_id < right_id + WHERE left_b0 + right_b0 = 30 + SETTINGS joined_subquery_requires_alias = 0 +) AS j +ON m.id = j.id +ORDER BY m.id +SETTINGS enable_analyzer = 1, enable_alias_marker = 1 +FORMAT TSVWithNames; + +SELECT 'case3_global_join_unnamed_identical_derived_subqueries_serialize_query_plan'; +SELECT m.id, j.left_b0, j.right_b0 +FROM test_marker_suite_main_dist AS m +GLOBAL INNER JOIN +( + SELECT left_id AS id, left_b0, right_b0 + FROM + (SELECT id AS left_id, b0 AS left_b0 FROM test_marker_suite_side_dist) + INNER JOIN + (SELECT id AS right_id, b0 AS right_b0 FROM test_marker_suite_side_dist) + ON left_id < right_id + WHERE left_b0 + right_b0 = 30 + SETTINGS joined_subquery_requires_alias = 0 +) AS j +ON m.id = j.id +ORDER BY m.id +SETTINGS enable_analyzer = 1, enable_alias_marker = 1, serialize_query_plan = 1 +FORMAT TSVWithNames; + +SELECT 'case4_global_join_unnamed_remote_over_distributed_subqueries'; +SELECT m.id, j.left_b0, j.right_b0 +FROM test_marker_suite_main_dist AS m +GLOBAL INNER JOIN +( + SELECT left_id AS id, left_b0, right_b0 + FROM + (SELECT id AS left_id, b0 AS left_b0 FROM remote('127.0.0.2', currentDatabase(), test_marker_suite_side_dist)) + INNER JOIN + (SELECT id AS right_id, b0 AS right_b0 FROM remote('127.0.0.2', currentDatabase(), test_marker_suite_side_dist)) + ON left_id < right_id + WHERE left_b0 + right_b0 = 30 + SETTINGS joined_subquery_requires_alias = 0 +) AS j +ON m.id = j.id +ORDER BY m.id +SETTINGS enable_analyzer = 1, enable_alias_marker = 1 +FORMAT TSVWithNames; + +SELECT 'case5_global_join_unnamed_identical_dual_alias_columns'; +SELECT m.id, j.left_b0, j.right_b1 +FROM test_marker_suite_main_dist AS m +GLOBAL INNER JOIN +( + SELECT left_id AS id, left_b0, right_b1 + FROM + (SELECT id AS left_id, b0 AS left_b0 FROM test_marker_suite_side_dist) + INNER JOIN + (SELECT id AS right_id, b1 AS right_b1 FROM test_marker_suite_side_dist) + ON left_id < right_id + WHERE left_b0 + right_b1 = 30 + SETTINGS joined_subquery_requires_alias = 0 +) AS j +ON m.id = j.id +ORDER BY m.id +SETTINGS enable_analyzer = 1, enable_alias_marker = 1 +FORMAT TSVWithNames; + +SELECT 'case6_local_join_unnamed_identical_derived_subqueries'; +SELECT m.id, j.left_b0, j.right_b0 +FROM test_marker_suite_main_dist AS m +INNER JOIN +( + SELECT left_id AS id, left_b0, right_b0 + FROM + (SELECT id AS left_id, b0 AS left_b0 FROM test_marker_suite_side_dist) + INNER JOIN + (SELECT id AS right_id, b0 AS right_b0 FROM test_marker_suite_side_dist) + ON left_id < right_id + WHERE left_b0 + right_b0 = 30 + SETTINGS joined_subquery_requires_alias = 0 +) AS j +ON m.id = j.id +ORDER BY m.id +SETTINGS enable_analyzer = 1, enable_alias_marker = 1, distributed_product_mode = 'local' +FORMAT TSVWithNames; + +SELECT 'case7_local_join_unnamed_identical_derived_subqueries_serialize_query_plan'; +SELECT m.id, j.left_b0, j.right_b0 +FROM test_marker_suite_main_dist AS m +INNER JOIN +( + SELECT left_id AS id, left_b0, right_b0 + FROM + (SELECT id AS left_id, b0 AS left_b0 FROM test_marker_suite_side_dist) + INNER JOIN + (SELECT id AS right_id, b0 AS right_b0 FROM test_marker_suite_side_dist) + ON left_id < right_id + WHERE left_b0 + right_b0 = 30 + SETTINGS joined_subquery_requires_alias = 0 +) AS j +ON m.id = j.id +ORDER BY m.id +SETTINGS enable_analyzer = 1, enable_alias_marker = 1, distributed_product_mode = 'local', serialize_query_plan = 1 +FORMAT TSVWithNames; + +SELECT 'case8_global_join_direct_distributed_serialize_query_plan'; +SELECT m.id, b0, b1 +FROM test_marker_suite_main_dist AS m +GLOBAL INNER JOIN test_marker_suite_side_dist USING (id) +ORDER BY m.id +SETTINGS enable_analyzer = 1, enable_alias_marker = 1, asterisk_include_alias_columns = 1, serialize_query_plan = 1 +FORMAT TSVWithNames; + +SELECT 'case9_global_join_direct_remote_over_distributed_serialize_query_plan'; +SELECT m.id, b0, b1 +FROM test_marker_suite_main_dist AS m +GLOBAL INNER JOIN remote('127.0.0.2', currentDatabase(), test_marker_suite_side_dist) USING (id) +ORDER BY m.id +SETTINGS enable_analyzer = 1, enable_alias_marker = 1, asterisk_include_alias_columns = 1, joined_subquery_requires_alias = 0, serialize_query_plan = 1 +FORMAT TSVWithNames; + +DROP TABLE test_marker_suite_main_dist; +DROP TABLE test_marker_suite_side_dist; +DROP TABLE test_marker_suite_main; +DROP TABLE test_marker_suite_side; + +CREATE TABLE test_wrapper_alias_a_local +( + id UInt64, + x UInt64 +) +ENGINE = MergeTree() +ORDER BY id; + +CREATE TABLE test_wrapper_alias_b_local +( + id UInt64, + x UInt64 +) +ENGINE = MergeTree() +ORDER BY id; + +INSERT INTO test_wrapper_alias_a_local VALUES (1, 1), (2, 20); +INSERT INTO test_wrapper_alias_b_local VALUES (1, 1), (2, 20); + +CREATE TABLE test_wrapper_alias_a_dist +( + id UInt64, + x UInt64, + foo UInt64 ALIAS x +) +ENGINE = Distributed('test_shard_localhost', currentDatabase(), test_wrapper_alias_a_local, rand()); + +CREATE TABLE test_wrapper_alias_b_dist +( + id UInt64, + x UInt64, + foo UInt64 ALIAS x +) +ENGINE = Distributed('test_shard_localhost', currentDatabase(), test_wrapper_alias_b_local, rand()); + +SELECT 'case10_wrapper_alias_subquery_serialize_query_plan'; +SELECT a.id, j.left_foo, j.right_foo +FROM test_wrapper_alias_a_dist AS a +GLOBAL INNER JOIN +( + SELECT l.id, l.foo AS left_foo, r.foo AS right_foo + FROM test_wrapper_alias_a_dist AS l + INNER JOIN test_wrapper_alias_b_dist AS r ON l.id < r.id + WHERE l.foo + r.foo = 21 +) AS j +ON a.id = j.id +ORDER BY a.id +SETTINGS enable_analyzer = 1, enable_alias_marker = 1, serialize_query_plan = 1 +FORMAT TSVWithNames; + +DROP TABLE test_wrapper_alias_a_dist; +DROP TABLE test_wrapper_alias_b_dist; +DROP TABLE test_wrapper_alias_a_local; +DROP TABLE test_wrapper_alias_b_local; + +CREATE TABLE test_wrapper_const_alias_a_local +( + id UInt64 +) +ENGINE = MergeTree() +ORDER BY id; + +CREATE TABLE test_wrapper_const_alias_b_local +( + id UInt64 +) +ENGINE = MergeTree() +ORDER BY id; + +INSERT INTO test_wrapper_const_alias_a_local VALUES (1), (2); +INSERT INTO test_wrapper_const_alias_b_local VALUES (1), (2); + +CREATE TABLE test_wrapper_const_alias_a_dist +( + id UInt64, + foo String ALIAS 'foo' +) +ENGINE = Distributed('test_shard_localhost', currentDatabase(), test_wrapper_const_alias_a_local, rand()); + +CREATE TABLE test_wrapper_const_alias_b_dist +( + id UInt64, + foo String ALIAS 'foo' +) +ENGINE = Distributed('test_shard_localhost', currentDatabase(), test_wrapper_const_alias_b_local, rand()); + +SELECT 'case11_wrapper_constant_alias_subquery_serialize_query_plan'; +SELECT a.id, j.left_foo, j.right_foo +FROM test_wrapper_const_alias_a_dist AS a +GLOBAL INNER JOIN +( + SELECT l.id, l.foo AS left_foo, r.foo AS right_foo + FROM test_wrapper_const_alias_a_dist AS l + INNER JOIN test_wrapper_const_alias_b_dist AS r ON l.id = r.id + WHERE l.foo = 'foo' AND r.foo = 'foo' +) AS j +ON a.id = j.id +ORDER BY a.id +SETTINGS enable_analyzer = 1, enable_alias_marker = 1, serialize_query_plan = 1 +FORMAT TSVWithNames; + +DROP TABLE test_wrapper_const_alias_a_dist; +DROP TABLE test_wrapper_const_alias_b_dist; +DROP TABLE test_wrapper_const_alias_a_local; +DROP TABLE test_wrapper_const_alias_b_local; diff --git a/tests/queries/0_stateless/05055_distributed_over_distributed_double_aliases.reference b/tests/queries/0_stateless/05055_distributed_over_distributed_double_aliases.reference new file mode 100644 index 000000000000..750abc85a605 --- /dev/null +++ b/tests/queries/0_stateless/05055_distributed_over_distributed_double_aliases.reference @@ -0,0 +1,56 @@ +prefer_localhost_replica_0 +x a b c d inner_c inner_d +1 1 1 2 2 2 2 +1 1 1 2 2 2 2 +1 1 1 2 2 2 2 +1 1 1 2 2 2 2 +2 1 1 3 3 3 3 +2 1 1 3 3 3 3 +2 1 1 3 3 3 3 +2 1 1 3 3 3 3 +10 1 1 11 11 11 11 +10 1 1 11 11 11 11 +10 1 1 11 11 11 11 +10 1 1 11 11 11 11 +prefer_localhost_replica_1 +x a b c d inner_c inner_d +1 1 1 2 2 2 2 +1 1 1 2 2 2 2 +1 1 1 2 2 2 2 +1 1 1 2 2 2 2 +2 1 1 3 3 3 3 +2 1 1 3 3 3 3 +2 1 1 3 3 3 3 +2 1 1 3 3 3 3 +10 1 1 11 11 11 11 +10 1 1 11 11 11 11 +10 1 1 11 11 11 11 +10 1 1 11 11 11 11 +prefer_localhost_replica_0_serialize_query_plan_1 +x a b c d inner_c inner_d +1 1 1 2 2 2 2 +1 1 1 2 2 2 2 +1 1 1 2 2 2 2 +1 1 1 2 2 2 2 +2 1 1 3 3 3 3 +2 1 1 3 3 3 3 +2 1 1 3 3 3 3 +2 1 1 3 3 3 3 +10 1 1 11 11 11 11 +10 1 1 11 11 11 11 +10 1 1 11 11 11 11 +10 1 1 11 11 11 11 +prefer_localhost_replica_1_serialize_query_plan_1 +x a b c d inner_c inner_d +1 1 1 2 2 2 2 +1 1 1 2 2 2 2 +1 1 1 2 2 2 2 +1 1 1 2 2 2 2 +2 1 1 3 3 3 3 +2 1 1 3 3 3 3 +2 1 1 3 3 3 3 +2 1 1 3 3 3 3 +10 1 1 11 11 11 11 +10 1 1 11 11 11 11 +10 1 1 11 11 11 11 +10 1 1 11 11 11 11 diff --git a/tests/queries/0_stateless/05055_distributed_over_distributed_double_aliases.sql b/tests/queries/0_stateless/05055_distributed_over_distributed_double_aliases.sql new file mode 100644 index 000000000000..e4bbceb7e6cf --- /dev/null +++ b/tests/queries/0_stateless/05055_distributed_over_distributed_double_aliases.sql @@ -0,0 +1,92 @@ +DROP TABLE IF EXISTS test_dod_double_alias_outer; +DROP TABLE IF EXISTS test_dod_double_alias_inner; +DROP TABLE IF EXISTS test_dod_double_alias_local; + +CREATE TABLE test_dod_double_alias_local +( + x UInt64 +) +ENGINE = MergeTree() +ORDER BY x; + +INSERT INTO test_dod_double_alias_local VALUES (1), (2), (10); + +CREATE TABLE test_dod_double_alias_inner +( + x UInt64, + a UInt64 ALIAS 2, + b UInt64 ALIAS 2, + inner_c UInt64 ALIAS x + 1, + inner_d UInt64 ALIAS x + 1 +) +ENGINE = Distributed(test_cluster_two_shards, currentDatabase(), test_dod_double_alias_local); + +CREATE TABLE test_dod_double_alias_outer +( + x UInt64, + inner_c UInt64, + a UInt64 ALIAS 1, + b UInt64 ALIAS 1, + c UInt64 ALIAS inner_c, + d UInt64 ALIAS inner_c, + inner_d UInt64 +) +ENGINE = Distributed(test_cluster_two_shards, currentDatabase(), test_dod_double_alias_inner); + +SELECT 'prefer_localhost_replica_0'; +SELECT x, a, b, c, d, inner_c, inner_d +FROM test_dod_double_alias_outer +ORDER BY x +SETTINGS + enable_analyzer = 1, + enable_alias_marker = 1, + prefer_localhost_replica = 0, + enable_parallel_replicas = 0, + max_parallel_replicas = 1, + parallel_replicas_local_plan = 0 +FORMAT TSVWithNames; + +SELECT 'prefer_localhost_replica_1'; +SELECT x, a, b, c, d, inner_c, inner_d +FROM test_dod_double_alias_outer +ORDER BY x +SETTINGS + enable_analyzer = 1, + enable_alias_marker = 1, + prefer_localhost_replica = 1, + enable_parallel_replicas = 0, + max_parallel_replicas = 1, + parallel_replicas_local_plan = 0 +FORMAT TSVWithNames; + +SELECT 'prefer_localhost_replica_0_serialize_query_plan_1'; +SELECT x, a, b, c, d, inner_c, inner_d +FROM test_dod_double_alias_outer +ORDER BY x +SETTINGS + enable_analyzer = 1, + enable_alias_marker = 1, + prefer_localhost_replica = 0, + enable_parallel_replicas = 0, + max_parallel_replicas = 1, + parallel_replicas_local_plan = 0, + serialize_query_plan = 1 +FORMAT TSVWithNames; + +SELECT 'prefer_localhost_replica_1_serialize_query_plan_1'; +SELECT x, a, b, c, d, inner_c, inner_d +FROM test_dod_double_alias_outer +ORDER BY x +SETTINGS + enable_analyzer = 1, + enable_alias_marker = 1, + prefer_localhost_replica = 1, + enable_parallel_replicas = 0, + max_parallel_replicas = 1, + parallel_replicas_local_plan = 0, + serialize_query_plan = 1 +FORMAT TSVWithNames; + +DROP TABLE test_dod_double_alias_outer; +DROP TABLE test_dod_double_alias_inner; +DROP TABLE test_dod_double_alias_local; diff --git a/tests/queries/0_stateless/05057_merge_over_distributed_alias_marker_column_swap.reference b/tests/queries/0_stateless/05057_merge_over_distributed_alias_marker_column_swap.reference new file mode 100644 index 000000000000..f32381f38096 --- /dev/null +++ b/tests/queries/0_stateless/05057_merge_over_distributed_alias_marker_column_swap.reference @@ -0,0 +1,20 @@ +local +1 2 3 +2 3 4 +10 11 12 +merge_prefer0 +1 2 3 +2 3 4 +10 11 12 +merge_prefer1 +1 2 3 +2 3 4 +10 11 12 +merge_prefer0_plan +1 2 3 +2 3 4 +10 11 12 +merge_prefer1_plan +1 2 3 +2 3 4 +10 11 12 diff --git a/tests/queries/0_stateless/05057_merge_over_distributed_alias_marker_column_swap.sql b/tests/queries/0_stateless/05057_merge_over_distributed_alias_marker_column_swap.sql new file mode 100644 index 000000000000..c5817fd07f34 --- /dev/null +++ b/tests/queries/0_stateless/05057_merge_over_distributed_alias_marker_column_swap.sql @@ -0,0 +1,58 @@ +-- Plain Merge over Distributed over MergeTree (no Hybrid, no explicit __aliasMarker). +-- Nested ALIAS columns (b contains a's subexpression). Reading the alias columns through the +-- Merge table must reconcile the child (Distributed) header by name; a positional reconciliation +-- in StorageMerge::convertAndFilterSourceStream would swap the columns (or fill them with 0). +-- The correct result equals the single-node ('local') result. +-- +-- Determinism notes: `x` is kept in GROUP BY so the ALIAS expansion can resolve it (the alias +-- expressions are defined in terms of x); GROUP BY also deduplicates the rows the two shards +-- produce, and ORDER BY x (distinct values) gives a total order independent of the distributed +-- merge order. So every block - local and the distributed variants - yields the same rows. +DROP TABLE IF EXISTS test_merge_alias_swap_merge; +DROP TABLE IF EXISTS test_merge_alias_swap_dist; +DROP TABLE IF EXISTS test_merge_alias_swap_local; + +CREATE TABLE test_merge_alias_swap_local +( + x UInt64, + a UInt64 ALIAS x + 1, + b UInt64 ALIAS a + 1 +) +ENGINE = MergeTree() +ORDER BY x; + +INSERT INTO test_merge_alias_swap_local VALUES (1), (2), (10); + +CREATE TABLE test_merge_alias_swap_dist AS test_merge_alias_swap_local +ENGINE = Distributed(test_cluster_two_shards, currentDatabase(), test_merge_alias_swap_local); + +CREATE TABLE test_merge_alias_swap_merge +( + x UInt64, + a UInt64, + b UInt64 +) +ENGINE = Merge(currentDatabase(), '^test_merge_alias_swap_dist$'); + +SELECT 'local'; +SELECT x, a, b FROM test_merge_alias_swap_local GROUP BY x, a, b ORDER BY x; + +SELECT 'merge_prefer0'; +SELECT x, a, b FROM test_merge_alias_swap_merge GROUP BY x, a, b ORDER BY x +SETTINGS enable_analyzer = 1, enable_alias_marker = 1, prefer_localhost_replica = 0; + +SELECT 'merge_prefer1'; +SELECT x, a, b FROM test_merge_alias_swap_merge GROUP BY x, a, b ORDER BY x +SETTINGS enable_analyzer = 1, enable_alias_marker = 1, prefer_localhost_replica = 1; + +SELECT 'merge_prefer0_plan'; +SELECT x, a, b FROM test_merge_alias_swap_merge GROUP BY x, a, b ORDER BY x +SETTINGS enable_analyzer = 1, enable_alias_marker = 1, prefer_localhost_replica = 0, serialize_query_plan = 1; + +SELECT 'merge_prefer1_plan'; +SELECT x, a, b FROM test_merge_alias_swap_merge GROUP BY x, a, b ORDER BY x +SETTINGS enable_analyzer = 1, enable_alias_marker = 1, prefer_localhost_replica = 1, serialize_query_plan = 1; + +DROP TABLE test_merge_alias_swap_merge; +DROP TABLE test_merge_alias_swap_dist; +DROP TABLE test_merge_alias_swap_local; diff --git a/tests/queries/0_stateless/05058_distributed_alias_swap_planner.reference b/tests/queries/0_stateless/05058_distributed_alias_swap_planner.reference new file mode 100644 index 000000000000..b58a303171aa --- /dev/null +++ b/tests/queries/0_stateless/05058_distributed_alias_swap_planner.reference @@ -0,0 +1,24 @@ +local +11 12 +21 22 +dist_prefer0 +11 12 +21 22 +dist_prefer1 +11 12 +21 22 +dist_prefer0_plan +11 12 +21 22 +dist_prefer1_plan +11 12 +21 22 +reordered_local +12 11 23 +22 21 43 +reordered_dist +12 11 23 +22 21 43 +reordered_dist_plan +12 11 23 +22 21 43 diff --git a/tests/queries/0_stateless/05058_distributed_alias_swap_planner.sql b/tests/queries/0_stateless/05058_distributed_alias_swap_planner.sql new file mode 100644 index 000000000000..32494b67da55 --- /dev/null +++ b/tests/queries/0_stateless/05058_distributed_alias_swap_planner.sql @@ -0,0 +1,44 @@ +-- Nested `ALIAS` columns: `a2` contains `a1`'s subexpression, so planner CSE may reorder +-- the remote header. Check both alias-only and reordered/computed projections. +DROP TABLE IF EXISTS t_local_05058; +DROP TABLE IF EXISTS t_dist_05058; + +CREATE TABLE t_local_05058 (x UInt32, a1 UInt32 ALIAS x + 1, a2 UInt32 ALIAS a1 + 1) +ENGINE = MergeTree ORDER BY x; +INSERT INTO t_local_05058 VALUES (10), (20); + +CREATE TABLE t_dist_05058 AS t_local_05058 +ENGINE = Distributed(test_shard_localhost, currentDatabase(), t_local_05058); + +SELECT 'local'; +SELECT a1, a2 FROM t_local_05058 ORDER BY a1; + +SELECT 'dist_prefer0'; +SELECT a1, a2 FROM t_dist_05058 ORDER BY a1 +SETTINGS enable_analyzer = 1, enable_alias_marker = 1, prefer_localhost_replica = 0; + +SELECT 'dist_prefer1'; +SELECT a1, a2 FROM t_dist_05058 ORDER BY a1 +SETTINGS enable_analyzer = 1, enable_alias_marker = 1, prefer_localhost_replica = 1; + +SELECT 'dist_prefer0_plan'; +SELECT a1, a2 FROM t_dist_05058 ORDER BY a1 +SETTINGS enable_analyzer = 1, enable_alias_marker = 1, prefer_localhost_replica = 0, serialize_query_plan = 1; + +SELECT 'dist_prefer1_plan'; +SELECT a1, a2 FROM t_dist_05058 ORDER BY a1 +SETTINGS enable_analyzer = 1, enable_alias_marker = 1, prefer_localhost_replica = 1, serialize_query_plan = 1; + +SELECT 'reordered_local'; +SELECT a2, a1, a1 + a2 AS s FROM t_local_05058 ORDER BY x; + +SELECT 'reordered_dist'; +SELECT a2, a1, a1 + a2 AS s FROM t_dist_05058 ORDER BY x +SETTINGS enable_analyzer = 1, enable_alias_marker = 1, prefer_localhost_replica = 0, serialize_query_plan = 0; + +SELECT 'reordered_dist_plan'; +SELECT a2, a1, a1 + a2 AS s FROM t_dist_05058 ORDER BY x +SETTINGS enable_analyzer = 1, enable_alias_marker = 1, prefer_localhost_replica = 0, serialize_query_plan = 1; + +DROP TABLE t_dist_05058; +DROP TABLE t_local_05058; diff --git a/tests/queries/0_stateless/05061_distributed_alias_column_swap_without_marker.reference b/tests/queries/0_stateless/05061_distributed_alias_column_swap_without_marker.reference new file mode 100644 index 000000000000..f48ffcc87bad --- /dev/null +++ b/tests/queries/0_stateless/05061_distributed_alias_column_swap_without_marker.reference @@ -0,0 +1,70 @@ +prefer_localhost_replica_0_uint64 +x a_num inner_c +1 1 2 +1 1 2 +1 1 2 +1 1 2 +2 1 3 +2 1 3 +2 1 3 +2 1 3 +10 1 11 +10 1 11 +10 1 11 +10 1 11 +prefer_localhost_replica_0_string +x a_str inner_c +1 aaaa 2 +1 aaaa 2 +1 aaaa 2 +1 aaaa 2 +2 aaaa 3 +2 aaaa 3 +2 aaaa 3 +2 aaaa 3 +10 aaaa 11 +10 aaaa 11 +10 aaaa 11 +10 aaaa 11 +prefer_localhost_replica_1_uint64 +x a_num inner_c +1 1 2 +1 1 2 +1 1 2 +1 1 2 +2 1 3 +2 1 3 +2 1 3 +2 1 3 +10 1 11 +10 1 11 +10 1 11 +10 1 11 +prefer_localhost_replica_1_string +x a_str inner_c +1 aaaa 2 +1 aaaa 2 +1 aaaa 2 +1 aaaa 2 +2 aaaa 3 +2 aaaa 3 +2 aaaa 3 +2 aaaa 3 +10 aaaa 11 +10 aaaa 11 +10 aaaa 11 +10 aaaa 11 +prefer_localhost_replica_0_string_marker_on +x a_str inner_c +1 aaaa 2 +1 aaaa 2 +1 aaaa 2 +1 aaaa 2 +2 aaaa 3 +2 aaaa 3 +2 aaaa 3 +2 aaaa 3 +10 aaaa 11 +10 aaaa 11 +10 aaaa 11 +10 aaaa 11 diff --git a/tests/queries/0_stateless/05061_distributed_alias_column_swap_without_marker.sql b/tests/queries/0_stateless/05061_distributed_alias_column_swap_without_marker.sql new file mode 100644 index 000000000000..44e2d818e936 --- /dev/null +++ b/tests/queries/0_stateless/05061_distributed_alias_column_swap_without_marker.sql @@ -0,0 +1,105 @@ +-- Compare the same `String` alias with the marker on and off; the setting must not change results. +DROP TABLE IF EXISTS test_dod_alias_swap_no_marker_outer; +DROP TABLE IF EXISTS test_dod_alias_swap_no_marker_inner; +DROP TABLE IF EXISTS test_dod_alias_swap_no_marker_local; + +CREATE TABLE test_dod_alias_swap_no_marker_local +( + x UInt64 +) +ENGINE = MergeTree() +ORDER BY x; + +INSERT INTO test_dod_alias_swap_no_marker_local VALUES (1), (2), (10); + +CREATE TABLE test_dod_alias_swap_no_marker_inner +( + x UInt64, + inner_c UInt64 ALIAS x + 1 +) +ENGINE = Distributed(test_cluster_two_shards, currentDatabase(), test_dod_alias_swap_no_marker_local); + +CREATE TABLE test_dod_alias_swap_no_marker_outer +( + x UInt64, + inner_c UInt64, + a_num UInt64 ALIAS 1, + a_str String ALIAS 'aaaa' +) +ENGINE = Distributed(test_cluster_two_shards, currentDatabase(), test_dod_alias_swap_no_marker_inner); + +SELECT 'prefer_localhost_replica_0_uint64'; +SELECT + x, + a_num, + inner_c +FROM test_dod_alias_swap_no_marker_outer +ORDER BY x +SETTINGS + allow_experimental_analyzer = 1, + enable_alias_marker = 0, + prefer_localhost_replica = 0, + enable_parallel_replicas = 0, + max_parallel_replicas = 1, + parallel_replicas_local_plan = 0 +FORMAT TSVWithNames; + +SELECT 'prefer_localhost_replica_0_string'; +SELECT + x, + a_str, + inner_c +FROM test_dod_alias_swap_no_marker_outer +ORDER BY x +SETTINGS + allow_experimental_analyzer = 1, + enable_alias_marker = 0, + prefer_localhost_replica = 0, + enable_parallel_replicas = 0, + max_parallel_replicas = 1, + parallel_replicas_local_plan = 0 +FORMAT TSVWithNames; + +SELECT 'prefer_localhost_replica_1_uint64'; +SELECT + x, + a_num, + inner_c +FROM test_dod_alias_swap_no_marker_outer +ORDER BY x +SETTINGS + allow_experimental_analyzer = 1, + enable_alias_marker = 0, + prefer_localhost_replica = 1, + enable_parallel_replicas = 0, + max_parallel_replicas = 1, + parallel_replicas_local_plan = 0 +FORMAT TSVWithNames; + +SELECT 'prefer_localhost_replica_1_string'; +SELECT + x, + a_str, + inner_c +FROM test_dod_alias_swap_no_marker_outer +ORDER BY x +SETTINGS + allow_experimental_analyzer = 1, + enable_alias_marker = 0, + prefer_localhost_replica = 1, + enable_parallel_replicas = 0, + max_parallel_replicas = 1, + parallel_replicas_local_plan = 0 +FORMAT TSVWithNames; + +SELECT 'prefer_localhost_replica_0_string_marker_on'; +SELECT x, a_str, inner_c +FROM test_dod_alias_swap_no_marker_outer +ORDER BY x +SETTINGS enable_analyzer = 1, enable_alias_marker = 1, prefer_localhost_replica = 0, + enable_parallel_replicas = 0, max_parallel_replicas = 1, parallel_replicas_local_plan = 0 +FORMAT TSVWithNames; + +DROP TABLE test_dod_alias_swap_no_marker_outer; +DROP TABLE test_dod_alias_swap_no_marker_inner; +DROP TABLE test_dod_alias_swap_no_marker_local; diff --git a/tests/queries/0_stateless/05062_parallel_replicas_dod_alias_column_swap.reference b/tests/queries/0_stateless/05062_parallel_replicas_dod_alias_column_swap.reference new file mode 100644 index 000000000000..ee571cf0d1d4 --- /dev/null +++ b/tests/queries/0_stateless/05062_parallel_replicas_dod_alias_column_swap.reference @@ -0,0 +1,23 @@ +no_pr_uint64 +x a_num inner_c +1 1 2 +2 1 3 +10 1 11 +no_pr_string +x a_str inner_c +1 aaaa 2 +2 aaaa 3 +10 aaaa 11 +pr_uint64 +x a_num inner_c +1 1 2 +2 1 3 +10 1 11 +pr_string +x a_str inner_c +1 aaaa 2 +2 aaaa 3 +10 aaaa 11 +parallel_replicas_used +05062_pr_string 1 1 +05062_pr_uint64 1 1 diff --git a/tests/queries/0_stateless/05062_parallel_replicas_dod_alias_column_swap.sql b/tests/queries/0_stateless/05062_parallel_replicas_dod_alias_column_swap.sql new file mode 100644 index 000000000000..c56ca47b41e3 --- /dev/null +++ b/tests/queries/0_stateless/05062_parallel_replicas_dod_alias_column_swap.sql @@ -0,0 +1,117 @@ +DROP TABLE IF EXISTS test_pr_dod_alias_swap_outer; +DROP TABLE IF EXISTS test_pr_dod_alias_swap_inner; +DROP TABLE IF EXISTS test_pr_dod_alias_swap_local; + +CREATE TABLE test_pr_dod_alias_swap_local +( + x UInt64 +) +ENGINE = MergeTree() +ORDER BY x; + +INSERT INTO test_pr_dod_alias_swap_local VALUES (1), (2), (10); + +CREATE TABLE test_pr_dod_alias_swap_inner +( + x UInt64, + inner_c UInt64 ALIAS x + 1 +) +ENGINE = Distributed(test_cluster_one_shard_three_replicas_localhost, currentDatabase(), test_pr_dod_alias_swap_local); + +CREATE TABLE test_pr_dod_alias_swap_outer +( + x UInt64, + inner_c UInt64, + a_num UInt64 ALIAS 1, + a_str String ALIAS 'aaaa' +) +ENGINE = Distributed(test_cluster_one_shard_three_replicas_localhost, currentDatabase(), test_pr_dod_alias_swap_inner); + +SELECT 'no_pr_uint64'; +SELECT x, a_num, inner_c +FROM test_pr_dod_alias_swap_outer +ORDER BY x +SETTINGS + enable_analyzer = 1, + enable_alias_marker = 0, + enable_parallel_replicas = 0, + max_parallel_replicas = 1, + parallel_replicas_local_plan = 0, + parallel_replicas_for_non_replicated_merge_tree = 1, + cluster_for_parallel_replicas = 'test_cluster_one_shard_three_replicas_localhost' +FORMAT TSVWithNames; + +SELECT 'no_pr_string'; +SELECT x, a_str, inner_c +FROM test_pr_dod_alias_swap_outer +ORDER BY x +SETTINGS + enable_analyzer = 1, + enable_alias_marker = 0, + enable_parallel_replicas = 0, + max_parallel_replicas = 1, + parallel_replicas_local_plan = 0, + parallel_replicas_for_non_replicated_merge_tree = 1, + cluster_for_parallel_replicas = 'test_cluster_one_shard_three_replicas_localhost' +FORMAT TSVWithNames; + +-- Serialized `Distributed` plans bypass parallel replicas; automatic mode 2 collects statistics only. +-- Pin both for queries whose replica participation is checked below. +SELECT 'pr_uint64'; +SELECT x, a_num, inner_c +FROM test_pr_dod_alias_swap_outer +ORDER BY x +SETTINGS + enable_analyzer = 1, + enable_alias_marker = 0, + enable_parallel_replicas = 2, + max_parallel_replicas = 3, + parallel_replicas_local_plan = 1, + parallel_replicas_for_non_replicated_merge_tree = 1, + parallel_replicas_min_number_of_rows_per_replica = 0, + automatic_parallel_replicas_mode = 0, + cluster_for_parallel_replicas = 'test_cluster_one_shard_three_replicas_localhost', + serialize_query_plan = 0, + log_queries = 1, + log_comment = '05062_pr_uint64' +FORMAT TSVWithNames; + +SELECT 'pr_string'; +SELECT x, a_str, inner_c +FROM test_pr_dod_alias_swap_outer +ORDER BY x +SETTINGS + enable_analyzer = 1, + enable_alias_marker = 0, + enable_parallel_replicas = 2, + max_parallel_replicas = 3, + parallel_replicas_local_plan = 1, + parallel_replicas_for_non_replicated_merge_tree = 1, + parallel_replicas_min_number_of_rows_per_replica = 0, + automatic_parallel_replicas_mode = 0, + cluster_for_parallel_replicas = 'test_cluster_one_shard_three_replicas_localhost', + serialize_query_plan = 0, + log_queries = 1, + log_comment = '05062_pr_string' +FORMAT TSVWithNames; + +SYSTEM FLUSH LOGS query_log; +-- Check the remote legs as well as the initiator: a correct result alone does not prove parallel reading. +SELECT 'parallel_replicas_used'; +SELECT log_comment, + countIf(ProfileEvents['ParallelReplicasQueryCount'] > 0) > 0, + countIf(ProfileEvents['ParallelReplicasUsedCount'] > 0) > 0 +FROM system.query_log +WHERE type = 'QueryFinish' AND is_initial_query = 0 + AND initial_query_id IN + ( + SELECT query_id FROM system.query_log + WHERE type = 'QueryFinish' AND is_initial_query + AND current_database = currentDatabase() + AND log_comment IN ('05062_pr_uint64', '05062_pr_string') + ) +GROUP BY log_comment ORDER BY log_comment; + +DROP TABLE test_pr_dod_alias_swap_outer; +DROP TABLE test_pr_dod_alias_swap_inner; +DROP TABLE test_pr_dod_alias_swap_local; diff --git a/tests/queries/0_stateless/05063_distributed_alias_marker_explicit_column_swap.reference b/tests/queries/0_stateless/05063_distributed_alias_marker_explicit_column_swap.reference new file mode 100644 index 000000000000..f3797cb0ce0e --- /dev/null +++ b/tests/queries/0_stateless/05063_distributed_alias_marker_explicit_column_swap.reference @@ -0,0 +1,32 @@ +prefer_localhost_replica_0_uint64 +a inner_c +1 2 +1 2 +1 3 +1 3 +1 11 +1 11 +prefer_localhost_replica_0_string +a inner_c +aaaa 2 +aaaa 2 +aaaa 3 +aaaa 3 +aaaa 11 +aaaa 11 +prefer_localhost_replica_1_uint64 +a inner_c +1 2 +1 2 +1 3 +1 3 +1 11 +1 11 +prefer_localhost_replica_1_string +a inner_c +aaaa 2 +aaaa 2 +aaaa 3 +aaaa 3 +aaaa 11 +aaaa 11 diff --git a/tests/queries/0_stateless/05063_distributed_alias_marker_explicit_column_swap.sql b/tests/queries/0_stateless/05063_distributed_alias_marker_explicit_column_swap.sql new file mode 100644 index 000000000000..50085531b6dd --- /dev/null +++ b/tests/queries/0_stateless/05063_distributed_alias_marker_explicit_column_swap.sql @@ -0,0 +1,82 @@ +DROP TABLE IF EXISTS test_dod_alias_swap_local; +DROP TABLE IF EXISTS test_dod_alias_swap_inner; + +CREATE TABLE test_dod_alias_swap_local +( + x UInt64 +) +ENGINE = MergeTree() +ORDER BY x; + +INSERT INTO test_dod_alias_swap_local VALUES (1), (2), (10); + +CREATE TABLE test_dod_alias_swap_inner +( + x UInt64, + a UInt64 ALIAS 2, + inner_c UInt64 ALIAS x + 1 +) +ENGINE = Distributed(test_cluster_two_shards, currentDatabase(), test_dod_alias_swap_local); + +SELECT 'prefer_localhost_replica_0_uint64'; +SELECT + __aliasMarker(_CAST(1, 'UInt64'), '__table1.a') AS a, + __table1.inner_c AS inner_c +FROM test_dod_alias_swap_inner AS __table1 +ORDER BY __table1.x +SETTINGS + enable_analyzer = 1, + enable_alias_marker = 1, + prefer_localhost_replica = 0, + enable_parallel_replicas = 0, + max_parallel_replicas = 1, + parallel_replicas_local_plan = 0 +FORMAT TSVWithNames; + +SELECT 'prefer_localhost_replica_0_string'; +SELECT + __aliasMarker(_CAST('aaaa', 'String'), '__table1.a') AS a, + __table1.inner_c AS inner_c +FROM test_dod_alias_swap_inner AS __table1 +ORDER BY __table1.x +SETTINGS + enable_analyzer = 1, + enable_alias_marker = 1, + prefer_localhost_replica = 0, + enable_parallel_replicas = 0, + max_parallel_replicas = 1, + parallel_replicas_local_plan = 0 +FORMAT TSVWithNames; + +SELECT 'prefer_localhost_replica_1_uint64'; +SELECT + __aliasMarker(_CAST(1, 'UInt64'), '__table1.a') AS a, + __table1.inner_c AS inner_c +FROM test_dod_alias_swap_inner AS __table1 +ORDER BY __table1.x +SETTINGS + enable_analyzer = 1, + enable_alias_marker = 1, + prefer_localhost_replica = 1, + enable_parallel_replicas = 0, + max_parallel_replicas = 1, + parallel_replicas_local_plan = 0 +FORMAT TSVWithNames; + +SELECT 'prefer_localhost_replica_1_string'; +SELECT + __aliasMarker(_CAST('aaaa', 'String'), '__table1.a') AS a, + __table1.inner_c AS inner_c +FROM test_dod_alias_swap_inner AS __table1 +ORDER BY __table1.x +SETTINGS + enable_analyzer = 1, + enable_alias_marker = 1, + prefer_localhost_replica = 1, + enable_parallel_replicas = 0, + max_parallel_replicas = 1, + parallel_replicas_local_plan = 0 +FORMAT TSVWithNames; + +DROP TABLE test_dod_alias_swap_inner; +DROP TABLE test_dod_alias_swap_local; diff --git a/tests/queries/0_stateless/05064_alias_marker_in_lambda_over_distributed.reference b/tests/queries/0_stateless/05064_alias_marker_in_lambda_over_distributed.reference new file mode 100644 index 000000000000..8262875092b6 --- /dev/null +++ b/tests/queries/0_stateless/05064_alias_marker_in_lambda_over_distributed.reference @@ -0,0 +1,9 @@ +control +2 +6 +lambda +[12,22] +[11] +lambda_no_marker +[12,22] +[11] diff --git a/tests/queries/0_stateless/05064_alias_marker_in_lambda_over_distributed.sql b/tests/queries/0_stateless/05064_alias_marker_in_lambda_over_distributed.sql new file mode 100644 index 000000000000..848cc84b8f41 --- /dev/null +++ b/tests/queries/0_stateless/05064_alias_marker_in_lambda_over_distributed.sql @@ -0,0 +1,43 @@ +-- An `ALIAS` column referenced from inside a lambda body is inlined and marked like any other, but +-- `finalizeAliasMarkersForDistributedSerialization` used to refuse to descend into a lambda. The marker's id then +-- stayed a live `ColumnNode` and was rendered back into the shipped SQL as `__table1.computed`, so the shard had to +-- resolve the very `ALIAS` column the inlining exists to remove. Here the column is declared on the `Distributed` +-- table and not on the local one, so the shard cannot resolve it and the query fails. +-- +-- The finalize pass still leaves a hand-written marker inside a lambda alone, because its id resolves to the lambda +-- parameter rather than to a column of a table -- that is what 03933 covers. + +DROP TABLE IF EXISTS t_local_05064; +DROP TABLE IF EXISTS t_dist_05064; + +CREATE TABLE t_local_05064 (value UInt64, arr Array(UInt64)) ENGINE = MergeTree ORDER BY value; +INSERT INTO t_local_05064 VALUES (1, [10, 20]), (3, [5]); + +CREATE TABLE t_dist_05064 +( + value UInt64, + arr Array(UInt64), + computed UInt64 ALIAS value * 2 +) +ENGINE = Distributed(test_shard_localhost, currentDatabase(), t_local_05064); + +SELECT 'control'; +SELECT computed +FROM t_dist_05064 +ORDER BY value +SETTINGS enable_alias_marker = 1, prefer_localhost_replica = 0; + +SELECT 'lambda'; +SELECT arrayMap(x -> x + computed, arr) +FROM t_dist_05064 +ORDER BY value +SETTINGS enable_alias_marker = 1, prefer_localhost_replica = 0; + +SELECT 'lambda_no_marker'; +SELECT arrayMap(x -> x + computed, arr) +FROM t_dist_05064 +ORDER BY value +SETTINGS enable_alias_marker = 0, prefer_localhost_replica = 0; + +DROP TABLE t_dist_05064; +DROP TABLE t_local_05064; diff --git a/tests/queries/0_stateless/05065_shadowed_dotted_alias_merge_over_distributed.reference b/tests/queries/0_stateless/05065_shadowed_dotted_alias_merge_over_distributed.reference new file mode 100644 index 000000000000..f69812ea180c --- /dev/null +++ b/tests/queries/0_stateless/05065_shadowed_dotted_alias_merge_over_distributed.reference @@ -0,0 +1,18 @@ +local +1 10 100 +2 20 200 +merge_prefer0 +1 10 100 +2 20 200 +merge_prefer1 +1 10 100 +2 20 200 +multiple dotted local +1 1000 10000 +2 2000 20000 +multiple dotted merge_prefer0 +1 1000 10000 +2 2000 20000 +multiple dotted merge_prefer1 +1 1000 10000 +2 2000 20000 diff --git a/tests/queries/0_stateless/05065_shadowed_dotted_alias_merge_over_distributed.sql b/tests/queries/0_stateless/05065_shadowed_dotted_alias_merge_over_distributed.sql new file mode 100644 index 000000000000..d92def21e373 --- /dev/null +++ b/tests/queries/0_stateless/05065_shadowed_dotted_alias_merge_over_distributed.sql @@ -0,0 +1,72 @@ +-- A `Merge` over `Distributed` must preserve multiple dotted `ALIAS` names, including +-- a dotted name whose tail collides with a plain `ALIAS` name (`b` and `` `a.b` ``). +-- +-- `test_cluster_two_shards` reads the local table twice, so every query groups to dedup. + +DROP TABLE IF EXISTS t_local_05065; +DROP TABLE IF EXISTS t_dist_05065; +DROP TABLE IF EXISTS t_merge_05065; + +CREATE TABLE t_local_05065 +( + id UInt32, + b UInt32 ALIAS id * 10, + `a.b` UInt32 ALIAS id * 100, + `n.a` UInt32 ALIAS id * 1000, + `m.b` UInt32 ALIAS id * 10000 +) +ENGINE = MergeTree +ORDER BY id; + +INSERT INTO t_local_05065 VALUES (1), (2); + +CREATE TABLE t_dist_05065 AS t_local_05065 +ENGINE = Distributed(test_cluster_two_shards, currentDatabase(), t_local_05065); + +CREATE TABLE t_merge_05065 +( + id UInt32, + b UInt32, + `a.b` UInt32, + `n.a` UInt32, + `m.b` UInt32 +) +ENGINE = Merge(currentDatabase(), '^t_dist_05065$'); + +SELECT 'local'; +SELECT id, b, `a.b` +FROM t_local_05065 +GROUP BY id, b, `a.b` +ORDER BY id; + +SELECT 'merge_prefer0'; +SELECT id, b, `a.b` +FROM t_merge_05065 +GROUP BY id, b, `a.b` +ORDER BY id +SETTINGS prefer_localhost_replica = 0; + +SELECT 'merge_prefer1'; +SELECT id, b, `a.b` +FROM t_merge_05065 +GROUP BY id, b, `a.b` +ORDER BY id +SETTINGS prefer_localhost_replica = 1; + +SELECT 'multiple dotted local'; +SELECT id, `n.a`, `m.b` FROM t_local_05065 +GROUP BY id, `n.a`, `m.b` ORDER BY id; + +SELECT 'multiple dotted merge_prefer0'; +SELECT id, `n.a`, `m.b` FROM t_merge_05065 +GROUP BY id, `n.a`, `m.b` ORDER BY id +SETTINGS prefer_localhost_replica = 0; + +SELECT 'multiple dotted merge_prefer1'; +SELECT id, `n.a`, `m.b` FROM t_merge_05065 +GROUP BY id, `n.a`, `m.b` ORDER BY id +SETTINGS prefer_localhost_replica = 1; + +DROP TABLE t_merge_05065; +DROP TABLE t_dist_05065; +DROP TABLE t_local_05065; diff --git a/tests/queries/0_stateless/05066_global_join_alias_only_on_distributed.reference b/tests/queries/0_stateless/05066_global_join_alias_only_on_distributed.reference new file mode 100644 index 000000000000..48bb2c730573 --- /dev/null +++ b/tests/queries/0_stateless/05066_global_join_alias_only_on_distributed.reference @@ -0,0 +1,18 @@ +projected_marker_on +1 20 +2 40 +3 60 +clause_only_marker_on +2 +3 +projected_marker_off +1 20 +2 40 +3 60 +clause_only_marker_off +2 +3 +implicit_global_join +1 20 +2 40 +3 60 diff --git a/tests/queries/0_stateless/05066_global_join_alias_only_on_distributed.sql b/tests/queries/0_stateless/05066_global_join_alias_only_on_distributed.sql new file mode 100644 index 000000000000..c5e86a074721 --- /dev/null +++ b/tests/queries/0_stateless/05066_global_join_alias_only_on_distributed.sql @@ -0,0 +1,75 @@ +-- A `GLOBAL JOIN` ships its right side as a temporary table, whose columns are the ones +-- `CollectColumnSourceToColumnsVisitor` gathered from the query tree. The `__aliasMarker` id is a +-- `ColumnNode` naming the `ALIAS` column an inlined expression came from, so it used to be gathered +-- as if it were a column the query reads. The temporary table's subquery is rebuilt from names and +-- types alone, which drops the alias body, and the shard is then asked for a column its local table +-- does not declare. +-- +-- `foo` exists only on the `Distributed` table here, so nothing can resolve it on the shard. + +DROP TABLE IF EXISTS t_left_local_05066; +DROP TABLE IF EXISTS t_left_dist_05066; +DROP TABLE IF EXISTS t_right_local_05066; +DROP TABLE IF EXISTS t_right_dist_05066; + +CREATE TABLE t_left_local_05066 (id UInt64) ENGINE = MergeTree ORDER BY id; +INSERT INTO t_left_local_05066 VALUES (1), (2), (3); + +CREATE TABLE t_left_dist_05066 AS t_left_local_05066 +ENGINE = Distributed(test_shard_localhost, currentDatabase(), t_left_local_05066); + +CREATE TABLE t_right_local_05066 (id UInt64, x UInt64) ENGINE = MergeTree ORDER BY id; +INSERT INTO t_right_local_05066 VALUES (1, 10), (2, 20), (3, 30); + +CREATE TABLE t_right_dist_05066 +( + id UInt64, + x UInt64, + foo UInt64 ALIAS x * 2 +) +ENGINE = Distributed(test_shard_localhost, currentDatabase(), t_right_local_05066); + +SELECT 'projected_marker_on'; +SELECT m.id, r.foo +FROM t_left_dist_05066 AS m +GLOBAL INNER JOIN t_right_dist_05066 AS r ON m.id = r.id +ORDER BY m.id +SETTINGS enable_alias_marker = 1, prefer_localhost_replica = 0; + +-- The projection path also sets an alias on the marker. Referencing the column only in a clause +-- exercises the path where no alias is set at all. +SELECT 'clause_only_marker_on'; +SELECT m.id +FROM t_left_dist_05066 AS m +GLOBAL INNER JOIN t_right_dist_05066 AS r ON m.id = r.id +WHERE r.foo > 30 +ORDER BY m.id +SETTINGS enable_alias_marker = 1, prefer_localhost_replica = 0; + +SELECT 'projected_marker_off'; +SELECT m.id, r.foo +FROM t_left_dist_05066 AS m +GLOBAL INNER JOIN t_right_dist_05066 AS r ON m.id = r.id +ORDER BY m.id +SETTINGS enable_alias_marker = 0, prefer_localhost_replica = 0; + +SELECT 'clause_only_marker_off'; +SELECT m.id +FROM t_left_dist_05066 AS m +GLOBAL INNER JOIN t_right_dist_05066 AS r ON m.id = r.id +WHERE r.foo > 30 +ORDER BY m.id +SETTINGS enable_alias_marker = 0, prefer_localhost_replica = 0; + +-- The same rewrite reaches a plain `JOIN` through `prefer_global_in_and_join`. +SELECT 'implicit_global_join'; +SELECT m.id, r.foo +FROM t_left_dist_05066 AS m +INNER JOIN t_right_dist_05066 AS r ON m.id = r.id +ORDER BY m.id +SETTINGS enable_alias_marker = 1, prefer_localhost_replica = 0, prefer_global_in_and_join = 1; + +DROP TABLE t_right_dist_05066; +DROP TABLE t_right_local_05066; +DROP TABLE t_left_dist_05066; +DROP TABLE t_left_local_05066; diff --git a/tests/queries/0_stateless/05067_alias_marker_direct_distributed_identity.reference b/tests/queries/0_stateless/05067_alias_marker_direct_distributed_identity.reference new file mode 100644 index 000000000000..2ad1f43be009 --- /dev/null +++ b/tests/queries/0_stateless/05067_alias_marker_direct_distributed_identity.reference @@ -0,0 +1,28 @@ +local +4 +2 +3 +4 +5 +6 +distributed_marker_on +4 +2 +3 +4 +5 +6 +distributed_marker_off +4 +2 +3 +4 +5 +6 +distributed_serialized_plan +4 +2 +3 +4 +5 +6 diff --git a/tests/queries/0_stateless/05067_alias_marker_direct_distributed_identity.sql b/tests/queries/0_stateless/05067_alias_marker_direct_distributed_identity.sql new file mode 100644 index 000000000000..8d11e82482f3 --- /dev/null +++ b/tests/queries/0_stateless/05067_alias_marker_direct_distributed_identity.sql @@ -0,0 +1,36 @@ +-- A direct two-argument `__aliasMarker` must not acquire a transport ID from its column argument. +SET enable_analyzer = 1; + +DROP TABLE IF EXISTS t_local_05067; +DROP TABLE IF EXISTS t_dist_05067; + +CREATE TABLE t_local_05067 (x UInt64) ENGINE = MergeTree ORDER BY x; +INSERT INTO t_local_05067 VALUES (1), (2), (3), (4), (5); + +CREATE TABLE t_dist_05067 AS t_local_05067 +ENGINE = Distributed(test_shard_localhost, currentDatabase(), t_local_05067); + +SELECT 'local'; +SELECT count() FROM t_local_05067 WHERE __aliasMarker(x + 1, x) > 2; +SELECT __aliasMarker(x + 1, x) FROM t_local_05067 ORDER BY x; + +SELECT 'distributed_marker_on'; +SELECT count() FROM t_dist_05067 WHERE __aliasMarker(x + 1, x) > 2 +SETTINGS enable_alias_marker = 1, prefer_localhost_replica = 0; +SELECT __aliasMarker(x + 1, x) FROM t_dist_05067 ORDER BY x +SETTINGS enable_alias_marker = 1, prefer_localhost_replica = 0; + +SELECT 'distributed_marker_off'; +SELECT count() FROM t_dist_05067 WHERE __aliasMarker(x + 1, x) > 2 +SETTINGS enable_alias_marker = 0, prefer_localhost_replica = 0; +SELECT __aliasMarker(x + 1, x) FROM t_dist_05067 ORDER BY x +SETTINGS enable_alias_marker = 0, prefer_localhost_replica = 0; + +SELECT 'distributed_serialized_plan'; +SELECT count() FROM t_dist_05067 WHERE __aliasMarker(x + 1, x) > 2 +SETTINGS enable_alias_marker = 1, prefer_localhost_replica = 0, serialize_query_plan = 1; +SELECT __aliasMarker(x + 1, x) FROM t_dist_05067 ORDER BY x +SETTINGS enable_alias_marker = 1, prefer_localhost_replica = 0, serialize_query_plan = 1; + +DROP TABLE t_dist_05067; +DROP TABLE t_local_05067; diff --git a/tests/queries/0_stateless/05068_alias_marker_direct_id_dependencies.reference b/tests/queries/0_stateless/05068_alias_marker_direct_id_dependencies.reference new file mode 100644 index 000000000000..5e2db433c5d3 --- /dev/null +++ b/tests/queries/0_stateless/05068_alias_marker_direct_id_dependencies.reference @@ -0,0 +1,40 @@ +alias_on_both_marker_on +4 +2 +3 +4 +5 +6 +alias_on_both_marker_off +4 +2 +3 +4 +5 +6 +alias_only_on_distributed_marker_on +4 +2 +3 +4 +5 +6 +alias_only_on_distributed_marker_off +4 +2 +3 +4 +5 +6 +pending_token_survives_scalar_rewrite +10 +20 +30 +40 +50 +global_join_id_only +1 1 +2 1 +3 1 +4 1 +5 1 diff --git a/tests/queries/0_stateless/05068_alias_marker_direct_id_dependencies.sql b/tests/queries/0_stateless/05068_alias_marker_direct_id_dependencies.sql new file mode 100644 index 000000000000..a36559ce02ca --- /dev/null +++ b/tests/queries/0_stateless/05068_alias_marker_direct_id_dependencies.sql @@ -0,0 +1,63 @@ +-- A hand-written marker's second argument is still a SQL column reference until the shard analyzes it. +-- It must be inlined when it is an `ALIAS` and collected when it is needed by a `GLOBAL JOIN` temporary table. +SET enable_analyzer = 1; + +DROP TABLE IF EXISTS t_local_05068; +DROP TABLE IF EXISTS t_dist_05068; +DROP TABLE IF EXISTS t_dist_only_alias_05068; +DROP TABLE IF EXISTS t_left_05068; +DROP TABLE IF EXISTS t_right_05068; + +CREATE TABLE t_local_05068 (x UInt64, computed UInt64 ALIAS x * 10) ENGINE = MergeTree ORDER BY x; +INSERT INTO t_local_05068 (x) VALUES (1), (2), (3), (4), (5); +CREATE TABLE t_dist_05068 AS t_local_05068 +ENGINE = Distributed(test_shard_localhost, currentDatabase(), t_local_05068); + +CREATE TABLE t_left_05068 (x UInt64) ENGINE = MergeTree ORDER BY x; +INSERT INTO t_left_05068 VALUES (1), (2), (3), (4), (5); +-- This alias exists only on the initiator's `Distributed` schema, not on the local storage. +CREATE TABLE t_dist_only_alias_05068 (x UInt64, computed UInt64 ALIAS x * 10) +ENGINE = Distributed(test_shard_localhost, currentDatabase(), t_left_05068); + +SELECT 'alias_on_both_marker_on'; +SELECT count() FROM t_dist_05068 WHERE __aliasMarker(x + 1, computed) > 2 +SETTINGS enable_alias_marker = 1, prefer_localhost_replica = 0; +SELECT __aliasMarker(x + 1, computed) FROM t_dist_05068 ORDER BY x +SETTINGS enable_alias_marker = 1, prefer_localhost_replica = 0; +SELECT 'alias_on_both_marker_off'; +SELECT count() FROM t_dist_05068 WHERE __aliasMarker(x + 1, computed) > 2 +SETTINGS enable_alias_marker = 0, prefer_localhost_replica = 0; +SELECT __aliasMarker(x + 1, computed) FROM t_dist_05068 ORDER BY x +SETTINGS enable_alias_marker = 0, prefer_localhost_replica = 0; + +SELECT 'alias_only_on_distributed_marker_on'; +SELECT count() FROM t_dist_only_alias_05068 WHERE __aliasMarker(x + 1, computed) > 2 +SETTINGS enable_alias_marker = 1, prefer_localhost_replica = 0; +SELECT __aliasMarker(x + 1, computed) FROM t_dist_only_alias_05068 ORDER BY x +SETTINGS enable_alias_marker = 1, prefer_localhost_replica = 0; +SELECT 'alias_only_on_distributed_marker_off'; +SELECT count() FROM t_dist_only_alias_05068 WHERE __aliasMarker(x + 1, computed) > 2 +SETTINGS enable_alias_marker = 0, prefer_localhost_replica = 0; +SELECT __aliasMarker(x + 1, computed) FROM t_dist_only_alias_05068 ORDER BY x +SETTINGS enable_alias_marker = 0, prefer_localhost_replica = 0; + +SELECT 'pending_token_survives_scalar_rewrite'; +SELECT computed FROM t_dist_only_alias_05068 ORDER BY x +SETTINGS enable_alias_marker = 1, prefer_localhost_replica = 0, optimize_const_name_size = 0; + +CREATE TABLE t_right_05068 (x UInt64, extra UInt64) ENGINE = MergeTree ORDER BY x; +INSERT INTO t_right_05068 VALUES (1, 10), (2, 20), (3, 30), (4, 40), (5, 50); + +-- `extra` is mentioned only in the marker's ID. It must still appear in the materialized right side. +SELECT 'global_join_id_only'; +SELECT l.x, __aliasMarker(1, r.extra) AS marker_value +FROM t_dist_only_alias_05068 AS l +GLOBAL INNER JOIN t_right_05068 AS r ON l.x = r.x +ORDER BY l.x +SETTINGS enable_alias_marker = 1, prefer_localhost_replica = 0; + +DROP TABLE t_right_05068; +DROP TABLE t_dist_only_alias_05068; +DROP TABLE t_left_05068; +DROP TABLE t_dist_05068; +DROP TABLE t_local_05068; diff --git a/tests/queries/0_stateless/05069_alias_marker_projection_join_using.reference b/tests/queries/0_stateless/05069_alias_marker_projection_join_using.reference new file mode 100644 index 000000000000..d52367ba8326 --- /dev/null +++ b/tests/queries/0_stateless/05069_alias_marker_projection_join_using.reference @@ -0,0 +1,12 @@ +local +2 20 +3 30 +4 40 +distributed_marker_on +2 20 +3 30 +4 40 +distributed_marker_off +2 20 +3 30 +4 40 diff --git a/tests/queries/0_stateless/05069_alias_marker_projection_join_using.sql b/tests/queries/0_stateless/05069_alias_marker_projection_join_using.sql new file mode 100644 index 000000000000..1361c00b304c --- /dev/null +++ b/tests/queries/0_stateless/05069_alias_marker_projection_join_using.sql @@ -0,0 +1,32 @@ +-- `JOIN USING` can resolve a projection alias to an expression-bearing `ColumnNode`. +-- The matching query-tree test checks the injected side marker: `USING (k)` renders only the key name. +SET enable_analyzer = 1; + +DROP TABLE IF EXISTS t_left_05069; +DROP TABLE IF EXISTS t_dist_05069; +DROP TABLE IF EXISTS t_right_05069; + +CREATE TABLE t_left_05069 (x UInt64) ENGINE = MergeTree ORDER BY x; +INSERT INTO t_left_05069 VALUES (1), (2), (3); +CREATE TABLE t_dist_05069 AS t_left_05069 +ENGINE = Distributed(test_shard_localhost, currentDatabase(), t_left_05069); +CREATE TABLE t_right_05069 (k UInt64, v UInt64) ENGINE = MergeTree ORDER BY k; +INSERT INTO t_right_05069 VALUES (2, 20), (3, 30), (4, 40); + +SELECT 'local'; +SELECT l.x + 1 AS k, r.v FROM t_left_05069 AS l INNER JOIN t_right_05069 AS r USING (k) ORDER BY k +SETTINGS analyzer_compatibility_join_using_top_level_identifier = 1; + +SELECT 'distributed_marker_on'; +SELECT l.x + 1 AS k, r.v FROM t_dist_05069 AS l INNER JOIN t_right_05069 AS r USING (k) ORDER BY k +SETTINGS analyzer_compatibility_join_using_top_level_identifier = 1, enable_alias_marker = 1, + prefer_global_in_and_join = 1, prefer_localhost_replica = 0; + +SELECT 'distributed_marker_off'; +SELECT l.x + 1 AS k, r.v FROM t_dist_05069 AS l INNER JOIN t_right_05069 AS r USING (k) ORDER BY k +SETTINGS analyzer_compatibility_join_using_top_level_identifier = 1, enable_alias_marker = 0, + prefer_global_in_and_join = 1, prefer_localhost_replica = 0; + +DROP TABLE t_right_05069; +DROP TABLE t_dist_05069; +DROP TABLE t_left_05069; diff --git a/tests/queries/0_stateless/05070_alias_marker_pending_boundaries.reference b/tests/queries/0_stateless/05070_alias_marker_pending_boundaries.reference new file mode 100644 index 000000000000..18cb72eab2b4 --- /dev/null +++ b/tests/queries/0_stateless/05070_alias_marker_pending_boundaries.reference @@ -0,0 +1,8 @@ +local_pending +1 +1 +bad_token +bad_arity +distributed_sql +distributed_plan +global_in_subquery diff --git a/tests/queries/0_stateless/05070_alias_marker_pending_boundaries.sql b/tests/queries/0_stateless/05070_alias_marker_pending_boundaries.sql new file mode 100644 index 000000000000..229b4c9d4cfc --- /dev/null +++ b/tests/queries/0_stateless/05070_alias_marker_pending_boundaries.sql @@ -0,0 +1,37 @@ +-- The three-argument form is an internal pending marker. A local query evaluates its payload. +-- The distributed cases fail in the finalizer, since `1` is not a column id; `gtest_alias_marker_pending` covers the shipping check. +SET enable_analyzer = 1; + +DROP TABLE IF EXISTS t_local_05070; +DROP TABLE IF EXISTS t_dist_05070; +CREATE TABLE t_local_05070 (x UInt64) ENGINE = MergeTree ORDER BY x; +INSERT INTO t_local_05070 VALUES (1), (2); +CREATE TABLE t_dist_05070 AS t_local_05070 +ENGINE = Distributed(test_shard_localhost, currentDatabase(), t_local_05070); + +SELECT 'local_pending'; +SELECT __aliasMarker(1, 1, '__aliasMarker_pending_v1') FROM t_local_05070 ORDER BY x +SETTINGS enable_parallel_replicas = 0; + +SELECT 'bad_token'; +SELECT __aliasMarker(1, 1, 'wrong_token') FROM t_local_05070; -- { serverError BAD_ARGUMENTS } +SELECT __aliasMarker(NULL, 1, 'wrong_token') FROM t_local_05070; -- { serverError BAD_ARGUMENTS } + +SELECT 'bad_arity'; +SELECT __aliasMarker(1, 1, 'wrong_token', 1) FROM t_local_05070; -- { serverError BAD_ARGUMENTS } + +SELECT 'distributed_sql'; +SELECT count() FROM t_dist_05070 WHERE __aliasMarker(x + 1, 1, '__aliasMarker_pending_v1') > 2 +SETTINGS prefer_localhost_replica = 0, serialize_query_plan = 0; -- { serverError BAD_ARGUMENTS } + +SELECT 'distributed_plan'; +SELECT count() FROM t_dist_05070 WHERE __aliasMarker(x + 1, 1, '__aliasMarker_pending_v1') > 2 +SETTINGS prefer_localhost_replica = 0, serialize_query_plan = 1; -- { serverError BAD_ARGUMENTS } + +SELECT 'global_in_subquery'; +SELECT count() FROM t_dist_05070 WHERE x GLOBAL IN + (SELECT __aliasMarker(number + 1, 1, '__aliasMarker_pending_v1') FROM numbers(1)) +SETTINGS prefer_localhost_replica = 0; -- { serverError BAD_ARGUMENTS } + +DROP TABLE t_dist_05070; +DROP TABLE t_local_05070; diff --git a/tests/queries/0_stateless/05071_alias_marker_shipped_sql_form.reference b/tests/queries/0_stateless/05071_alias_marker_shipped_sql_form.reference new file mode 100644 index 000000000000..fbf003931474 --- /dev/null +++ b/tests/queries/0_stateless/05071_alias_marker_shipped_sql_form.reference @@ -0,0 +1,4 @@ +sql_transport +1 1 +plan_attached_sql +1 1 diff --git a/tests/queries/0_stateless/05071_alias_marker_shipped_sql_form.sql b/tests/queries/0_stateless/05071_alias_marker_shipped_sql_form.sql new file mode 100644 index 000000000000..4ce3ae45f467 --- /dev/null +++ b/tests/queries/0_stateless/05071_alias_marker_shipped_sql_form.sql @@ -0,0 +1,48 @@ +-- The SQL attached to each remote query keeps the finalized two-argument marker, even when +-- the server also sends a serialized plan. The plan packet itself contains actions, not this SQL call. +SET enable_analyzer = 1; + +DROP TABLE IF EXISTS t_local_05071; +DROP TABLE IF EXISTS t_dist_05071; + +CREATE TABLE t_local_05071 (x UInt64) ENGINE = MergeTree ORDER BY x; +INSERT INTO t_local_05071 VALUES (1), (2), (3); +CREATE TABLE t_dist_05071 (x UInt64, computed UInt64 ALIAS x * 10) +ENGINE = Distributed(test_shard_localhost, currentDatabase(), t_local_05071); + +SELECT computed FROM t_dist_05071 ORDER BY x +SETTINGS enable_alias_marker = 1, prefer_localhost_replica = 0, serialize_query_plan = 0, + log_queries = 1, log_comment = '05071_alias_marker_sql' FORMAT Null; + +SELECT computed FROM t_dist_05071 ORDER BY x +SETTINGS enable_alias_marker = 1, prefer_localhost_replica = 0, serialize_query_plan = 1, + distributed_group_by_no_merge = 0, log_queries = 1, log_comment = '05071_alias_marker_plan' FORMAT Null; + +SYSTEM FLUSH LOGS query_log; + +SELECT 'sql_transport'; +SELECT count() > 0 AS shipped, countIf( + position(query, '__aliasMarker(') > 0 + AND match(query, '__aliasMarker[(].+, ''__table[0-9]+[.]computed''[)]') + AND position(query, '__aliasMarker_pending_v1') = 0) = count() AS finalized +FROM system.query_log +WHERE type = 'QueryFinish' AND is_initial_query = 0 + AND initial_query_id IN ( + SELECT query_id FROM system.query_log + WHERE type = 'QueryFinish' AND is_initial_query = 1 + AND current_database = currentDatabase() AND log_comment = '05071_alias_marker_sql'); + +SELECT 'plan_attached_sql'; +SELECT count() > 0 AS shipped, countIf( + position(query, '__aliasMarker(') > 0 + AND match(query, '__aliasMarker[(].+, ''__table[0-9]+[.]computed''[)]') + AND position(query, '__aliasMarker_pending_v1') = 0) = count() AS finalized +FROM system.query_log +WHERE type = 'QueryFinish' AND is_initial_query = 0 + AND initial_query_id IN ( + SELECT query_id FROM system.query_log + WHERE type = 'QueryFinish' AND is_initial_query = 1 + AND current_database = currentDatabase() AND log_comment = '05071_alias_marker_plan'); + +DROP TABLE t_dist_05071; +DROP TABLE t_local_05071; diff --git a/tests/queries/0_stateless/05072_storage_merge_quoted_alias_identifiers.reference b/tests/queries/0_stateless/05072_storage_merge_quoted_alias_identifiers.reference new file mode 100644 index 000000000000..ae525de7485f --- /dev/null +++ b/tests/queries/0_stateless/05072_storage_merge_quoted_alias_identifiers.reference @@ -0,0 +1,29 @@ +local source +1 10 100 11 +2 20 200 21 +local Merge +1 10 100 11 +2 20 200 21 +Distributed AST +1 10 100 +2 20 200 +Distributed plan +1 10 100 +2 20 200 +intermediate alias +1 11 +2 21 +row policy +2 20 +row policy all aliases +2 20 200 +policy-only alias +2 +flag alias policy +2 1 +flag alias policy-only column +2 +missing child aliases +1 10 100 11 +2 20 200 21 +3 0 0 0 diff --git a/tests/queries/0_stateless/05072_storage_merge_quoted_alias_identifiers.sql b/tests/queries/0_stateless/05072_storage_merge_quoted_alias_identifiers.sql new file mode 100644 index 000000000000..afed9851d6e8 --- /dev/null +++ b/tests/queries/0_stateless/05072_storage_merge_quoted_alias_identifiers.sql @@ -0,0 +1,87 @@ +-- Qualified `ALIAS` names must use the planner's quoting convention in `Merge` header +-- reconciliation. A missing match used to fill both quoted outputs with zeros. +SET enable_analyzer = 1, enable_alias_marker = 1; + +DROP TABLE IF EXISTS t_merge_dist_05072; +DROP TABLE IF EXISTS t_merge_local_05072; +DROP TABLE IF EXISTS t_dist_05072; +DROP TABLE IF EXISTS t_local_05072; + +CREATE TABLE t_local_05072 +( + id UInt32, + `a-b` UInt32 ALIAS id * 10, + `a b` UInt32 ALIAS id * 100, + chained UInt32 ALIAS `a-b` + 1, + flag UInt8 ALIAS id = 2 +) ENGINE = MergeTree ORDER BY id; +INSERT INTO t_local_05072 VALUES (1), (2); + +CREATE TABLE t_dist_05072 AS t_local_05072 +ENGINE = Distributed(test_cluster_two_shards, currentDatabase(), t_local_05072); +CREATE TABLE t_merge_local_05072 +(id UInt32, `a-b` UInt32, `a b` UInt32, chained UInt32, flag UInt8) +ENGINE = Merge(currentDatabase(), '^t_local_05072$'); +CREATE TABLE t_merge_dist_05072 AS t_merge_local_05072 +ENGINE = Merge(currentDatabase(), '^t_dist_05072$'); + +SELECT 'local source'; +SELECT id, `a-b`, `a b`, chained FROM t_local_05072 ORDER BY id; + +SELECT 'local Merge'; +SELECT id, `a-b`, `a b`, chained FROM t_merge_local_05072 ORDER BY id; + +SELECT 'Distributed AST'; +SELECT id, `a-b`, `a b` FROM t_merge_dist_05072 +GROUP BY id, `a-b`, `a b` ORDER BY id +SETTINGS prefer_localhost_replica = 0, serialize_query_plan = 0; + +SELECT 'Distributed plan'; +SELECT id, `a-b`, `a b` FROM t_merge_dist_05072 +GROUP BY id, `a-b`, `a b` ORDER BY id +SETTINGS prefer_localhost_replica = 0, serialize_query_plan = 1; + +-- The `a-b` alias is used to calculate `chained`, but is not an output column. +SELECT 'intermediate alias'; +SELECT id, chained FROM t_merge_dist_05072 GROUP BY id, chained ORDER BY id +SETTINGS prefer_localhost_replica = 0; + +-- The row policy on the `Distributed` child runs after the qualified alias reconstruction. +CREATE ROW POLICY rp_05072 ON t_dist_05072 USING id >= 2 AND `a-b` >= 20 TO ALL; +SELECT 'row policy'; +SELECT id, `a-b` FROM t_merge_dist_05072 GROUP BY id, `a-b` ORDER BY id +SETTINGS prefer_localhost_replica = 0; +SELECT 'row policy all aliases'; +SELECT id, `a-b`, `a b` FROM t_merge_dist_05072 +GROUP BY id, `a-b`, `a b` ORDER BY id +SETTINGS prefer_localhost_replica = 0, serialize_query_plan = 1; +SELECT 'policy-only alias'; +SELECT id FROM t_merge_dist_05072 GROUP BY id ORDER BY id +SETTINGS prefer_localhost_replica = 0; +DROP ROW POLICY rp_05072 ON t_dist_05072; + +-- A policy over a UInt8 `ALIAS` preserves the selected column and also works when +-- the alias is needed only for the filter. +CREATE ROW POLICY rp_05072 ON t_dist_05072 USING flag = 1 TO ALL; +SELECT 'flag alias policy'; +SELECT id, flag FROM t_merge_dist_05072 GROUP BY id, flag ORDER BY id +SETTINGS prefer_localhost_replica = 0; +SELECT 'flag alias policy-only column'; +SELECT id FROM t_merge_dist_05072 GROUP BY id ORDER BY id +SETTINGS prefer_localhost_replica = 0; +DROP ROW POLICY rp_05072 ON t_dist_05072; + +-- A child without these columns still uses the ordinary missing-column defaults. +CREATE TABLE t_missing_05072 (id UInt32) ENGINE = MergeTree ORDER BY id; +INSERT INTO t_missing_05072 VALUES (3); +CREATE TABLE t_merge_mixed_05072 AS t_merge_local_05072 +ENGINE = Merge(currentDatabase(), '^t_(local|missing)_05072$'); +SELECT 'missing child aliases'; +SELECT id, `a-b`, `a b`, chained FROM t_merge_mixed_05072 ORDER BY id; +DROP TABLE t_merge_mixed_05072; +DROP TABLE t_missing_05072; + +DROP TABLE t_merge_dist_05072; +DROP TABLE t_merge_local_05072; +DROP TABLE t_dist_05072; +DROP TABLE t_local_05072; diff --git a/tests/queries/0_stateless/05073_storage_merge_bare_row_policy_column.reference b/tests/queries/0_stateless/05073_storage_merge_bare_row_policy_column.reference new file mode 100644 index 000000000000..10652999a625 --- /dev/null +++ b/tests/queries/0_stateless/05073_storage_merge_bare_row_policy_column.reference @@ -0,0 +1,24 @@ +local alias selected +2 5 +local alias policy-only +2 +local physical selected +2 1 +3 1 +4 5 +local physical policy-only +2 +3 +4 +Distributed AST alias selected +2 5 +Distributed plan alias selected +2 5 +Distributed alias policy-only +2 +Distributed plan alias policy-only +2 +Distributed physical selected +2 1 +3 1 +4 5 diff --git a/tests/queries/0_stateless/05073_storage_merge_bare_row_policy_column.sql b/tests/queries/0_stateless/05073_storage_merge_bare_row_policy_column.sql new file mode 100644 index 000000000000..87095e28b05a --- /dev/null +++ b/tests/queries/0_stateless/05073_storage_merge_bare_row_policy_column.sql @@ -0,0 +1,64 @@ +-- A bare row-policy column is the filter input, not a newly computed output. +-- The value must survive filtering when selected, and rows with a false value +-- must be excluded even when the column is used only by the policy. +SET enable_analyzer = 1, enable_alias_marker = 1; + +DROP TABLE IF EXISTS t_merge_dist_05073; +DROP TABLE IF EXISTS t_merge_local_05073; +DROP TABLE IF EXISTS t_dist_05073; +DROP TABLE IF EXISTS t_local_05073; + +CREATE TABLE t_local_05073 +( + id UInt32, + visible UInt8, + flag UInt8 ALIAS if(id = 2, 5, 0) +) ENGINE = MergeTree ORDER BY id; +INSERT INTO t_local_05073 VALUES (1, 0), (2, 1), (3, 1), (4, 5); + +CREATE TABLE t_dist_05073 AS t_local_05073 +ENGINE = Distributed(test_cluster_two_shards, currentDatabase(), t_local_05073); +CREATE TABLE t_merge_local_05073 (id UInt32, visible UInt8, flag UInt8) +ENGINE = Merge(currentDatabase(), '^t_local_05073$'); +CREATE TABLE t_merge_dist_05073 AS t_merge_local_05073 +ENGINE = Merge(currentDatabase(), '^t_dist_05073$'); + +CREATE ROW POLICY rp_05073 ON t_local_05073 USING flag TO ALL; +SELECT 'local alias selected'; +SELECT id, flag FROM t_merge_local_05073 ORDER BY id; +SELECT 'local alias policy-only'; +SELECT id FROM t_merge_local_05073 ORDER BY id; +DROP ROW POLICY rp_05073 ON t_local_05073; + +CREATE ROW POLICY rp_05073 ON t_local_05073 USING visible TO ALL; +SELECT 'local physical selected'; +SELECT id, visible FROM t_merge_local_05073 ORDER BY id; +SELECT 'local physical policy-only'; +SELECT id FROM t_merge_local_05073 ORDER BY id; +DROP ROW POLICY rp_05073 ON t_local_05073; + +CREATE ROW POLICY rp_05073 ON t_dist_05073 USING flag TO ALL; +SELECT 'Distributed AST alias selected'; +SELECT id, flag FROM t_merge_dist_05073 GROUP BY id, flag ORDER BY id +SETTINGS prefer_localhost_replica = 0, serialize_query_plan = 0; +SELECT 'Distributed plan alias selected'; +SELECT id, flag FROM t_merge_dist_05073 GROUP BY id, flag ORDER BY id +SETTINGS prefer_localhost_replica = 0, serialize_query_plan = 1; +SELECT 'Distributed alias policy-only'; +SELECT id FROM t_merge_dist_05073 GROUP BY id ORDER BY id +SETTINGS prefer_localhost_replica = 0; +SELECT 'Distributed plan alias policy-only'; +SELECT id FROM t_merge_dist_05073 GROUP BY id ORDER BY id +SETTINGS prefer_localhost_replica = 0, serialize_query_plan = 1; +DROP ROW POLICY rp_05073 ON t_dist_05073; + +CREATE ROW POLICY rp_05073 ON t_dist_05073 USING visible TO ALL; +SELECT 'Distributed physical selected'; +SELECT id, visible FROM t_merge_dist_05073 GROUP BY id, visible ORDER BY id +SETTINGS prefer_localhost_replica = 0; +DROP ROW POLICY rp_05073 ON t_dist_05073; + +DROP TABLE t_merge_dist_05073; +DROP TABLE t_merge_local_05073; +DROP TABLE t_dist_05073; +DROP TABLE t_local_05073; diff --git a/tests/queries/0_stateless/05074_alias_marker_low_cardinality_lambda.reference b/tests/queries/0_stateless/05074_alias_marker_low_cardinality_lambda.reference new file mode 100644 index 000000000000..a2f29fe401b1 --- /dev/null +++ b/tests/queries/0_stateless/05074_alias_marker_low_cardinality_lambda.reference @@ -0,0 +1,28 @@ +payload_type +LowCardinality(Nullable(Int64)) LowCardinality(Nullable(Int64)) +LowCardinality(Nullable(Int64)) LowCardinality(Nullable(Int64)) +LowCardinality(Nullable(Int64)) LowCardinality(Nullable(Int64)) +lambda_local +[1] +[2] +[NULL] +lambda_capture_local +[1] +[2] +[NULL] +lambda_distributed_ast +[1] +[2] +[NULL] +lambda_capture_distributed_ast +[1] +[2] +[NULL] +lambda_distributed_plan +[1] +[2] +[NULL] +lambda_capture_distributed_plan +[1] +[2] +[NULL] diff --git a/tests/queries/0_stateless/05074_alias_marker_low_cardinality_lambda.sql b/tests/queries/0_stateless/05074_alias_marker_low_cardinality_lambda.sql new file mode 100644 index 000000000000..97d9a885c54d --- /dev/null +++ b/tests/queries/0_stateless/05074_alias_marker_low_cardinality_lambda.sql @@ -0,0 +1,42 @@ +-- The planner removes `__aliasMarker` without converting its payload, so type inference must preserve `LowCardinality`. +SET enable_analyzer = 1; +SET enable_alias_marker = 1; +SET allow_suspicious_low_cardinality_types = 1; + +DROP TABLE IF EXISTS alias_marker_lc_dist; +DROP TABLE IF EXISTS alias_marker_lc_local; + +CREATE TABLE alias_marker_lc_local (x LowCardinality(Nullable(Int64))) ENGINE = MergeTree ORDER BY tuple(); +INSERT INTO alias_marker_lc_local VALUES (1), (2), (NULL); + +CREATE TABLE alias_marker_lc_dist AS alias_marker_lc_local +ENGINE = Distributed(test_shard_localhost, currentDatabase(), alias_marker_lc_local); + +SELECT 'payload_type'; +SELECT toTypeName(x), toTypeName(__aliasMarker(x, x)) FROM alias_marker_lc_local ORDER BY x; + +SELECT 'lambda_local'; +SELECT arrayMap(lx -> __aliasMarker(lx, lx), [x]) FROM alias_marker_lc_local ORDER BY x; + +-- The fuzzer's asterisk expands to the table column captured by the lambda, not its parameter. +SELECT 'lambda_capture_local'; +SELECT arrayMap(lx -> __aliasMarker(*, lx), [x]) FROM alias_marker_lc_local ORDER BY x; + +SELECT 'lambda_distributed_ast'; +SELECT arrayMap(lx -> __aliasMarker(lx, lx), [x]) FROM alias_marker_lc_dist ORDER BY x +SETTINGS prefer_localhost_replica = 0, serialize_query_plan = 0; + +SELECT 'lambda_capture_distributed_ast'; +SELECT arrayMap(lx -> __aliasMarker(*, lx), [x]) FROM alias_marker_lc_dist ORDER BY x +SETTINGS prefer_localhost_replica = 0, serialize_query_plan = 0; + +SELECT 'lambda_distributed_plan'; +SELECT arrayMap(lx -> __aliasMarker(lx, lx), [x]) FROM alias_marker_lc_dist ORDER BY x +SETTINGS prefer_localhost_replica = 0, serialize_query_plan = 1; + +SELECT 'lambda_capture_distributed_plan'; +SELECT arrayMap(lx -> __aliasMarker(*, lx), [x]) FROM alias_marker_lc_dist ORDER BY x +SETTINGS prefer_localhost_replica = 0, serialize_query_plan = 1; + +DROP TABLE alias_marker_lc_dist; +DROP TABLE alias_marker_lc_local;