Skip to main content

densa_data/
group.rs

1//! Grouped aggregates: count, sum, min, max and mean per group of key values, kept up to date
2//! as rows change, the same way views are.
3
4use std::collections::{BTreeMap, BTreeSet};
5use std::sync::Arc;
6
7use arrow_array::cast::AsArray;
8use arrow_array::types::{Float16Type, Float32Type, Float64Type};
9use arrow_array::{
10    new_null_array, Array, ArrayRef, ArrowPrimitiveType, Decimal128Array, Float64Array,
11    RecordBatch, RecordBatchOptions, UInt64Array,
12};
13use arrow_schema::{DataType, Field, Schema};
14use arrow_select::interleave::interleave;
15
16use crate::dictionary::dictionary_index;
17use crate::filter::{resolve, Filter, Predicate};
18use crate::sorted::{Entry, SortedRows};
19use crate::value::{canonical, prefix, unscale, Kind, Value, ValueFn};
20use crate::view::{entry, entry_order, Rows, SortColumn};
21use crate::{DataError, RowId, RowRef};
22
23/// The largest magnitude a `Decimal128` of precision 38 holds, plus one.
24const DECIMAL_LIMIT: u128 = 10u128.pow(38);
25
26/// 2^`exponent`, for the exponent of a normal float.
27const fn power_of_two(exponent: i32) -> f64 {
28    f64::from_bits(((1023 + exponent) as u64) << 52)
29}
30
31/// Floats at least this large are summed apart, scaled by [`UNSCALE`], so no running sum can
32/// overflow: 2^32 values (a table's most) below 2^960 sum below 2^992, and scaled values,
33/// below 2^896, sum below 2^928.
34const HUGE: f64 = power_of_two(960);
35const UNSCALE: f64 = power_of_two(-128);
36const SCALE: f64 = power_of_two(128);
37
38/// What an aggregate computes, and over which column.
39#[derive(Debug, Clone, PartialEq, Eq)]
40enum Function {
41    Count,
42    Sum(String),
43    Min(String),
44    Max(String),
45    Mean(String),
46}
47
48/// One aggregate column of a [`GroupSpec`]: what it computes, over which column, and its name
49/// in the result.
50#[derive(Debug, Clone, PartialEq, Eq)]
51pub struct Aggregate {
52    function: Function,
53    name: Option<String>,
54}
55
56impl Aggregate {
57    /// Rows in the group. Named `count` unless [`named`](Self::named).
58    #[must_use]
59    pub fn count() -> Self {
60        Self::of(Function::Count)
61    }
62
63    /// Sum of an integer, decimal or float column's non-null values; null when there are none.
64    /// Integers and decimals sum exactly (see [`Groups::to_batch`] for the result types).
65    /// Floats sum with compensation for rounding, so the error stays near that of one
66    /// rounding of the largest partial sum the group has held; the sum is NaN when a value is
67    /// NaN or both infinities occur, and infinite when one does. Named `sum(column)`.
68    #[must_use]
69    pub fn sum(column: impl Into<String>) -> Self {
70        Self::of(Function::Sum(column.into()))
71    }
72
73    /// Smallest non-null value of any column views can sort, in their order: `-0.0` equals
74    /// `0.0`, NaN sorts after infinity. Named `min(column)`.
75    #[must_use]
76    pub fn min(column: impl Into<String>) -> Self {
77        Self::of(Function::Min(column.into()))
78    }
79
80    /// Largest non-null value of any column views can sort, in their order: NaN if the
81    /// column holds one. Named `max(column)`.
82    #[must_use]
83    pub fn max(column: impl Into<String>) -> Self {
84        Self::of(Function::Max(column.into()))
85    }
86
87    /// Mean of an integer, decimal or float column's non-null values, as a float (NaN and
88    /// infinities as for [`sum`](Self::sum)). Named `mean(column)`.
89    #[must_use]
90    pub fn mean(column: impl Into<String>) -> Self {
91        Self::of(Function::Mean(column.into()))
92    }
93
94    /// The aggregate, named `name` in the result.
95    #[must_use]
96    pub fn named(mut self, name: impl Into<String>) -> Self {
97        self.name = Some(name.into());
98        self
99    }
100
101    fn of(function: Function) -> Self {
102        Self {
103            function,
104            name: None,
105        }
106    }
107
108    fn name(&self) -> String {
109        if let Some(name) = &self.name {
110            return name.clone();
111        }
112        let (function, column) = match &self.function {
113            Function::Count => return "count".to_owned(),
114            Function::Sum(column) => ("sum", column),
115            Function::Min(column) => ("min", column),
116            Function::Max(column) => ("max", column),
117            Function::Mean(column) => ("mean", column),
118        };
119        format!("{function}({column})")
120    }
121}
122
123/// Groups of rows with equal key values, and aggregates over each group.
124///
125/// ```
126/// use std::sync::Arc;
127/// use arrow_array::{ArrayRef, Decimal128Array, Int64Array, RecordBatch, StringArray};
128/// use densa_data::{Aggregate, GroupSpec, LiveTable};
129///
130/// let trades = |ids: Vec<i64>, desks: Vec<&str>, qty: Vec<i64>| {
131///     RecordBatch::try_from_iter([
132///         ("id", Arc::new(Int64Array::from(ids)) as ArrayRef),
133///         ("desk", Arc::new(StringArray::from(desks)) as ArrayRef),
134///         ("qty", Arc::new(Int64Array::from(qty)) as ArrayRef),
135///     ])
136/// };
137/// let mut table = LiveTable::new(trades(vec![], vec![], vec![])?.schema(), "id")?;
138/// let by_desk = table.add_groups(
139///     GroupSpec::new()
140///         .group_by("desk")
141///         .aggregate(Aggregate::count())
142///         .aggregate(Aggregate::sum("qty")),
143/// )?;
144/// table.upsert(trades(vec![1, 2, 3], vec!["fx", "rates", "fx"], vec![5, 7, 10])?)?;
145/// table.upsert(trades(vec![3], vec!["rates"], vec![10])?)?; // trade 3 moves desk
146///
147/// let totals = table.groups(by_desk).unwrap().to_batch()?; // fx: 1 trade, rates: 2
148/// let qty = totals.column(2).as_any().downcast_ref::<Decimal128Array>().unwrap();
149/// assert_eq!((qty.value(0), qty.value(1)), (5, 17));
150/// # Ok::<(), Box<dyn std::error::Error>>(())
151/// ```
152#[derive(Debug, Clone, Default, PartialEq)]
153pub struct GroupSpec {
154    keys: Vec<String>,
155    aggregates: Vec<Aggregate>,
156    filter: Option<Filter>,
157}
158
159impl GroupSpec {
160    /// No keys and no aggregates yet. Without keys there is exactly one group, of every
161    /// counted row, even when there are none.
162    #[must_use]
163    pub fn new() -> Self {
164        Self::default()
165    }
166
167    /// Groups by `column` too, after the key columns given before it. Groups are listed by
168    /// key in the order views sort: ascending, nulls last. `-0.0` and `0.0` are one key, and
169    /// so are all NaNs.
170    #[must_use]
171    pub fn group_by(mut self, column: impl Into<String>) -> Self {
172        self.keys.push(column.into());
173        self
174    }
175
176    /// Adds an aggregate column. Every key and aggregate needs its own name.
177    #[must_use]
178    pub fn aggregate(mut self, aggregate: Aggregate) -> Self {
179        self.aggregates.push(aggregate);
180        self
181    }
182
183    /// Counts only rows for which `filter` is true.
184    #[must_use]
185    pub fn filter(mut self, filter: Filter) -> Self {
186        self.filter = Some(filter);
187        self
188    }
189}
190
191/// One value of a key, ordered like [`crate::value::compare`] ascending, nulls last (the
192/// variant order).
193#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
194enum Part {
195    Bool(bool),
196    Int(i64),
197    UInt(u64),
198    Decimal(i128),
199    /// A float's IEEE total-order bits (canonical NaN and zero).
200    Float(u64),
201    Str(Box<str>),
202    Null,
203}
204
205impl Part {
206    fn of(value: Value<'_>) -> Self {
207        match value {
208            Value::Null => Self::Null,
209            Value::Bool(v) => Self::Bool(v),
210            Value::Int(v) => Self::Int(v),
211            Value::UInt(v) => Self::UInt(v),
212            Value::Decimal(v) => Self::Decimal(v),
213            Value::Float(_) => Self::Float(prefix(value, false)),
214            Value::Str(v) => Self::Str(v.into()),
215        }
216    }
217}
218
219/// A column the groups read: its index, reader, kind, data type and name.
220#[derive(Debug, Clone)]
221struct Source {
222    column: usize,
223    read: ValueFn,
224    kind: Kind,
225    data_type: DataType,
226    name: String,
227}
228
229impl Source {
230    fn new(schema: &Schema, name: &str) -> Result<Self, DataError> {
231        let (column, kind, read) = resolve(schema, name)?;
232        Ok(Self {
233            column,
234            read,
235            kind,
236            data_type: schema.field(column).data_type().clone(),
237            name: name.to_owned(),
238        })
239    }
240
241    fn read<'a>(&self, batches: &'a [RecordBatch], at: RowRef) -> Value<'a> {
242        (self.read)(batches[at.batch].column(self.column).as_ref(), at.row)
243    }
244
245    /// The type its values come out as: a dictionary's value type (a dictionary's key type
246    /// may not have room for every group's value), otherwise its own.
247    fn output_type(&self) -> &DataType {
248        match &self.data_type {
249            DataType::Dictionary(_, values) => values,
250            other => other,
251        }
252    }
253}
254
255/// The column types sums and means read.
256#[derive(Debug, Clone, Copy, PartialEq, Eq)]
257enum Numeric {
258    Integer,
259    Decimal { scale: i8 },
260    Float,
261}
262
263impl Numeric {
264    fn of(data_type: &DataType) -> Option<Self> {
265        Some(match data_type {
266            DataType::Decimal128(_, scale) => Self::Decimal { scale: *scale },
267            t if t.is_integer() => Self::Integer,
268            t if t.is_floating() => Self::Float,
269            _ => return None,
270        })
271    }
272}
273
274/// What the groups keep about a column, shared by the aggregates that need it: a running sum
275/// (for sum and mean) or its values in order (for min and max).
276#[derive(Debug, Clone)]
277enum Track {
278    Sum(Numeric),
279    Order(SortColumn),
280}
281
282#[derive(Debug, Clone)]
283struct Tracker {
284    source: Source,
285    track: Track,
286}
287
288impl Tracker {
289    /// Adds (`sign` 1) or removes (`sign` -1) the value of row `id`, at `at`, to `state`.
290    fn apply(&self, state: &mut State, rows: Rows<'_>, id: RowId, at: RowRef, sign: i8) {
291        match (&self.track, state, self.source.read(rows.batches, at)) {
292            (_, _, Value::Null) => {}
293            (Track::Order(column), State::Ordered(sorted), _) => {
294                let sort = std::slice::from_ref(column);
295                let probe = entry(sort, rows.batches, id, at);
296                let order = |other: Entry| entry_order(sort, rows, other, probe, at);
297                if sign > 0 {
298                    sorted.insert(probe, order);
299                } else {
300                    let found = sorted.remove(id, order);
301                    debug_assert!(found, "a counted value is among the sorted values");
302                }
303            }
304            (_, State::Exact(sum), Value::Int(v)) => sum.apply(i128::from(v), sign),
305            (_, State::Exact(sum), Value::UInt(v)) => sum.apply(i128::from(v), sign),
306            (_, State::Exact(sum), Value::Decimal(v)) => sum.apply(v, sign),
307            (_, State::Float(sum), Value::Float(v)) => sum.apply(v, sign),
308            _ => debug_assert!(false, "a tracker's state does not fit its column"),
309        }
310    }
311}
312
313/// `count` plus or minus one.
314fn step(count: u64, sign: i8) -> u64 {
315    if sign > 0 {
316        count + 1
317    } else {
318        count - 1
319    }
320}
321
322/// An exact sum of integers or decimals, `sum + carry * 2^128`: adding and removing a value
323/// are exact inverses, even while the sum is beyond `i128`.
324#[derive(Debug, Clone, Copy, Default)]
325struct ExactSum {
326    sum: i128,
327    carry: i64,
328    values: u64,
329}
330
331impl ExactSum {
332    fn apply(&mut self, value: i128, sign: i8) {
333        let (sum, wrapped) = if sign > 0 {
334            self.sum.overflowing_add(value)
335        } else {
336            self.sum.overflowing_sub(value)
337        };
338        if wrapped {
339            // Past `i128::MAX` if the change was positive, past `i128::MIN` if negative.
340            self.carry += if (sign > 0) == (value > 0) { 1 } else { -1 };
341        }
342        self.sum = sum;
343        self.values = step(self.values, sign);
344    }
345
346    /// The sum, if there are values and it fits `i128`.
347    fn total(&self) -> Option<i128> {
348        (self.values > 0 && self.carry == 0).then_some(self.sum)
349    }
350
351    fn mean(&self) -> Option<f64> {
352        // Lossy by design: a mean is a float. 2^128 is exact in f64.
353        let total = self.sum as f64 + self.carry as f64 * SCALE;
354        (self.values > 0).then(|| total / self.values as f64)
355    }
356}
357
358/// A compensated (Neumaier) float sum and how many values are in it.
359#[derive(Debug, Clone, Copy, Default)]
360struct Compensated {
361    sum: f64,
362    compensation: f64,
363    values: u64,
364}
365
366impl Compensated {
367    fn apply(&mut self, value: f64, sign: i8) {
368        self.values = step(self.values, sign);
369        if self.values == 0 {
370            // Nothing left: drop the rounding the removed values leave behind.
371            *self = Self::default();
372            return;
373        }
374        let value = if sign > 0 { value } else { -value };
375        let total = self.sum + value;
376        if self.sum.abs() >= value.abs() {
377            self.compensation += (self.sum - total) + value;
378        } else {
379            self.compensation += (value - total) + self.sum;
380        }
381        self.sum = total;
382    }
383
384    fn total(&self) -> f64 {
385        self.sum + self.compensation
386    }
387}
388
389/// A float sum that NaN and infinities cannot poison: they are counted, not added, so the sum
390/// recovers when their rows change. Huge values are summed apart (see [`HUGE`]).
391#[derive(Debug, Clone, Copy, Default)]
392struct FloatSum {
393    normal: Compensated,
394    /// Values of at least [`HUGE`], times [`UNSCALE`].
395    huge: Compensated,
396    nan: u64,
397    infinity: u64,
398    negative_infinity: u64,
399}
400
401impl FloatSum {
402    fn apply(&mut self, value: f64, sign: i8) {
403        if value.is_nan() {
404            self.nan = step(self.nan, sign);
405        } else if value == f64::INFINITY {
406            self.infinity = step(self.infinity, sign);
407        } else if value == f64::NEG_INFINITY {
408            self.negative_infinity = step(self.negative_infinity, sign);
409        } else if value.abs() >= HUGE {
410            self.huge.apply(value * UNSCALE, sign);
411        } else {
412            self.normal.apply(value, sign);
413        }
414    }
415
416    fn values(&self) -> u64 {
417        self.normal.values + self.huge.values + self.nan + self.infinity + self.negative_infinity
418    }
419
420    fn sum(&self) -> Option<f64> {
421        (self.values() > 0).then(|| self.divided(1.0))
422    }
423
424    fn mean(&self) -> Option<f64> {
425        let values = self.values();
426        (values > 0).then(|| self.divided(values as f64))
427    }
428
429    /// The sum divided by `divisor`, overflowing only if the result itself is beyond `f64`.
430    fn divided(&self, divisor: f64) -> f64 {
431        if self.nan > 0 || (self.infinity > 0 && self.negative_infinity > 0) {
432            return f64::NAN;
433        }
434        if self.infinity > 0 {
435            return f64::INFINITY;
436        }
437        if self.negative_infinity > 0 {
438            return f64::NEG_INFINITY;
439        }
440        let normal = self.normal.total() / divisor;
441        let huge = self.huge.total() / divisor;
442        if huge == 0.0 {
443            normal
444        } else {
445            (huge + normal * UNSCALE) * SCALE
446        }
447    }
448}
449
450/// Running state of one tracker in one group.
451#[derive(Debug, Clone)]
452enum State {
453    Exact(ExactSum),
454    Float(FloatSum),
455    /// Non-null values in order: min first, max last.
456    Ordered(SortedRows),
457}
458
459impl State {
460    fn new(track: &Track) -> Self {
461        match track {
462            Track::Sum(Numeric::Float) => Self::Float(FloatSum::default()),
463            Track::Sum(_) => Self::Exact(ExactSum::default()),
464            Track::Order(_) => Self::Ordered(SortedRows::default()),
465        }
466    }
467
468    fn exact(&self) -> Option<&ExactSum> {
469        match self {
470            Self::Exact(sum) => Some(sum),
471            _ => None,
472        }
473    }
474
475    fn float(&self) -> Option<&FloatSum> {
476        match self {
477            Self::Float(sum) => Some(sum),
478            _ => None,
479        }
480    }
481
482    /// The row of the smallest value, or of the largest if `max`.
483    fn extreme(&self, max: bool) -> Option<RowId> {
484        match self {
485            Self::Ordered(sorted) if max => sorted.last(),
486            Self::Ordered(sorted) => sorted.first(),
487            _ => None,
488        }
489    }
490}
491
492/// What an aggregate column shows; the numbers index `GroupState::trackers`.
493#[derive(Debug, Clone, Copy)]
494enum Output {
495    Count,
496    Sum(usize, Numeric),
497    Mean(usize, Numeric),
498    Min(usize),
499    Max(usize),
500}
501
502/// A compiled aggregate.
503#[derive(Debug, Clone)]
504struct Compiled {
505    output: Output,
506    name: String,
507}
508
509/// One group: its rows (any of them gives the key values) and a state per tracker.
510#[derive(Debug, Clone)]
511struct Group {
512    rows: BTreeSet<RowId>,
513    states: Vec<State>,
514}
515
516impl Group {
517    fn new(trackers: &[Tracker]) -> Self {
518        Self {
519            rows: BTreeSet::new(),
520            states: trackers.iter().map(|t| State::new(&t.track)).collect(),
521        }
522    }
523}
524
525/// A group spec's compiled form and its groups.
526#[derive(Debug, Clone)]
527pub(crate) struct GroupState {
528    keys: Vec<Source>,
529    trackers: Vec<Tracker>,
530    aggregates: Vec<Compiled>,
531    filter: Option<Predicate>,
532    groups: BTreeMap<Vec<Part>, Group>,
533    /// Whether each row id is counted.
534    member: Vec<bool>,
535    revision: u64,
536}
537
538impl GroupState {
539    pub(crate) fn compile(spec: &GroupSpec, schema: &Schema) -> Result<Self, DataError> {
540        let keys = spec
541            .keys
542            .iter()
543            .map(|k| Source::new(schema, k))
544            .collect::<Result<Vec<_>, _>>()?;
545        let mut trackers = Vec::new();
546        let aggregates = spec
547            .aggregates
548            .iter()
549            .map(|aggregate| {
550                let output = match &aggregate.function {
551                    Function::Count => Output::Count,
552                    Function::Sum(column) => {
553                        let (index, numeric) = sum_tracker(&mut trackers, schema, column)?;
554                        Output::Sum(index, numeric)
555                    }
556                    Function::Mean(column) => {
557                        let (index, numeric) = sum_tracker(&mut trackers, schema, column)?;
558                        Output::Mean(index, numeric)
559                    }
560                    Function::Min(column) => {
561                        Output::Min(order_tracker(&mut trackers, schema, column)?)
562                    }
563                    Function::Max(column) => {
564                        Output::Max(order_tracker(&mut trackers, schema, column)?)
565                    }
566                };
567                Ok(Compiled {
568                    output,
569                    name: aggregate.name(),
570                })
571            })
572            .collect::<Result<Vec<_>, DataError>>()?;
573        let mut names = BTreeSet::new();
574        let all_names = keys
575            .iter()
576            .map(|k| &k.name)
577            .chain(aggregates.iter().map(|a| &a.name));
578        for name in all_names {
579            if !names.insert(name) {
580                return Err(DataError::DuplicateName(name.clone()));
581            }
582        }
583        let filter = spec
584            .filter
585            .as_ref()
586            .map(|f| Predicate::compile(f, schema))
587            .transpose()?;
588        let mut state = Self {
589            keys,
590            trackers,
591            aggregates,
592            filter,
593            groups: BTreeMap::new(),
594            member: Vec::new(),
595            revision: 0,
596        };
597        state.clear();
598        Ok(state)
599    }
600
601    /// No counted rows: no groups, or the one group of a spec without keys, empty.
602    fn clear(&mut self) {
603        self.groups.clear();
604        self.member.clear();
605        if self.keys.is_empty() {
606            self.groups.insert(Vec::new(), Group::new(&self.trackers));
607        }
608    }
609
610    fn passes(&self, batches: &[RecordBatch], at: RowRef) -> bool {
611        self.filter
612            .as_ref()
613            .is_none_or(|f| f.eval(&batches[at.batch], at.row) == Some(true))
614    }
615
616    fn key(&self, batches: &[RecordBatch], at: RowRef) -> Vec<Part> {
617        self.keys
618            .iter()
619            .map(|k| Part::of(k.read(batches, at)))
620            .collect()
621    }
622
623    /// Counts row `id` (values at `at`) if it passes the filter, and says whether it did.
624    /// `sorted` false leaves min and max values out, for [`sort_values`](Self::sort_values).
625    fn count(&mut self, rows: Rows<'_>, id: RowId, at: RowRef, sorted: bool) -> bool {
626        if !self.passes(rows.batches, at) {
627            return false;
628        }
629        let key = self.key(rows.batches, at);
630        let trackers = &self.trackers;
631        let group = self
632            .groups
633            .entry(key)
634            .or_insert_with(|| Group::new(trackers));
635        group.rows.insert(id);
636        for (tracker, state) in trackers.iter().zip(&mut group.states) {
637            if sorted || !matches!(tracker.track, Track::Order(_)) {
638                tracker.apply(state, rows, id, at, 1);
639            }
640        }
641        if self.member.len() <= id as usize {
642            self.member.resize(id as usize + 1, false);
643        }
644        self.member[id as usize] = true;
645        true
646    }
647
648    /// Counts row `id` (values at `at`) if it passes the filter.
649    pub(crate) fn insert(&mut self, rows: Rows<'_>, id: RowId, at: RowRef) {
650        if self.count(rows, id, at, true) {
651            self.revision += 1;
652        }
653    }
654
655    /// Uncounts row `id`, whose values are at `at` (where they were when it was counted).
656    pub(crate) fn remove(&mut self, rows: Rows<'_>, id: RowId, at: RowRef) {
657        if !self.member.get(id as usize).copied().unwrap_or(false) {
658            return;
659        }
660        let key = self.key(rows.batches, at);
661        let Some(group) = self.groups.get_mut(&key) else {
662            debug_assert!(false, "a counted row's group exists");
663            return;
664        };
665        group.rows.remove(&id);
666        for (tracker, state) in self.trackers.iter().zip(&mut group.states) {
667            tracker.apply(state, rows, id, at, -1);
668        }
669        // Without keys, the one group stays, empty.
670        if group.rows.is_empty() && !self.keys.is_empty() {
671            self.groups.remove(&key);
672        }
673        self.member[id as usize] = false;
674        self.revision += 1;
675    }
676
677    /// Recounts every live row.
678    pub(crate) fn rebuild(&mut self, rows: Rows<'_>) {
679        let had_rows = self.groups.values().any(|g| !g.rows.is_empty());
680        self.clear();
681        let mut counted = false;
682        for (id, slot) in rows.slots.iter().enumerate() {
683            if let (Ok(id), Some(slot)) = (RowId::try_from(id), slot) {
684                counted |= self.count(rows, id, slot.row_ref(), false);
685            }
686        }
687        self.sort_values(rows);
688        if had_rows || counted {
689            self.revision += 1;
690        }
691    }
692
693    /// Fills each group's min and max values from its rows, sorting them at once rather than
694    /// inserting them one by one.
695    fn sort_values(&mut self, rows: Rows<'_>) {
696        for (index, tracker) in self.trackers.iter().enumerate() {
697            let Track::Order(column) = &tracker.track else {
698                continue;
699            };
700            let sort = std::slice::from_ref(column);
701            for group in self.groups.values_mut() {
702                let mut entries: Vec<Entry> = group
703                    .rows
704                    .iter()
705                    .map(|&id| (id, rows.at(id)))
706                    .filter(|&(_, at)| tracker.source.read(rows.batches, at) != Value::Null)
707                    .map(|(id, at)| entry(sort, rows.batches, id, at))
708                    .collect();
709                entries.sort_unstable_by(|&a, &b| entry_order(sort, rows, a, b, rows.at(b.id)));
710                if let State::Ordered(sorted) = &mut group.states[index] {
711                    sorted.rebuild(&entries);
712                }
713            }
714        }
715    }
716}
717
718/// The tracker index for `column`'s sum, and the column's numeric type.
719fn sum_tracker(
720    trackers: &mut Vec<Tracker>,
721    schema: &Schema,
722    column: &str,
723) -> Result<(usize, Numeric), DataError> {
724    let source = Source::new(schema, column)?;
725    let numeric =
726        Numeric::of(&source.data_type).ok_or_else(|| DataError::NotNumeric(column.to_owned()))?;
727    Ok((tracker(trackers, source, Track::Sum(numeric)), numeric))
728}
729
730/// The tracker index for `column`'s values in order.
731fn order_tracker(
732    trackers: &mut Vec<Tracker>,
733    schema: &Schema,
734    column: &str,
735) -> Result<usize, DataError> {
736    let source = Source::new(schema, column)?;
737    let sort = SortColumn::ascending(source.column, source.read);
738    Ok(tracker(trackers, source, Track::Order(sort)))
739}
740
741/// The index of the tracker of `source` that keeps `track`, added if no aggregate needed it
742/// yet.
743fn tracker(trackers: &mut Vec<Tracker>, source: Source, track: Track) -> usize {
744    let same = |t: &Tracker| {
745        t.source.column == source.column
746            && std::mem::discriminant(&t.track) == std::mem::discriminant(&track)
747    };
748    if let Some(index) = trackers.iter().position(same) {
749        return index;
750    }
751    trackers.push(Tracker { source, track });
752    trackers.len() - 1
753}
754
755/// The groups of a [`GroupSpec`], ordered by key. Cheap to get.
756#[derive(Debug, Clone, Copy)]
757pub struct Groups<'a> {
758    pub(crate) state: &'a GroupState,
759    pub(crate) rows: Rows<'a>,
760}
761
762impl Groups<'_> {
763    /// Number of groups: the rows of [`to_batch`](Self::to_batch). A spec without keys always
764    /// has one.
765    pub fn len(&self) -> usize {
766        self.state.groups.len()
767    }
768
769    /// Whether there are no groups: the spec has keys and no row is counted.
770    pub fn is_empty(&self) -> bool {
771        self.len() == 0
772    }
773
774    /// Increases whenever a group changes (and sometimes when a bulk change recounts the
775    /// same groups): rebuild a display of [`to_batch`](Self::to_batch) only when it moved.
776    pub fn revision(&self) -> u64 {
777        self.state.revision
778    }
779
780    /// The groups as one record batch, one row per group in key order: the key columns,
781    /// then each aggregate. Keys, min and max have the column's type, except that dictionary
782    /// columns come out decoded (as their value type) and floats canonical (`0.0` for `-0.0`,
783    /// one NaN). Counts are `UInt64`, never null. Sums of integers are `Decimal128(38, 0)`,
784    /// which holds any sum a table can produce; sums of decimals `Decimal128(38, scale)`,
785    /// null if the exact sum needs more than 38 digits; sums of floats `Float64`. Means are
786    /// `Float64`. Any other aggregate over no values is null. Allocates.
787    ///
788    /// # Errors
789    /// [`DataError::Arrow`] if Arrow cannot assemble a column (over 2 GiB of strings).
790    pub fn to_batch(&self) -> Result<RecordBatch, DataError> {
791        let groups: Vec<&Group> = self.state.groups.values().collect();
792        let mut fields = Vec::new();
793        let mut columns = Vec::new();
794        for key in &self.state.keys {
795            let rows = groups.iter().map(|g| g.rows.first().copied());
796            columns.push(self.gather(key, rows)?);
797            fields.push(Field::new(&key.name, key.output_type().clone(), true));
798        }
799        for aggregate in &self.state.aggregates {
800            let (column, data_type) = self.column(aggregate.output, &groups)?;
801            let nullable = !matches!(aggregate.output, Output::Count);
802            fields.push(Field::new(&aggregate.name, data_type, nullable));
803            columns.push(column);
804        }
805        let schema = Arc::new(Schema::new(fields));
806        let options = RecordBatchOptions::new().with_row_count(Some(groups.len()));
807        RecordBatch::try_new_with_options(schema, columns, &options).map_err(DataError::arrow)
808    }
809
810    fn column(&self, output: Output, groups: &[&Group]) -> Result<(ArrayRef, DataType), DataError> {
811        Ok(match output {
812            Output::Count => {
813                // Lossless: a group has at most 2^32 rows.
814                let counts: UInt64Array =
815                    groups.iter().map(|g| Some(g.rows.len() as u64)).collect();
816                (Arc::new(counts), DataType::UInt64)
817            }
818            Output::Sum(index, numeric) => sums(numeric, groups.iter().map(|g| &g.states[index]))?,
819            Output::Mean(index, numeric) => {
820                let means: Float64Array = groups
821                    .iter()
822                    .map(|g| mean(&g.states[index], numeric))
823                    .collect();
824                (Arc::new(means), DataType::Float64)
825            }
826            Output::Min(index) | Output::Max(index) => {
827                let max = matches!(output, Output::Max(_));
828                let rows = groups.iter().map(|g| g.states[index].extreme(max));
829                let source = &self.state.trackers[index].source;
830                (self.gather(source, rows)?, source.output_type().clone())
831            }
832        })
833    }
834
835    /// `source`'s values at rows `ids` (`None`: null), dictionaries decoded and floats
836    /// canonical.
837    fn gather(
838        &self,
839        source: &Source,
840        ids: impl Iterator<Item = Option<RowId>>,
841    ) -> Result<ArrayRef, DataError> {
842        let batches = self.rows.batches;
843        let column = |batch: usize| batches[batch].column(source.column).as_ref();
844        let decode = matches!(source.data_type, DataType::Dictionary(..));
845        let null = new_null_array(source.output_type(), 1);
846        let mut arrays: Vec<&dyn Array> = (0..batches.len())
847            .map(|batch| {
848                let array = column(batch);
849                match array.as_any_dictionary_opt() {
850                    Some(dictionary) if decode => dictionary.values().as_ref(),
851                    _ => array,
852                }
853            })
854            .collect();
855        arrays.push(null.as_ref());
856        let null_at = (arrays.len() - 1, 0);
857        let indices: Vec<(usize, usize)> = ids
858            .map(|id| {
859                let place = id.and_then(|id| {
860                    let at = self.rows.at(id);
861                    let row = if decode {
862                        dictionary_index(column(at.batch), at.row)?
863                    } else {
864                        at.row
865                    };
866                    Some((at.batch, row))
867                });
868                place.unwrap_or(null_at)
869            })
870            .collect();
871        let array = interleave(&arrays, &indices).map_err(DataError::arrow)?;
872        Ok(if source.kind == Kind::Float {
873            canonical_floats(&array)
874        } else {
875            array
876        })
877    }
878}
879
880/// `array` with canonical floats: `0.0` for `-0.0`, one NaN.
881fn canonical_floats(array: &ArrayRef) -> ArrayRef {
882    type F16 = <Float16Type as ArrowPrimitiveType>::Native;
883    if let Some(a) = array.as_primitive_opt::<Float64Type>() {
884        Arc::new(a.unary::<_, Float64Type>(canonical))
885    } else if let Some(a) = array.as_primitive_opt::<Float32Type>() {
886        // Lossless: an f32 converts to f64 and back exactly.
887        Arc::new(a.unary::<_, Float32Type>(|v| canonical(f64::from(v)) as f32))
888    } else if let Some(a) = array.as_primitive_opt::<Float16Type>() {
889        Arc::new(a.unary::<_, Float16Type>(|v| F16::from_f64(canonical(v.into()))))
890    } else {
891        Arc::clone(array)
892    }
893}
894
895fn mean(state: &State, numeric: Numeric) -> Option<f64> {
896    match (state, numeric) {
897        (State::Float(sum), _) => sum.mean(),
898        (State::Exact(sum), Numeric::Decimal { scale }) => sum.mean().map(|m| unscale(m, scale)),
899        (State::Exact(sum), _) => sum.mean(),
900        (State::Ordered(_), _) => None,
901    }
902}
903
904/// The sums of `numeric` values, one per state, and their type.
905fn sums<'g>(
906    numeric: Numeric,
907    states: impl Iterator<Item = &'g State>,
908) -> Result<(ArrayRef, DataType), DataError> {
909    let scale = match numeric {
910        Numeric::Float => {
911            let sums: Float64Array = states.map(|s| s.float().and_then(FloatSum::sum)).collect();
912            return Ok((Arc::new(sums), DataType::Float64));
913        }
914        // Integer sums always fit: 2^32 values of at most 2^64 stay below 2^96 < 10^38.
915        Numeric::Integer => 0,
916        Numeric::Decimal { scale } => scale,
917    };
918    let sums: Decimal128Array = states
919        .map(|s| {
920            let sum = s.exact().and_then(ExactSum::total);
921            sum.filter(|v| v.unsigned_abs() < DECIMAL_LIMIT)
922        })
923        .collect();
924    let sums = sums
925        .with_precision_and_scale(38, scale)
926        .map_err(DataError::arrow)?;
927    Ok((Arc::new(sums), DataType::Decimal128(38, scale)))
928}
929
930#[cfg(test)]
931mod tests {
932    use super::*;
933
934    #[test]
935    fn a_compensated_sum_keeps_what_a_plain_sum_loses() {
936        // A plain sum gives 0 either way: 1e16 + 1 rounds to 1e16.
937        for (first, second) in [(1e16, 1.0), (1.0, 1e16)] {
938            let mut sum = FloatSum::default();
939            sum.apply(first, 1);
940            sum.apply(second, 1);
941            sum.apply(1e16, -1);
942            assert_eq!(sum.sum(), Some(1.0), "{first} then {second}");
943        }
944    }
945
946    #[test]
947    fn a_float_sum_forgets_its_rounding_once_its_values_are_gone() {
948        let huge = [1.7e308, 1.3e308, 0.9e308, 1.1e308];
949        let mut sum = FloatSum::default();
950        sum.apply(1.0, 1);
951        for v in huge {
952            sum.apply(v, 1);
953        }
954        for v in huge.iter().rev() {
955            sum.apply(*v, -1);
956        }
957        assert_eq!(sum.sum(), Some(1.0));
958        let mut sum = FloatSum::default();
959        for v in [0.1, 0.2, 0.3] {
960            sum.apply(v, 1);
961        }
962        for v in [0.2, 0.1, 0.3] {
963            sum.apply(v, -1);
964        }
965        sum.apply(1e-30, 1);
966        assert_eq!(sum.sum(), Some(1e-30));
967    }
968
969    #[test]
970    fn nan_and_infinities_leave_a_float_sum_with_their_values() {
971        let mut sum = FloatSum::default();
972        for _ in 0..100 {
973            sum.apply(1.0, 1);
974        }
975        sum.apply(f64::NAN, 1);
976        assert!(sum.sum().is_some_and(f64::is_nan));
977        sum.apply(f64::NAN, -1);
978        assert_eq!(sum.sum(), Some(100.0));
979        sum.apply(f64::INFINITY, 1);
980        assert_eq!(sum.sum(), Some(f64::INFINITY));
981        sum.apply(f64::NEG_INFINITY, 1);
982        assert!(sum.sum().is_some_and(f64::is_nan), "inf + -inf");
983        sum.apply(f64::INFINITY, -1);
984        assert_eq!(sum.sum(), Some(f64::NEG_INFINITY));
985        sum.apply(f64::NEG_INFINITY, -1);
986        assert_eq!(sum.sum(), Some(100.0));
987        assert_eq!(sum.mean(), Some(1.0));
988    }
989
990    #[test]
991    fn huge_floats_sum_past_f64_and_back() {
992        let mut sum = FloatSum::default();
993        sum.apply(1.0, 1);
994        sum.apply(f64::MAX, 1);
995        sum.apply(f64::MAX, 1);
996        assert_eq!(sum.sum(), Some(f64::INFINITY));
997        let mean = sum.mean().unwrap();
998        let expected = f64::MAX / 3.0 * 2.0;
999        assert!(
1000            (mean - expected).abs() <= expected * 1e-15,
1001            "{mean} vs {expected}"
1002        );
1003        sum.apply(f64::MAX, -1);
1004        assert_eq!(sum.sum(), Some(f64::MAX), "f64::MAX + 1");
1005        sum.apply(f64::MAX, -1);
1006        assert_eq!(sum.sum(), Some(1.0));
1007    }
1008
1009    #[test]
1010    fn a_sum_just_below_overflow_does_not_round_to_infinity() {
1011        let mut sum = FloatSum::default();
1012        sum.apply(power_of_two(1023), 1);
1013        sum.apply(power_of_two(1023), 1);
1014        for _ in 0..4_096 {
1015            sum.apply(-power_of_two(959), 1);
1016        }
1017        // 2^1024 - 4096 * 2^959 = 2^1024 - 2^971: exactly f64::MAX.
1018        assert_eq!(sum.sum(), Some(f64::MAX));
1019    }
1020
1021    #[test]
1022    fn huge_values_keep_their_rounding_too() {
1023        // Their scaled running sum rounds: only the compensation gets the last bit right.
1024        let (a, b, c) = (f64::MAX, 1e308, -1.5e308);
1025        let mut sum = FloatSum::default();
1026        for v in [a, b, c] {
1027            sum.apply(v, 1);
1028        }
1029        // a + c is exact (Sterbenz), so adding b to it rounds once: the correctly rounded sum.
1030        assert_eq!(sum.sum(), Some((a + c) + b));
1031    }
1032
1033    #[test]
1034    fn an_exact_sum_past_i128_comes_back_when_the_values_leave() {
1035        for big in [i128::MAX, i128::MIN] {
1036            let mut sum = ExactSum::default();
1037            sum.apply(7, 1);
1038            sum.apply(big, 1);
1039            sum.apply(big, 1);
1040            assert_eq!(sum.total(), None, "7 + 2 * {big}");
1041            let mean = sum.mean().unwrap();
1042            assert!((mean - big as f64 * 2.0 / 3.0).abs() <= 1e25, "{mean}");
1043            sum.apply(big, -1);
1044            assert_eq!(sum.total(), big.checked_add(7));
1045            sum.apply(big, -1);
1046            assert_eq!(sum.total(), Some(7));
1047        }
1048    }
1049}