This is an automated email from the ASF dual-hosted git repository.

yuxiqian pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/flink-cdc.git


The following commit(s) were added to refs/heads/master by this push:
     new 78b4fef85 [FLINK-40337][runtime] Preserve operator precedence in 
generated Janino expressions (#4499)
78b4fef85 is described below

commit 78b4fef854bcd6f7ba7581c2b67cda4958595f39
Author: haruki <[email protected]>
AuthorDate: Fri Aug 7 17:32:36 2026 +0800

    [FLINK-40337][runtime] Preserve operator precedence in generated Janino 
expressions (#4499)
---
 .../flink/cdc/runtime/parser/JaninoCompiler.java   |  8 ++-
 .../cdc/runtime/parser/JaninoCompilerTest.java     | 77 ++++++++++++++++++++++
 .../cdc/runtime/parser/TransformParserTest.java    | 32 ++++++++-
 3 files changed, 115 insertions(+), 2 deletions(-)

diff --git 
a/flink-cdc-runtime/src/main/java/org/apache/flink/cdc/runtime/parser/JaninoCompiler.java
 
b/flink-cdc-runtime/src/main/java/org/apache/flink/cdc/runtime/parser/JaninoCompiler.java
index 909c628fd..539a2cd55 100644
--- 
a/flink-cdc-runtime/src/main/java/org/apache/flink/cdc/runtime/parser/JaninoCompiler.java
+++ 
b/flink-cdc-runtime/src/main/java/org/apache/flink/cdc/runtime/parser/JaninoCompiler.java
@@ -46,7 +46,9 @@ import org.codehaus.commons.compiler.CompileException;
 import org.codehaus.commons.compiler.Location;
 import org.codehaus.janino.ExpressionEvaluator;
 import org.codehaus.janino.Java;
+import org.codehaus.janino.Unparser;
 
+import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -131,7 +133,11 @@ public class JaninoCompiler {
     public static String translateSqlNodeToJaninoExpression(Context context, 
SqlNode transform) {
         Java.Rvalue rvalue = translateSqlNodeToJaninoRvalue(context, 
transform);
         if (rvalue != null) {
-            return rvalue.toString();
+            StringWriter writer = new StringWriter();
+            Unparser unparser = new Unparser(writer);
+            unparser.unparseAtom(rvalue);
+            unparser.close();
+            return writer.toString();
         }
         return "";
     }
diff --git 
a/flink-cdc-runtime/src/test/java/org/apache/flink/cdc/runtime/parser/JaninoCompilerTest.java
 
b/flink-cdc-runtime/src/test/java/org/apache/flink/cdc/runtime/parser/JaninoCompilerTest.java
index 8298d384a..adf0ee159 100644
--- 
a/flink-cdc-runtime/src/test/java/org/apache/flink/cdc/runtime/parser/JaninoCompilerTest.java
+++ 
b/flink-cdc-runtime/src/test/java/org/apache/flink/cdc/runtime/parser/JaninoCompilerTest.java
@@ -287,6 +287,83 @@ class JaninoCompilerTest {
         Assertions.assertThat(nullableOrEvaluator.evaluate(new Object[] 
{null})).isNull();
     }
 
+    @Test
+    void testTranslatedNestedExpressionPreservesSemantics() throws 
InvocationTargetException {
+        List<Column> arithmeticColumns =
+                List.of(
+                        Column.physicalColumn("a", DataTypes.INT()),
+                        Column.physicalColumn("b", DataTypes.INT()),
+                        Column.physicalColumn("c", DataTypes.INT()));
+        Map<String, String> columnNameMap = Map.of("a", "$0", "b", "$1", "c", 
"$2");
+        List<String> columnNames = List.of("$0", "$1", "$2");
+        List<Class<?>> columnTypes = List.of(Integer.class, Integer.class, 
Integer.class);
+
+        ExpressionEvaluator additiveOperandEvaluator =
+                compileTranslatedFilterExpression(
+                        "(a + b) * c = 9",
+                        arithmeticColumns,
+                        columnNameMap,
+                        columnNames,
+                        columnTypes);
+        Assertions.assertThat(additiveOperandEvaluator.evaluate(new Object[] 
{1, 2, 3}))
+                .isEqualTo(true);
+
+        ExpressionEvaluator multiplicativeRightOperandEvaluator =
+                compileTranslatedFilterExpression(
+                        "a / (b * c) = 2",
+                        arithmeticColumns,
+                        columnNameMap,
+                        columnNames,
+                        columnTypes);
+        Assertions.assertThat(multiplicativeRightOperandEvaluator.evaluate(new 
Object[] {12, 2, 3}))
+                .isEqualTo(true);
+
+        ExpressionEvaluator subtractiveRightOperandEvaluator =
+                compileTranslatedFilterExpression(
+                        "a - (b - c) = 6",
+                        arithmeticColumns,
+                        columnNameMap,
+                        columnNames,
+                        columnTypes);
+        Assertions.assertThat(subtractiveRightOperandEvaluator.evaluate(new 
Object[] {10, 6, 2}))
+                .isEqualTo(true);
+
+        ExpressionEvaluator conditionalLeftOperandEvaluator =
+                compileTranslatedFilterExpression(
+                        "IF(a > b, a, b) + c = 13",
+                        arithmeticColumns,
+                        columnNameMap,
+                        columnNames,
+                        columnTypes);
+        Assertions.assertThat(conditionalLeftOperandEvaluator.evaluate(new 
Object[] {10, 2, 3}))
+                .isEqualTo(true);
+
+        ExpressionEvaluator conditionalRightOperandEvaluator =
+                compileTranslatedFilterExpression(
+                        "c * IF(a > b, a, b) = 30",
+                        arithmeticColumns,
+                        columnNameMap,
+                        columnNames,
+                        columnTypes);
+        Assertions.assertThat(conditionalRightOperandEvaluator.evaluate(new 
Object[] {10, 2, 3}))
+                .isEqualTo(true);
+
+        List<Column> booleanColumns =
+                List.of(
+                        Column.physicalColumn("a", 
DataTypes.BOOLEAN().notNull()),
+                        Column.physicalColumn("b", 
DataTypes.BOOLEAN().notNull()),
+                        Column.physicalColumn("c", 
DataTypes.BOOLEAN().notNull()));
+        ExpressionEvaluator logicalOperandEvaluator =
+                compileTranslatedFilterExpression(
+                        "(a OR b) AND c",
+                        booleanColumns,
+                        columnNameMap,
+                        columnNames,
+                        List.of(Boolean.class, Boolean.class, Boolean.class));
+        Assertions.assertThat(logicalOperandEvaluator.evaluate(new Object[] 
{true, false, false}))
+                .isEqualTo(false);
+    }
+
     @Test
     void testLargeNumericLiterals() {
         // Test parsing integer literals
diff --git 
a/flink-cdc-runtime/src/test/java/org/apache/flink/cdc/runtime/parser/TransformParserTest.java
 
b/flink-cdc-runtime/src/test/java/org/apache/flink/cdc/runtime/parser/TransformParserTest.java
index 519fe2669..281ea4721 100644
--- 
a/flink-cdc-runtime/src/test/java/org/apache/flink/cdc/runtime/parser/TransformParserTest.java
+++ 
b/flink-cdc-runtime/src/test/java/org/apache/flink/cdc/runtime/parser/TransformParserTest.java
@@ -451,6 +451,36 @@ class TransformParserTest {
                 columns);
     }
 
+    @Test
+    void testTranslateNestedExpressionPreservesOperatorPrecedence() {
+        List<Column> arithmeticColumns =
+                List.of(
+                        Column.physicalColumn("a", DataTypes.INT()),
+                        Column.physicalColumn("b", DataTypes.INT()),
+                        Column.physicalColumn("c", DataTypes.INT()));
+
+        testFilterExpressionWithColumns("(a + b) * c", "((( a + b ))) * c", 
arithmeticColumns);
+        testFilterExpressionWithColumns("a / (b * c)", "a / ((( b * c )))", 
arithmeticColumns);
+        testFilterExpressionWithColumns("a - (b - c)", "a - ((( b - c )))", 
arithmeticColumns);
+        testFilterExpressionWithColumns("a + b * c", "a + b * c", 
arithmeticColumns);
+        testFilterExpressionWithColumns("(a - b) - c", "a - b - c", 
arithmeticColumns);
+        testFilterExpressionWithColumns(
+                "IF(a > b, a, b) + c",
+                "((( isTrue(greaterThan(a, b)) ? a : b ))) + c",
+                arithmeticColumns);
+        testFilterExpressionWithColumns(
+                "c * IF(a > b, a, b)",
+                "c * ((( isTrue(greaterThan(a, b)) ? a : b )))",
+                arithmeticColumns);
+
+        List<Column> booleanColumns =
+                List.of(
+                        Column.physicalColumn("a", 
DataTypes.BOOLEAN().notNull()),
+                        Column.physicalColumn("b", 
DataTypes.BOOLEAN().notNull()),
+                        Column.physicalColumn("c", 
DataTypes.BOOLEAN().notNull()));
+        testFilterExpressionWithColumns("(a OR b) AND c", "((( a || b ))) && 
c", booleanColumns);
+    }
+
     @Test
     public void testTranslateItemAccessToJaninoExpression() {
         // Test collection access functions (ARRAY, MAP) with proper column 
schema
@@ -588,7 +618,7 @@ class TransformParserTest {
                         "ProjectionColumn{column=`newCreateTime` TIMESTAMP(3) 
'newCreateTime', expression='createTime', scriptExpression='$0', 
originalColumnNames=[createTime], columnNameMap={createTime=$0}}",
                         "ProjectionColumn{column=`newAddress` VARCHAR(50) 
'newAddress', expression='address', scriptExpression='$0', 
originalColumnNames=[address], columnNameMap={address=$0}}",
                         "ProjectionColumn{column=`deposits` DECIMAL(10, 2) 
'deposit', expression='deposit', scriptExpression='$0', 
originalColumnNames=[deposit], columnNameMap={deposit=$0}}",
-                        "ProjectionColumn{column=`bmi` DOUBLE, 
expression='`TB`.`weight` / (`TB`.`height` * `TB`.`height`)', 
scriptExpression='$0 / $1 * $1', originalColumnNames=[weight, height, height], 
columnNameMap={weight=$0, height=$1}}");
+                        "ProjectionColumn{column=`bmi` DOUBLE, 
expression='`TB`.`weight` / (`TB`.`height` * `TB`.`height`)', 
scriptExpression='$0 / ((( $1 * $1 )))', originalColumnNames=[weight, height, 
height], columnNameMap={weight=$0, height=$1}}");
         Assertions.assertThat(result).hasToString("[" + String.join(", ", 
expected) + "]");
 
         List<ProjectionColumn> regexpResult =

Reply via email to