Chris Johnson created FLINK-40799:
-------------------------------------

             Summary: ClassCastException in FlinkMarkChangelogNormalizeProgram 
when an upsert source is read twice with the same boolean column filter
                 Key: FLINK-40799
                 URL: https://issues.apache.org/jira/browse/FLINK-40799
             Project: Flink
          Issue Type: Bug
          Components: Table SQL / Planner
    Affects Versions: 2.3.0
            Reporter: Chris Johnson


Planning fails with a ClassCastException when a query reads the same upsert 
source more than once and every reader filters on the same bare boolean column. 
The query is valid SQL. 2.3.0 is affected; 2.2.1 is not.

*Reproduce*

{code:sql}
CREATE TABLE foo (
  id INT,
  flag BOOLEAN,
  PRIMARY KEY (id) NOT ENFORCED
) WITH (
  'connector' = 'values',
  'changelog-mode' = 'I,UA,D'
);

EXPLAIN
SELECT id FROM foo WHERE flag
UNION ALL
SELECT id FROM foo WHERE flag;
{code}

The 'values' connector is the planner test connector (flink-table-planner 
test-jar). 'I,UA,D' makes it an upsert source, so the plan gets a 
ChangelogNormalize. Any upsert source should behave the same, for example 
upsert-kafka.

A self join fails the same way:

{code:sql}
EXPLAIN
SELECT f1.id FROM foo AS f1 JOIN foo AS f2 ON f1.id = f2.id
WHERE f1.flag AND f2.flag;
{code}

Verified with TableEnvironment.explainSql against the released Maven Central 
artifacts: both queries fail on 2.3.0 and plan successfully on 2.2.1.

*Actual*

{noformat}
java.lang.ClassCastException: class org.apache.calcite.rex.RexInputRef cannot 
be cast to class org.apache.calcite.rex.RexCall
    at 
org.apache.flink.table.planner.plan.rules.physical.stream.FlinkMarkChangelogNormalizeProgram.calculateCommonCondition(FlinkMarkChangelogNormalizeProgram.java:183)
    at 
org.apache.flink.table.planner.plan.rules.physical.stream.FlinkMarkChangelogNormalizeProgram.optimize(FlinkMarkChangelogNormalizeProgram.java:158)
    at 
org.apache.flink.table.planner.plan.rules.physical.stream.FlinkMarkChangelogNormalizeProgram.optimize(FlinkMarkChangelogNormalizeProgram.java:135)
    at 
org.apache.flink.table.planner.plan.optimize.program.FlinkGroupProgram.$anonfun$optimize$2(FlinkGroupProgram.scala:59)
{noformat}

*Cause*

FlinkMarkChangelogNormalizeProgram looks for filter conditions shared by all 
ChangelogNormalize nodes on the same source. Since FLINK-38693 it ORs the 
conditions together and calls RexUtil.pullFactors, then casts the result:

{code:java}
final RexCall factors = (RexCall) RexUtil.pullFactors(rexBuilder, or);
{code}

https://github.com/apache/flink/blob/654f0edc88676df691b8a59f5e44186602b5820f/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/physical/stream/FlinkMarkChangelogNormalizeProgram.java#L183

pullFactors does not always return a RexCall. When every reader has the same 
single boolean column as its filter, pullFactors(OR(flag, flag)) returns the 
RexInputRef for flag, and the cast fails. Writing the filter as flag = TRUE or 
flag IS TRUE does not help, because both are simplified to flag before this 
point.

The cast was added in FLINK-38693 (commit 
e5f53f272dcdac4249e2433091fbacf0e1df2daf). Before that, in 2.2.x, 
calculateCommonCondition used a set intersection and did not have this problem.

*What does not trigger it*

- The source is read only once.
- Only one reader has a filter (the method returns early when conditions are 
null).
- The shared filter is a call, for example WHERE id > 0 on both sides.
- The source is append only, so there is no ChangelogNormalize.

*Workaround*

Wrap the boolean in at least one reader, for example WHERE COALESCE(flag, 
FALSE).

*Suggested fix*

Check the type before casting and return no common condition when it is not a 
RexCall, the same as the existing early return:

{code:java}
final RexNode pulled = RexUtil.pullFactors(rexBuilder, or);
if (!(pulled instanceof RexCall)) {
    return List.of();
}
final RexCall factors = (RexCall) pulled;
{code}

Returning List.of(pulled) as the common filter may be better, since the whole 
condition is shared in this case. I am not sure which is preferred.




--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to