From 11dcb8632f18f79726ba87698fdc52145be89543 Mon Sep 17 00:00:00 2001 From: 1fanwang <1fannnw@gmail.com> Date: Mon, 27 Apr 2026 02:01:36 -0700 Subject: [PATCH 1/3] GH-48344: [Python] Fix Table.from_struct_array for empty ChunkedArray Round-tripping an empty Table through to_struct_array -> from_struct_array raised "Must pass schema, or at least one RecordBatch": to_struct_array returns a ChunkedArray with zero chunks for an empty table (since #46355), and from_struct_array then called Table.from_batches([]) without a schema. Pass schema=schema(struct_array.type.fields) so the zero-chunk path preserves the field names and dtypes from the ChunkedArray's struct type. Mirrors the inverse fix in #46355. Signed-off-by: 1fanwang <1fannnw@gmail.com> --- python/pyarrow/table.pxi | 11 +++++++---- python/pyarrow/tests/test_table.py | 17 +++++++++++++++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/python/pyarrow/table.pxi b/python/pyarrow/table.pxi index 1abe4235c411..5d0db36f16f8 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 - ]) + return Table.from_batches( + [ + RecordBatch.from_struct_array(chunk) + for chunk in struct_array.chunks + ], + schema=schema(struct_array.type.fields), + ) 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 bf6e5773ddf1..0c52bda7a959 100644 --- a/python/pyarrow/tests/test_table.py +++ b/python/pyarrow/tests/test_table.py @@ -941,6 +941,23 @@ def test_table_from_struct_array_chunked_array(): )) +def test_table_from_struct_array_for_empty_chunked_array(): + # GH-48344: round-trip Table.to_struct_array -> Table.from_struct_array + # for an empty table produces a ChunkedArray with zero chunks, which + # previously raised "Must pass schema, or at least one RecordBatch". + 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( [ From 7b874baa24266979299f13b00a7543ab61f0987a Mon Sep 17 00:00:00 2001 From: Alenka Frim Date: Mon, 22 Jun 2026 10:45:08 +0200 Subject: [PATCH 2/3] Update python/pyarrow/tests/test_table.py Signed-off-by: 1fanwang <1fannnw@gmail.com> --- python/pyarrow/tests/test_table.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/python/pyarrow/tests/test_table.py b/python/pyarrow/tests/test_table.py index 0c52bda7a959..715e375dcd03 100644 --- a/python/pyarrow/tests/test_table.py +++ b/python/pyarrow/tests/test_table.py @@ -942,9 +942,7 @@ def test_table_from_struct_array_chunked_array(): def test_table_from_struct_array_for_empty_chunked_array(): - # GH-48344: round-trip Table.to_struct_array -> Table.from_struct_array - # for an empty table produces a ChunkedArray with zero chunks, which - # previously raised "Must pass schema, or at least one RecordBatch". + # 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) From e4ad179ae6ae40ceea939a572cb45cee3ed2544c Mon Sep 17 00:00:00 2001 From: 1fanwang <1fannnw@gmail.com> Date: Sun, 23 Aug 2026 06:39:16 -0400 Subject: [PATCH 3/3] GH-48344: Preserve TypeError for invalid empty ChunkedArray Signed-off-by: 1fanwang <1fannnw@gmail.com> --- python/pyarrow/table.pxi | 6 +++--- python/pyarrow/tests/test_table.py | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/python/pyarrow/table.pxi b/python/pyarrow/table.pxi index 5d0db36f16f8..cb6a2e0acb4f 100644 --- a/python/pyarrow/table.pxi +++ b/python/pyarrow/table.pxi @@ -4912,12 +4912,12 @@ cdef class Table(_Tabular): if isinstance(struct_array, Array): return Table.from_batches([RecordBatch.from_struct_array(struct_array)]) else: + chunks = struct_array.chunks or [struct_array.combine_chunks()] return Table.from_batches( [ RecordBatch.from_struct_array(chunk) - for chunk in struct_array.chunks - ], - schema=schema(struct_array.type.fields), + 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 715e375dcd03..cfe47e4ed05a 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}],