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 e917aa7cd0 GH-50260: [C++] Add ComputeLogicalNullCount to ChunkedArray
(#50261)
e917aa7cd0 is described below
commit e917aa7cd0115616ad97b8ddcb2b098dc8ba21bb
Author: Rahul Goel <[email protected]>
AuthorDate: Mon Jun 29 05:48:22 2026 -0400
GH-50260: [C++] Add ComputeLogicalNullCount to ChunkedArray (#50261)
### Rationale for this change
`Array` and `ArrayData` expose `ComputeLogicalNullCount()`, which counts
logical nulls for types that carry them without a validity bitmap (e.g. unions
and run-end encoded arrays).
`ChunkedArray` only exposed `null_count()`, which does not account for
those logical nulls, so callers had to hand-roll a loop over the chunks. This
PR closes that gap.
### What changes are included in this PR?
Add `ChunkedArray::ComputeLogicalNullCount()`, returning the sum of
`Array::ComputeLogicalNullCount()` over the chunks. As with the `Array`level
method, the value is recomputed on each call rather than cached.
### Are these changes tested?
Yes. A new `TestChunkedArray.ComputeLogicalNullCount` test covers three
cases:
- a type with a validity bitmap (result matches `null_count()`),
- an empty chunked array, and,
- a run-end encoded chunked array (where `null_count()` is 0 but the
logical null count is not).
### Are there any user-facing changes?
Yes, this adds a new public method,
`ChunkedArray::ComputeLogicalNullCount()`. The change is purely additive; no
existing APIs are modified.
* GitHub Issue: #50260
Authored-by: Rahul Goel <[email protected]>
Signed-off-by: Antoine Pitrou <[email protected]>
---
cpp/src/arrow/chunked_array.cc | 8 ++++++++
cpp/src/arrow/chunked_array.h | 10 ++++++++++
cpp/src/arrow/chunked_array_test.cc | 32 ++++++++++++++++++++++++++++++++
3 files changed, 50 insertions(+)
diff --git a/cpp/src/arrow/chunked_array.cc b/cpp/src/arrow/chunked_array.cc
index edf23e970d..d5bb1a8c1e 100644
--- a/cpp/src/arrow/chunked_array.cc
+++ b/cpp/src/arrow/chunked_array.cc
@@ -62,6 +62,14 @@ ChunkedArray::ChunkedArray(ArrayVector chunks,
std::shared_ptr<DataType> type)
}
}
+int64_t ChunkedArray::ComputeLogicalNullCount() const {
+ int64_t count = 0;
+ for (const auto& chunk : chunks_) {
+ count += chunk->ComputeLogicalNullCount();
+ }
+ return count;
+}
+
Result<std::shared_ptr<ChunkedArray>> ChunkedArray::Make(ArrayVector chunks,
std::shared_ptr<DataType> type) {
if (type == nullptr) {
diff --git a/cpp/src/arrow/chunked_array.h b/cpp/src/arrow/chunked_array.h
index 2b581d0bb6..82e173ca31 100644
--- a/cpp/src/arrow/chunked_array.h
+++ b/cpp/src/arrow/chunked_array.h
@@ -108,6 +108,16 @@ class ARROW_EXPORT ChunkedArray {
/// \return the total number of nulls among all chunks
int64_t null_count() const { return null_count_; }
+ /// \brief Computes the logical null count across all chunks
+ ///
+ /// This returns the sum of Array::ComputeLogicalNullCount() over the chunks.
+ /// Unlike null_count(), it accounts for types that carry logical nulls
+ /// without a validity bitmap, such as union and run-end encoded arrays; for
+ /// those types the count is recomputed on every call.
+ ///
+ /// \see Array::ComputeLogicalNullCount
+ int64_t ComputeLogicalNullCount() const;
+
/// \return the total number of chunks in the chunked array
int num_chunks() const { return static_cast<int>(chunks_.size()); }
diff --git a/cpp/src/arrow/chunked_array_test.cc
b/cpp/src/arrow/chunked_array_test.cc
index 90b90a731b..1862e308b3 100644
--- a/cpp/src/arrow/chunked_array_test.cc
+++ b/cpp/src/arrow/chunked_array_test.cc
@@ -23,6 +23,7 @@
#include <memory>
#include <vector>
+#include "arrow/array/builder_run_end.h"
#include "arrow/chunk_resolver.h"
#include "arrow/scalar.h"
#include "arrow/status.h"
@@ -76,6 +77,37 @@ TEST_F(TestChunkedArray, Make) {
ASSERT_RAISES(TypeError, ChunkedArray::Make({chunk0}, int16()));
}
+TEST_F(TestChunkedArray, ComputeLogicalNullCount) {
+ // For types with a validity bitmap, the logical null count matches
+ // null_count() (the sum over chunks).
+ auto chunk0 = ArrayFromJSON(int32(), "[1, null, 3]");
+ auto chunk1 = ArrayFromJSON(int32(), "[null, 5]");
+ ChunkedArray with_bitmap({chunk0, chunk1});
+ ASSERT_EQ(with_bitmap.null_count(), 2);
+ ASSERT_EQ(with_bitmap.ComputeLogicalNullCount(), 2);
+
+ // An empty chunked array has no logical nulls.
+ ASSERT_OK_AND_ASSIGN(auto empty, ChunkedArray::MakeEmpty(int32()));
+ ASSERT_EQ(empty->ComputeLogicalNullCount(), 0);
+
+ // Run-end encoded arrays carry logical nulls without a top-level validity
+ // bitmap, so null_count() is 0 while the logical null count is not.
+ auto pool = default_memory_pool();
+ auto ree_type = run_end_encoded(int32(), int32());
+ RunEndEncodedBuilder ree_builder(pool, std::make_shared<Int32Builder>(pool),
+ std::make_shared<Int32Builder>(pool),
ree_type);
+ ASSERT_OK(ree_builder.AppendScalar(*MakeScalar<int32_t>(2), 2));
+ ASSERT_OK(ree_builder.AppendNulls(3));
+ ASSERT_OK_AND_ASSIGN(auto ree_chunk0, ree_builder.Finish());
+ ASSERT_OK(ree_builder.AppendNulls(4));
+ ASSERT_OK(ree_builder.AppendScalar(*MakeScalar<int32_t>(8), 5));
+ ASSERT_OK_AND_ASSIGN(auto ree_chunk1, ree_builder.Finish());
+
+ ChunkedArray ree_ca({ree_chunk0, ree_chunk1}, ree_type);
+ ASSERT_EQ(ree_ca.null_count(), 0);
+ ASSERT_EQ(ree_ca.ComputeLogicalNullCount(), 7);
+}
+
TEST_F(TestChunkedArray, MakeEmpty) {
ASSERT_OK_AND_ASSIGN(std::shared_ptr<ChunkedArray> empty,
ChunkedArray::MakeEmpty(int64()));