Skip to content

Commit 2e82f92

Browse files
committed
GH-50338: [C++] Add ComputeLogicalNullCount to Datum
Datum::null_count() does not account for types that carry logical nulls without a validity bitmap (union, dictionary and run-end encoded types). Add Datum::ComputeLogicalNullCount(), delegating to ArrayData::ComputeLogicalNullCount() and ChunkedArray::ComputeLogicalNullCount() for array-like data; for scalars, is_valid already reflects logical validity.
1 parent 98347d2 commit 2e82f92

3 files changed

Lines changed: 58 additions & 0 deletions

File tree

cpp/src/arrow/datum.cc

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,22 @@ int64_t Datum::null_count() const {
147147
}
148148
}
149149

150+
int64_t Datum::ComputeLogicalNullCount() const {
151+
if (this->kind() == Datum::ARRAY) {
152+
return std::get<std::shared_ptr<ArrayData>>(this->value)->ComputeLogicalNullCount();
153+
} else if (this->kind() == Datum::CHUNKED_ARRAY) {
154+
return std::get<std::shared_ptr<ChunkedArray>>(this->value)
155+
->ComputeLogicalNullCount();
156+
} else if (this->kind() == Datum::SCALAR) {
157+
// Scalar::is_valid already reflects logical validity for all types.
158+
const auto& val = *std::get<std::shared_ptr<Scalar>>(this->value);
159+
return val.is_valid ? 0 : 1;
160+
} else {
161+
DCHECK(false) << "This function only valid for array-like values";
162+
return 0;
163+
}
164+
}
165+
150166
ArrayVector Datum::chunks() const {
151167
if (!this->is_arraylike()) {
152168
return {};

cpp/src/arrow/datum.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,17 @@ struct ARROW_EXPORT Datum {
276276
/// Only valid for scalar and array-like data.
277277
int64_t null_count() const;
278278

279+
/// \brief Compute the logical null count.
280+
///
281+
/// Only valid for scalar and array-like data. Unlike null_count(), this
282+
/// accounts for types that carry logical nulls without a validity bitmap,
283+
/// such as union, dictionary and run-end encoded types; for those types
284+
/// the count is recomputed on every call.
285+
///
286+
/// \see ArrayData::ComputeLogicalNullCount
287+
/// \see ChunkedArray::ComputeLogicalNullCount
288+
int64_t ComputeLogicalNullCount() const;
289+
279290
/// \brief The value type of the variant, if any
280291
///
281292
/// \return nullptr if no type

cpp/src/arrow/datum_test.cc

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,37 @@ TEST(Datum, NullCount) {
139139
ASSERT_EQ(3, val3.null_count());
140140
}
141141

142+
TEST(Datum, ComputeLogicalNullCount) {
143+
// For scalars, is_valid already reflects logical validity.
144+
Datum val1(std::make_shared<Int8Scalar>(1));
145+
ASSERT_EQ(0, val1.ComputeLogicalNullCount());
146+
147+
Datum val2(MakeNullScalar(int8()));
148+
ASSERT_EQ(1, val2.ComputeLogicalNullCount());
149+
150+
// For arrays with a validity bitmap, the logical null count matches
151+
// null_count().
152+
Datum val3(ArrayFromJSON(int8(), "[1, null, null, null]"));
153+
ASSERT_EQ(3, val3.null_count());
154+
ASSERT_EQ(3, val3.ComputeLogicalNullCount());
155+
156+
// Union arrays carry logical nulls in their children without a top-level
157+
// validity bitmap, so null_count() is 0 while the logical null count is not.
158+
auto union_type = sparse_union({field("a", int8()), field("b", boolean())});
159+
auto union_arr =
160+
ArrayFromJSON(union_type, R"([[0, null], [1, true], [0, 5], [1, null]])");
161+
Datum val4(union_arr);
162+
ASSERT_EQ(0, val4.null_count());
163+
ASSERT_EQ(2, val4.ComputeLogicalNullCount());
164+
165+
// Chunked arrays sum the logical null count over the chunks.
166+
auto union_chunk = ArrayFromJSON(union_type, R"([[0, 1], [1, null]])");
167+
ASSERT_OK_AND_ASSIGN(auto chunked, ChunkedArray::Make({union_arr, union_chunk}));
168+
Datum val5(chunked);
169+
ASSERT_EQ(0, val5.null_count());
170+
ASSERT_EQ(3, val5.ComputeLogicalNullCount());
171+
}
172+
142173
TEST(Datum, MutableArray) {
143174
auto arr = ArrayFromJSON(int8(), "[1, 2, 3, 4]");
144175

0 commit comments

Comments
 (0)