From 0313591bcc057e351efb15ad17078fc91b8d0d44 Mon Sep 17 00:00:00 2001 From: Guja <127162872+GujaLomsadze@users.noreply.github.com> Date: Sun, 9 Aug 2026 14:58:18 +0200 Subject: [PATCH] GH-46918: [Python] Respect explicit Field type in add_column/set_column add_column and set_column converted the column data before looking at field_, so all-null data was inferred as null type and then rejected against an explicitly typed Field. Cast to the field type when the inferred type is null. Non-null data is left alone, so a real type mismatch still errors out. --- python/pyarrow/table.pxi | 8 +++++ python/pyarrow/tests/test_table.py | 51 ++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/python/pyarrow/table.pxi b/python/pyarrow/table.pxi index 1abe4235c411..c26e5eef9bf8 100644 --- a/python/pyarrow/table.pxi +++ b/python/pyarrow/table.pxi @@ -2896,6 +2896,8 @@ cdef class RecordBatch(_Tabular): if isinstance(field_, Field): c_field = field_ + if c_arr.type == null() and c_field.type != null(): + c_arr = c_arr.cast(c_field.type) else: c_field = field(field_, c_arr.type) @@ -2994,6 +2996,8 @@ cdef class RecordBatch(_Tabular): if isinstance(field_, Field): c_field = field_ + if c_arr.type == null() and c_field.type != null(): + c_arr = c_arr.cast(c_field.type) else: c_field = field(field_, c_arr.type) @@ -5430,6 +5434,8 @@ cdef class Table(_Tabular): if isinstance(field_, Field): c_field = field_ + if c_arr.type == null() and c_field.type != null(): + c_arr = c_arr.cast(c_field.type) else: c_field = field(field_, c_arr.type) @@ -5519,6 +5525,8 @@ cdef class Table(_Tabular): if isinstance(field_, Field): c_field = field_ + if c_arr.type == null() and c_field.type != null(): + c_arr = c_arr.cast(c_field.type) else: c_field = field(field_, c_arr.type) diff --git a/python/pyarrow/tests/test_table.py b/python/pyarrow/tests/test_table.py index cb010f4387b6..e9c28f17d17b 100644 --- a/python/pyarrow/tests/test_table.py +++ b/python/pyarrow/tests/test_table.py @@ -1680,6 +1680,57 @@ def test_table_add_column(cls): assert t4.equals(expected) +@pytest.mark.parametrize( + ('cls'), + [ + (pa.Table), + (pa.RecordBatch) + ] +) +@pytest.mark.parametrize('method', ['add_column', 'set_column']) +def test_table_add_column_all_null_data_typed_field(cls, method): + # GH-46918: an explicitly typed field must win over the null type + # inferred from all-null column data + table = cls.from_arrays([pa.array([1.0, 2.0])], names=('a',)) + column = [[None, None]] if cls is pa.Table else [None, None] + + new_field = pa.field('b', pa.float64(), nullable=True) + result = getattr(table, method)(0, new_field, column) + + assert result.schema.field('b').type == pa.float64() + assert result.column('b').to_pylist() == [None, None] + + # a null field with null data stays null + null_field = pa.field('b', pa.null()) + result = getattr(table, method)(0, null_field, column) + assert result.schema.field('b').type == pa.null() + + # non-null data that does not match the field type is still rejected + values = [[1.5, 2.5]] if cls is pa.Table else [1.5, 2.5] + with pytest.raises(pa.ArrowInvalid if cls is pa.Table + else pa.ArrowTypeError): + getattr(table, method)(0, pa.field('b', pa.int64()), values) + + +@pytest.mark.parametrize( + ('cls'), + [ + (pa.Table), + (pa.RecordBatch) + ] +) +def test_table_append_column_all_null_data_typed_field(cls): + # GH-46918 + table = cls.from_arrays([pa.array([1.0, 2.0])], names=('a',)) + column = [[None, None]] if cls is pa.Table else [None, None] + + result = table.append_column( + pa.field('b', pa.float64(), nullable=True), column) + + assert result.schema.field('b').type == pa.float64() + assert result.column('b').to_pylist() == [None, None] + + @pytest.mark.parametrize( ('cls'), [