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 4622176a0733..32017ace1d43 100644 --- a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java +++ b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java @@ -1870,7 +1870,7 @@ public RelNode convertToSingleValueSubq( if (leftKeys.size() == 1) { SqlCall sqlCall = comparisonOp.createCall(rightVals.getParserPosition(), leftKeys.get(0), rightVals); - rexComparison = bb.convertExpression(sqlCall); + rexComparison = ensureComparisonTypes(bb.convertExpression(sqlCall)); } else { assert rightVals instanceof SqlCall; final SqlBasicCall call = (SqlBasicCall) rightVals; @@ -1880,9 +1880,10 @@ public RelNode convertToSingleValueSubq( RexUtil.composeConjunction(rexBuilder, transform( Pair.zip(leftKeys, call.getOperandList()), - pair -> bb.convertExpression( - comparisonOp.createCall(rightVals.getParserPosition(), - pair.left, pair.right)))); + pair -> ensureComparisonTypes( + bb.convertExpression( + comparisonOp.createCall(rightVals.getParserPosition(), + pair.left, pair.right))))); } comparisons.add(rexComparison); } @@ -1901,6 +1902,31 @@ public RelNode convertToSingleValueSubq( } } + /** + * Ensures that a comparison expression has matching operand types. If the + * operands have different type names, casts the right operand to match the + * left operand's type. This handles the case where type coercion is disabled + * and the IN-to-OR expansion produces comparisons with mismatched types + * (e.g., DATE = CHAR). + */ + private RexNode ensureComparisonTypes(RexNode node) { + if (validator != null && validator.config().typeCoercionEnabled()) { + return node; + } + if (node instanceof RexCall) { + final RexCall call = (RexCall) node; + if (call.operands.size() == 2) { + final RexNode left = call.operands.get(0); + final RexNode right = call.operands.get(1); + if (left.getType().getSqlTypeName() != right.getType().getSqlTypeName()) { + final RexNode castRight = rexBuilder.ensureType(left.getType(), right, true); + return rexBuilder.makeCall(call.getOperator(), left, castRight); + } + } + } + return node; + } + /** * Converts a {@link SqlNodeList} (for example an IN-list or VALUES list) * into a relational expression and produces a Rex-level sub-query that 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 4e8d17cd7a27..723d749be6b5 100644 --- a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java +++ b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java @@ -2213,6 +2213,24 @@ void checkCorrelatedMapSubQuery(boolean expand) { sql(sql).withExpand(false).ok(); } + /** Test case for + * [CALCITE-7562] + * SqlToRel misses CAST in case IN expression without type coercion. */ + @Test void testInDateColumnWithoutTypeCoercion() { + final String sql = + "select * from emp_b where birthdate in ('2000-06-30', '2000-09-27')"; + sql(sql).withTypeCoercion(false).ok(); + } + + /** Test case for + * [CALCITE-7562] + * SqlToRel misses CAST in case IN expression without type coercion. */ + @Test void testInDateColumnWithTypeCoercion() { + final String sql = + "select * from emp_b where birthdate in ('2000-06-30', '2000-09-27')"; + sql(sql).ok(); + } + @Test void testInValueListLong() { // Go over the default threshold of 20 to force a sub-query. final String sql = "select empno from emp where deptno in" 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 5ee7180efa75..8aa2171d3f0b 100644 --- a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml +++ b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml @@ -3266,6 +3266,30 @@ LogicalFilter(condition=[=($cor0.DEPTNO, $7)]) LogicalAggregate(group=[{0}], S=[SUM($1)], agg#1=[COUNT()]) LogicalProject(DEPTNO=[$7], SAL=[$5]) LogicalTableScan(table=[[CATALOG, SALES, EMP]]) +]]> + + + + + + + + + + + + + + + + diff --git a/testkit/src/main/java/org/apache/calcite/test/SqlToRelFixture.java b/testkit/src/main/java/org/apache/calcite/test/SqlToRelFixture.java index a6685cf952f5..784cb1eba1dd 100644 --- a/testkit/src/main/java/org/apache/calcite/test/SqlToRelFixture.java +++ b/testkit/src/main/java/org/apache/calcite/test/SqlToRelFixture.java @@ -178,6 +178,11 @@ public SqlToRelFixture withConformance(SqlConformance conformance) { .withValidatorConfig(c -> c.withConformance(conformance))); } + public SqlToRelFixture withTypeCoercion(boolean enabled) { + return withFactory(f -> + f.withValidatorConfig(c -> c.withTypeCoercionEnabled(enabled))); + } + public SqlToRelFixture withDiffRepos(DiffRepository diffRepos) { return new SqlToRelFixture(sql, decorrelate, tester, factory, trim, expression, diffRepos);