Use node alias for ALIAS lookup, position-based unique action names

Fix two gaps:

1. Renamed projections (`SELECT a1 AS first, a2 AS second`): use the
   node alias (set by `ReplaseAliasColumnsVisitor` to the original ALIAS
   column name) instead of projection display name for the `getDefault`
   lookup.  This correctly identifies ALIAS columns even after user renames.

2. Repeated projections (`SELECT a1, a2, a2`): generate the action name
   as `column_name + "_" + position` (e.g. `a2_1`, `a2_2`) to
   guarantee uniqueness per projection position, preventing collisions
   when the same ALIAS column appears multiple times.

Add regression tests for both cases.

Co-authored-by: Yakov Olkhovskiy <[email protected]>
This commit is contained in:
2026-06-04 14:13:30 +00:00
co-authored by Yakov Olkhovskiy
parent 7a8b07ce26
commit 2f996780e9
3 changed files with 22 additions and 9 deletions
+13 -9
View File
@@ -942,15 +942,15 @@ QueryTreeNodePtr buildQueryTreeDistributed(SelectQueryInfo & query_info,
/// The planner assigns action-node names by expression structure, so duplicates
/// get the same name and downstream deduplication shrinks the column count,
/// causing NUMBER_OF_COLUMNS_DOESNT_MATCH. Wrap subsequent duplicates in
/// __actionName(expr, 'column_name') which forces a unique action-node name.
/// Only wrap items that correspond to ALIAS columns (identified by checking
/// whether the column has a Default of kind Alias in the storage metadata).
/// __actionName(expr, '<unique_name>') which forces a unique action-node name.
/// Identify ALIAS columns via the alias set by ReplaseAliasColumnsVisitor
/// (which is the original column name), and use position-based naming to
/// guarantee uniqueness even for repeated projections like SELECT a1, a2, a2.
auto * query_node_ptr = query_tree_to_modify->as<QueryNode>();
if (query_node_ptr)
{
const auto & columns_description = distributed_storage_snapshot->metadata->getColumns();
auto & projection_nodes = query_node_ptr->getProjection().getNodes();
const auto & projection_columns = query_node_ptr->getProjectionColumns();
struct TreeHashHash { size_t operator()(const IQueryTreeNode::Hash & h) const { return CityHash_v1_0_2::Hash128to64(h); } };
std::unordered_set<IQueryTreeNode::Hash, TreeHashHash> seen_hashes;
bool has_duplicate = false;
@@ -960,7 +960,10 @@ QueryTreeNodePtr buildQueryTreeDistributed(SelectQueryInfo & query_info,
auto tree_hash = projection_nodes[i]->getTreeHash({.compare_aliases = false});
if (!seen_hashes.emplace(tree_hash).second)
{
const auto & col_default = columns_description.getDefault(projection_columns[i].name);
/// The alias on the node was set by ReplaseAliasColumnsVisitor
/// to the original ALIAS column name (before any user AS rename).
const auto & node_alias = projection_nodes[i]->getAlias();
const auto & col_default = columns_description.getDefault(node_alias);
if (col_default && col_default->kind == ColumnDefaultKind::Alias)
{
has_duplicate = true;
@@ -979,18 +982,19 @@ QueryTreeNodePtr buildQueryTreeDistributed(SelectQueryInfo & query_info,
auto tree_hash = projection_nodes[i]->getTreeHash({.compare_aliases = false});
if (!seen_hashes.emplace(tree_hash).second)
{
const auto & col_default = columns_description.getDefault(projection_columns[i].name);
const auto & node_alias = projection_nodes[i]->getAlias();
const auto & col_default = columns_description.getDefault(node_alias);
if (!col_default || col_default->kind != ColumnDefaultKind::Alias)
continue;
const auto & col_name = projection_columns[i].name;
auto unique_name = node_alias + "_" + std::to_string(i);
projection_nodes[i]->removeAlias();
auto name_node = std::make_shared<ConstantNode>(col_name);
auto name_node = std::make_shared<ConstantNode>(unique_name);
auto wrapper = std::make_shared<FunctionNode>("__actionName");
wrapper->getArguments().getNodes().push_back(std::move(projection_nodes[i]));
wrapper->getArguments().getNodes().push_back(std::move(name_node));
wrapper->resolveAsFunction(action_name_resolver);
wrapper->setAlias(col_name);
wrapper->setAlias(node_alias);
projection_nodes[i] = std::move(wrapper);
}
}
@@ -2,3 +2,5 @@
7 7
7 7
7 7 2024-01-01 00:00:00
7 7
7 7 7
@@ -28,11 +28,18 @@ ENGINE = Distributed('test_cluster_two_shards_localhost', currentDatabase(), loc
INSERT INTO local_same_expr VALUES ('2024-01-01 00:00:00', 7);
-- Basic case: two ALIAS columns with same expression + ORDER BY
SELECT a1, a2 FROM dist_same_expr ORDER BY dt DESC LIMIT 1;
SELECT a1, a2 FROM dist_same_expr ORDER BY dt DESC;
SELECT a1, a2, dt FROM dist_same_expr ORDER BY dt DESC LIMIT 1;
-- Renamed ALIAS projections: user applies AS rename
SELECT a1 AS first, a2 AS second FROM dist_same_expr ORDER BY dt DESC LIMIT 1;
-- Repeated ALIAS projection: same ALIAS column selected twice
SELECT a1, a2, a2 FROM dist_same_expr ORDER BY dt DESC LIMIT 1;
DROP TABLE dist_same_expr;
DROP TABLE local_same_expr;