Skip to content

Commit f8f0ab8

Browse files
committed
AVG DISTINCT support
Signed-off-by: Adam Gutglick <adamgsal@gmail.com>
1 parent 7a695d2 commit f8f0ab8

4 files changed

Lines changed: 208 additions & 114 deletions

File tree

datafusion/functions-aggregate-common/src/aggregate/avg_distinct/decimal.rs

Lines changed: 151 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -16,48 +16,61 @@
1616
// under the License.
1717

1818
use arrow::{
19-
array::{ArrayRef, ArrowNumericType},
20-
datatypes::{
21-
Decimal32Type, Decimal64Type, Decimal128Type, Decimal256Type, DecimalType, i256,
22-
},
19+
array::{ArrayRef, ArrowNativeTypeOp, ArrowNumericType},
20+
compute::DecimalCast,
21+
datatypes::{ArrowNativeType, DecimalType},
2322
};
24-
use datafusion_common::{Result, ScalarValue};
23+
use datafusion_common::{Result, ScalarValue, exec_datafusion_err, exec_err};
2524
use datafusion_expr_common::accumulator::Accumulator;
2625
use std::fmt::Debug;
26+
use std::marker::PhantomData;
2727
use std::mem::size_of_val;
2828

2929
use crate::aggregate::sum_distinct::DistinctSumAccumulator;
3030
use crate::utils::DecimalAverager;
3131

3232
/// Generic implementation of `AVG DISTINCT` for Decimal types.
3333
/// Handles both all Arrow decimal types (32, 64, 128 and 256 bits).
34+
///
35+
/// The distinct values are stored in the input type `I`; only the intermediate
36+
/// sum is computed in the (never narrower) sum type `S` so it cannot overflow
37+
/// `I`'s native type.
3438
#[derive(Debug)]
35-
pub struct DecimalDistinctAvgAccumulator<T: DecimalType + Debug> {
36-
sum_accumulator: DistinctSumAccumulator<T>,
39+
pub struct DecimalDistinctAvgAccumulator<
40+
I: DecimalType + Debug,
41+
S: DecimalType + Debug = I,
42+
> {
43+
sum_accumulator: DistinctSumAccumulator<I>,
3744
sum_scale: i8,
3845
target_precision: u8,
3946
target_scale: i8,
47+
_sum_type: PhantomData<S>,
4048
}
4149

42-
impl<T: DecimalType + Debug> DecimalDistinctAvgAccumulator<T> {
50+
impl<I: DecimalType + Debug, S: DecimalType + Debug> DecimalDistinctAvgAccumulator<I, S> {
4351
pub fn with_decimal_params(
4452
sum_scale: i8,
4553
target_precision: u8,
4654
target_scale: i8,
4755
) -> Self {
48-
let data_type = T::TYPE_CONSTRUCTOR(T::MAX_PRECISION, sum_scale);
56+
let data_type = I::TYPE_CONSTRUCTOR(I::MAX_PRECISION, sum_scale);
4957

5058
Self {
5159
sum_accumulator: DistinctSumAccumulator::new(&data_type),
5260
sum_scale,
5361
target_precision,
5462
target_scale,
63+
_sum_type: PhantomData,
5564
}
5665
}
5766
}
5867

59-
impl<T: DecimalType + ArrowNumericType + Debug> Accumulator
60-
for DecimalDistinctAvgAccumulator<T>
68+
impl<I, S> Accumulator for DecimalDistinctAvgAccumulator<I, S>
69+
where
70+
I: DecimalType + ArrowNumericType + Debug,
71+
S: DecimalType + ArrowNumericType + Debug,
72+
I::Native: Into<S::Native> + DecimalCast,
73+
S::Native: DecimalCast,
6174
{
6275
fn state(&mut self) -> Result<Vec<ScalarValue>> {
6376
self.sum_accumulator.state()
@@ -72,78 +85,43 @@ impl<T: DecimalType + ArrowNumericType + Debug> Accumulator
7285
}
7386

7487
fn evaluate(&mut self) -> Result<ScalarValue> {
75-
if self.sum_accumulator.distinct_count() == 0 {
76-
return ScalarValue::new_primitive::<T>(
77-
None,
78-
&T::TYPE_CONSTRUCTOR(self.target_precision, self.target_scale),
79-
);
88+
let out_type = I::TYPE_CONSTRUCTOR(self.target_precision, self.target_scale);
89+
let count = self.sum_accumulator.distinct_count();
90+
if count == 0 {
91+
return ScalarValue::new_primitive::<I>(None, &out_type);
8092
}
8193

82-
let sum_scalar = self.sum_accumulator.evaluate()?;
83-
84-
match sum_scalar {
85-
ScalarValue::Decimal32(Some(sum), _, _) => {
86-
let decimal_averager = DecimalAverager::<Decimal32Type>::try_new(
87-
self.sum_scale,
88-
self.target_precision,
89-
self.target_scale,
90-
)?;
91-
let avg = decimal_averager
92-
.avg(sum, self.sum_accumulator.distinct_count() as i32)?;
93-
Ok(ScalarValue::Decimal32(
94-
Some(avg),
95-
self.target_precision,
96-
self.target_scale,
97-
))
98-
}
99-
ScalarValue::Decimal64(Some(sum), _, _) => {
100-
let decimal_averager = DecimalAverager::<Decimal64Type>::try_new(
101-
self.sum_scale,
102-
self.target_precision,
103-
self.target_scale,
104-
)?;
105-
let avg = decimal_averager
106-
.avg(sum, self.sum_accumulator.distinct_count() as i64)?;
107-
Ok(ScalarValue::Decimal64(
108-
Some(avg),
109-
self.target_precision,
110-
self.target_scale,
111-
))
112-
}
113-
ScalarValue::Decimal128(Some(sum), _, _) => {
114-
let decimal_averager = DecimalAverager::<Decimal128Type>::try_new(
115-
self.sum_scale,
116-
self.target_precision,
117-
self.target_scale,
118-
)?;
119-
let avg = decimal_averager
120-
.avg(sum, self.sum_accumulator.distinct_count() as i128)?;
121-
Ok(ScalarValue::Decimal128(
122-
Some(avg),
123-
self.target_precision,
124-
self.target_scale,
125-
))
126-
}
127-
ScalarValue::Decimal256(Some(sum), _, _) => {
128-
let decimal_averager = DecimalAverager::<Decimal256Type>::try_new(
129-
self.sum_scale,
130-
self.target_precision,
131-
self.target_scale,
132-
)?;
133-
// `distinct_count` returns `u64`, but `avg` expects `i256`
134-
// first convert `u64` to `i128`, then convert `i128` to `i256` to avoid overflow
135-
let distinct_cnt: i128 = self.sum_accumulator.distinct_count() as i128;
136-
let count: i256 = i256::from_i128(distinct_cnt);
137-
let avg = decimal_averager.avg(sum, count)?;
138-
Ok(ScalarValue::Decimal256(
139-
Some(avg),
140-
self.target_precision,
141-
self.target_scale,
142-
))
143-
}
144-
145-
_ => unreachable!("Unsupported decimal type: {:?}", sum_scalar),
94+
// Sum the distinct input values in the wider `S` so the total cannot
95+
// overflow the input's native width (mirrors the non-distinct path).
96+
let mut sum = S::Native::usize_as(0);
97+
for value in self.sum_accumulator.distinct_values() {
98+
sum = sum.add_wrapping(value.into());
14699
}
100+
101+
let Some(count) = S::Native::from_usize(count) else {
102+
return exec_err!(
103+
"Arithmetic overflow in avg: the distinct count {count} cannot \
104+
be represented in the sum type"
105+
);
106+
};
107+
108+
let averager = DecimalAverager::<S>::try_new(
109+
self.sum_scale,
110+
self.target_precision,
111+
self.target_scale,
112+
)?;
113+
// Narrowing the average back to the (never wider) output type cannot
114+
// fail in practice: `DecimalAverager::avg` validates the average
115+
// against the output precision, whose bound fits the output's native
116+
// type by construction
117+
let avg =
118+
I::Native::from_decimal(averager.avg(sum, count)?).ok_or_else(|| {
119+
exec_datafusion_err!(
120+
"Arithmetic overflow in avg: the computed average does not fit \
121+
the output type"
122+
)
123+
})?;
124+
ScalarValue::new_primitive::<I>(Some(avg), &out_type)
147125
}
148126

149127
fn size(&self) -> usize {
@@ -160,6 +138,9 @@ mod tests {
160138
use arrow::array::{
161139
Decimal32Array, Decimal64Array, Decimal128Array, Decimal256Array,
162140
};
141+
use arrow::datatypes::{
142+
Decimal32Type, Decimal64Type, Decimal128Type, Decimal256Type, i256,
143+
};
163144
use std::sync::Arc;
164145

165146
#[test]
@@ -279,4 +260,94 @@ mod tests {
279260

280261
Ok(())
281262
}
263+
264+
// The overflow regression tests below use odd-count ranges symmetric
265+
// around a center value, so the exact sum is `count * center` and the
266+
// average is exactly `center`.
267+
268+
#[test]
269+
fn test_decimal32_distinct_avg_widens_to_decimal64() -> Result<()> {
270+
// 42951 distinct values centered on 50000:
271+
// sum = 42951 * 50000 = 2,147,550,000 > i32::MAX
272+
let array = Decimal32Array::from_iter_values(28525..=71475)
273+
.with_precision_and_scale(5, 0)?;
274+
275+
let mut accumulator = DecimalDistinctAvgAccumulator::<
276+
Decimal32Type,
277+
Decimal64Type,
278+
>::with_decimal_params(0, 9, 4);
279+
accumulator.update_batch(&[Arc::new(array)])?;
280+
281+
assert_eq!(
282+
accumulator.evaluate()?,
283+
ScalarValue::Decimal32(Some(500_000_000), 9, 4)
284+
);
285+
286+
Ok(())
287+
}
288+
289+
#[test]
290+
fn test_decimal32_distinct_avg_widens_to_decimal128() -> Result<()> {
291+
// 21477 distinct values centered on 99999:
292+
// sum = 21477 * 99999 = 2,147,678,523 > i32::MAX
293+
let array = Decimal32Array::from_iter_values(89261..=110737)
294+
.with_precision_and_scale(9, 0)?;
295+
296+
let mut accumulator = DecimalDistinctAvgAccumulator::<
297+
Decimal32Type,
298+
Decimal128Type,
299+
>::with_decimal_params(0, 9, 4);
300+
accumulator.update_batch(&[Arc::new(array)])?;
301+
302+
assert_eq!(
303+
accumulator.evaluate()?,
304+
ScalarValue::Decimal32(Some(999_990_000), 9, 4)
305+
);
306+
307+
Ok(())
308+
}
309+
310+
#[test]
311+
fn test_decimal64_distinct_avg_widens_to_decimal128() -> Result<()> {
312+
// 92235 distinct values centered on 10^14 - 1:
313+
// sum = 92235 * (10^14 - 1) ~= 9.22e18 > i64::MAX
314+
let center: i64 = 100_000_000_000_000 - 1;
315+
let array = Decimal64Array::from_iter_values(center - 46117..=center + 46117)
316+
.with_precision_and_scale(18, 0)?;
317+
318+
let mut accumulator = DecimalDistinctAvgAccumulator::<
319+
Decimal64Type,
320+
Decimal128Type,
321+
>::with_decimal_params(0, 18, 4);
322+
accumulator.update_batch(&[Arc::new(array)])?;
323+
324+
assert_eq!(
325+
accumulator.evaluate()?,
326+
ScalarValue::Decimal64(Some(999_999_999_999_990_000), 18, 4)
327+
);
328+
329+
Ok(())
330+
}
331+
332+
#[test]
333+
fn test_decimal128_distinct_avg_widens_to_decimal256() -> Result<()> {
334+
// 21477 distinct values ending at 10^34 - 1, centered on 10^34 - 10739:
335+
// sum = 21477 * (10^34 - 10739) ~= 2.15e38 > i128::MAX
336+
let center: i128 = 10_i128.pow(34) - 10739;
337+
let array = Decimal128Array::from_iter_values(center - 10738..=center + 10738)
338+
.with_precision_and_scale(34, 0)?;
339+
340+
let mut accumulator = DecimalDistinctAvgAccumulator::<
341+
Decimal128Type,
342+
Decimal256Type,
343+
>::with_decimal_params(0, 38, 4);
344+
accumulator.update_batch(&[Arc::new(array)])?;
345+
346+
assert_eq!(
347+
accumulator.evaluate()?,
348+
ScalarValue::Decimal128(Some(center * 10_000), 38, 4)
349+
);
350+
351+
Ok(())
352+
}
282353
}

datafusion/functions-aggregate-common/src/aggregate/sum_distinct/numeric.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,13 @@ impl<T: ArrowPrimitiveType> DistinctSumAccumulator<T> {
5050
pub fn distinct_count(&self) -> usize {
5151
self.values.values.len()
5252
}
53+
54+
/// Iterates the distinct values collected so far. `AVG(DISTINCT)` re-sums
55+
/// them in a wider type instead of using [`Self::evaluate`]'s input-typed
56+
/// sum.
57+
pub(crate) fn distinct_values(&self) -> impl Iterator<Item = T::Native> + '_ {
58+
self.values.values.iter().map(|v| v.0)
59+
}
5360
}
5461

5562
impl<T: ArrowPrimitiveType + Debug> Accumulator for DistinctSumAccumulator<T> {

datafusion/functions-aggregate/src/average.rs

Lines changed: 36 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ impl Default for Avg {
138138
/// of reach. `Decimal256` input near max precision is the exception: no wider
139139
/// type exists, so its sum keeps only whatever headroom `Decimal256(76, _)` has
140140
/// left, as before this budget was introduced.
141-
const AVG_SUM_HEADROOM_DIGITS: u8 = 4 + 9;
141+
const AVG_SUM_HEADROOM_DIGITS: u8 = 13;
142142

143143
/// The narrowest decimal that can accumulate `avg`'s sum over `data_type`, never
144144
/// narrower than `data_type` itself. Other types accumulate as themselves.
@@ -261,39 +261,19 @@ impl AggregateUDFImpl for Avg {
261261
// Numeric types are converted to Float64 via `coerce_avg_type` during logical plan creation
262262
(Float64, _) => Ok(Box::new(Float64DistinctAvgAccumulator::default())),
263263

264-
(
265-
Decimal32(_, scale),
266-
Decimal32(target_precision, target_scale),
267-
) => Ok(Box::new(DecimalDistinctAvgAccumulator::<Decimal32Type>::with_decimal_params(
268-
*scale,
269-
*target_precision,
270-
*target_scale,
271-
))),
272-
(
273-
Decimal64(_, scale),
274-
Decimal64(target_precision, target_scale),
275-
) => Ok(Box::new(DecimalDistinctAvgAccumulator::<Decimal64Type>::with_decimal_params(
276-
*scale,
277-
*target_precision,
278-
*target_scale,
279-
))),
280-
(
281-
Decimal128(_, scale),
282-
Decimal128(target_precision, target_scale),
283-
) => Ok(Box::new(DecimalDistinctAvgAccumulator::<Decimal128Type>::with_decimal_params(
284-
*scale,
285-
*target_precision,
286-
*target_scale,
287-
))),
288-
289-
(
290-
Decimal256(_, scale),
291-
Decimal256(target_precision, target_scale),
292-
) => Ok(Box::new(DecimalDistinctAvgAccumulator::<Decimal256Type>::with_decimal_params(
293-
*scale,
294-
*target_precision,
295-
*target_scale,
296-
))),
264+
(Decimal32(..), Decimal32(..))
265+
| (Decimal64(..), Decimal64(..))
266+
| (Decimal128(..), Decimal128(..))
267+
| (Decimal256(..), Decimal256(..)) => {
268+
let sum_data_type = avg_sum_data_type(data_type);
269+
decimal_avg_dispatch!(
270+
data_type,
271+
&sum_data_type,
272+
decimal_distinct_avg_accumulator,
273+
&sum_data_type,
274+
acc_args.return_type()
275+
)
276+
}
297277

298278
(dt, return_type) => exec_err!(
299279
"AVG(DISTINCT) for ({} --> {}) not supported",
@@ -550,6 +530,28 @@ where
550530
)))
551531
}
552532

533+
fn decimal_distinct_avg_accumulator<I, S>(
534+
sum_data_type: &DataType,
535+
return_data_type: &DataType,
536+
) -> Result<Box<dyn Accumulator>>
537+
where
538+
I: DecimalType + ArrowNumericType + Debug + Send + Sync,
539+
S: DecimalType + ArrowNumericType + Debug + Send + Sync,
540+
I::Native: Into<S::Native> + DecimalCast,
541+
S::Native: DecimalCast,
542+
{
543+
let (_, sum_scale) = decimal_parts(sum_data_type)?;
544+
let (target_precision, target_scale) = decimal_parts(return_data_type)?;
545+
546+
Ok(Box::new(
547+
DecimalDistinctAvgAccumulator::<I, S>::with_decimal_params(
548+
sum_scale,
549+
target_precision,
550+
target_scale,
551+
),
552+
))
553+
}
554+
553555
fn decimal_avg_groups_accumulator<I, S>(
554556
sum_data_type: &DataType,
555557
return_data_type: &DataType,

0 commit comments

Comments
 (0)