[FLINK-39986][table-planner][python] Support full-tree CSE for nested Python UDF calls - #28998
Open
raoraoxiong wants to merge 2 commits into
Open
[FLINK-39986][table-planner][python] Support full-tree CSE for nested Python UDF calls#28998raoraoxiong wants to merge 2 commits into
raoraoxiong wants to merge 2 commits into
Conversation
Collaborator
raoraoxiong
force-pushed
the
FLINK-39986-python-cse-2
branch
from
August 21, 2026 07:41
3f7ab56 to
94db80c
Compare
1 task
… in projection and condition Deduplicates Python UDF calls on the JVM side to reduce cross-process (JVM <-> Python worker) invocation overhead. This covers two scenarios: - Top-level projection duplicates: identical deterministic calls in the projection (e.g. SELECT udf(a), udf(a)) are sent to the Python worker only once; a codegen expansion projection maps the deduplicated results back to the original output schema - Condition-projection sharing: after RemoteCalcSplitConditionRule splits a Calc with Python UDFs in its condition, the new RemoteCalcConditionProjectionCseRule rewrites the top Calc so that projections reuse the Python UDF results already computed for the WHERE condition (e.g. SELECT udf(a) + 1 FROM T WHERE udf(a) > 0), including calls nested inside Java expressions Non-deterministic calls are never deduplicated and are always evaluated independently. This is a pure JVM-side change: no protocol or Python worker changes are involved. Deduplication of Python UDF calls nested inside other Python UDF calls (e.g. udf(udf(a))) will be addressed in a follow-up. Key changes: - Add PythonCallDeduplicator and PythonCallCseResult for structural deduplication of top-level projection calls - Append a CSE expansion projection in CommonExecPythonCalc when duplicated results need to be restored to the output schema - Add ProjectionCodeGenerator#generateProjectionOperator for the codegen column-mapping projection operator - Add RemoteCalcConditionProjectionCseRule and register it in stream and batch rule sets after SPLIT_CONDITION - Add unit tests, plan tests and integration tests Generated-by: Claude-4.6-Opus
… Python UDF calls Extends the Python UDF deduplication from top-level projection calls to nested sub-expressions (full-tree CSE). Python UDF call trees are flattened in post-order and deduplicated by structural equivalence, enabling cross-subtree reuse: in SELECT udf1(x), udf2(udf1(x)), the inner udf1(x) is computed only once and its result is passed to udf2 by reference. A nested call whose argument was already computed is no longer re-executed inside the Python worker: the planner emits a ResultRef carrying the index of the pre-computed result, which is transferred via the new refIndex field in the protobuf Input message. The Python worker executes the deduplicated calls sequentially and resolves result references before invoking each UDF. The protobuf change is backward compatible: inputs without refIndex follow the original nested-evaluation path. Key changes: - Extend PythonCallDeduplicator to flatten nested call trees and build a sub-expression cross-reference map (refMap) - Add PythonFunctionInfo.ResultRef for referencing pre-computed results and wire it through CommonPythonUtil/ProtoUtils - Extend protobuf Input message with refIndex field - Modify Python worker (operations.py) to support sequential execution with result references - Add a CSE annotation to the PythonCalc plan description showing top-level reuse relationships, e.g. (CSE: EXPR$2->EXPR$1) - Add unit tests, plan tests, Python worker tests and integration tests Generated-by: Claude-4.6-Opus
raoraoxiong
force-pushed
the
FLINK-39986-python-cse-2
branch
from
August 21, 2026 12:44
94db80c to
c3647d5
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What is the purpose of the change
This PR is the second of two PRs implementing common sub-expression elimination (CSE) for Python UDFs (FLINK-39986), building on top of #28638 (which covers top-level projection deduplication and condition-projection sharing on the JVM side).
This PR extends the deduplication to nested sub-expressions (full-tree CSE). Python UDF call trees are flattened in post-order and deduplicated by structural equivalence, enabling cross-subtree reuse: in
SELECT udf1(x), udf2(udf1(x)), the innerudf1(x)is computed only once and its result is passed toudf2by reference.To achieve this, the JVM-to-worker protocol is extended: a nested call whose result is already computed is no longer re-executed inside the Python worker. Instead, the planner emits a
ResultRefcarrying the index of the pre-computed result, transferred via the newrefIndexfield in the protobufInputmessage. The Python worker executes the deduplicated calls sequentially and resolves result references before invoking each UDF.The protobuf change is backward compatible: inputs without
refIndexfollow the original nested-evaluation path.Brief change log
PythonCallDeduplicatorto flatten nested call trees (post-order, so child results are available before parents) and build a sub-expression cross-reference map (refMap); non-deterministic children are not flattened to prevent incorrect sharingPythonFunctionInfo.ResultReffor referencing pre-computed results and wire it throughCommonPythonUtil/ProtoUtilsInputmessage withrefIndexfieldoperations.py) to support sequential execution with result referencesPythonCalcplan description showing top-level reuse relationships, e.g.(CSE: EXPR$2->EXPR$1)Verifying this change
This change added tests and can be verified as follows:
CommonExecPythonCalcCseTest#testRefMapResolvesStructurallyEqualSubExpressions: unit test for cross-subtree reference resolutionPythonCalcConditionCseTest#testNestedUdfWithCseAnnotation(+ plan XML): plan test verifying nested deduplication and the CSE plan annotationflink-python/pyflink/fn_execution/tests/test_scalar_function_cse.py: Python worker tests for sequential execution with result referencestest_python_local_ref_reuseinflink-python/pyflink/table/tests/test_udf.py: end-to-end tests verifying nested deterministic calls are reusedDoes this pull request potentially affect one of the following parts:
@Public(Evolving): (no)Documentation
AI Usage Disclosure
Generated-by: Claude-4.6-Opus