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);