This is an automated email from the ASF dual-hosted git repository.
AlenkaF pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow.git
The following commit(s) were added to refs/heads/main by this push:
new f52076ca064 GH-48344: [Python] Fix Table.from_struct_array for empty
ChunkedArray (#49869)
f52076ca064 is described below
commit f52076ca0646aac885093ffda201cbe7224eaf25
Author: Stefan Wang <[email protected]>
AuthorDate: Fri Aug 28 01:14:37 2026 -0400
GH-48344: [Python] Fix Table.from_struct_array for empty ChunkedArray
(#49869)
### Rationale for this change
Round-tripping an empty table through `to_struct_array()` and
`Table.from_struct_array()` raises
`ValueError: Must pass schema, or at least one RecordBatch`.
`to_struct_array()` returns a zero-chunk struct `ChunkedArray`, so
`from_struct_array()` has no batch from which to recover the schema.
For a zero-chunk input, `from_struct_array()` now calls `combine_chunks()`
and
converts the resulting empty struct array into a `RecordBatch`. Nonempty
inputs
keep the existing path. An empty non-struct `ChunkedArray` still raises the
expected `TypeError`.
### What changes are included in this PR?
The empty `ChunkedArray` path creates one empty batch before calling
`Table.from_batches`. The regression coverage checks the round-trip result,
schema preservation, and the invalid non-struct input.
### Are these changes tested?
I ran this script against PyArrow 25.0.1 and the compiled branch:
```bash
python - <<'PY'
import pyarrow as pa
value = pa.chunked_array(
[], type=pa.struct([("ints", pa.int32()), ("floats", pa.float32())])
)
print(f"pyarrow={pa.__version__}, chunks={value.num_chunks}")
try:
result = pa.Table.from_struct_array(value)
print(f"rows={result.num_rows}, schema={result.schema}")
except Exception as error:
print(f"{type(error).__name__}: {error}")
PY
```
Before:
```text
pyarrow=25.0.1, chunks=0
ValueError: Must pass schema, or at least one RecordBatch
```
After:
```text
pyarrow=26.0.0.dev175+ge4ad179ae, chunks=0
rows=0, schema=ints: int32
floats: float
```
The focused regression suite also runs against the compiled branch:
```text
$ python -m pytest python/pyarrow/tests/test_table.py -k from_struct_array
-q
.......
[100%]
7 passed, 208 deselected in 0.51s
```
### Are there any user-facing changes?
`Table.from_struct_array()` now returns the expected empty table for a
zero-chunk struct `ChunkedArray` instead of raising `ValueError`.
* GitHub Issue: https://github.com/apache/arrow/issues/48344
* GitHub Issue: #48344
Lead-authored-by: 1fanwang <[email protected]>
Co-authored-by: Alenka Frim <[email protected]>
Signed-off-by: AlenkaF <[email protected]>
---
python/pyarrow/table.pxi | 11 +++++++----
python/pyarrow/tests/test_table.py | 20 ++++++++++++++++++++
2 files changed, 27 insertions(+), 4 deletions(-)
diff --git a/python/pyarrow/table.pxi b/python/pyarrow/table.pxi
index 1abe4235c41..cb6a2e0acb4 100644
--- a/python/pyarrow/table.pxi
+++ b/python/pyarrow/table.pxi
@@ -4912,10 +4912,13 @@ cdef class Table(_Tabular):
if isinstance(struct_array, Array):
return
Table.from_batches([RecordBatch.from_struct_array(struct_array)])
else:
- return Table.from_batches([
- RecordBatch.from_struct_array(chunk)
- for chunk in struct_array.chunks
- ])
+ chunks = struct_array.chunks or [struct_array.combine_chunks()]
+ return Table.from_batches(
+ [
+ RecordBatch.from_struct_array(chunk)
+ for chunk in chunks
+ ]
+ )
def to_struct_array(self, max_chunksize=None):
"""
diff --git a/python/pyarrow/tests/test_table.py
b/python/pyarrow/tests/test_table.py
index bf6e5773ddf..cfe47e4ed05 100644
--- a/python/pyarrow/tests/test_table.py
+++ b/python/pyarrow/tests/test_table.py
@@ -913,6 +913,11 @@ def test_table_from_struct_array_invalid():
pa.Table.from_struct_array(pa.array(range(5)))
+def test_table_from_struct_array_empty_chunked_array_invalid():
+ with pytest.raises(TypeError, match="Argument 'struct_array' has incorrect
type"):
+ pa.Table.from_struct_array(pa.chunked_array([], type=pa.int64()))
+
+
def test_table_from_struct_array():
struct_array = pa.array(
[{"ints": 1}, {"floats": 1.0}],
@@ -941,6 +946,21 @@ def test_table_from_struct_array_chunked_array():
))
+def test_table_from_struct_array_for_empty_chunked_array():
+ # GH-48344
+ struct_type = pa.struct([("ints", pa.int32()), ("floats", pa.float32())])
+ empty_chunked_struct_array = pa.chunked_array([], type=struct_type)
+ result = pa.Table.from_struct_array(empty_chunked_struct_array)
+ expected = pa.Table.from_arrays(
+ [
+ pa.array([], type=pa.int32()),
+ pa.array([], type=pa.float32()),
+ ], ["ints", "floats"]
+ )
+ assert result.equals(expected)
+ assert result.schema == expected.schema
+
+
def test_table_to_struct_array():
table = pa.Table.from_arrays(
[