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()));

Reply via email to