Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions src/Interpreters/QueryNormalizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#include <Parsers/ASTQueryParameter.h>
#include <Parsers/ASTTablesInSelectQuery.h>
#include <Parsers/ASTInterpolateElement.h>
#include <Common/StringUtils.h>
#include <Parsers/ASTColumnsTransformers.h>
#include <Common/quoteString.h>
#include <IO/WriteHelpers.h>

Expand Down Expand Up @@ -174,7 +174,19 @@ void QueryNormalizer::visit(ASTTablesInSelectQueryElement & node, const ASTPtr &
static bool needVisitChild(const ASTPtr & child)
{
/// exclude interpolate elements - they are not subject for normalization and will be processed in filling transform
return !(child->as<ASTSelectQuery>() || child->as<ASTTableExpression>() || child->as<ASTInterpolateElement>());
if (child->as<ASTSelectQuery>() || child->as<ASTTableExpression>() || child->as<ASTInterpolateElement>())
return false;

/// Column transformer children (EXCEPT, REPLACE, APPLY) contain column name references
/// that must not be substituted with alias expressions. For example, in
/// `SELECT * EXCEPT (Budget), toFloat64(Budget) AS Budget FROM t`, the `Budget` inside
/// EXCEPT refers to a column name to exclude, not to the alias `Budget`.
/// If the normalizer replaces it, the downstream ASTColumnsExceptTransformer::transform
/// will encounter a non-ASTIdentifier child and throw a LOGICAL_ERROR.
if (child->as<IASTColumnsTransformer>())
return false;

return true;
}

/// special visitChildren() for ASTSelectQuery
Expand Down
7 changes: 6 additions & 1 deletion src/Parsers/ASTColumnsTransformers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,12 @@ void ASTColumnsExceptTransformer::transform(ASTs & nodes) const
if (!pattern)
{
for (const auto & child : children)
expected_columns.insert(child->as<const ASTIdentifier &>().name());
{
if (const auto * identifier = child->as<ASTIdentifier>())
expected_columns.insert(identifier->name());
else
expected_columns.insert(child->getAliasOrColumnName());
}

for (auto * it = nodes.begin(); it != nodes.end();)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
1 x 3
2 y 5
1 1.5
2 2.5
1 1.5
2 2.5
1 X 15
2 Y 25
1 x 3
2 y 5
1 1.5
2 2.5
1 1.5
2 2.5
1 X 15
2 Y 25
64 changes: 64 additions & 0 deletions tests/queries/0_stateless/04033_except_transformer_with_alias.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
-- Test that SELECT * EXCEPT (col) works when 'col' is also used as an alias in the same SELECT.
-- This used to cause LOGICAL_ERROR "Bad cast from type DB::ASTFunction to DB::ASTIdentifier"
-- because QueryNormalizer could replace the ASTIdentifier inside the EXCEPT transformer
-- with an ASTFunction from the alias before the transformer was expanded.
-- https://github.com/ClickHouse/clickhouse-core-incidents/issues/1433

SET allow_experimental_analyzer = 0;

DROP TABLE IF EXISTS t_except_alias;
CREATE TABLE t_except_alias (a UInt64, b String, c Float64) ENGINE = Memory;
INSERT INTO t_except_alias VALUES (1, 'x', 1.5), (2, 'y', 2.5);

-- Basic case: EXCEPT column name matches an alias in the same SELECT
SELECT * EXCEPT (c), toFloat64(c) * 2 AS c FROM t_except_alias ORDER BY a;

-- Same pattern but via subquery in JOIN (triggers interpretSubquery with removeDuplicates)
SELECT t.a, sub.c
FROM t_except_alias AS t
LEFT JOIN (
SELECT * EXCEPT (c), toString(c) AS c FROM t_except_alias
) AS sub ON t.a = sub.a
ORDER BY t.a;

-- CTE pattern matching the original incident
WITH data AS (
SELECT * EXCEPT (c), toFloat64(c) AS c FROM t_except_alias
)
SELECT t.a, d.c
FROM t_except_alias AS t
LEFT JOIN data AS d ON t.a = d.a
ORDER BY t.a;

-- Multiple columns in EXCEPT with aliases
SELECT * EXCEPT (b, c), upper(b) AS b, toFloat64(c) * 10 AS c FROM t_except_alias ORDER BY a;

DROP TABLE t_except_alias;

-- Now test the same with the new analyzer
SET allow_experimental_analyzer = 1;

DROP TABLE IF EXISTS t_except_alias;
CREATE TABLE t_except_alias (a UInt64, b String, c Float64) ENGINE = Memory;
INSERT INTO t_except_alias VALUES (1, 'x', 1.5), (2, 'y', 2.5);

SELECT * EXCEPT (c), toFloat64(c) * 2 AS c FROM t_except_alias ORDER BY a;

SELECT t.a, sub.c
FROM t_except_alias AS t
LEFT JOIN (
SELECT * EXCEPT (c), toString(c) AS c FROM t_except_alias
) AS sub ON t.a = sub.a
ORDER BY t.a;

WITH data AS (
SELECT * EXCEPT (c), toFloat64(c) AS c FROM t_except_alias
)
SELECT t.a, d.c
FROM t_except_alias AS t
LEFT JOIN data AS d ON t.a = d.a
ORDER BY t.a;

SELECT * EXCEPT (b, c), upper(b) AS b, toFloat64(c) * 10 AS c FROM t_except_alias ORDER BY a;

DROP TABLE t_except_alias;
Loading