diff --git a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java index f0dc70c0f1c9..08065467040c 100644 --- a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java +++ b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java @@ -1235,9 +1235,19 @@ private void substituteSubQuery(Blackboard bb, SubQuery subQuery) { // SET empno = 1 WHERE emp.empno IN ( // SELECT emp.empno FROM emp WHERE emp.empno = 2) // - // In such case, when converting SqlUpdate#condition, bb.root is null - // and it makes no sense to do the sub-query substitution. - if (bb.root == null) { + // In such case, when converting SqlUpdate#condition, the bb.root is null, + // but we should skip the expr like 'ON (...) AND (...) IN (...) within JOIN' + // because in this case, bb.root can also be null. + // for example: + // "SELECT e1.empno FROM emps as e1 + // LEFT JOIN depts as d1 + // ON e1.deptno=d1.deptno + // AND e1.deptno IN (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)" + // note: if we use 'WHERE e1.deptno IN (...)', + // the bb.root is not null, doesn't apply to the case here + // + // and SqlUpdate#condition makes no sense to do the sub-query substitution here, return it. + if (bb.root == null && !(bb.scope.getNode() instanceof SqlJoin)) { return; } final RelDataType targetRowType = diff --git a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java index a38ebf0b3be2..0af4d8cfa67a 100644 --- a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java +++ b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java @@ -4934,6 +4934,30 @@ void checkUserDefinedOrderByOver(NullCollation nullCollation) { .convertsTo("${planNotExpanded}"); } + /** + * Test case for + * [CALCITE-6028] + * SqlToRelConverter#substituteSubQuery gives NullPointerException when convert + * expr JOIN ... ON ... AND ... IN and if size of IN exceeds IN_SUBQUERY_THRESHOLD. + */ + @Test void testInSubQueryWithinJoin0() { + String sql = "SELECT t1.x FROM (values (1, 'a')) as t1(x, y)\n" + + "left join (values (1, 'b')) as t2(x, y)\n" + + "ON t1.x=t2.x AND t1.x IN (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)"; + sql(sql).ok(); + } + + /** + * As {@link #testInSubQueryWithinJoin0()} but value size of IN < 20. + */ + @Test void testInSubQueryWithinJoin1() { + String sql = "SELECT t1.x FROM (values (1, 'a')) as t1(x, y)\n" + + "left join (values (1, 'b')) as t2(x, y)\n" + + "ON t1.x=t2.x AND t1.x IN (1,2,3,4,5)"; + sql(sql).ok(); + } + + /** * Test case for * [CALCITE-4295] diff --git a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml index b914e14dc749..5564473fade4 100644 --- a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml +++ b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml @@ -2779,6 +2779,40 @@ FROM dept, emp WHERE emp.deptno = dept.deptno AND emp.sal < ( )]]> + + + + + + + + + + + + + + + + 20 +SELECT e1.empno FROM emp as e1 LEFT JOIN dept as d1 ON e1.deptno=d1.deptno +WHERE e1.deptno IN (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21); ++-------+ +| EMPNO | ++-------+ +| 7369 | +| 7566 | +| 7782 | +| 7788 | +| 7839 | +| 7876 | +| 7902 | +| 7934 | ++-------+ +(8 rows) + +!ok + # End sub-query.iq diff --git a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java index 891df133c38d..01586d0f68a0 100644 --- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java +++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java @@ -10630,6 +10630,13 @@ private static void checkArrayConcatAggFuncFails(SqlOperatorFixture t) { f.checkNull("floor(cast(null as date) to month)"); } + @Test void testAAA() { + final SqlOperatorFixture f = Fixtures.forOperators(true); + f.check("select t1.x from (values (1, 'a')) as t1(x, y) left join (values (1, 'b')) " + + "as t2(x, y) on t1.x=t2.x and t1.y " + + "in (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)", "INTEGER NOT NULL", "1"); + } + @Test void testCeilFuncDateTime() { final SqlOperatorFixture f = fixture(); f.enableTypeCoercion(false)