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

Yicong-Huang pushed a commit to branch branch-4.x
in repository https://gitbox.apache.org/repos/asf/spark.git


The following commit(s) were added to refs/heads/branch-4.x by this push:
     new 53e4901ef5c8 [SPARK-46776][PYTHON][FOLLOWUP] Cast 
large_string/large_binary to the requested type on pyarrow < 19
53e4901ef5c8 is described below

commit 53e4901ef5c8c4ed6b01d42b33f5a8f492a1888e
Author: Yicong Huang <[email protected]>
AuthorDate: Fri Jun 12 18:59:55 2026 +0000

    [SPARK-46776][PYTHON][FOLLOWUP] Cast large_string/large_binary to the 
requested type on pyarrow < 19
    
    ### What changes were proposed in this pull request?
    
    This is a follow-up to #56157 (commit `2f4ed64204e`, `[SPARK-46776][PYTHON] 
Support pa.ChunkedArray columns in createDataFrame from pandas`), which 
introduced a regression in the minimum-dependencies CI job (`pyarrow==18.0.0`).
    
    In `create_arrow_array_from_pandas` 
(`python/pyspark/sql/pandas/conversion.py`), after `pa.Array.from_pandas(...)`, 
cast the result back to the requested `arrow_type` only when pyarrow is older 
than 19.0.0 and the produced type is the `large_string`/`large_binary` 
counterpart of the requested `string`/`binary` type. Both guards are required: 
the version check avoids touching pyarrow >= 19.0.0 (which already honors the 
requested type), and the exact type check ensures only this offset- [...]
    
    ### Why are the changes needed?
    
    Since pandas 2.2.0, the `string[pyarrow]` extension dtype is backed by 
`large_string` (64-bit offsets). When such a series is converted via the 
`__arrow_array__` protocol, pyarrow < 19.0.0 ignores the requested `type` 
argument, so `pa.Array.from_pandas(series, type=pa.string())` returns a 
`large_string` array even though `string` was requested. The JVM then reads the 
64-bit offset buffers against the 32-bit schema, silently corrupting the data.
    
    pyarrow 19.0.0 fixed this in the protocol path (apache/arrow#44195) by 
casting the result to the requested type. This change replicates that behavior 
for older pyarrow so the minimum-dependencies build (`pyarrow==18.0.0`) 
produces correct data instead of corrupt rows.
    
    ### Does this PR introduce _any_ user-facing change?
    
    Yes. On pyarrow < 19.0.0, `createDataFrame` from a pandas object containing 
`string[pyarrow]`/`large_string`-backed columns (and the `binary` equivalent) 
previously produced silently corrupted data; it now produces correct data. On 
pyarrow >= 19.0.0 there is no behavior change.
    
    ### How was this patch tested?
    
    Existing `pyspark.sql.tests.test_conversion` suite passes locally. The 
regression environment was validated by manually dispatching the "Build / 
Python-only (Minimum dependencies of PySpark)" workflow on my fork against this 
branch with `pyarrow==18.0.0`; the `pyspark-sql`, `pyspark-pandas`, and related 
modules all passed: 
https://github.com/Yicong-Huang/spark/actions/runs/27244934810
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    No.
    
    Closes #56380 from 
Yicong-Huang/SPARK-46776-followup/fix/cast-large-string-pyarrow18.
    
    Authored-by: Yicong Huang <[email protected]>
    Signed-off-by: Yicong-Huang <[email protected]>
    (cherry picked from commit 3b5cbeb1f05dc3b7d32ee1107e7e8147ad518cd1)
    Signed-off-by: Yicong-Huang <[email protected]>
---
 python/pyspark/sql/pandas/conversion.py | 19 ++++++++++++++++++-
 1 file changed, 18 insertions(+), 1 deletion(-)

diff --git a/python/pyspark/sql/pandas/conversion.py 
b/python/pyspark/sql/pandas/conversion.py
index b488a49bcb6f..d0b89354e100 100644
--- a/python/pyspark/sql/pandas/conversion.py
+++ b/python/pyspark/sql/pandas/conversion.py
@@ -88,6 +88,7 @@ def create_arrow_array_from_pandas(
     """
     import pyarrow as pa
     import pandas as pd
+    from pyspark.loose_version import LooseVersion
     from pyspark.sql.pandas.types import to_arrow_type, 
_create_converter_from_pandas
 
     if isinstance(series.dtype, pd.CategoricalDtype):
@@ -113,7 +114,23 @@ def create_arrow_array_from_pandas(
     else:
         mask = series.isnull()
     try:
-        return pa.Array.from_pandas(series, mask=mask, type=arrow_type, 
safe=safecheck)
+        result = pa.Array.from_pandas(series, mask=mask, type=arrow_type, 
safe=safecheck)
+        # SPARK-46776: pyarrow < 19.0.0 ignores the requested ``type`` in the
+        # ``__arrow_array__`` protocol used by pyarrow-backed extension 
dtypes, so a
+        # ``string[pyarrow]`` series (backed by ``large_string`` since pandas 
2.2.0) can
+        # come back as ``large_string`` even when ``string`` was requested, 
silently
+        # corrupting data on the JVM side. Cast back only for this exact 
(large_)string /
+        # (large_)binary offset-width mismatch; pyarrow >= 19.0.0 already 
honors the type.
+        if (
+            arrow_type is not None
+            and LooseVersion(pa.__version__) < LooseVersion("19.0.0")
+            and (
+                (pa.types.is_large_string(result.type) and 
pa.types.is_string(arrow_type))
+                or (pa.types.is_large_binary(result.type) and 
pa.types.is_binary(arrow_type))
+            )
+        ):
+            result = result.cast(arrow_type)
+        return result
     except TypeError as e:
         error_msg = (
             "Exception thrown when converting pandas.Series (%s) "


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

Reply via email to