blob: f0de9d984862761257badd8d17236a7ee9ee043a [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
use arrow::{
array::{Array, ArrayRef, AsArray, BooleanArray, Int64Array, PrimitiveArray},
buffer::BooleanBuffer,
compute,
datatypes::{
DataType, Date32Type, Date64Type, Decimal128Type, Decimal256Type, Field,
FieldRef, Float16Type, Float32Type, Float64Type, Int8Type, Int16Type, Int32Type,
Int64Type, Time32MillisecondType, Time32SecondType, Time64MicrosecondType,
Time64NanosecondType, TimeUnit, TimestampMicrosecondType,
TimestampMillisecondType, TimestampNanosecondType, TimestampSecondType,
UInt8Type, UInt16Type, UInt32Type, UInt64Type,
},
};
use datafusion_common::hash_utils::RandomState;
use datafusion_common::{
HashMap, Result, ScalarValue, downcast_value, exec_err, internal_err, not_impl_err,
stats::Precision, utils::expr::COUNT_STAR_EXPANSION,
};
use datafusion_expr::{
Accumulator, AggregateUDFImpl, Documentation, EmitTo, Expr, GroupsAccumulator,
ReversedUDAF, SetMonotonicity, Signature, StatisticsArgs, TypeSignature, Volatility,
WindowFunctionDefinition,
expr::WindowFunction,
function::{AccumulatorArgs, StateFieldsArgs},
utils::format_state_name,
};
use datafusion_functions_aggregate_common::aggregate::count_distinct::PrimitiveDistinctCountGroupsAccumulator;
use datafusion_functions_aggregate_common::aggregate::{
count_distinct::Bitmap65536DistinctCountAccumulator,
count_distinct::Bitmap65536DistinctCountAccumulatorI16,
count_distinct::BoolArray256DistinctCountAccumulator,
count_distinct::BoolArray256DistinctCountAccumulatorI8,
count_distinct::BytesDistinctCountAccumulator,
count_distinct::BytesViewDistinctCountAccumulator,
count_distinct::DictionaryCountAccumulator,
count_distinct::FloatDistinctCountAccumulator,
count_distinct::PrimitiveDistinctCountAccumulator,
groups_accumulator::accumulate::accumulate_indices,
};
use datafusion_macros::user_doc;
use datafusion_physical_expr::expressions;
use datafusion_physical_expr_common::binary_map::OutputType;
use std::{
collections::HashSet,
fmt::Debug,
mem::{size_of, size_of_val},
ops::BitAnd,
sync::Arc,
};
make_udaf_expr_and_func!(
Count,
count,
expr,
"Count the number of non-null values in the column",
count_udaf
);
pub fn count_distinct(expr: Expr) -> Expr {
Expr::AggregateFunction(datafusion_expr::expr::AggregateFunction::new_udf(
count_udaf(),
vec![expr],
true,
None,
vec![],
None,
))
}
/// Creates aggregation to count all rows.
///
/// In SQL this is `SELECT COUNT(*) ... `
///
/// The expression is equivalent to `COUNT(*)`, `COUNT()`, `COUNT(1)`, and is
/// aliased to a column named `"count(*)"` for backward compatibility.
///
/// Example
/// ```
/// # use datafusion_functions_aggregate::count::count_all;
/// # use datafusion_expr::col;
/// // create `count(*)` expression
/// let expr = count_all();
/// assert_eq!(expr.schema_name().to_string(), "count(*)");
/// // if you need to refer to this column, use the `schema_name` function
/// let expr = col(expr.schema_name().to_string());
/// ```
pub fn count_all() -> Expr {
count(Expr::Literal(COUNT_STAR_EXPANSION, None)).alias("count(*)")
}
/// Creates window aggregation to count all rows.
///
/// In SQL this is `SELECT COUNT(*) OVER (..) ... `
///
/// The expression is equivalent to `COUNT(*)`, `COUNT()`, `COUNT(1)`
///
/// Example
/// ```
/// # use datafusion_functions_aggregate::count::count_all_window;
/// # use datafusion_expr::col;
/// // create `count(*)` OVER ... window function expression
/// let expr = count_all_window();
/// assert_eq!(
/// expr.schema_name().to_string(),
/// "count(Int64(1)) ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING"
/// );
/// // if you need to refer to this column, use the `schema_name` function
/// let expr = col(expr.schema_name().to_string());
/// ```
pub fn count_all_window() -> Expr {
Expr::from(WindowFunction::new(
WindowFunctionDefinition::AggregateUDF(count_udaf()),
vec![Expr::Literal(COUNT_STAR_EXPANSION, None)],
))
}
#[user_doc(
doc_section(label = "General Functions"),
description = "Returns the number of non-null values in the specified column. To include null values in the total count, use `count(*)`.",
syntax_example = "count(expression)",
sql_example = r#"```sql
> SELECT count(column_name) FROM table_name;
+-----------------------+
| count(column_name) |
+-----------------------+
| 100 |
+-----------------------+
> SELECT count(*) FROM table_name;
+------------------+
| count(*) |
+------------------+
| 120 |
+------------------+
```"#,
standard_argument(name = "expression",)
)]
#[derive(PartialEq, Eq, Hash, Debug)]
pub struct Count {
signature: Signature,
}
impl Default for Count {
fn default() -> Self {
Self::new()
}
}
impl Count {
pub fn new() -> Self {
Self {
signature: Signature::one_of(
vec![TypeSignature::VariadicAny, TypeSignature::Nullary],
Volatility::Immutable,
),
}
}
}
fn get_count_accumulator(data_type: &DataType) -> Box<dyn Accumulator> {
match data_type {
// HashSet-based accumulator for larger integer types
DataType::Int32 => Box::new(PrimitiveDistinctCountAccumulator::<Int32Type>::new(
data_type,
)),
DataType::Int64 => Box::new(PrimitiveDistinctCountAccumulator::<Int64Type>::new(
data_type,
)),
DataType::UInt32 => Box::new(
PrimitiveDistinctCountAccumulator::<UInt32Type>::new(data_type),
),
DataType::UInt64 => Box::new(
PrimitiveDistinctCountAccumulator::<UInt64Type>::new(data_type),
),
// Small int types - cold path
DataType::UInt8 | DataType::Int8 | DataType::UInt16 | DataType::Int16 => {
get_small_int_accumulator(data_type).unwrap()
}
DataType::Decimal128(_, _) => Box::new(PrimitiveDistinctCountAccumulator::<
Decimal128Type,
>::new(data_type)),
DataType::Decimal256(_, _) => Box::new(PrimitiveDistinctCountAccumulator::<
Decimal256Type,
>::new(data_type)),
DataType::Date32 => Box::new(
PrimitiveDistinctCountAccumulator::<Date32Type>::new(data_type),
),
DataType::Date64 => Box::new(
PrimitiveDistinctCountAccumulator::<Date64Type>::new(data_type),
),
DataType::Time32(TimeUnit::Millisecond) => Box::new(
PrimitiveDistinctCountAccumulator::<Time32MillisecondType>::new(data_type),
),
DataType::Time32(TimeUnit::Second) => Box::new(
PrimitiveDistinctCountAccumulator::<Time32SecondType>::new(data_type),
),
DataType::Time64(TimeUnit::Microsecond) => Box::new(
PrimitiveDistinctCountAccumulator::<Time64MicrosecondType>::new(data_type),
),
DataType::Time64(TimeUnit::Nanosecond) => Box::new(
PrimitiveDistinctCountAccumulator::<Time64NanosecondType>::new(data_type),
),
DataType::Timestamp(TimeUnit::Microsecond, _) => Box::new(
PrimitiveDistinctCountAccumulator::<TimestampMicrosecondType>::new(data_type),
),
DataType::Timestamp(TimeUnit::Millisecond, _) => Box::new(
PrimitiveDistinctCountAccumulator::<TimestampMillisecondType>::new(data_type),
),
DataType::Timestamp(TimeUnit::Nanosecond, _) => Box::new(
PrimitiveDistinctCountAccumulator::<TimestampNanosecondType>::new(data_type),
),
DataType::Timestamp(TimeUnit::Second, _) => Box::new(
PrimitiveDistinctCountAccumulator::<TimestampSecondType>::new(data_type),
),
DataType::Float16 => {
Box::new(FloatDistinctCountAccumulator::<Float16Type>::new())
}
DataType::Float32 => {
Box::new(FloatDistinctCountAccumulator::<Float32Type>::new())
}
DataType::Float64 => {
Box::new(FloatDistinctCountAccumulator::<Float64Type>::new())
}
DataType::Utf8 => {
Box::new(BytesDistinctCountAccumulator::<i32>::new(OutputType::Utf8))
}
DataType::Utf8View => {
Box::new(BytesViewDistinctCountAccumulator::new(OutputType::Utf8View))
}
DataType::LargeUtf8 => {
Box::new(BytesDistinctCountAccumulator::<i64>::new(OutputType::Utf8))
}
DataType::Binary => Box::new(BytesDistinctCountAccumulator::<i32>::new(
OutputType::Binary,
)),
DataType::BinaryView => Box::new(BytesViewDistinctCountAccumulator::new(
OutputType::BinaryView,
)),
DataType::LargeBinary => Box::new(BytesDistinctCountAccumulator::<i64>::new(
OutputType::Binary,
)),
// Use the generic accumulator based on `ScalarValue` for all other types
_ => Box::new(DistinctCountAccumulator {
values: HashSet::default(),
state_data_type: data_type.clone(),
}),
}
}
/// Uses optimized bitmap accumulators but separated to keep hot path small
#[cold]
fn get_small_int_accumulator(data_type: &DataType) -> Result<Box<dyn Accumulator>> {
match data_type {
DataType::UInt8 => Ok(Box::new(BoolArray256DistinctCountAccumulator::new())),
DataType::Int8 => Ok(Box::new(BoolArray256DistinctCountAccumulatorI8::new())),
DataType::UInt16 => Ok(Box::new(Bitmap65536DistinctCountAccumulator::new())),
DataType::Int16 => Ok(Box::new(Bitmap65536DistinctCountAccumulatorI16::new())),
_ => exec_err!("unsupported accumulator for datatype: {}", data_type),
}
}
impl AggregateUDFImpl for Count {
fn name(&self) -> &str {
"count"
}
fn signature(&self) -> &Signature {
&self.signature
}
fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
Ok(DataType::Int64)
}
fn is_nullable(&self) -> bool {
false
}
fn state_fields(&self, args: StateFieldsArgs) -> Result<Vec<FieldRef>> {
if args.is_distinct {
let dtype: DataType = match &args.input_fields[0].data_type() {
DataType::Dictionary(_, values_type) => (**values_type).clone(),
&dtype => dtype.clone(),
};
Ok(vec![
Field::new_list(
format_state_name(args.name, "count distinct"),
// See COMMENTS.md to understand why nullable is set to true
Field::new_list_field(dtype, true),
false,
)
.into(),
])
} else {
Ok(vec![
Field::new(
format_state_name(args.name, "count"),
DataType::Int64,
false,
)
.into(),
])
}
}
fn accumulator(&self, acc_args: AccumulatorArgs) -> Result<Box<dyn Accumulator>> {
if !acc_args.is_distinct {
return Ok(Box::new(CountAccumulator::new()));
}
if acc_args.exprs.len() > 1 {
return not_impl_err!("COUNT DISTINCT with multiple arguments");
}
let data_type = acc_args.expr_fields[0].data_type();
Ok(match data_type {
DataType::Dictionary(_, values_type) => {
let inner = get_count_accumulator(values_type);
Box::new(DictionaryCountAccumulator::new(inner))
}
_ => get_count_accumulator(data_type),
})
}
fn groups_accumulator_supported(&self, args: AccumulatorArgs) -> bool {
if args.exprs.len() != 1 {
return false;
}
if !args.is_distinct {
return true;
}
matches!(
args.expr_fields[0].data_type(),
DataType::Int8
| DataType::Int16
| DataType::Int32
| DataType::Int64
| DataType::UInt8
| DataType::UInt16
| DataType::UInt32
| DataType::UInt64
)
}
fn create_groups_accumulator(
&self,
args: AccumulatorArgs,
) -> Result<Box<dyn GroupsAccumulator>> {
if !args.is_distinct {
return Ok(Box::new(CountGroupsAccumulator::new()));
}
create_distinct_count_groups_accumulator(&args)
}
fn reverse_expr(&self) -> ReversedUDAF {
ReversedUDAF::Identical
}
fn default_value(&self, _data_type: &DataType) -> Result<ScalarValue> {
Ok(ScalarValue::Int64(Some(0)))
}
fn value_from_stats(&self, statistics_args: &StatisticsArgs) -> Option<ScalarValue> {
let [expr] = statistics_args.exprs else {
return None;
};
let col_stats = &statistics_args.statistics.column_statistics;
if statistics_args.is_distinct {
// Only column references can be resolved from statistics;
// expressions like casts or literals are not supported.
let col_expr = expr.downcast_ref::<expressions::Column>()?;
if let Precision::Exact(dc) = col_stats[col_expr.index()].distinct_count {
let dc = i64::try_from(dc).ok()?;
return Some(ScalarValue::Int64(Some(dc)));
}
return None;
}
let Precision::Exact(num_rows) = statistics_args.statistics.num_rows else {
return None;
};
// TODO optimize with exprs other than Column
if let Some(col_expr) = expr.downcast_ref::<expressions::Column>() {
if let Precision::Exact(val) = col_stats[col_expr.index()].null_count {
let count = i64::try_from(num_rows - val).ok()?;
return Some(ScalarValue::Int64(Some(count)));
}
} else if let Some(lit_expr) = expr.downcast_ref::<expressions::Literal>()
&& lit_expr.value() == &COUNT_STAR_EXPANSION
{
let num_rows = i64::try_from(num_rows).ok()?;
return Some(ScalarValue::Int64(Some(num_rows)));
}
None
}
fn documentation(&self) -> Option<&Documentation> {
self.doc()
}
fn set_monotonicity(&self, _data_type: &DataType) -> SetMonotonicity {
// `COUNT` is monotonically increasing as it always increases or stays
// the same as new values are seen.
SetMonotonicity::Increasing
}
fn create_sliding_accumulator(
&self,
args: AccumulatorArgs,
) -> Result<Box<dyn Accumulator>> {
if args.is_distinct {
let acc =
SlidingDistinctCountAccumulator::try_new(args.return_field.data_type())?;
Ok(Box::new(acc))
} else {
let acc = CountAccumulator::new();
Ok(Box::new(acc))
}
}
}
#[cold]
fn create_distinct_count_groups_accumulator(
args: &AccumulatorArgs,
) -> Result<Box<dyn GroupsAccumulator>> {
let data_type = args.expr_fields[0].data_type();
match data_type {
DataType::Int8 => Ok(Box::new(
PrimitiveDistinctCountGroupsAccumulator::<Int8Type>::new(),
)),
DataType::Int16 => Ok(Box::new(PrimitiveDistinctCountGroupsAccumulator::<
Int16Type,
>::new())),
DataType::Int32 => Ok(Box::new(PrimitiveDistinctCountGroupsAccumulator::<
Int32Type,
>::new())),
DataType::Int64 => Ok(Box::new(PrimitiveDistinctCountGroupsAccumulator::<
Int64Type,
>::new())),
DataType::UInt8 => Ok(Box::new(PrimitiveDistinctCountGroupsAccumulator::<
UInt8Type,
>::new())),
DataType::UInt16 => Ok(Box::new(PrimitiveDistinctCountGroupsAccumulator::<
UInt16Type,
>::new())),
DataType::UInt32 => Ok(Box::new(PrimitiveDistinctCountGroupsAccumulator::<
UInt32Type,
>::new())),
DataType::UInt64 => Ok(Box::new(PrimitiveDistinctCountGroupsAccumulator::<
UInt64Type,
>::new())),
_ => not_impl_err!(
"GroupsAccumulator not supported for COUNT(DISTINCT) with {}",
data_type
),
}
}
// DistinctCountAccumulator does not support retract_batch and sliding window
// this is a specialized accumulator for distinct count that supports retract_batch
// and sliding window.
#[derive(Debug)]
pub struct SlidingDistinctCountAccumulator {
counts: HashMap<ScalarValue, usize, RandomState>,
data_type: DataType,
}
impl SlidingDistinctCountAccumulator {
pub fn try_new(data_type: &DataType) -> Result<Self> {
Ok(Self {
counts: HashMap::default(),
data_type: data_type.clone(),
})
}
}
impl Accumulator for SlidingDistinctCountAccumulator {
fn state(&mut self) -> Result<Vec<ScalarValue>> {
let keys = self.counts.keys().cloned().collect::<Vec<_>>();
Ok(vec![ScalarValue::List(ScalarValue::new_list_nullable(
keys.as_slice(),
&self.data_type,
))])
}
fn update_batch(&mut self, values: &[ArrayRef]) -> Result<()> {
let arr = &values[0];
for i in 0..arr.len() {
let v = ScalarValue::try_from_array(arr, i)?;
if !v.is_null() {
*self.counts.entry(v).or_default() += 1;
}
}
Ok(())
}
fn retract_batch(&mut self, values: &[ArrayRef]) -> Result<()> {
let arr = &values[0];
for i in 0..arr.len() {
let v = ScalarValue::try_from_array(arr, i)?;
if !v.is_null()
&& let Some(cnt) = self.counts.get_mut(&v)
{
*cnt -= 1;
if *cnt == 0 {
self.counts.remove(&v);
}
}
}
Ok(())
}
fn merge_batch(&mut self, states: &[ArrayRef]) -> Result<()> {
let list_arr = states[0].as_list::<i32>();
for inner in list_arr.iter().flatten() {
for j in 0..inner.len() {
let v = ScalarValue::try_from_array(&*inner, j)?;
*self.counts.entry(v).or_default() += 1;
}
}
Ok(())
}
fn evaluate(&mut self) -> Result<ScalarValue> {
Ok(ScalarValue::Int64(Some(self.counts.len() as i64)))
}
fn supports_retract_batch(&self) -> bool {
true
}
fn size(&self) -> usize {
// Mirrors `DistinctCountAccumulator::full_size`: self + HashMap
// bucket array + per-key inner heap + DataType inner heap.
size_of_val(self)
+ (size_of::<ScalarValue>() + size_of::<usize>()) * self.counts.capacity()
+ self
.counts
.keys()
.map(|k| k.size() - size_of_val(k))
.sum::<usize>()
+ self.data_type.size()
- size_of_val(&self.data_type)
}
}
#[derive(Debug)]
struct CountAccumulator {
count: i64,
}
impl CountAccumulator {
/// new count accumulator
pub fn new() -> Self {
Self { count: 0 }
}
}
impl Accumulator for CountAccumulator {
fn state(&mut self) -> Result<Vec<ScalarValue>> {
Ok(vec![ScalarValue::Int64(Some(self.count))])
}
fn update_batch(&mut self, values: &[ArrayRef]) -> Result<()> {
let array = &values[0];
self.count += (array.len() - null_count_for_multiple_cols(values)) as i64;
Ok(())
}
fn retract_batch(&mut self, values: &[ArrayRef]) -> Result<()> {
let array = &values[0];
self.count -= (array.len() - null_count_for_multiple_cols(values)) as i64;
Ok(())
}
fn merge_batch(&mut self, states: &[ArrayRef]) -> Result<()> {
let counts = downcast_value!(states[0], Int64Array);
let delta = &compute::sum(counts);
if let Some(d) = delta {
self.count += *d;
}
Ok(())
}
fn evaluate(&mut self) -> Result<ScalarValue> {
Ok(ScalarValue::Int64(Some(self.count)))
}
fn supports_retract_batch(&self) -> bool {
true
}
fn size(&self) -> usize {
size_of_val(self)
}
}
/// An accumulator to compute the counts of [`PrimitiveArray<T>`].
/// Stores values as native types, and does overflow checking
///
/// Unlike most other accumulators, COUNT never produces NULLs. If no
/// non-null values are seen in any group the output is 0. Thus, this
/// accumulator has no additional null or seen filter tracking.
#[derive(Debug)]
struct CountGroupsAccumulator {
/// Count per group.
///
/// Note this is an i64 and not a u64 (or usize) because the
/// output type of count is `DataType::Int64`. Thus by using `i64`
/// for the counts, the output [`Int64Array`] can be created
/// without copy.
counts: Vec<i64>,
}
impl CountGroupsAccumulator {
pub fn new() -> Self {
Self { counts: vec![] }
}
}
impl GroupsAccumulator for CountGroupsAccumulator {
fn update_batch(
&mut self,
values: &[ArrayRef],
group_indices: &[usize],
opt_filter: Option<&BooleanArray>,
total_num_groups: usize,
) -> Result<()> {
assert_eq!(values.len(), 1, "single argument to update_batch");
let values = &values[0];
// Add one to each group's counter for each non null, non
// filtered value
self.counts.resize(total_num_groups, 0);
accumulate_indices(
group_indices,
values.logical_nulls().as_ref(),
opt_filter,
|group_index| {
// SAFETY: group_index is guaranteed to be in bounds
let count = unsafe { self.counts.get_unchecked_mut(group_index) };
*count += 1;
},
);
Ok(())
}
fn merge_batch(
&mut self,
values: &[ArrayRef],
group_indices: &[usize],
total_num_groups: usize,
) -> Result<()> {
assert_eq!(values.len(), 1, "one argument to merge_batch");
// first batch is counts, second is partial sums
let partial_counts = values[0].as_primitive::<Int64Type>();
// intermediate counts are always created as non null
assert_eq!(partial_counts.null_count(), 0);
let partial_counts = partial_counts.values();
// Adds the counts with the partial counts
self.counts.resize(total_num_groups, 0);
group_indices.iter().zip(partial_counts.iter()).for_each(
|(&group_index, partial_count)| {
self.counts[group_index] += partial_count;
},
);
Ok(())
}
fn evaluate(&mut self, emit_to: EmitTo) -> Result<ArrayRef> {
let counts = emit_to.take_needed(&mut self.counts);
// Count is always non null (null inputs just don't contribute to the overall values)
let nulls = None;
let array = PrimitiveArray::<Int64Type>::new(counts.into(), nulls);
Ok(Arc::new(array))
}
// return arrays for counts
fn state(&mut self, emit_to: EmitTo) -> Result<Vec<ArrayRef>> {
let counts = emit_to.take_needed(&mut self.counts);
let counts: PrimitiveArray<Int64Type> = Int64Array::from(counts); // zero copy, no nulls
Ok(vec![Arc::new(counts) as ArrayRef])
}
/// Converts an input batch directly to a state batch
///
/// The state of `COUNT` is always a single Int64Array:
/// * `1` (for non-null, non filtered values)
/// * `0` (for null values)
fn convert_to_state(
&self,
values: &[ArrayRef],
opt_filter: Option<&BooleanArray>,
) -> Result<Vec<ArrayRef>> {
let values = &values[0];
let state_array = match (values.logical_nulls(), opt_filter) {
(None, None) => {
// In case there is no nulls in input and no filter, returning array of 1
Arc::new(Int64Array::from_value(1, values.len()))
}
(Some(nulls), None) => {
// If there are any nulls in input values -- casting `nulls` (true for values, false for nulls)
// of input array to Int64
let nulls = BooleanArray::new(nulls.into_inner(), None);
compute::cast(&nulls, &DataType::Int64)?
}
(None, Some(filter)) => {
// If there is only filter
// - applying filter null mask to filter values by bitand filter values and nulls buffers
// (using buffers guarantees absence of nulls in result)
// - casting result of bitand to Int64 array
let (filter_values, filter_nulls) = filter.clone().into_parts();
let state_buf = match filter_nulls {
Some(filter_nulls) => &filter_values & filter_nulls.inner(),
None => filter_values,
};
let boolean_state = BooleanArray::new(state_buf, None);
compute::cast(&boolean_state, &DataType::Int64)?
}
(Some(nulls), Some(filter)) => {
// For both input nulls and filter
// - applying filter null mask to filter values by bitand filter values and nulls buffers
// (using buffers guarantees absence of nulls in result)
// - applying values null mask to filter buffer by another bitand on filter result and
// nulls from input values
// - casting result to Int64 array
let (filter_values, filter_nulls) = filter.clone().into_parts();
let filter_buf = match filter_nulls {
Some(filter_nulls) => &filter_values & filter_nulls.inner(),
None => filter_values,
};
let state_buf = &filter_buf & nulls.inner();
let boolean_state = BooleanArray::new(state_buf, None);
compute::cast(&boolean_state, &DataType::Int64)?
}
};
Ok(vec![state_array])
}
fn size(&self) -> usize {
self.counts.capacity() * size_of::<usize>()
}
}
/// count null values for multiple columns
/// for each row if one column value is null, then null_count + 1
fn null_count_for_multiple_cols(values: &[ArrayRef]) -> usize {
if values.len() > 1 {
let result_bool_buf: Option<BooleanBuffer> = values
.iter()
.map(|a| a.logical_nulls())
.fold(None, |acc, b| match (acc, b) {
(Some(acc), Some(b)) => Some(acc.bitand(b.inner())),
(Some(acc), None) => Some(acc),
(None, Some(b)) => Some(b.into_inner()),
_ => None,
});
result_bool_buf.map_or(0, |b| values[0].len() - b.count_set_bits())
} else {
values[0]
.logical_nulls()
.map_or(0, |nulls| nulls.null_count())
}
}
/// General purpose distinct accumulator that works for any DataType by using
/// [`ScalarValue`].
///
/// It stores intermediate results as a `ListArray`
///
/// Note that many types have specialized accumulators that are (much)
/// more efficient such as [`PrimitiveDistinctCountAccumulator`] and
/// [`BytesDistinctCountAccumulator`]
#[derive(Debug)]
struct DistinctCountAccumulator {
values: HashSet<ScalarValue, RandomState>,
state_data_type: DataType,
}
impl DistinctCountAccumulator {
// calculating the size for fixed length values, taking first batch size *
// number of batches This method is faster than .full_size(), however it is
// not suitable for variable length values like strings or complex types
fn fixed_size(&self) -> usize {
size_of_val(self)
+ (size_of::<ScalarValue>() * self.values.capacity())
+ self
.values
.iter()
.next()
.map(|vals| ScalarValue::size(vals) - size_of_val(vals))
.unwrap_or(0)
+ size_of::<DataType>()
}
// calculates the size as accurately as possible. Note that calling this
// method is expensive
fn full_size(&self) -> usize {
size_of_val(self)
+ (size_of::<ScalarValue>() * self.values.capacity())
+ self
.values
.iter()
.map(|vals| ScalarValue::size(vals) - size_of_val(vals))
.sum::<usize>()
+ size_of::<DataType>()
}
}
impl Accumulator for DistinctCountAccumulator {
/// Returns the distinct values seen so far as (one element) ListArray.
fn state(&mut self) -> Result<Vec<ScalarValue>> {
let scalars = self.values.iter().cloned().collect::<Vec<_>>();
let arr =
ScalarValue::new_list_nullable(scalars.as_slice(), &self.state_data_type);
Ok(vec![ScalarValue::List(arr)])
}
fn update_batch(&mut self, values: &[ArrayRef]) -> Result<()> {
if values.is_empty() {
return Ok(());
}
let arr = &values[0];
if arr.data_type() == &DataType::Null {
return Ok(());
}
(0..arr.len()).try_for_each(|index| {
let scalar = ScalarValue::try_from_array(arr, index)?;
if !scalar.is_null() {
self.values.insert(scalar);
}
Ok(())
})
}
/// Merges multiple sets of distinct values into the current set.
///
/// The input to this function is a `ListArray` with **multiple** rows,
/// where each row contains the values from a partial aggregate's phase (e.g.
/// the result of calling `Self::state` on multiple accumulators).
fn merge_batch(&mut self, states: &[ArrayRef]) -> Result<()> {
if states.is_empty() {
return Ok(());
}
assert_eq!(states.len(), 1, "array_agg states must be singleton!");
let array = &states[0];
let list_array = array.as_list::<i32>();
for inner_array in list_array.iter() {
let Some(inner_array) = inner_array else {
return internal_err!(
"Intermediate results of COUNT DISTINCT should always be non null"
);
};
self.update_batch(&[inner_array])?;
}
Ok(())
}
fn evaluate(&mut self) -> Result<ScalarValue> {
Ok(ScalarValue::Int64(Some(self.values.len() as i64)))
}
fn size(&self) -> usize {
match &self.state_data_type {
DataType::Boolean | DataType::Null => self.fixed_size(),
d if d.is_primitive() => self.fixed_size(),
_ => self.full_size(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use arrow::{
array::{DictionaryArray, Int32Array, NullArray, StringArray},
datatypes::{DataType, Field, Int32Type, Schema},
};
use datafusion_expr::function::AccumulatorArgs;
use datafusion_physical_expr::{PhysicalExpr, expressions::Column};
use std::sync::Arc;
/// Helper function to create a dictionary array with non-null keys but some null values
/// Returns a dictionary array where:
/// - keys are [0, 1, 2, 0, 1] (all non-null)
/// - values are ["a", null, "c"]
/// - so the keys reference: "a", null, "c", "a", null
fn create_dictionary_with_null_values() -> Result<DictionaryArray<Int32Type>> {
let values = StringArray::from(vec![Some("a"), None, Some("c")]);
let keys = Int32Array::from(vec![0, 1, 2, 0, 1]); // references "a", null, "c", "a", null
Ok(DictionaryArray::<Int32Type>::try_new(
keys,
Arc::new(values),
)?)
}
#[test]
fn count_accumulator_nulls() -> Result<()> {
let mut accumulator = CountAccumulator::new();
accumulator.update_batch(&[Arc::new(NullArray::new(10))])?;
assert_eq!(accumulator.evaluate()?, ScalarValue::Int64(Some(0)));
Ok(())
}
#[test]
fn test_nested_dictionary() -> Result<()> {
let schema = Arc::new(Schema::new(vec![Field::new(
"dict_col",
DataType::Dictionary(
Box::new(DataType::Int32),
Box::new(DataType::Dictionary(
Box::new(DataType::Int32),
Box::new(DataType::Utf8),
)),
),
true,
)]));
// Using Count UDAF's accumulator
let count = Count::new();
let expr = Arc::new(Column::new("dict_col", 0));
let expr_field = expr.return_field(&schema)?;
let args = AccumulatorArgs {
schema: &schema,
expr_fields: &[expr_field],
exprs: &[expr],
is_distinct: true,
name: "count",
ignore_nulls: false,
is_reversed: false,
return_field: Arc::new(Field::new_list_field(DataType::Int64, true)),
order_bys: &[],
};
let inner_dict =
DictionaryArray::<Int32Type>::from_iter(["a", "b", "c", "d", "a", "b"]);
let keys = Int32Array::from(vec![0, 1, 2, 0, 3, 1]);
let dict_of_dict =
DictionaryArray::<Int32Type>::try_new(keys, Arc::new(inner_dict))?;
let mut acc = count.accumulator(args)?;
acc.update_batch(&[Arc::new(dict_of_dict)])?;
assert_eq!(acc.evaluate()?, ScalarValue::Int64(Some(4)));
Ok(())
}
#[test]
fn count_distinct_accumulator_dictionary_with_null_values() -> Result<()> {
let dict_array = create_dictionary_with_null_values()?;
// The expected behavior is that count_distinct should count only non-null values
// which in this case are "a" and "c" (appearing as 0 and 2 in keys)
let mut accumulator = DistinctCountAccumulator {
values: HashSet::default(),
state_data_type: dict_array.data_type().clone(),
};
accumulator.update_batch(&[Arc::new(dict_array)])?;
// Should have 2 distinct non-null values ("a" and "c")
assert_eq!(accumulator.evaluate()?, ScalarValue::Int64(Some(2)));
Ok(())
}
#[test]
fn count_accumulator_dictionary_with_null_values() -> Result<()> {
let dict_array = create_dictionary_with_null_values()?;
// The expected behavior is that count should only count non-null values
let mut accumulator = CountAccumulator::new();
accumulator.update_batch(&[Arc::new(dict_array)])?;
// 5 elements in the array, of which 2 reference null values (the two 1s in the keys)
// So we should count 3 non-null values
assert_eq!(accumulator.evaluate()?, ScalarValue::Int64(Some(3)));
Ok(())
}
#[test]
fn count_distinct_accumulator_dictionary_all_null_values() -> Result<()> {
// Create a dictionary array that only contains null values
let dict_values = StringArray::from(vec![None, Some("abc")]);
let dict_indices = Int32Array::from(vec![0; 5]);
let dict_array =
DictionaryArray::<Int32Type>::try_new(dict_indices, Arc::new(dict_values))?;
let mut accumulator = DistinctCountAccumulator {
values: HashSet::default(),
state_data_type: dict_array.data_type().clone(),
};
accumulator.update_batch(&[Arc::new(dict_array)])?;
// All referenced values are null so count(distinct) should be 0
assert_eq!(accumulator.evaluate()?, ScalarValue::Int64(Some(0)));
Ok(())
}
#[test]
fn sliding_distinct_count_accumulator_basic() -> Result<()> {
// Basic update_batch + evaluate functionality
let mut acc = SlidingDistinctCountAccumulator::try_new(&DataType::Int32)?;
// Create an Int32Array: [1, 2, 2, 3, null]
let values: ArrayRef = Arc::new(Int32Array::from(vec![
Some(1),
Some(2),
Some(2),
Some(3),
None,
]));
acc.update_batch(&[values])?;
// Expect distinct values {1,2,3} → count = 3
assert_eq!(acc.evaluate()?, ScalarValue::Int64(Some(3)));
Ok(())
}
#[test]
fn sliding_distinct_count_accumulator_retract() -> Result<()> {
// Test that retract_batch properly decrements counts
let mut acc = SlidingDistinctCountAccumulator::try_new(&DataType::Utf8)?;
// Initial batch: ["a", "b", "a"]
let arr1 = Arc::new(StringArray::from(vec![Some("a"), Some("b"), Some("a")]))
as ArrayRef;
acc.update_batch(&[arr1])?;
assert_eq!(acc.evaluate()?, ScalarValue::Int64(Some(2))); // {"a","b"}
// Retract batch: ["a", null, "b"]
let arr2 =
Arc::new(StringArray::from(vec![Some("a"), None, Some("b")])) as ArrayRef;
acc.retract_batch(&[arr2])?;
// Before: a→2, b→1; after retract a→1, b→0 → b removed; remaining {"a"}
assert_eq!(acc.evaluate()?, ScalarValue::Int64(Some(1)));
Ok(())
}
#[test]
fn sliding_distinct_count_accumulator_merge_states() -> Result<()> {
// Test merging multiple accumulator states with merge_batch
let mut acc1 = SlidingDistinctCountAccumulator::try_new(&DataType::Int32)?;
let mut acc2 = SlidingDistinctCountAccumulator::try_new(&DataType::Int32)?;
// acc1 sees [1, 2]
acc1.update_batch(&[Arc::new(Int32Array::from(vec![Some(1), Some(2)]))])?;
// acc2 sees [2, 3]
acc2.update_batch(&[Arc::new(Int32Array::from(vec![Some(2), Some(3)]))])?;
// Extract their states as Vec<ScalarValue>
let state_sv1 = acc1.state()?;
let state_sv2 = acc2.state()?;
// Convert ScalarValue states into Vec<ArrayRef>, propagating errors
// NOTE we pass `1` because each ScalarValue.to_array produces a 1‑row ListArray
let state_arr1: Vec<ArrayRef> = state_sv1
.into_iter()
.map(|sv| sv.to_array())
.collect::<Result<_>>()?;
let state_arr2: Vec<ArrayRef> = state_sv2
.into_iter()
.map(|sv| sv.to_array())
.collect::<Result<_>>()?;
// Merge both states into a fresh accumulator
let mut merged = SlidingDistinctCountAccumulator::try_new(&DataType::Int32)?;
merged.merge_batch(&state_arr1)?;
merged.merge_batch(&state_arr2)?;
// Expect distinct {1,2,3} → count = 3
assert_eq!(merged.evaluate()?, ScalarValue::Int64(Some(3)));
Ok(())
}
}