Uh oh!
There was an error while loading. Please reload this page.
[SPARK-55206][PYTHON][FOLLOWUP] Preserve Python semantics in UDF transpilation - #58185
[SPARK-55206][PYTHON][FOLLOWUP] Preserve Python semantics in UDF transpilation#58185cloud-fan wants to merge 4 commits into
Conversation
cloud-fan
commented
Aug 21, 2026
cc @holdenk |
holdenk
commented
Aug 21, 2026
Oooh very cool :) let me take a look |
holdenk
commented
Aug 21, 2026
Hey @cloud-fan see some CI failures that seem related, can you take a look? |
| # where Python promotes to a big int; arithmetic is not | ||
| # NULL-guarded (`x + 1` on NULL -> NULL vs Python TypeError). | ||
| # TODO (SPARK-55210): map overflow / divide-by-zero precisely. | ||
| # Numeric arithmetic is never lowered: fixed-width Catalyst |
There was a problem hiding this comment.
Ok so this is a design choice we can think about, I'm in favor of transpiling the numeric operators even if overflow is possible provided we document that behavior. Otherwise we could catch the overflow and promote (for example try_... and then on nulls we promote).
There was a problem hiding this comment.
Or more simply add a catalyst expr which does the automatic promotion instead of encoding that in weird branching. But I think (for v0) document and throw is ok.
There was a problem hiding this comment.
Agreed. For v0, I restored numeric and unary transpilation and kept ANSI overflow as a documented divergence. Exact promotion via try_* or a dedicated Catalyst expression can follow under SPARK-55210.
The PR still fixes NULL handling, NaN comparisons, and string concatenation; unsafe string repetition falls back to Python. I also fixed the related CI and formatting failures in cb4b842. Thanks!
holdenk
commented
Aug 24, 2026
@cloud-fan still seeing a lot of related unit test failures around UDF presence in plan. |
# Conflicts: # python/pyspark/sql/tests/test_udf_transpile_unit.py
cloud-fan
commented
Aug 25, 2026
@holdenk CI green now :) |
dongjoon-hyun
commented
Aug 25, 2026
Thanks for the follow-up. The direction looks right to me -- failing closed on NULL arithmetic, handling NaN in comparisons, and guarding string concat are all real fidelity improvements, and CI is green. Three things I'd like to raise before this goes in. 1. The NULL guard duplicates operand subtrees, so nested arithmetic grows exponentiallyIn null_guard=left_col.isNull() |right_col.isNull()
...
returnwhen(null_guard, raise_error(null_error)).otherwise(left_col.__add__(right_col))Each nesting level therefore emits two copies of each child subtree. Counting node occurrences,
Catalyst expressions are immutable case classes, so the initial object graph is shared, but To be clear about what is new here: the same duplication pattern already existed in Suggestion: track nullability statically on the Python side and only emit an
With that, 2. |
holdenk
commented
Aug 26, 2026
For #1 I've got https://issues.apache.org/jira/browse/SPARK-58628 filed I'm happy to take it on post merge of this if you're ok with that @dongjoon-hyun ? I could also try and land it first and we could go rebase on top |
cloud-fan
commented
Aug 27, 2026
Thanks @dongjoon-hyun. I addressed these in
Verification passed: |
There was a problem hiding this comment.
LGTM if @dongjoon-hyun is ok with the defered null guard resolution.
What changes were proposed in this pull request?
Followup to #56327.
This change makes the experimental Python UDF transpiler preserve Python semantics more
conservatively. Numeric and non-literal unary arithmetic, modulo, and string repetition fall back
to interpreted Python when fixed-width Catalyst operations cannot preserve Python behavior.
String concatenation raises on null inputs as Python would, and numeric equality and ordering
handle NaN according to Python semantics.
The transpilation unit tests are updated to assert the safe fallback paths and the corrected NaN
behavior.
Why are the changes needed?
Python integers have arbitrary precision, while Catalyst numeric arithmetic uses fixed-width
types. Lowering these expressions can therefore overflow or otherwise produce behavior different
from the original Python UDF. Spark also treats NaN differently from Python for equality and
ordering, and Catalyst normally propagates null through concatenation where Python raises a
TypeError. The transpiler must fail closed whenever it cannot preserve the source UDF's behavior.
Does this PR introduce any user-facing change?
Yes. When the unreleased experimental Python UDF transpilation feature is explicitly enabled,
unsafe arithmetic expressions now remain interpreted Python UDFs, and transpiled comparisons and
string concatenation more closely match Python behavior.
How was this patch tested?
Updated
pyspark.sql.tests.test_udf_transpile_unitwith positive and negative cases coveringnumeric fallback, unary operations, modulo, string operations, nulls, and NaN comparisons.
build/sbt -Phive packagepython/run-tests --testnames pyspark.sql.tests.test_udf_transpile_unitWas this patch authored or co-authored using generative AI tooling?
Generated-by: OpenAI Codex (GPT-5)