From cca5373f9d7ca470310bf610e294499441a8ce15 Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Fri, 19 Aug 2022 14:34:46 -0700 Subject: [PATCH] Fix improper expression aliasing during pushdown EqualityInference was considering an expression of the form cast(null AS ...) as an inference candidate, even though it doens't satisfy the condition of not returning nulls on null input. As a result, unrelated expressions were being incorrectly equated to each other. In particular, for the example in the test, CAST(null AS varchar) was being considered equivalent to VARCHAR 'hello' and then substituted in the expression: CAST(CAST(null AS varchar) AS bigint) to produce the (invalid) expression: CAST(VARCHAR 'hello' AS bigint) --- .../trino/sql/planner/NullabilityAnalyzer.java | 9 +++++++++ .../test/java/io/trino/sql/query/TestJoin.java | 18 ++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/NullabilityAnalyzer.java b/core/trino-main/src/main/java/io/trino/sql/planner/NullabilityAnalyzer.java index dd503f403ea7..7e702405e26f 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/NullabilityAnalyzer.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/NullabilityAnalyzer.java @@ -21,6 +21,7 @@ import io.trino.sql.tree.IfExpression; import io.trino.sql.tree.InPredicate; import io.trino.sql.tree.NullIfExpression; +import io.trino.sql.tree.NullLiteral; import io.trino.sql.tree.SearchedCaseExpression; import io.trino.sql.tree.SimpleCaseExpression; import io.trino.sql.tree.SubscriptExpression; @@ -65,6 +66,7 @@ protected Void visitCast(Cast node, AtomicBoolean result) // except for the CAST(NULL AS x) case -- we should fix this at some point) // // Also, try_cast (i.e., safe cast) can return null + process(node.getExpression(), result); result.compareAndSet(false, node.isSafe() || !node.isTypeOnly()); return null; } @@ -132,5 +134,12 @@ protected Void visitFunctionCall(FunctionCall node, AtomicBoolean result) result.set(true); return null; } + + @Override + protected Void visitNullLiteral(NullLiteral node, AtomicBoolean result) + { + result.set(true); + return null; + } } } diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestJoin.java b/core/trino-main/src/test/java/io/trino/sql/query/TestJoin.java index a1ef5aac4a3a..72fa7712355e 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestJoin.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestJoin.java @@ -21,6 +21,9 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import java.util.List; + +import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.sql.planner.assertions.PlanMatchPattern.aggregation; import static io.trino.sql.planner.assertions.PlanMatchPattern.anyTree; import static io.trino.sql.planner.assertions.PlanMatchPattern.functionCall; @@ -111,6 +114,21 @@ FROM t1 JOIN t2 ON (t1.id = t2.id)) .matches("VALUES (10, CAST('b' AS varchar(2)))"); } + @Test + public void testAliasingOfNullCasts() + { + // Test for https://github.com/trinodb/trino/issues/13565 + assertThat(assertions.query(""" + WITH t AS ( + SELECT CAST(null AS varchar) AS x, CAST(null AS varchar) AS y + FROM (VALUES 1) t(a) JOIN (VALUES 1) u(a) USING (a)) + SELECT * FROM t + WHERE CAST(x AS bigint) IS NOT NULL AND y = 'hello' + """)) + .hasOutputTypes(List.of(VARCHAR, VARCHAR)) + .returnsEmptyResult(); + } + @Test public void testInPredicateInJoinCriteria() {