diff --git a/core/src/main/java/org/apache/calcite/rex/RexBuilder.java b/core/src/main/java/org/apache/calcite/rex/RexBuilder.java index 4c1e3d7cb400..18252de644fa 100644 --- a/core/src/main/java/org/apache/calcite/rex/RexBuilder.java +++ b/core/src/main/java/org/apache/calcite/rex/RexBuilder.java @@ -960,17 +960,33 @@ public RexNode makeReinterpretCast( } /** - * Makes a cast of a value to NOT NULL; - * no-op if the type already has NOT NULL. + * Makes a cast of an expression to nullable; + * returns the expression unchanged if its type is already nullable. + */ + public RexNode makeNullable(RexNode exp) { + return makeNullable(exp, true); + } + + /** + * Makes a cast of an expression to NOT NULL; + * returns the expression unchanged if its type already has NOT NULL. */ public RexNode makeNotNull(RexNode exp) { + return makeNullable(exp, false); + } + + /** + * Makes a cast of an expression to the required nullability; returns + * the expression unchanged if its type already has the desired nullability. + */ + public RexNode makeNullable(RexNode exp, boolean nullability) { final RelDataType type = exp.getType(); - if (!type.isNullable()) { + if (type.isNullable() == nullability) { return exp; } - final RelDataType notNullType = - typeFactory.createTypeWithNullability(type, false); - return makeAbstractCast(notNullType, exp, false); + final RelDataType type2 = + typeFactory.createTypeWithNullability(type, nullability); + return makeAbstractCast(type2, exp, false); } /** diff --git a/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java b/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java index 6f81cf5da5f8..1a63cf2d58e6 100644 --- a/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java +++ b/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java @@ -412,7 +412,13 @@ protected RexNode removeCorrelationExpr( RemoveCorrelationRexShuttle shuttle = new RemoveCorrelationRexShuttle(relBuilder.getRexBuilder(), projectPulledAboveLeftCorrelator, null, isCount); - return exp.accept(shuttle); + RexNode exp2 = exp.accept(shuttle); + + // Fix the nullability. + if (projectPulledAboveLeftCorrelator) { + exp2 = relBuilder.getRexBuilder().makeNullable(exp2); + } + return exp2; } /** Fallback if none of the other {@code decorrelateRel} methods match. */ @@ -1432,7 +1438,6 @@ private RelNode projectJoinOutputWithNullability( pair.left, projectPulledAboveLeftCorrelator, nullIndicator); - newProjExprs.add(newProjExpr, pair.right); } diff --git a/core/src/test/resources/sql/lateral.iq b/core/src/test/resources/sql/lateral.iq index 2dcdf1beac18..8af6a5edc126 100644 --- a/core/src/test/resources/sql/lateral.iq +++ b/core/src/test/resources/sql/lateral.iq @@ -219,4 +219,31 @@ from t, !ok +# LEFT JOIN LATERAL +select ename, deptno, ename2 +from emp as o + left join lateral (select ename as ename2 + from emp + where deptno = o.deptno + 10) on true +where job = 'MANAGER'; ++-------+--------+--------+ +| ENAME | DEPTNO | ENAME2 | ++-------+--------+--------+ +| BLAKE | 30 | | +| CLARK | 10 | ADAMS | +| CLARK | 10 | FORD | +| CLARK | 10 | JONES | +| CLARK | 10 | SCOTT | +| CLARK | 10 | SMITH | +| JONES | 20 | ALLEN | +| JONES | 20 | BLAKE | +| JONES | 20 | JAMES | +| JONES | 20 | MARTIN | +| JONES | 20 | TURNER | +| JONES | 20 | WARD | ++-------+--------+--------+ +(12 rows) + +!ok + # End lateral.iq diff --git a/core/src/test/resources/sql/measure.iq b/core/src/test/resources/sql/measure.iq index e6aa11131e80..aa8ee999fd77 100644 --- a/core/src/test/resources/sql/measure.iq +++ b/core/src/test/resources/sql/measure.iq @@ -179,6 +179,57 @@ from empm; !ok +# [CALCITE-6559] Query with measure that applies AVG to SMALLINT +# throws AssertionError "Cannot add expression of different type to set" +# +# Occurs when AVG measure is applied to SMALLINT or TINYINT column. +# A CAST is added internally to the result of SUM / COUNT, but this cast +# prevents a cast to nullable for the null-generating side of an outer join. +WITH empm AS ( + SELECT *, AVG(empno) AS MEASURE avg_empno + FROM emp) +SELECT job, avg_empno +FROM empm +GROUP BY job; ++-----------+-----------+ +| JOB | AVG_EMPNO | ++-----------+-----------+ +| ANALYST | 7845 | +| CLERK | 7769 | +| MANAGER | 7682 | +| PRESIDENT | 7839 | +| SALESMAN | 7629 | ++-----------+-----------+ +(5 rows) + +!ok + +!if (false) { +# [CALCITE-6564] Support queries with measures on top of a VALUES relation +# +# The following querey is the original test case for [CALCITE-6559]; that +# bug is fixed, but this query now fails during planning due to VALUES. +WITH tbl_with_null_dim AS ( + SELECT e.deptno, e.grade, AVG(e.grade) AS MEASURE avg_grade + FROM (VALUES (1, 70), + (1, 50), + (NULL, 50), + (3, 82)) AS e (deptno, grade)) +SELECT deptno, avg_grade +FROM tbl_with_null_dim +GROUP BY deptno; ++--------+-----------+ +| DEPTNO | AVG_GRADE | ++--------+-----------+ +| 1 | 60 | +| 3 | 82 | +| | 50 | ++--------+-----------+ +(3 rows) + +!ok +!} + # CTE with literal measure with emp2 as ( select *, 2 as measure two diff --git a/core/src/test/resources/sql/scalar.iq b/core/src/test/resources/sql/scalar.iq index 365c90e4af64..0f26eb1bddb7 100644 --- a/core/src/test/resources/sql/scalar.iq +++ b/core/src/test/resources/sql/scalar.iq @@ -239,4 +239,66 @@ select deptno, (select empno from "scott".emp order by empno limit 1) as x from !ok +# Scalar subquery on VALUES; based on a query mentioned in +# [CALCITE-3244] RelDecorrelator unable to decorrelate expression with filter and aggregate on top +select + (select count(t2.id) + from (values (1), (2)) t2(id) + where t2.id = t1.id) as c +from (values (1), (2)) t1(id); ++---+ +| C | ++---+ +| 1 | +| 1 | ++---+ +(2 rows) + +!ok + +select + (select count(t2.id) + from (values (1), (2), (2), (4)) t2(id) + where t2.id = t1.id) as c +from (values (1), (3)) t1(id); ++---+ +| C | ++---+ +| 1 | +| 0 | ++---+ +(2 rows) + +!ok + +# Scalar subquery using EXISTS on VALUES +select exists (select count(t2.id) + from (values (1), (2), (2), (4)) t2(id) + where t2.id = t1.id) as e +from (values (1), (3)) t1(id); ++------+ +| E | ++------+ +| true | +| true | ++------+ +(2 rows) + +!ok + +# Variation on previous query +select exists (select * + from (values (1), (2), (2), (4)) t2(id) + where t2.id = t1.id) as e +from (values (1), (3)) t1(id); ++-------+ +| E | ++-------+ +| false | +| true | ++-------+ +(2 rows) + +!ok + # End scalar.iq