Skip to content

[C++][Python] Casting a MapArray whose keys child carries an all-valid validity bitmap aborts the process (residual case of GH-38553) #51029

Description

@max-parke-scale

Describe the bug, including details regarding any error messages, version, and platform.

Casting a map array still hard-aborts the process (SIGABRT, not a raised exception) with Map array keys array should have no nulls when the keys child carries a validity bitmap — even an all-valid one. This is a residual case of #38553: the fix in #41957 changed MapArray::ValidateChildData to use MayHaveNulls(), which resolved the no-bitmap repro on that issue, but MayHaveNulls() answers true whenever a validity bitmap is present and the null count is unknown — it never counts. A cast's output starts with an unknown null count, so the fatal ARROW_CHECK_OK(ValidateChildData(...)) in MapArray::SetData (array_nested.cc:912 as of current main) kills the process.

Three-line reproduction, all public API, on pyarrow 24.0.0 (the current release):

import pyarrow as pa
m = pa.array([{"a": "1"}, {"b": "2"}], type=pa.map_(pa.string(), pa.string()))
m.filter(pa.array([False, True])).cast(pa.map_(pa.large_string(), pa.large_string()))
/Users/runner/work/crossbow/crossbow/arrow/cpp/src/arrow/array/array_nested.cc:912:  Check failed: _s.ok() Operation failed: ValidateChildData(data->child_data)
Bad status: Invalid: Map array keys array should have no nulls

Exit code 134. The filter call itself succeeds and the filtered array passes validate(full=True).

filter matters only because it materializes new child arrays that carry an all-valid validity bitmap; slice re-views the original buffers without one, which is why the slice→cast variant works after #41957. The bitmap alone is sufficient — same abort with no filter involved:

import pyarrow as pa
keys = pa.array(["a", "b"])
validity = pa.array([True, True]).buffers()[1]  # all-valid bitmap
keys = pa.Array.from_buffers(pa.string(), 2, [validity, keys.buffers()[1], keys.buffers()[2]])
m = pa.MapArray.from_arrays(pa.array([0, 1, 2], type=pa.int32()), keys, pa.array(["1", "2"]))
m.cast(pa.map_(pa.large_string(), pa.large_string()))  # aborts

Forcing null-count computation on the input (m.keys.null_count → 0) does not help: the cast produces a fresh keys ArrayData whose count is unknown again.

Real-world impact: pyiceberg's scan path does filter-then-cast (row-filtered Parquet read, then cast to large types), so any row-filtered scan that projects a map column kills the worker process.

Two candidate fixes, possibly both:

  • MapArray::ValidateChildData (array_nested.cc:899/905) could force-compute the null count (GetNullCount() != 0) rather than ask MayHaveNulls(); counting an all-valid bitmap is cheap next to aborting the process.
  • The ARROW_CHECK_OK at array_nested.cc:912 turns an Invalid status into process death even when the data is genuinely invalid; a returned/raised error would be recoverable.

Environment: pyarrow 24.0.0 (PyPI wheel), Python 3.13.13, macOS 26.4.1 arm64.

Component(s)

C++, Python

🤖 — posted via Claude Code

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions