1use rudb_common::{Error, LogicalType, Result, Value};
42use rudb_vector::{Data, Form, Validity, Vector};
43
44use crate::compare::order;
45use crate::fallback::{self, Kernel};
46use crate::number::{fit, integral, pow10, rescale};
47use crate::shape::{identity, nulls_of};
48
49#[derive(Debug, Clone)]
51pub struct Accumulator {
52 kind: Kind,
53 returns: LogicalType,
54 state: State,
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59enum Kind {
60 CountStar,
61 Count,
62 Sum,
63 Avg,
64 Min,
65 Max,
66}
67
68#[derive(Debug, Clone)]
70enum State {
71 Counted(i64),
73 Whole { total: i128, seen: bool },
75 Real { total: f64, seen: i64 },
77 Mean { whole: i128, real: f64, seen: i64, exact: bool },
89 Scaled { total: i128, scale: u8, seen: bool },
91 Extreme(Option<Value>),
93}
94
95impl Accumulator {
96 pub fn new(name: &str, returns: &LogicalType) -> Result<Self> {
102 let kind = match name {
103 "count_star" => Kind::CountStar,
104 "count" => Kind::Count,
105 "sum" => Kind::Sum,
106 "avg" => Kind::Avg,
107 "min" => Kind::Min,
108 "max" => Kind::Max,
109 other => {
110 return Err(Error::not_implemented(format!("the {other} aggregate")));
111 }
112 };
113 let state = match kind {
114 Kind::CountStar | Kind::Count => State::Counted(0),
115 Kind::Avg => State::Mean { whole: 0, real: 0.0, seen: 0, exact: true },
116 Kind::Min | Kind::Max => State::Extreme(None),
117 Kind::Sum => match returns {
118 LogicalType::Decimal { scale, .. } => {
119 State::Scaled { total: 0, scale: *scale, seen: false }
120 }
121 LogicalType::Float | LogicalType::Double => State::Real { total: 0.0, seen: 0 },
122 _ => State::Whole { total: 0, seen: false },
123 },
124 };
125 Ok(Self { kind, returns: returns.clone(), state })
126 }
127
128 pub fn update(&mut self, args: &[Value]) -> Result<()> {
135 if self.kind == Kind::CountStar {
136 if let State::Counted(count) = &mut self.state {
137 *count += 1;
138 }
139 return Ok(());
140 }
141 let value = match args {
142 [only] => only,
143 _ => {
144 return Err(Error::internal(format!("an aggregate over {} arguments", args.len())));
145 }
146 };
147 if value.is_null() {
148 return Ok(());
149 }
150 match &mut self.state {
151 State::Counted(count) => *count += 1,
152 State::Whole { total, seen } => {
153 let whole = integral(value).ok_or_else(|| not_narrow(value))?;
154 *total = total.checked_add(whole).ok_or_else(overflowed)?;
155 *seen = true;
156 }
157 State::Real { total, seen } => {
158 *total += approximate_or_error(value)?;
159 *seen += 1;
160 }
161 State::Mean { whole, real, seen, exact } => {
162 match integral(value)
163 .filter(|_| *exact)
164 .and_then(|number| whole.checked_add(number))
165 {
166 Some(total) => *whole = total,
167 None => {
168 if *exact {
172 *real = exactly(*whole);
173 *exact = false;
174 }
175 *real += approximate_or_error(value)?;
176 }
177 }
178 *seen += 1;
179 }
180 State::Scaled { total, scale, seen } => {
181 let unscaled = at_scale(value, *scale).ok_or_else(|| not_narrow(value))?;
182 *total = total.checked_add(unscaled).ok_or_else(overflowed)?;
183 *seen = true;
184 }
185 State::Extreme(held) => {
186 let replace = match held {
187 None => true,
188 Some(current) => {
189 let ordering = order(value, current)?;
190 match self.kind {
191 Kind::Min => ordering.is_lt(),
192 _ => ordering.is_gt(),
193 }
194 }
195 };
196 if replace {
197 *held = Some(value.clone());
198 }
199 }
200 }
201 Ok(())
202 }
203
204 pub fn update_run(&mut self, args: &[Vector], rows: usize) -> Result<()> {
220 if self.kind == Kind::CountStar {
221 if let State::Counted(count) = &mut self.state {
222 *count += i64::try_from(rows).map_err(|_| overlong())?;
223 }
224 return Ok(());
225 }
226 let input = match args {
227 [only] => only,
228 _ => {
229 return Err(Error::internal(format!("an aggregate over {} arguments", args.len())));
230 }
231 };
232 if input.len() < rows {
233 return Err(Error::internal(format!(
234 "an aggregate handed {rows} rows and a vector of {}",
235 input.len()
236 )));
237 }
238 if self.folded(input, rows)? {
239 return Ok(());
240 }
241 fallback::record(Kernel::Aggregate, input.form(), input.form());
244 for row in 0..rows {
247 let value = input.value_at(row);
248 self.update(std::slice::from_ref(&value))?;
249 }
250 Ok(())
251 }
252
253 fn folded(&mut self, input: &Vector, rows: usize) -> Result<bool> {
259 let nulls = nulls_of(input);
260 if let State::Counted(count) = &mut self.state {
263 *count += i64::try_from(nulls.count_valid(rows)).map_err(|_| overlong())?;
264 return Ok(true);
265 }
266 let least = self.kind == Kind::Min;
267 let want = match (&self.state, input.logical_type()) {
268 (State::Whole { .. }, _) => Want::Whole,
269 (State::Real { total, .. }, ty) => {
270 Want::Real { scale: decimal_scale(ty), from: *total }
271 }
272 (State::Mean { exact: true, .. }, ty) if ty.is_integer() => Want::Whole,
276 (State::Mean { whole, real, exact, .. }, ty) => {
277 let from = if *exact { exactly(*whole) } else { *real };
278 Want::Real { scale: decimal_scale(ty), from }
279 }
280 (State::Scaled { scale, .. }, LogicalType::Decimal { scale: held, .. })
287 if held == scale =>
288 {
289 Want::Whole
290 }
291 (State::Scaled { .. }, _) => return Ok(false),
292 (State::Extreme(_), _) => Want::Extreme(least),
293 (State::Counted(_), _) => return Ok(false),
294 };
295 let Some(contribution) = gather(input, rows, &nulls, want) else {
296 return Ok(false);
297 };
298 let live = nulls.count_valid(rows);
299 match (&mut self.state, contribution) {
300 (
301 State::Whole { total, seen } | State::Scaled { total, seen, .. },
302 Contribution::Whole(sum),
303 ) => {
304 *total = total.checked_add(sum).ok_or_else(overflowed)?;
305 *seen |= live > 0;
306 }
307 (State::Real { total, seen }, Contribution::Real { total: carried, seen: added }) => {
308 *total = carried;
309 *seen += added;
310 }
311 (State::Mean { whole, seen, .. }, Contribution::Whole(sum)) => {
312 let Some(total) = whole.checked_add(sum) else { return Ok(false) };
316 *whole = total;
317 *seen += i64::try_from(live).map_err(|_| overlong())?;
318 }
319 (
320 State::Mean { real, seen, exact, .. },
321 Contribution::Real { total: carried, seen: added },
322 ) => {
323 *real = carried;
324 *exact = false;
325 *seen += added;
326 }
327 (State::Extreme(held), Contribution::Extreme(Some(index))) => {
328 let candidate = input.value_at(index);
331 let replace = match held {
332 None => true,
333 Some(current) => {
334 let ordering = order(&candidate, current)?;
335 if least { ordering.is_lt() } else { ordering.is_gt() }
336 }
337 };
338 if replace {
339 *held = Some(candidate);
340 }
341 }
342 (State::Extreme(_), Contribution::Extreme(None)) => {}
343 _ => return Ok(false),
346 }
347 Ok(true)
348 }
349
350 pub fn finish(&self) -> Result<Value> {
356 match &self.state {
357 State::Counted(count) => Ok(Value::BigInt(*count)),
358 State::Whole { total, seen } => {
359 if !seen {
360 return Ok(Value::Null);
361 }
362 fit(*total, &self.returns).ok_or_else(|| {
363 Error::out_of_range(format!(
364 "a sum of {total} does not fit in {}",
365 self.returns
366 ))
367 })
368 }
369 State::Real { total, seen } => {
370 if *seen == 0 {
371 return Ok(Value::Null);
372 }
373 #[expect(
374 clippy::cast_precision_loss,
375 reason = "the count of rows in one group is well inside the exact range"
376 )]
377 let answer = if self.kind == Kind::Avg { total / *seen as f64 } else { *total };
378 if matches!(self.returns, LogicalType::Float) {
379 #[expect(
380 clippy::cast_possible_truncation,
381 reason = "a declared FLOAT result is a FLOAT"
382 )]
383 return Ok(Value::Float(answer as f32));
384 }
385 Ok(Value::Double(answer))
386 }
387 State::Mean { whole, real, seen, exact } => {
388 if *seen == 0 {
389 return Ok(Value::Null);
390 }
391 let total = if *exact { exactly(*whole) } else { *real };
392 #[expect(
393 clippy::cast_precision_loss,
394 reason = "the count of rows in one group is well inside the exact range"
395 )]
396 let answer = total / *seen as f64;
397 if matches!(self.returns, LogicalType::Float) {
398 #[expect(
399 clippy::cast_possible_truncation,
400 reason = "a declared FLOAT result is a FLOAT"
401 )]
402 return Ok(Value::Float(answer as f32));
403 }
404 Ok(Value::Double(answer))
405 }
406 State::Scaled { total, scale, seen } => {
407 if !seen {
408 return Ok(Value::Null);
409 }
410 let width = match self.returns {
411 LogicalType::Decimal { width, .. } => width,
412 _ => rudb_common::MAX_DECIMAL_WIDTH,
413 };
414 Ok(Value::Decimal { unscaled: *total, width, scale: *scale })
415 }
416 State::Extreme(held) => Ok(held.clone().unwrap_or(Value::Null)),
417 }
418 }
419}
420
421fn not_narrow(value: &Value) -> Error {
422 Error::not_implemented(format!("summing a {}", value.logical_type()))
423}
424
425#[expect(
428 clippy::cast_precision_loss,
429 reason = "a total past 2^53 rounding once here is the definition of a double result"
430)]
431fn exactly(total: i128) -> f64 {
432 total as f64
433}
434
435fn approximate_or_error(value: &Value) -> Result<f64> {
436 crate::number::approximate(value).ok_or_else(|| not_narrow(value))
437}
438
439fn at_scale(value: &Value, scale: u8) -> Option<i128> {
441 match *value {
442 Value::Decimal { unscaled, scale: held, .. } => rescale(unscaled, held, scale),
443 _ => integral(value).and_then(|whole| whole.checked_mul(pow10(scale))),
444 }
445}
446
447fn overflowed() -> Error {
448 Error::out_of_range("Overflow in the running total of a sum".to_string())
449}
450
451fn overlong() -> Error {
452 Error::out_of_range("more rows in one vector than a count can hold".to_string())
453}
454
455fn decimal_scale(ty: &LogicalType) -> u8 {
457 match *ty {
458 LogicalType::Decimal { scale, .. } => scale,
459 _ => 0,
460 }
461}
462
463#[derive(Clone, Copy)]
465enum Want {
466 Whole,
468 Real { scale: u8, from: f64 },
470 Extreme(bool),
472}
473
474enum Contribution {
476 Whole(i128),
477 Real { total: f64, seen: i64 },
478 Extreme(Option<usize>),
479}
480
481fn gather(input: &Vector, rows: usize, nulls: &Validity, want: Want) -> Option<Contribution> {
483 match input.form() {
484 Form::Flat => {
485 let data = input.data()?;
486 if data.len() < rows {
487 return None;
488 }
489 collect(data, identity, rows, nulls, want)
490 }
491 Form::Dictionary => {
492 let (codes, values) = input.dictionary_parts()?;
493 if codes.len() < rows {
494 return None;
495 }
496 collect(values.data()?, |index| codes[index] as usize, rows, nulls, want)
499 }
500 _ => None,
504 }
505}
506
507fn collect<M: Fn(usize) -> usize>(
508 data: &Data,
509 at: M,
510 rows: usize,
511 nulls: &Validity,
512 want: Want,
513) -> Option<Contribution> {
514 match want {
515 Want::Whole => whole_sum(data, at, rows, nulls).map(Contribution::Whole),
516 Want::Real { scale, from } => real_sum(data, at, rows, nulls, scale, from),
517 Want::Extreme(least) => extreme(data, at, rows, nulls, least).map(Contribution::Extreme),
518 }
519}
520
521fn whole_sum<M: Fn(usize) -> usize>(
528 data: &Data,
529 at: M,
530 rows: usize,
531 nulls: &Validity,
532) -> Option<i128> {
533 macro_rules! summed {
534 ($(($variant:ident, $native:ty, $zero:expr)),+ $(,)?) => {
535 match data {
536 $(Data::$variant(values) => summed!(@run values),)+
537 _ => return None,
542 }
543 };
544 (@run $values:expr) => {{
545 let values = $values;
546 let mut total: i128 = 0;
547 match nulls {
548 Validity::AllValid => {
549 for index in 0..rows {
550 total += i128::from(values[at(index)]);
551 }
552 }
553 Validity::AllInvalid => {}
554 Validity::Mask(mask) => {
555 for start in (0..rows).step_by(64) {
559 let word = mask.word(start / 64);
560 for index in start..(start + 64).min(rows) {
561 let number = i128::from(values[at(index)]);
562 total += if word >> (index - start) & 1 == 1 { number } else { 0 };
563 }
564 }
565 }
566 }
567 total
568 }};
569 }
570 Some(rudb_vector::for_each_layout!(narrow, summed))
571}
572
573#[expect(
581 clippy::cast_precision_loss,
582 reason = "a wide integer past 2^53 losing digits is what a double is, and this is the float path"
583)]
584fn real_sum<M: Fn(usize) -> usize>(
585 data: &Data,
586 at: M,
587 rows: usize,
588 nulls: &Validity,
589 scale: u8,
590 from: f64,
591) -> Option<Contribution> {
592 let factor = pow10(scale) as f64;
593 let scaled = scale != 0;
594 let all = i64::try_from(rows).ok()?;
595 macro_rules! added {
600 ($(($variant:ident, $native:ty, $zero:expr)),+ $(,)?) => {
601 match data {
602 $(Data::$variant(values) => added!(@run values, |number| number as f64),)+
603 Data::Float32(values) => added!(@run values, f64::from),
604 Data::Float64(values) => added!(@run values, |number: f64| number),
605 _ => return None,
606 }
607 };
608 (@run $values:expr, $convert:expr) => {{
609 let values = $values;
610 let convert = $convert;
611 let mut total = from;
612 let mut seen: i64 = 0;
613 match nulls {
617 Validity::AllValid => {
618 for index in 0..rows {
619 let number = convert(values[at(index)]);
620 total += if scaled { number / factor } else { number };
621 }
622 seen = all;
623 }
624 Validity::AllInvalid => {}
625 Validity::Mask(mask) => {
626 for start in (0..rows).step_by(64) {
627 let word = mask.word(start / 64);
628 for index in start..(start + 64).min(rows) {
629 if word >> (index - start) & 1 == 0 {
630 continue;
631 }
632 let number = convert(values[at(index)]);
633 total += if scaled { number / factor } else { number };
634 seen += 1;
635 }
636 }
637 }
638 }
639 (total, seen)
640 }};
641 }
642 let (total, seen) = rudb_vector::for_each_layout!(integer, added);
643 Some(Contribution::Real { total, seen })
644}
645
646fn extreme<M: Fn(usize) -> usize>(
648 data: &Data,
649 at: M,
650 rows: usize,
651 nulls: &Validity,
652 least: bool,
653) -> Option<Option<usize>> {
654 macro_rules! best {
655 ($(($variant:ident, $native:ty, $zero:expr)),+ $(,)?) => {
656 match data {
657 $(Data::$variant(values) => best!(@run values),)+
658 _ => return None,
663 }
664 };
665 (@run $values:expr) => {{
666 let values = $values;
667 let mut held = usize::MAX;
672 let mut mark: i128 = 0;
673 match nulls {
674 Validity::AllValid => {
675 if rows > 0 {
676 mark = i128::from(values[at(0)]);
677 held = 0;
678 for index in 1..rows {
679 let number = i128::from(values[at(index)]);
680 let win = if least { number < mark } else { number > mark };
681 if win {
682 mark = number;
683 held = index;
684 }
685 }
686 }
687 }
688 Validity::AllInvalid => {}
689 Validity::Mask(mask) => {
690 for start in (0..rows).step_by(64) {
691 let word = mask.word(start / 64);
692 for index in start..(start + 64).min(rows) {
693 if word >> (index - start) & 1 == 0 {
694 continue;
695 }
696 let number = i128::from(values[at(index)]);
697 let win = if least { number < mark } else { number > mark };
698 if held == usize::MAX || win {
699 mark = number;
700 held = index;
701 }
702 }
703 }
704 }
705 }
706 (held != usize::MAX).then_some(held)
707 }};
708 }
709 Some(rudb_vector::for_each_layout!(narrow, best))
710}
711
712#[cfg(test)]
713mod tests {
714 use super::*;
715
716 fn run(name: &str, returns: &LogicalType, rows: &[Value]) -> Value {
717 let mut accumulator = Accumulator::new(name, returns).expect("a known aggregate");
718 for row in rows {
719 accumulator.update(std::slice::from_ref(row)).expect("accumulates");
720 }
721 accumulator.finish().expect("finishes")
722 }
723
724 #[test]
725 fn count_star_counts_rows_and_count_counts_values() {
726 let mut stars = Accumulator::new("count_star", &LogicalType::BigInt).expect("known");
727 for _ in 0..3 {
728 stars.update(&[]).expect("no arguments");
729 }
730 assert_eq!(stars.finish().expect("finishes"), Value::BigInt(3));
731 let counted = run(
732 "count",
733 &LogicalType::BigInt,
734 &[Value::Integer(1), Value::Null, Value::Integer(3)],
735 );
736 assert_eq!(counted, Value::BigInt(2));
737 }
738
739 #[test]
741 fn a_sum_of_nothing_is_null_and_a_count_of_nothing_is_zero() {
742 assert_eq!(run("sum", &LogicalType::HugeInt, &[]), Value::Null);
743 assert_eq!(run("sum", &LogicalType::HugeInt, &[Value::Null]), Value::Null);
744 assert_eq!(run("count", &LogicalType::BigInt, &[]), Value::BigInt(0));
745 assert_eq!(run("count_star", &LogicalType::BigInt, &[]), Value::BigInt(0));
746 }
747
748 #[test]
749 fn a_sum_of_integers_accumulates_wider_than_it_reads() {
750 let rows = vec![Value::Integer(i32::MAX); 4];
751 let total = run("sum", &LogicalType::HugeInt, &rows);
752 assert_eq!(total, Value::HugeInt(i128::from(i32::MAX) * 4));
753 }
754
755 #[test]
756 fn an_average_divides_by_the_rows_it_saw_rather_than_the_rows_there_were() {
757 let average =
758 run("avg", &LogicalType::Double, &[Value::Integer(1), Value::Null, Value::Integer(3)]);
759 assert_eq!(average, Value::Double(2.0));
760 }
761
762 const WIDE: [i64; 4] = [435090932899640449, 435090932899640450, 1000003, 999999999999999999];
768
769 fn wide_mean() -> f64 {
771 exactly(WIDE.iter().map(|&number| i128::from(number)).sum()) / 4.0
772 }
773
774 fn wide_values() -> Vec<Value> {
775 WIDE.iter().map(|&number| Value::BigInt(number)).collect()
776 }
777
778 #[test]
779 fn an_average_of_whole_numbers_adds_them_up_exactly_and_divides_once() {
780 let mut running = 0.0_f64;
785 for value in wide_values() {
786 running += crate::number::approximate(&value).expect("a number");
787 }
788 assert_ne!(running / 4.0, wide_mean(), "the two ways of averaging have to differ here");
789 assert_eq!(run("avg", &LogicalType::Double, &wide_values()), Value::Double(wide_mean()));
790 }
791
792 #[test]
793 fn the_vector_path_averages_whole_numbers_exactly_as_well() {
794 let values = wide_values();
795 let vector = Vector::from_values(LogicalType::BigInt, &values).expect("a vector of these");
796 let mut accumulator = Accumulator::new("avg", &LogicalType::Double).expect("a known one");
797 accumulator.update_run(std::slice::from_ref(&vector), values.len()).expect("folds them in");
798 assert_eq!(accumulator.finish().expect("finishes"), Value::Double(wide_mean()));
799 }
800
801 #[test]
803 fn an_average_of_doubles_is_the_running_total_the_float_path_produces() {
804 let rows = [Value::Double(1e17), Value::Double(1.0), Value::Double(3.0)];
805 let mut running = 0.0_f64;
806 for value in &rows {
807 running += crate::number::approximate(value).expect("a number");
808 }
809 assert_eq!(run("avg", &LogicalType::Double, &rows), Value::Double(running / 3.0));
810 }
811
812 #[test]
813 fn min_and_max_skip_nulls_and_keep_the_value_rather_than_a_number() {
814 let smallest = run(
815 "min",
816 &LogicalType::Varchar,
817 &[Value::Varchar("b".into()), Value::Null, Value::Varchar("a".into())],
818 );
819 assert_eq!(smallest, Value::Varchar("a".into()));
820 let largest = run(
821 "max",
822 &LogicalType::Integer,
823 &[Value::Integer(1), Value::Integer(7), Value::Integer(3)],
824 );
825 assert_eq!(largest, Value::Integer(7));
826 }
827
828 #[test]
829 fn a_decimal_sums_at_its_own_scale() {
830 let ty = LogicalType::decimal(10, 2).expect("a legal decimal");
831 let total = run(
832 "sum",
833 &ty,
834 &[
835 Value::Decimal { unscaled: 250, width: 10, scale: 2 },
836 Value::Decimal { unscaled: 125, width: 10, scale: 2 },
837 ],
838 );
839 assert_eq!(total, Value::Decimal { unscaled: 375, width: 10, scale: 2 });
840 }
841
842 #[test]
843 fn an_aggregate_nobody_has_written_says_which_one() {
844 let error = Accumulator::new("median", &LogicalType::Double)
845 .expect_err("median is not written yet");
846 assert!(error.message().contains("the median aggregate"), "{error}");
847 }
848
849 fn row_at_a_time(name: &str, returns: &LogicalType, batches: &[Vector]) -> Result<Value> {
851 let mut accumulator = Accumulator::new(name, returns)?;
852 for batch in batches {
853 for row in 0..batch.len() {
854 let value = batch.value_at(row);
855 accumulator.update(std::slice::from_ref(&value))?;
856 }
857 }
858 accumulator.finish()
859 }
860
861 fn a_vector_at_a_time(name: &str, returns: &LogicalType, batches: &[Vector]) -> Result<Value> {
862 let mut accumulator = Accumulator::new(name, returns)?;
863 for batch in batches {
864 accumulator.update_run(std::slice::from_ref(batch), batch.len())?;
865 }
866 accumulator.finish()
867 }
868
869 fn agrees(name: &str, returns: &LogicalType, batches: &[Vector], note: &str) {
871 let slow = row_at_a_time(name, returns, batches);
872 let fast = a_vector_at_a_time(name, returns, batches);
873 match (slow, fast) {
874 (Ok(slow), Ok(fast)) => assert_eq!(slow, fast, "{note}"),
875 (Err(slow), Err(fast)) => {
876 assert_eq!(slow.message(), fast.message(), "{note}");
877 }
878 (slow, fast) => {
879 panic!(
880 "{note}: one path answered and the other did not, {slow:?} against {fast:?}"
881 );
882 }
883 }
884 }
885
886 struct Rng(u64);
887
888 impl Rng {
889 fn next(&mut self) -> u64 {
890 self.0 ^= self.0 << 13;
891 self.0 ^= self.0 >> 7;
892 self.0 ^= self.0 << 17;
893 self.0
894 }
895 }
896
897 fn small(rng: &mut Rng) -> i64 {
900 (rng.next() % 201) as i64 - 100
901 }
902
903 fn sample(ty: &LogicalType, rng: &mut Rng) -> Value {
904 let number = small(rng);
905 let positive = number.unsigned_abs();
906 match *ty {
907 LogicalType::TinyInt => Value::TinyInt(number as i8),
908 LogicalType::SmallInt => Value::SmallInt(number as i16),
909 LogicalType::Integer => Value::Integer(number as i32),
910 LogicalType::BigInt => Value::BigInt(number),
911 LogicalType::HugeInt => Value::HugeInt(i128::from(number)),
912 LogicalType::UTinyInt => Value::UTinyInt(positive as u8),
913 LogicalType::USmallInt => Value::USmallInt(positive as u16),
914 LogicalType::UInteger => Value::UInteger(positive as u32),
915 LogicalType::UBigInt => Value::UBigInt(positive),
916 LogicalType::Float => Value::Float(number as f32 / 8.0),
917 LogicalType::Double => Value::Double(number as f64 / 8.0),
918 LogicalType::Decimal { width, scale } => {
919 Value::Decimal { unscaled: i128::from(number) * 7, width, scale }
920 }
921 LogicalType::Varchar => Value::Varchar(format!("w{number}")),
922 _ => panic!("no sample for {ty}"),
923 }
924 }
925
926 fn flat(ty: &LogicalType, rows: usize, nulls: usize, rng: &mut Rng) -> Vector {
927 let values: Vec<Value> = (0..rows)
928 .map(
929 |index| {
930 if nulls > 0 && index % nulls == 0 { Value::Null } else { sample(ty, rng) }
931 },
932 )
933 .collect();
934 Vector::from_values(ty.clone(), &values).expect("a vector of this type")
935 }
936
937 fn returns_of(name: &str, ty: &LogicalType) -> LogicalType {
939 match name {
940 "count" | "count_star" => LogicalType::BigInt,
941 "avg" => LogicalType::Double,
942 "min" | "max" => ty.clone(),
943 _ => match *ty {
944 LogicalType::Decimal { scale, .. } => {
945 LogicalType::decimal(rudb_common::MAX_DECIMAL_WIDTH, scale)
946 .expect("the widest decimal at this scale is legal")
947 }
948 LogicalType::Float | LogicalType::Double => LogicalType::Double,
949 _ => LogicalType::HugeInt,
950 },
951 }
952 }
953
954 #[test]
957 fn every_aggregate_over_every_type_agrees_with_the_row_at_a_time_path() {
958 let mut rng = Rng(0x5eed_ca11_ab1e_0003);
959 let types = [
960 LogicalType::TinyInt,
961 LogicalType::SmallInt,
962 LogicalType::Integer,
963 LogicalType::BigInt,
964 LogicalType::HugeInt,
965 LogicalType::UTinyInt,
966 LogicalType::USmallInt,
967 LogicalType::UInteger,
968 LogicalType::UBigInt,
969 LogicalType::Float,
970 LogicalType::Double,
971 LogicalType::decimal(9, 2).expect("a legal decimal"),
972 LogicalType::decimal(18, 4).expect("a legal decimal"),
973 LogicalType::decimal(30, 6).expect("a legal decimal"),
974 LogicalType::Varchar,
975 ];
976 for ty in &types {
977 for name in ["count_star", "count", "sum", "avg", "min", "max"] {
978 let returns = returns_of(name, ty);
979 for nulls in [0_usize, 4, 1] {
980 let first = flat(ty, 97, nulls, &mut rng);
983 let second = flat(ty, 64, nulls, &mut rng);
984 let note = format!("{name} over {ty}, flat, one null in {nulls}");
985 agrees(name, &returns, &[first.clone(), second.clone()], ¬e);
986 let codes: Vec<u32> = (0..97).map(|index| (index % 13) as u32).collect();
987 let coded = Vector::dictionary(codes, first).expect("codes are in range");
988 let note = format!("{name} over {ty}, dictionary, one null in {nulls}");
989 agrees(name, &returns, &[coded, second], ¬e);
990 }
991 }
992 }
993 }
994
995 #[test]
996 fn a_sum_of_numbers_stays_off_the_row_at_a_time_path_and_a_sum_of_strings_does_not() {
997 fallback::reset();
998 let numbers = Vector::from_values(
999 LogicalType::Integer,
1000 &[Value::Integer(1), Value::Integer(2), Value::Integer(3)],
1001 )
1002 .expect("a vector of integers");
1003 let mut summing =
1004 Accumulator::new("sum", &LogicalType::HugeInt).expect("a known aggregate");
1005 summing.update_run(std::slice::from_ref(&numbers), 3).expect("sums");
1006 assert_eq!(summing.finish().expect("finishes"), Value::HugeInt(6));
1007 assert_eq!(fallback::count(Kernel::Aggregate, Form::Flat, Form::Flat), 0);
1008
1009 let words = Vector::from_values(
1010 LogicalType::Varchar,
1011 &[Value::Varchar("a".into()), Value::Null, Value::Varchar("b".into())],
1012 )
1013 .expect("a vector of strings");
1014 let mut counting = Accumulator::new("count", &LogicalType::BigInt).expect("a known one");
1015 counting.update_run(std::slice::from_ref(&words), 3).expect("counts");
1016 assert_eq!(counting.finish().expect("finishes"), Value::BigInt(2));
1017 assert_eq!(fallback::count(Kernel::Aggregate, Form::Flat, Form::Flat), 0);
1019
1020 let mut wrong = Accumulator::new("sum", &LogicalType::HugeInt).expect("a known aggregate");
1021 let error =
1022 wrong.update_run(std::slice::from_ref(&words), 3).expect_err("cannot sum those");
1023 assert!(error.message().contains("summing a"), "{error}");
1024 assert_eq!(fallback::count(Kernel::Aggregate, Form::Flat, Form::Flat), 1);
1025 fallback::reset();
1026 }
1027
1028 #[test]
1031 fn a_floating_point_sum_carries_the_running_total_into_the_next_vector() {
1032 let first =
1033 Vector::from_values(LogicalType::Double, &[Value::Double(1.0e16)]).expect("a vector");
1034 let second = Vector::from_values(LogicalType::Double, &vec![Value::Double(1.0); 8])
1035 .expect("a vector");
1036 let batches = [first, second];
1037 let slow = row_at_a_time("sum", &LogicalType::Double, &batches).expect("sums");
1038 let fast = a_vector_at_a_time("sum", &LogicalType::Double, &batches).expect("sums");
1039 assert_eq!(slow, fast);
1040 assert_eq!(slow, Value::Double(1.0e16));
1043 assert_ne!(1.0e16 + 8.0, 1.0e16);
1044 }
1045
1046 #[test]
1048 fn a_null_behind_a_dictionary_code_is_skipped_by_every_aggregate() {
1049 let values = Vector::from_values(
1050 LogicalType::Integer,
1051 &[Value::Null, Value::Integer(5), Value::Integer(9)],
1052 )
1053 .expect("a vector of integers");
1054 let coded = Vector::dictionary(vec![0, 1, 0, 2, 0], values).expect("codes are in range");
1055 let batch = std::slice::from_ref(&coded);
1056 assert_eq!(
1057 a_vector_at_a_time("count", &LogicalType::BigInt, batch).expect("counts"),
1058 Value::BigInt(2)
1059 );
1060 assert_eq!(
1061 a_vector_at_a_time("sum", &LogicalType::HugeInt, batch).expect("sums"),
1062 Value::HugeInt(14)
1063 );
1064 assert_eq!(
1065 a_vector_at_a_time("min", &LogicalType::Integer, batch).expect("finds one"),
1066 Value::Integer(5)
1067 );
1068 }
1069
1070 #[test]
1073 fn a_total_of_hugeints_goes_the_row_at_a_time_way_and_still_overflows() {
1074 fallback::reset();
1075 let rows = vec![Value::HugeInt(i128::MAX); 2];
1076 let vector = Vector::from_values(LogicalType::HugeInt, &rows).expect("a vector");
1077 let mut accumulator = Accumulator::new("sum", &LogicalType::HugeInt).expect("a known one");
1078 let error =
1079 accumulator.update_run(std::slice::from_ref(&vector), 2).expect_err("overflows");
1080 assert!(error.message().contains("Overflow in the running total"), "{error}");
1081 assert_eq!(fallback::count(Kernel::Aggregate, Form::Flat, Form::Flat), 1);
1082 fallback::reset();
1083 }
1084}