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