This is an automated email from the ASF dual-hosted git repository.
pitrou 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 a4e3909bcf GH-51029: [C++] Fix MapArray validation with unknown null
count (#51093)
a4e3909bcf is described below
commit a4e3909bcf59911316a696990f1eb1d583cddae1
Author: Anurag Tryambak Raut <[email protected]>
AuthorDate: Wed Sep 2 19:01:28 2026 +0530
GH-51029: [C++] Fix MapArray validation with unknown null count (#51093)
### Rationale for this change
`MapArray` validation uses `MayHaveNulls()` to check that the map child and
keys contain no nulls. When an all-valid validity bitmap is present with an
unknown null count, `MayHaveNulls()` can report that the array may contain
nulls even though all values are valid.
During a cast of a `MapArray`, this can cause validation to return an
invalid status which is passed to `ARROW_CHECK_OK`, resulting in a process
abort instead of a recoverable error.
### What changes are included in this PR?
- Use `GetNullCount()` instead of `MayHaveNulls()` when validating the
`MapArray` child and keys.
- Add a regression test covering an all-valid validity bitmap with
`kUnknownNullCount`.
### Are these changes tested?
Yes, with existing and additional unit tests.
### Are there any user-facing changes?
Only a bugfix.
### This PR contains a "Critical Fix".
This fixes a bug that can cause a process crash when casting a valid
`MapArray` whose keys carry an all-valid validity bitmap with an unknown null
count. This is easily reproduced in Python:
```python
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()))
```
* GitHub Issue: #51029
Lead-authored-by: Anurag Raut <[email protected]>
Co-authored-by: Antoine Pitrou <[email protected]>
Signed-off-by: Antoine Pitrou <[email protected]>
---
cpp/src/arrow/array/array_list_test.cc | 37 ++++++++++++++++++++++++++++++++++
cpp/src/arrow/array/array_nested.cc | 10 ++++-----
2 files changed, 42 insertions(+), 5 deletions(-)
diff --git a/cpp/src/arrow/array/array_list_test.cc
b/cpp/src/arrow/array/array_list_test.cc
index 8406bd1d8e..9908eb5886 100644
--- a/cpp/src/arrow/array/array_list_test.cc
+++ b/cpp/src/arrow/array/array_list_test.cc
@@ -1532,6 +1532,43 @@ TEST_F(TestMapArray, ValueBuilder) {
ASSERT_ARRAYS_EQUAL(*actual_list, map_as_list);
}
+// GH-51029: Validate MapArray keys with an all-valid bitmap and unknown null
count.
+TEST_F(TestMapArray, ValidateKeysWithAllValidBitmap) {
+ auto keys = ArrayFromJSON(utf8(), R"(["a", "b"])");
+ auto items = ArrayFromJSON(int32(), "[1, 2]");
+ auto offsets = ArrayFromJSON(int32(), "[0, 1, 2]");
+
+ // Inject an all-valid validity bitmap with kUnknownNullCount.
+ auto keys_data = keys->data()->Copy();
+ keys_data->buffers[0] = ArrayFromJSON(boolean(), "[true,
true]")->data()->buffers[1];
+ keys_data->null_count = kUnknownNullCount;
+
+ ASSERT_OK_AND_ASSIGN(auto result,
+ MapArray::FromArrays(offsets, MakeArray(keys_data),
items));
+ ASSERT_OK(result->ValidateFull());
+ ASSERT_EQ(result->length(), 2);
+}
+
+TEST_F(TestMapArray, FromArraysWithAllValidOffsetsBitmap) {
+ auto offsets = ArrayFromJSON(int32(), "[0, 1, 2]");
+ auto keys = ArrayFromJSON(utf8(), R"(["a", "b"])");
+ auto items = ArrayFromJSON(int32(), "[1, 2]");
+
+ // Inject an all-valid validity bitmap with kUnknownNullCount.
+ auto offsets_data = offsets->data()->Copy();
+ offsets_data->buffers[0] =
+ ArrayFromJSON(boolean(), "[true, true, true]")->data()->buffers[1];
+ offsets_data->null_count = kUnknownNullCount;
+ offsets = MakeArray(offsets_data);
+
+ auto null_bitmap = ArrayFromJSON(boolean(), "[true,
true]")->data()->buffers[1];
+
+ ASSERT_OK_AND_ASSIGN(auto result,
+ MapArray::FromArrays(offsets, keys, items, pool_,
null_bitmap));
+ ASSERT_OK(result->ValidateFull());
+ ASSERT_EQ(result->length(), 2);
+}
+
// ----------------------------------------------------------------------
// FixedSizeList tests
diff --git a/cpp/src/arrow/array/array_nested.cc
b/cpp/src/arrow/array/array_nested.cc
index c5a26a475c..55bce4d0de 100644
--- a/cpp/src/arrow/array/array_nested.cc
+++ b/cpp/src/arrow/array/array_nested.cc
@@ -115,7 +115,7 @@ Result<std::shared_ptr<typename
TypeTraits<TYPE>::ArrayType>> ListArrayFromArray
return Status::TypeError("List offsets must be ",
OffsetArrowType::type_name());
}
- if (null_bitmap != nullptr && offsets.data()->MayHaveNulls()) {
+ if (null_bitmap != nullptr && offsets.data()->GetNullCount() != 0) {
return Status::Invalid(
"Ambiguous to specify both validity map and offsets with nulls");
}
@@ -826,7 +826,7 @@ Result<std::shared_ptr<Array>> MapArray::FromArraysInternal(
return Status::Invalid("Map key and item arrays must be equal length");
}
- if (null_bitmap != nullptr && offsets->data()->MayHaveNulls()) {
+ if (null_bitmap != nullptr && offsets->data()->GetNullCount() != 0) {
return Status::Invalid(
"Ambiguous to specify both validity map and offsets with nulls");
}
@@ -835,7 +835,7 @@ Result<std::shared_ptr<Array>> MapArray::FromArraysInternal(
return Status::NotImplemented("Null bitmap with offsets slice not
supported.");
}
- if (offsets->data()->MayHaveNulls()) {
+ if (offsets->data()->GetNullCount() != 0) {
ARROW_ASSIGN_OR_RAISE(auto buffers,
CleanListOffsets<MapType>(NULLPTR, *offsets, pool));
return std::make_shared<MapArray>(type, offsets->length() - 1,
std::move(buffers),
@@ -896,13 +896,13 @@ Status MapArray::ValidateChildData(
if (pair_data->type->id() != Type::STRUCT) {
return Status::Invalid("Map array child array should have struct type");
}
- if (pair_data->MayHaveNulls()) {
+ if (pair_data->GetNullCount() != 0) {
return Status::Invalid("Map array child array should have no nulls");
}
if (pair_data->child_data.size() != 2) {
return Status::Invalid("Map array child array should have two fields");
}
- if (pair_data->child_data[0]->MayHaveNulls()) {
+ if (pair_data->child_data[0]->GetNullCount() != 0) {
return Status::Invalid("Map array keys array should have no nulls");
}
return Status::OK();