Skip to content

Commit e917aa7

Browse files
authored
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 <goel.rahul4200@gmail.com> Signed-off-by: Antoine Pitrou <antoine@python.org>
1 parent 6b4c524 commit e917aa7

3 files changed

Lines changed: 50 additions & 0 deletions

File tree

cpp/src/arrow/chunked_array.cc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,14 @@ ChunkedArray::ChunkedArray(ArrayVector chunks, std::shared_ptr<DataType> type)
6262
}
6363
}
6464

65+
int64_t ChunkedArray::ComputeLogicalNullCount() const {
66+
int64_t count = 0;
67+
for (const auto& chunk : chunks_) {
68+
count += chunk->ComputeLogicalNullCount();
69+
}
70+
return count;
71+
}
72+
6573
Result<std::shared_ptr<ChunkedArray>> ChunkedArray::Make(ArrayVector chunks,
6674
std::shared_ptr<DataType> type) {
6775
if (type == nullptr) {

cpp/src/arrow/chunked_array.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,16 @@ class ARROW_EXPORT ChunkedArray {
108108
/// \return the total number of nulls among all chunks
109109
int64_t null_count() const { return null_count_; }
110110

111+
/// \brief Computes the logical null count across all chunks
112+
///
113+
/// This returns the sum of Array::ComputeLogicalNullCount() over the chunks.
114+
/// Unlike null_count(), it accounts for types that carry logical nulls
115+
/// without a validity bitmap, such as union and run-end encoded arrays; for
116+
/// those types the count is recomputed on every call.
117+
///
118+
/// \see Array::ComputeLogicalNullCount
119+
int64_t ComputeLogicalNullCount() const;
120+
111121
/// \return the total number of chunks in the chunked array
112122
int num_chunks() const { return static_cast<int>(chunks_.size()); }
113123

cpp/src/arrow/chunked_array_test.cc

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include <memory>
2424
#include <vector>
2525

26+
#include "arrow/array/builder_run_end.h"
2627
#include "arrow/chunk_resolver.h"
2728
#include "arrow/scalar.h"
2829
#include "arrow/status.h"
@@ -76,6 +77,37 @@ TEST_F(TestChunkedArray, Make) {
7677
ASSERT_RAISES(TypeError, ChunkedArray::Make({chunk0}, int16()));
7778
}
7879

80+
TEST_F(TestChunkedArray, ComputeLogicalNullCount) {
81+
// For types with a validity bitmap, the logical null count matches
82+
// null_count() (the sum over chunks).
83+
auto chunk0 = ArrayFromJSON(int32(), "[1, null, 3]");
84+
auto chunk1 = ArrayFromJSON(int32(), "[null, 5]");
85+
ChunkedArray with_bitmap({chunk0, chunk1});
86+
ASSERT_EQ(with_bitmap.null_count(), 2);
87+
ASSERT_EQ(with_bitmap.ComputeLogicalNullCount(), 2);
88+
89+
// An empty chunked array has no logical nulls.
90+
ASSERT_OK_AND_ASSIGN(auto empty, ChunkedArray::MakeEmpty(int32()));
91+
ASSERT_EQ(empty->ComputeLogicalNullCount(), 0);
92+
93+
// Run-end encoded arrays carry logical nulls without a top-level validity
94+
// bitmap, so null_count() is 0 while the logical null count is not.
95+
auto pool = default_memory_pool();
96+
auto ree_type = run_end_encoded(int32(), int32());
97+
RunEndEncodedBuilder ree_builder(pool, std::make_shared<Int32Builder>(pool),
98+
std::make_shared<Int32Builder>(pool), ree_type);
99+
ASSERT_OK(ree_builder.AppendScalar(*MakeScalar<int32_t>(2), 2));
100+
ASSERT_OK(ree_builder.AppendNulls(3));
101+
ASSERT_OK_AND_ASSIGN(auto ree_chunk0, ree_builder.Finish());
102+
ASSERT_OK(ree_builder.AppendNulls(4));
103+
ASSERT_OK(ree_builder.AppendScalar(*MakeScalar<int32_t>(8), 5));
104+
ASSERT_OK_AND_ASSIGN(auto ree_chunk1, ree_builder.Finish());
105+
106+
ChunkedArray ree_ca({ree_chunk0, ree_chunk1}, ree_type);
107+
ASSERT_EQ(ree_ca.null_count(), 0);
108+
ASSERT_EQ(ree_ca.ComputeLogicalNullCount(), 7);
109+
}
110+
79111
TEST_F(TestChunkedArray, MakeEmpty) {
80112
ASSERT_OK_AND_ASSIGN(std::shared_ptr<ChunkedArray> empty,
81113
ChunkedArray::MakeEmpty(int64()));

0 commit comments

Comments
 (0)