[FLINK-40204][runtime] Introduce statement-level codegen for YAML Transform expressions - #4482
[FLINK-40204][runtime] Introduce statement-level codegen for YAML Transform expressions#4482haruki-830 wants to merge 5 commits into
Conversation
…nsform expressions
|
@lvyanquan PTAL when you have time, Thanks! |
There was a problem hiding this comment.
Pull request overview
Note
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
This PR introduces statement-level Java code generation for Flink CDC YAML Transform expressions, switching evaluation to Janino ScriptEvaluator to enable explicit short-circuiting (if/else) for nullable AND/OR and improve diagnostics by surfacing full generated scripts.
Changes:
- Add
GeneratedExpressionas an intermediate representation (statements + result term + result type) and generate statement-level code for transforms. - Switch expression compilation/evaluation from Janino
ExpressionEvaluatortoScriptEvaluatorvia a smallTransformExpressionEvaluatorwrapper. - Update projection/filter processors and tests to assert short-circuit code generation and improved error/debug output.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| flink-cdc-runtime/src/main/java/org/apache/flink/cdc/runtime/parser/TransformParser.java | Adds translation entry point for filter expressions to GeneratedExpression and wires it into projection generation. |
| flink-cdc-runtime/src/main/java/org/apache/flink/cdc/runtime/parser/JaninoCompiler.java | Introduces statement-level codegen (GeneratedExpressionGenerator) and adds nullable AND/OR support without Supplier. |
| flink-cdc-runtime/src/main/java/org/apache/flink/cdc/runtime/parser/GeneratedExpression.java | Adds the new intermediate type representing generated statements and a returnable script. |
| flink-cdc-runtime/src/main/java/org/apache/flink/cdc/runtime/operators/transform/TransformExpressionKey.java | Stores GeneratedExpression and exposes compiled/full scripts; validates non-empty result term for full script. |
| flink-cdc-runtime/src/main/java/org/apache/flink/cdc/runtime/operators/transform/TransformExpressionCompiler.java | Switches compilation to ScriptEvaluator and caches TransformExpressionEvaluator. |
| flink-cdc-runtime/src/main/java/org/apache/flink/cdc/runtime/operators/transform/TransformExpressionEvaluator.java | Adds a wrapper around Janino ScriptEvaluator to evaluate compiled scripts. |
| flink-cdc-runtime/src/main/java/org/apache/flink/cdc/runtime/operators/transform/TransformFilterProcessor.java | Generates/uses GeneratedExpression for filters and updates error output to show compiled scripts. |
| flink-cdc-runtime/src/main/java/org/apache/flink/cdc/runtime/operators/transform/ProjectionColumnProcessor.java | Uses compiled scripts for projection evaluation error reporting and GeneratedExpression for compilation keys. |
| flink-cdc-runtime/src/main/java/org/apache/flink/cdc/runtime/operators/transform/ProjectionColumn.java | Stores GeneratedExpression for projections and updates string output to include compiled scripts. |
| flink-cdc-runtime/src/main/java/org/apache/flink/cdc/runtime/operators/transform/TransformFilter.java | Updates class-level documentation to remove obsolete scriptExpression notes. |
| flink-cdc-runtime/src/test/java/org/apache/flink/cdc/runtime/parser/TransformParserTest.java | Updates expectations for compiled scripts and adds assertions for statement-level codegen characteristics. |
| flink-cdc-runtime/src/test/java/org/apache/flink/cdc/runtime/parser/JaninoCompilerTest.java | Adds tests compiling/evaluating generated scripts and validates empty result-term rejection. |
| flink-cdc-runtime/src/test/java/org/apache/flink/cdc/runtime/operators/transform/PostTransformOperatorTest.java | Adds an end-to-end test ensuring nullable logical transforms work with statement-level codegen; updates error text expectation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| private void translateSqlNodeToGeneratedAtoms( | ||
| SqlNode sqlNode, List<GeneratedExpression> atoms) { | ||
| if (sqlNode instanceof SqlNodeList) { | ||
| for (SqlNode node : (SqlNodeList) sqlNode) { | ||
| translateSqlNodeToGeneratedAtoms(node, atoms); | ||
| } | ||
| } else if (sqlNode instanceof SqlIdentifier | ||
| || sqlNode instanceof SqlLiteral | ||
| || sqlNode instanceof SqlBasicCall | ||
| || sqlNode instanceof SqlCase) { | ||
| atoms.add(translate(sqlNode, deduceGeneratedExpressionClass(context, sqlNode))); | ||
| } | ||
| } |
There was a problem hiding this comment.
Addressed in the latest commit.
|
This change offers very limited benefits but has a relatively large impact surface. I think we need to evaluate it carefully. CC @yuxiqian |
yuxiqian
left a comment
There was a problem hiding this comment.
Thanks Haruki for the contribution!
I also doubt the necessity of such changes. Deducing types from SqlNode and manipulating generated code strings looks fragile. Is there any alternative solution to implement "short-circuit call"?
| public String asScript() { | ||
| StringBuilder script = new StringBuilder(); | ||
| appendCode(script, code); | ||
| script.append("return ").append(resultTerm).append(";"); | ||
| return script.toString(); | ||
| } |
There was a problem hiding this comment.
Better avoid string manipulations on dumped Java codes. Is Java.ReturnStatement useful here?
| private static void appendCode(StringBuilder builder, String code) { | ||
| if (code.isEmpty()) { | ||
| return; | ||
| } | ||
| builder.append(code); | ||
| if (code.charAt(code.length() - 1) != '\n') { | ||
| builder.append('\n'); | ||
| } | ||
| } |
| if (rvalue == null) { | ||
| throw new ParseException("Unrecognized expression: " + sqlNode); | ||
| } | ||
| return GeneratedExpression.fromExpression(rvalue.toString(), resultClass); |
There was a problem hiding this comment.
rvalue.toString() is for debugging purposes only and may not generate Java code with correct parentheses.
For example, SQL statement (a - b) / (c + 1) will be converted to a - b / c + 1.
| return Object.class; | ||
| } | ||
|
|
||
| private static boolean isBooleanResultCall(SqlBasicCall sqlBasicCall) { |
There was a problem hiding this comment.
One may write a UDF returning BOOLEAN?
|
Thanks @lvyanquan and @yuxiqian for the feedback. I ran a JMH benchmark comparing the Supplier path against an equivalent if/else script and found no statistically significant difference — all configurations fall in the 330k-372k ops/ms range with overlapping confidence intervals, that means the JIT escape analysis reliably eliminates the anonymous Given the lack of measurable benefit and the correctness risks @yuxiqian identified, I agree this change is not warranted. I'll go ahead and close this PR later. |
Thanks for the detailed benchmark report.
Seems this is an existing bug, not introduced by changes in this PR. Could you please investigate this later? |
Thanks for pointing out this bug! I confirmed that I'll open a separate PR to fix it. |
Summary
This commit introduces statement-level code generation for Flink CDC YAML Transform expressions. Transform expressions are now represented by a
GeneratedExpressionintermediate structure and compiled through JaninoScriptEvaluator, allowing nullableAND/ORshort-circuit logic to be generated as explicitif/elsestatements instead of per-recordSupplierwrappers.Key Changes
GeneratedExpressionto carry generated statements, result term, and result Java type.TransformExpressionEvaluatoras a small wrapper around JaninoScriptEvaluator.translateSqlNodeToGeneratedExpression(...)inJaninoCompiler.if/elseblocks for nullableANDandOR.result$0.AND/ORSQL three-valued logic.false AND (1 / 0 > 0)true OR (1 / 0 > 0)new Supplier.JIRA Reference
https://issues.apache.org/jira/browse/FLINK-40204