diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java index f930243f3584..bce0840f8f00 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java @@ -2126,6 +2126,9 @@ protected void inferUnknownTypes( setValidatedNodeType(node, newInferredType); } else if (node instanceof SqlNodeList) { SqlNodeList nodeList = (SqlNodeList) node; + if (inferRowSqlNodeList(inferredType, scope, nodeList)) { + return; + } if (inferredType.isStruct()) { if (inferredType.getFieldCount() != nodeList.size()) { // this can happen when we're validating an INSERT @@ -2204,6 +2207,25 @@ protected void inferUnknownTypes( } } + private boolean inferRowSqlNodeList(final RelDataType inferredType, + final SqlValidatorScope scope, final SqlNodeList nodeList) { + if (!inferredType.isStruct()) { + return false; + } + boolean inferred = false; + for (SqlNode child : nodeList) { + if (child instanceof SqlBasicCall && SqlKind.ROW == child.getKind()) { + List operands = ((SqlBasicCall) child).getOperandList(); + for (int index = 0; index < operands.size(); index++) { + RelDataType type = inferredType.getFieldList().get(index).getType(); + inferUnknownTypes(type, scope, operands.get(index)); + } + inferred = true; + } + } + return inferred; + } + /** * Adds an expression to a select list, ensuring that its alias does not * clash with any existing expressions on the list. diff --git a/core/src/test/java/org/apache/calcite/test/JdbcTest.java b/core/src/test/java/org/apache/calcite/test/JdbcTest.java index 64fb5b0caf36..8b2ec24da247 100644 --- a/core/src/test/java/org/apache/calcite/test/JdbcTest.java +++ b/core/src/test/java/org/apache/calcite/test/JdbcTest.java @@ -9012,6 +9012,31 @@ void checkCalciteSchemaGetSubSchemaMap(boolean cache) { }); } + /** Test case for + * [CALCITE-7197] + * UnsupportedOperationException when using dynamic parameters inside ROW expression. */ + @Test void testSelectWithPlaceholdersInRowExpression() { + CalciteAssert.that() + .with(Lex.MYSQL) + .with(CalciteAssert.Config.SCOTT) + .query("select empno, ename from emp\n" + + "where row(empno, ename) in ((?, ?))") + .consumesPreparedStatement(p -> { + p.setInt(1, 7782); + p.setString(2, "CLARK"); + }) + .returns(resultSet -> { + try { + assertTrue(resultSet.next()); + assertThat(resultSet.getInt(1), is(7782)); + assertThat(resultSet.getString(2), is("CLARK")); + assertFalse(resultSet.next()); + } catch (SQLException e) { + throw new RuntimeException(e); + } + }); + } + /** Test case for * [CALCITE-5414] * Convert between standard Gregorian and proleptic Gregorian calendars for diff --git a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java index 639503edc7fa..5b7ba2c2b440 100644 --- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java +++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java @@ -12405,6 +12405,26 @@ private void checkCustomColumnResolving(String table) { .fails(maxError); } + /** Test case for + * [CALCITE-7197] + * UnsupportedOperationException when using dynamic parameters inside ROW expression. */ + @Test void testSelectWithRowExpressionPredicate() { + // test single row expression values + sql("select empno, ename from emp where row(empno, ename) in ((?, ?))") + .ok() + .assertBindType(is("RecordType(INTEGER ?0, VARCHAR(20) ?1)")); + + // test multiple row expression values + sql("select empno, ename from emp where row(empno, ename) in ((?, ?), (7782, ?), (?, 'CLARK'))") + .ok() + .assertBindType(is("RecordType(INTEGER ?0, VARCHAR(20) ?1, VARCHAR(20) ?2, INTEGER ?3)")); + + // test single row expression values with expression + sql("select empno, ename from emp where row(empno, ename) in ((? + 1, ?))") + .ok() + .assertBindType(is("RecordType(INTEGER ?0, VARCHAR(20) ?1)")); + } + @Test void testRolledUpColumnInWhere() { final String error = "Rolled up column 'SLACKINGMIN' is not allowed in GREATER_THAN"; diff --git a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml index f3cbc5440417..d80af28fdb67 100644 --- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml +++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml @@ -19698,16 +19698,7 @@ LogicalProject(A=[$0], Y=[$1]) LogicalProject(EXPR$0=[$0], EXPR$1=[$1]) LogicalValues(tuples=[[{ 1, 2 }, { 3, null }, { 7369, null }, { 7499, 30 }, { null, 20 }, { null, 5 }]]) LogicalAggregate(group=[{0, 1}]) - LogicalUnion(all=[true]) - LogicalProject(EXPR$0=[3], EXPR$1=[null:INTEGER]) - LogicalValues(tuples=[[{ 0 }]]) - LogicalProject(EXPR$0=[7369], EXPR$1=[null:INTEGER]) - LogicalValues(tuples=[[{ 0 }]]) - LogicalProject(EXPR$0=[null:INTEGER], EXPR$1=[20]) - LogicalValues(tuples=[[{ 0 }]]) - LogicalProject(EXPR$0=[null:INTEGER], EXPR$1=[5]) - LogicalValues(tuples=[[{ 0 }]]) - LogicalValues(tuples=[[{ 1, 2 }, { 7499, 30 }]]) + LogicalValues(tuples=[[{ 1, 2 }, { 3, null }, { 7369, null }, { 7499, 30 }, { null, 20 }, { null, 5 }]]) ]]> @@ -19716,7 +19707,7 @@ LogicalProject(A=[$0], Y=[$1]) LogicalJoin(condition=[AND(=($0, $2), =($1, $3))], joinType=[inner]) LogicalValues(tuples=[[{ 1, 2 }, { 3, null }, { 7369, null }, { 7499, 30 }, { null, 20 }, { null, 5 }]]) LogicalAggregate(group=[{0, 1}]) - LogicalValues(tuples=[[{ 3, null }, { 7369, null }, { null, 20 }, { null, 5 }, { 1, 2 }, { 7499, 30 }]]) + LogicalValues(tuples=[[{ 1, 2 }, { 3, null }, { 7369, null }, { 7499, 30 }, { null, 20 }, { null, 5 }]]) ]]> @@ -19738,15 +19729,9 @@ LogicalProject(A=[$0], Y=[$1]) LogicalUnion(all=[true]) LogicalProject(EXPR$0=[1], EXPR$1=[2]) LogicalValues(tuples=[[{ 0 }]]) - LogicalProject(EXPR$0=[3], EXPR$1=[null:INTEGER]) - LogicalValues(tuples=[[{ 0 }]]) - LogicalProject(EXPR$0=[7369], EXPR$1=[null:INTEGER]) - LogicalValues(tuples=[[{ 0 }]]) LogicalProject(EXPR$0=[null:INTEGER], EXPR$1=[20]) LogicalValues(tuples=[[{ 0 }]]) - LogicalProject(EXPR$0=[null:INTEGER], EXPR$1=[5]) - LogicalValues(tuples=[[{ 0 }]]) - LogicalValues(tuples=[[{ 7499, 30 }]]) + LogicalValues(tuples=[[{ 3, null }, { 7369, null }, { 7499, 30 }, { null, 5 }]]) ]]> @@ -19755,7 +19740,7 @@ LogicalProject(A=[$0], Y=[$1]) LogicalJoin(condition=[AND(=($0, $2), =($1, $3))], joinType=[inner]) LogicalValues(tuples=[[{ 1, 2 }, { 3, null }, { 7369, null }, { 7499, 30 }, { null, 20 }, { null, 5 }]]) LogicalAggregate(group=[{0, 1}]) - LogicalValues(tuples=[[{ 1, 2 }, { 3, null }, { 7369, null }, { null, 20 }, { null, 5 }, { 7499, 30 }]]) + LogicalValues(tuples=[[{ 1, 2 }, { null, 20 }, { 3, null }, { 7369, null }, { 7499, 30 }, { null, 5 }]]) ]]> diff --git a/core/src/test/resources/sql/conditions.iq b/core/src/test/resources/sql/conditions.iq index a9fa54b5296f..84c441f001ef 100644 --- a/core/src/test/resources/sql/conditions.iq +++ b/core/src/test/resources/sql/conditions.iq @@ -547,4 +547,21 @@ where 5 < cast(deptno as integer) OR 5 >= cast(deptno as integer) OR deptno IS N EnumerableTableScan(table=[[scott, EMP]]) !plan +!use scott + +# Test case for [CALCITE-7197] UnsupportedOperationException when using dynamic parameters inside ROW expression +select * +from "scott".emp +where row(empno, ename) in ((7782, 'CLARK'), (7902, 'FORD'), (7839, 'KING')); ++-------+-------+-----------+------+------------+---------+------+--------+ +| EMPNO | ENAME | JOB | MGR | HIREDATE | SAL | COMM | DEPTNO | ++-------+-------+-----------+------+------------+---------+------+--------+ +| 7782 | CLARK | MANAGER | 7839 | 1981-06-09 | 2450.00 | | 10 | +| 7839 | KING | PRESIDENT | | 1981-11-17 | 5000.00 | | 10 | +| 7902 | FORD | ANALYST | 7566 | 1981-12-03 | 3000.00 | | 20 | ++-------+-------+-----------+------+------------+---------+------+--------+ +(3 rows) + +!ok + # End conditions.iq