diff --git a/core/src/main/java/org/apache/calcite/rex/RexCall.java b/core/src/main/java/org/apache/calcite/rex/RexCall.java
index 5be1fead88e..deac203607d 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexCall.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexCall.java
@@ -219,6 +219,12 @@ private boolean digestWithType() {
@Override public boolean isAlwaysTrue() {
// "c IS NOT NULL" occurs when we expand EXISTS.
// This reduction allows us to convert it to a semi-join.
+ // Only boolean-valued calls can be always-true; e.g. CAST(TRUE AS INTEGER)
+ // evaluates to 1 (INTEGER), not a boolean, even though its operand is
+ // always true.
+ if (getType().getSqlTypeName() != SqlTypeName.BOOLEAN) {
+ return false;
+ }
switch (getKind()) {
case IS_NOT_NULL:
return !operands.get(0).getType().isNullable();
@@ -241,6 +247,10 @@ private boolean digestWithType() {
}
@Override public boolean isAlwaysFalse() {
+ // Only boolean-valued calls can be always-false; see isAlwaysTrue().
+ if (getType().getSqlTypeName() != SqlTypeName.BOOLEAN) {
+ return false;
+ }
switch (getKind()) {
case IS_NULL:
return !operands.get(0).getType().isNullable();
diff --git a/core/src/test/java/org/apache/calcite/rex/RexProgramTest.java b/core/src/test/java/org/apache/calcite/rex/RexProgramTest.java
index 01da3e67727..2bdc7a1d56c 100644
--- a/core/src/test/java/org/apache/calcite/rex/RexProgramTest.java
+++ b/core/src/test/java/org/apache/calcite/rex/RexProgramTest.java
@@ -4130,6 +4130,55 @@ private void checkSarg(String message, Sarg sarg,
checkSimplify(isTrue(like(ref, literal("%"))), "IS NOT NULL($0)");
}
+ /** Unit tests for
+ * [CALCITE-7542]
+ * RexCall.isAlwaysTrue()/isAlwaysFalse() incorrectly returns true
+ * for CAST(boolean AS non-boolean). */
+ @Test void testIsAlwaysTrueCastBooleanToInteger() {
+ // CAST(TRUE AS INTEGER) is not a boolean expression; isAlwaysTrue() must be false.
+ final RexNode castTrueToInt = abstractCast(trueLiteral, tInt());
+ assertThat("CAST(TRUE AS INTEGER).isAlwaysTrue()",
+ castTrueToInt.isAlwaysTrue(), is(false));
+ assertThat("CAST(TRUE AS INTEGER).isAlwaysFalse()",
+ castTrueToInt.isAlwaysFalse(), is(false));
+ }
+
+ @Test void testIsAlwaysFalseCastBooleanToInteger() {
+ // CAST(FALSE AS INTEGER) is not a boolean expression; isAlwaysFalse() must be false.
+ final RexNode castFalseToInt = abstractCast(falseLiteral, tInt());
+ assertThat("CAST(FALSE AS INTEGER).isAlwaysFalse()",
+ castFalseToInt.isAlwaysFalse(), is(false));
+ assertThat("CAST(FALSE AS INTEGER).isAlwaysTrue()",
+ castFalseToInt.isAlwaysTrue(), is(false));
+ }
+
+ @Test void testIsAlwaysTrueCastBooleanToBoolean() {
+ // CAST(TRUE AS BOOLEAN) preserves the always-true property.
+ final RexNode castTrueToBool = abstractCast(trueLiteral, tBool());
+ assertThat("CAST(TRUE AS BOOLEAN).isAlwaysTrue()",
+ castTrueToBool.isAlwaysTrue(), is(true));
+ }
+
+ @Test void testIsAlwaysFalseCastBooleanToBoolean() {
+ // CAST(FALSE AS BOOLEAN) preserves the always-false property.
+ final RexNode castFalseToBool = abstractCast(falseLiteral, tBool());
+ assertThat("CAST(FALSE AS BOOLEAN).isAlwaysFalse()",
+ castFalseToBool.isAlwaysFalse(), is(true));
+ }
+
+ @Test void testIsAlwaysTrueFalseCastNonBooleanCallToBoolean() {
+ // CAST(1 + 1 AS BOOLEAN): the inner operand is a non-literal, non-boolean
+ // RexCall. isAlwaysTrue()/isAlwaysFalse() must safely return false on the
+ // recursive call rather than crashing — the contract is "ask freely, get
+ // a safe false for non-boolean expressions," matching RexLiteral and the
+ // RexNode base class.
+ final RexNode castIntExprToBool = abstractCast(plus(literal(1), literal(1)), tBool());
+ assertThat("CAST(1 + 1 AS BOOLEAN).isAlwaysTrue()",
+ castIntExprToBool.isAlwaysTrue(), is(false));
+ assertThat("CAST(1 + 1 AS BOOLEAN).isAlwaysFalse()",
+ castIntExprToBool.isAlwaysFalse(), is(false));
+ }
+
/** Unit tests for
* [CALCITE-2438]
* RexCall#isAlwaysTrue returns incorrect result. */
diff --git a/core/src/test/resources/sql/conditions.iq b/core/src/test/resources/sql/conditions.iq
index 7ce0c78c9c2..18150050b6b 100644
--- a/core/src/test/resources/sql/conditions.iq
+++ b/core/src/test/resources/sql/conditions.iq
@@ -595,4 +595,21 @@ where 5 < cast(deptno as integer) OR 5 >= cast(deptno as integer) OR deptno IS N
EnumerableTableScan(table=[[scott, EMP]])
!plan
+# Probe: CAST(int_expr AS BOOLEAN) in a WHERE clause exercises the recursion
+# path of RexCall.isAlwaysTrue/False through the CAST case.
+!use scott-babel
+
+with t(a) as (values (0), (1), (2))
+select * from t where cast(a + 1 as boolean);
++---+
+| A |
++---+
+| 0 |
+| 1 |
+| 2 |
++---+
+(3 rows)
+
+!ok
+
# End conditions.iq