HyukjinKwon opened a new pull request, #57801:
URL: https://github.com/apache/spark/pull/57801

   ### What changes were proposed in this pull request?
   
   This PR makes `_with_origin` in `python/pyspark/errors/utils.py` re-entrant.
   
   `_with_origin` captures the Python call site of a PySpark API call so that a
   `DataFrameQueryContext` can point at the user code that built a failing 
expression. A
   PySpark API can invoke other decorated APIs internally, for example 
`Column.__and__`
   calls `lit`. Today the nested call overwrites the origin and then clears it 
in its
   `finally` block, so the outer call continues with no origin at all.
   
   This PR guards against that the same way the JVM side `withOrigin` already 
does
   (`sql/api/src/main/scala/org/apache/spark/sql/catalyst/trees/origin.scala`):
   
   ```scala
   if (CurrentOrigin.get.stackTrace.isDefined || 
!SqlApiConf.get.dataFrameQueryContextEnabled) {
   ```
   
   > As there might be multiple nested `withOrigin` calls (e.g. any Spark API 
implementations
   > can invoke other APIs) only the first `withOrigin` is captured because 
that is closer to
   > the user code.
   
   When an origin is already set, capture is skipped entirely and the outermost 
origin is
   kept. The classic path now also records the call site on the Python side (in 
addition to
   the JVM side) because that is the state the guard reads.
   
   ### Why are the changes needed?
   
   The Python side is missing the nesting guard that the JVM side has, which 
makes the
   decorator non-composable: applying it to an API that is reachable from 
another decorated
   API silently drops the outer context.
   
   Today only `Column` methods and `col` are decorated, and nothing internally 
calls `col`,
   so this is currently latent and not user-facing. It becomes a blocker for 
expanding
   `_with_origin` coverage to `pyspark.sql.functions` (`F.split`, `F.to_date`, 
...), where
   expressions built from those functions carry no DataFrame context at all. 
Many of those
   functions call `lit` internally (`instr` and `make_interval` call it 7 times 
each), so
   without this fix the expansion would attribute the context to the nested 
`lit` or lose it.
   
   Fixing the decorator first keeps that follow-up change to purely adding 
decorators.
   
   As a side effect the guard also avoids redundant work in the nested case: it 
skips the
   stack walk, and on the classic path it skips the py4j `set`/`clear` round 
trips and the
   `spark.sql.stackTracesInDataFrameContext` conf read.
   
   ### Does this PR introduce _any_ user-facing change?
   
   No. Only `Column` methods and `col` are currently decorated and no decorated 
API invokes
   another one, so no error message changes. This fixes latent behavior in an 
internal
   decorator and unblocks a follow-up that will be user-facing.
   
   ### How was this patch tested?
   
   New unit test `test_with_origin_is_reentrant` in
   `python/pyspark/sql/tests/test_dataframe_query_context.py`, which asserts 
that a nested
   decorated call neither overwrites nor clears the outer origin, and that the 
origin is
   fully cleared once the outermost call returns. Verified that it fails 
without the fix
   (`observed == [None, None]`) and passes with it (`observed == ["outer", 
"outer"]`).
   
   Ran locally:
   
   - `pyspark.sql.tests.test_dataframe_query_context` (classic)
   - `pyspark.sql.tests.connect.test_parity_dataframe_query_context` (Connect)
   - `pyspark.errors.tests.test_errors`
   
   ### Was this patch authored or co-authored using generative AI tooling?
   
   Generated-by: Claude Code (Opus 5)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to