use std::sync::Arc;
use rudb_common::{Error, LogicalType, PhysicalType, Result, Value};
use rudb_vector::{Data, Form, Live, Validity, Vector};
use crate::compare::order;
use crate::fallback::{self, Kernel};
use crate::number::{fit, integral, pow10, rescale};
use crate::shape::{identity, nulls_of};
pub const NOWHERE: usize = usize::MAX;
#[derive(Debug, Clone)]
pub struct Accumulator {
state: State,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Return {
TinyInt,
SmallInt,
Integer,
BigInt,
HugeInt,
UTinyInt,
USmallInt,
UInteger,
UBigInt,
UHugeInt,
Float,
Double,
Decimal(u8),
Other,
}
impl Return {
fn new(ty: &LogicalType) -> Self {
match ty {
LogicalType::TinyInt => Self::TinyInt,
LogicalType::SmallInt => Self::SmallInt,
LogicalType::Integer => Self::Integer,
LogicalType::BigInt => Self::BigInt,
LogicalType::HugeInt => Self::HugeInt,
LogicalType::UTinyInt => Self::UTinyInt,
LogicalType::USmallInt => Self::USmallInt,
LogicalType::UInteger => Self::UInteger,
LogicalType::UBigInt => Self::UBigInt,
LogicalType::UHugeInt => Self::UHugeInt,
LogicalType::Float => Self::Float,
LogicalType::Double => Self::Double,
LogicalType::Decimal { width, .. } => Self::Decimal(*width),
_ => Self::Other,
}
}
fn logical(self) -> LogicalType {
match self {
Self::TinyInt => LogicalType::TinyInt,
Self::SmallInt => LogicalType::SmallInt,
Self::Integer => LogicalType::Integer,
Self::BigInt => LogicalType::BigInt,
Self::HugeInt => LogicalType::HugeInt,
Self::UTinyInt => LogicalType::UTinyInt,
Self::USmallInt => LogicalType::USmallInt,
Self::UInteger => LogicalType::UInteger,
Self::UBigInt => LogicalType::UBigInt,
Self::UHugeInt => LogicalType::UHugeInt,
Self::Float => LogicalType::Float,
Self::Double => LogicalType::Double,
Self::Decimal(width) => LogicalType::Decimal { width, scale: 0 },
Self::Other => LogicalType::Null,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Kind {
CountStar,
Count,
Sum,
Avg,
Min,
Max,
}
#[derive(Debug, Clone)]
enum State {
Counted { count: i64, star: bool },
Whole { total: i128, seen: bool, returns: Return },
Real { total: f64, seen: i64, kind: Kind, returns: Return },
Mean { total: i128, seen: i64, exact: bool, scale: u8, returns: Return },
Scaled { total: i128, scale: u8, seen: bool, returns: Return },
Extreme { held: Option<Box<Extremum>>, least: bool },
}
#[derive(Debug, Clone, Copy)]
enum Answer {
Null,
Whole(i128),
Real(f64),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Route {
Whole,
Double,
Float,
}
#[derive(Debug, Clone)]
enum Extremum {
Held(Value),
Ranked { dictionary: Arc<Vector>, code: u32, rank: u32 },
}
impl Extremum {
fn value(&self) -> Result<Value> {
match self {
Self::Held(value) => Ok(value.clone()),
Self::Ranked { dictionary, code, .. } => dictionary.try_value_at(*code as usize),
}
}
fn settle(&mut self) -> Result<&mut Value> {
if let Self::Ranked { .. } = self {
let value = self.value()?;
*self = Self::Held(value);
}
match self {
Self::Held(value) => Ok(value),
Self::Ranked { .. } => {
Err(Error::internal("a settled extreme kept its rank".to_string()))
}
}
}
fn offer(&mut self, dictionary: &Arc<Vector>, code: u32, rank: u32, least: bool) -> Result<()> {
if let Self::Ranked { dictionary: mine, code: held_code, rank: held_rank } = self {
if Arc::ptr_eq(mine, dictionary) {
if if least { rank < *held_rank } else { rank > *held_rank } {
*held_code = code;
*held_rank = rank;
}
return Ok(());
}
}
let candidate = dictionary.try_value_at(code as usize)?;
let ordering = order(&candidate, self.settle()?)?;
if if least { ordering.is_lt() } else { ordering.is_gt() } {
*self = Self::Held(candidate);
}
Ok(())
}
}
impl Accumulator {
#[must_use]
pub fn counted(&self) -> Option<i64> {
match self.state {
State::Counted { count, .. } => Some(count),
_ => None,
}
}
#[must_use]
pub fn exact_sum(total: i128, seen: bool, returns: &LogicalType) -> Self {
Self { state: State::Whole { total, seen, returns: Return::new(returns) } }
}
#[must_use]
pub fn exact_avg(total: i128, seen: i64, returns: &LogicalType) -> Self {
Self {
state: State::Mean {
total,
seen,
exact: true,
scale: 0,
returns: Return::new(returns),
},
}
}
fn kind(&self) -> Kind {
match self.state {
State::Counted { star, .. } => {
if star {
Kind::CountStar
} else {
Kind::Count
}
}
State::Whole { .. } | State::Scaled { .. } => Kind::Sum,
State::Real { kind, .. } => kind,
State::Mean { .. } => Kind::Avg,
State::Extreme { least, .. } => {
if least {
Kind::Min
} else {
Kind::Max
}
}
}
}
fn returns(&self) -> Return {
match self.state {
State::Counted { .. } => Return::BigInt,
State::Whole { returns, .. }
| State::Real { returns, .. }
| State::Mean { returns, .. }
| State::Scaled { returns, .. } => returns,
State::Extreme { .. } => Return::Other,
}
}
pub fn new(name: &str, returns: &LogicalType) -> Result<Self> {
let kind = match name {
"count_star" => Kind::CountStar,
"count" => Kind::Count,
"sum" => Kind::Sum,
"avg" => Kind::Avg,
"min" => Kind::Min,
"max" => Kind::Max,
other => {
return Err(Error::not_implemented(format!("the {other} aggregate")));
}
};
let scale = match returns {
LogicalType::Decimal { scale, .. } => *scale,
_ => 0,
};
let returns = Return::new(returns);
let state = match kind {
Kind::CountStar | Kind::Count => {
State::Counted { count: 0, star: kind == Kind::CountStar }
}
Kind::Avg => State::Mean { total: 0, seen: 0, exact: true, scale: 0, returns },
Kind::Min | Kind::Max => State::Extreme { held: None, least: kind == Kind::Min },
Kind::Sum => match returns {
Return::Decimal(_) => State::Scaled { total: 0, scale, seen: false, returns },
Return::Float | Return::Double => {
State::Real { total: 0.0, seen: 0, kind, returns }
}
_ => State::Whole { total: 0, seen: false, returns },
},
};
Ok(Self { state })
}
pub fn update(&mut self, args: &[Value]) -> Result<()> {
if self.kind() == Kind::CountStar {
if let State::Counted { count, .. } = &mut self.state {
*count += 1;
}
return Ok(());
}
let value = match args {
[only] => only,
_ => {
return Err(Error::internal(format!("an aggregate over {} arguments", args.len())));
}
};
if value.is_null() {
return Ok(());
}
match &mut self.state {
State::Counted { count, .. } => *count += 1,
State::Whole { total, seen, .. } => {
let whole = integral(value).ok_or_else(|| not_narrow(value))?;
*total = total.checked_add(whole).ok_or_else(overflowed)?;
*seen = true;
}
State::Real { total, seen, .. } => {
*total += approximate_or_error(value)?;
*seen += 1;
}
State::Mean { total, seen, exact, scale, .. } => {
let whole = match *value {
Value::Decimal { unscaled, scale: held, .. } => {
*scale = held;
Some(unscaled)
}
_ => integral(value),
};
match whole.filter(|_| *exact).and_then(|number| total.checked_add(number)) {
Some(sum) => *total = sum,
None => {
let real = if *exact { exactly(*total) } else { mean_real(*total) };
let add = match whole {
Some(number) => exactly(number),
None => approximate_or_error(value)?,
};
*total = mean_bits(real + add);
*exact = false;
}
}
*seen += 1;
}
State::Scaled { total, scale, seen, .. } => {
let unscaled = at_scale(value, *scale).ok_or_else(|| not_narrow(value))?;
*total = total.checked_add(unscaled).ok_or_else(overflowed)?;
*seen = true;
}
State::Extreme { held, least } => {
let replace = match held {
None => true,
Some(current) => {
let ordering = order(value, current.settle()?)?;
if *least { ordering.is_lt() } else { ordering.is_gt() }
}
};
if replace {
*held = Some(Box::new(Extremum::Held(value.clone())));
}
}
}
Ok(())
}
pub fn update_run(&mut self, args: &[Vector], rows: usize) -> Result<()> {
if self.kind() == Kind::CountStar {
if let State::Counted { count, .. } = &mut self.state {
*count += i64::try_from(rows).map_err(|_| overlong())?;
}
return Ok(());
}
let input = match args {
[only] => only,
_ => {
return Err(Error::internal(format!("an aggregate over {} arguments", args.len())));
}
};
if input.len() < rows {
return Err(Error::internal(format!(
"an aggregate handed {rows} rows and a vector of {}",
input.len()
)));
}
if self.folded(input, rows)? {
return Ok(());
}
fallback::record(Kernel::Aggregate, input.form(), input.form());
for row in 0..rows {
let value = input.try_value_at(row)?;
self.update(std::slice::from_ref(&value))?;
}
Ok(())
}
fn folded(&mut self, input: &Vector, rows: usize) -> Result<bool> {
let nulls = nulls_of(input);
if let State::Counted { count, .. } = &mut self.state {
*count += i64::try_from(nulls.count_valid(rows)).map_err(|_| overlong())?;
return Ok(true);
}
let least = self.kind() == Kind::Min;
if matches!(self.state, State::Extreme { .. })
&& ranked_extreme(&mut self.state, input, rows, &nulls, least)?
{
return Ok(true);
}
if let (State::Mean { scale, .. }, LogicalType::Decimal { scale: held, .. }) =
(&mut self.state, input.logical_type())
{
*scale = *held;
}
let want = match (&self.state, input.logical_type()) {
(State::Whole { .. }, ty) if ty.is_integer() => Want::Whole,
(State::Real { total, .. }, ty) if addable(ty) => {
Want::Real { scale: decimal_scale(ty), from: *total }
}
(State::Mean { exact: true, .. }, ty)
if ty.is_integer() || matches!(ty, LogicalType::Decimal { .. }) =>
{
Want::Whole
}
(State::Mean { total, exact, .. }, ty) if addable(ty) => {
let from = if *exact { exactly(*total) } else { mean_real(*total) };
Want::Real { scale: 0, from }
}
(State::Scaled { scale, .. }, LogicalType::Decimal { scale: held, .. })
if held == scale =>
{
Want::Whole
}
(State::Extreme { .. }, _) => Want::Extreme(least),
_ => return Ok(false),
};
let Some(contribution) = gather(input, rows, &nulls, want) else {
return Ok(false);
};
let live = nulls.count_valid(rows);
match (&mut self.state, contribution) {
(
State::Whole { total, seen, .. } | State::Scaled { total, seen, .. },
Contribution::Whole(sum),
) => {
*total = total.checked_add(sum).ok_or_else(overflowed)?;
*seen |= live > 0;
}
(
State::Real { total, seen, .. },
Contribution::Real { total: carried, seen: added },
) => {
*total = carried;
*seen += added;
}
(State::Mean { total, seen, .. }, Contribution::Whole(sum)) => {
let Some(sum) = total.checked_add(sum) else { return Ok(false) };
*total = sum;
*seen += i64::try_from(live).map_err(|_| overlong())?;
}
(
State::Mean { total, seen, exact, .. },
Contribution::Real { total: carried, seen: added },
) => {
*total = mean_bits(carried);
*exact = false;
*seen += added;
}
(State::Extreme { held, .. }, Contribution::Extreme(Some(index))) => {
let candidate = input.try_value_at(index)?;
let replace = match held {
None => true,
Some(current) => {
let ordering = order(&candidate, current.settle()?)?;
if least { ordering.is_lt() } else { ordering.is_gt() }
}
};
if replace {
*held = Some(Box::new(Extremum::Held(candidate)));
}
}
(State::Extreme { .. }, Contribution::Extreme(None)) => {}
_ => return Ok(false),
}
Ok(true)
}
pub fn combine(&mut self, other: &Self) -> Result<()> {
match (&mut self.state, &other.state) {
(State::Counted { count, star }, State::Counted { count: added, star: same })
if star == same =>
{
*count += added;
}
(State::Whole { total, seen, .. }, State::Whole { total: added, seen: any, .. }) => {
*total = total.checked_add(*added).ok_or_else(overflowed)?;
*seen |= any;
}
(
State::Scaled { total, scale, seen, .. },
State::Scaled { total: added, scale: same, seen: any, .. },
) if scale == same => {
*total = total.checked_add(*added).ok_or_else(overflowed)?;
*seen |= any;
}
(State::Real { total, seen, .. }, State::Real { total: added, seen: more, .. }) => {
*total += added;
*seen += more;
}
(
State::Mean { total, seen, exact, scale, .. },
State::Mean { total: added, seen: more, exact: whole, scale: from, .. },
) => {
if *seen == 0 {
*scale = *from;
}
let both = if *exact && *whole { total.checked_add(*added) } else { None };
match both {
Some(sum) => *total = sum,
None => {
let here = if *exact { exactly(*total) } else { mean_real(*total) };
let there = if *whole { exactly(*added) } else { mean_real(*added) };
*total = mean_bits(here + there);
*exact = false;
}
}
*seen += more;
}
(State::Extreme { held, least }, State::Extreme { held: candidate, least: same })
if least == same =>
{
if let Some(candidate) = candidate {
match (held.as_deref_mut(), candidate.as_ref()) {
(None, _) => *held = Some(candidate.clone()),
(
Some(Extremum::Ranked { dictionary, rank, .. }),
Extremum::Ranked { dictionary: theirs, rank: other, .. },
) if Arc::ptr_eq(dictionary, theirs) => {
if if *least { other < rank } else { other > rank } {
*held = Some(candidate.clone());
}
}
(Some(current), _) => {
let value = candidate.value()?;
let ordering = order(&value, current.settle()?)?;
if if *least { ordering.is_lt() } else { ordering.is_gt() } {
*held = Some(candidate.clone());
}
}
}
}
}
(here, there) => {
return Err(Error::internal(format!(
"combining a {here:?} aggregate state with a {there:?} one, which are not the \
same aggregate over the same type"
)));
}
}
Ok(())
}
pub fn finish(&self) -> Result<Value> {
let Some(answer) = self.answer() else {
let State::Extreme { held, .. } = &self.state else {
return Err(Error::internal(
"an accumulator with no number and no extreme in it".to_string(),
));
};
return held.as_deref().map_or(Ok(Value::Null), Extremum::value);
};
match answer {
Answer::Null => Ok(Value::Null),
Answer::Whole(total) => match &self.state {
State::Counted { count, .. } => Ok(Value::BigInt(*count)),
State::Scaled { scale, .. } => {
let width = match self.returns() {
Return::Decimal(width) => width,
_ => rudb_common::MAX_DECIMAL_WIDTH,
};
Ok(Value::Decimal { unscaled: total, width, scale: *scale })
}
_ => {
let returns = self.returns().logical();
fit(total, &returns).ok_or_else(|| {
Error::out_of_range(format!("a sum of {total} does not fit in {}", returns))
})
}
},
Answer::Real(answer) => {
if self.returns() == Return::Float {
#[expect(
clippy::cast_possible_truncation,
reason = "a declared FLOAT result is a FLOAT"
)]
return Ok(Value::Float(answer as f32));
}
Ok(Value::Double(answer))
}
}
}
fn answer(&self) -> Option<Answer> {
match &self.state {
State::Counted { count, .. } => Some(Answer::Whole(i128::from(*count))),
State::Whole { total, seen, .. } => {
Some(if *seen { Answer::Whole(*total) } else { Answer::Null })
}
State::Real { total, seen, .. } => {
if *seen == 0 {
return Some(Answer::Null);
}
#[expect(
clippy::cast_precision_loss,
reason = "the count of rows in one group is well inside the exact range"
)]
let answer = if self.kind() == Kind::Avg { total / *seen as f64 } else { *total };
Some(Answer::Real(answer))
}
State::Mean { total, seen, exact, scale, .. } => {
if *seen == 0 {
return Some(Answer::Null);
}
let total = if *exact { exactly(*total) } else { mean_real(*total) };
#[expect(
clippy::cast_precision_loss,
reason = "the count of rows in one group is well inside the exact range"
)]
let divisor = *seen as f64 * pow10(*scale) as f64;
Some(Answer::Real(total / divisor))
}
State::Scaled { total, seen, .. } => {
Some(if *seen { Answer::Whole(*total) } else { Answer::Null })
}
State::Extreme { .. } => None,
}
}
fn route(&self, ty: &LogicalType) -> Option<Route> {
let returns = Return::new(ty);
match &self.state {
State::Counted { .. } => (returns == Return::BigInt).then_some(Route::Whole),
State::Whole { returns: held, .. } => (returns == *held).then_some(Route::Whole),
State::Scaled { returns: held, scale, .. } => match ty {
LogicalType::Decimal { scale: column, .. }
if column == scale && returns == *held =>
{
Some(Route::Whole)
}
_ => None,
},
State::Real { returns: held, .. } | State::Mean { returns: held, .. } => {
match (returns, held) {
(Return::Float, Return::Float) => Some(Route::Float),
(Return::Double, Return::Double) => Some(Route::Double),
_ => None,
}
}
State::Extreme { .. } => None,
}
}
pub fn finish_offset(&self, offset: i64, rows: i64) -> Result<Value> {
let Value::HugeInt(total) = self.finish()? else {
return Ok(Value::Null);
};
let added = i128::from(offset).checked_mul(i128::from(rows)).ok_or_else(overflowed)?;
Ok(Value::HugeInt(total.checked_add(added).ok_or_else(overflowed)?))
}
}
pub fn finish_run(
states: &[Accumulator],
at: &[usize],
stride: usize,
offset: usize,
ty: &LogicalType,
) -> Result<Option<Vector>> {
let Some(first) = at.first() else {
return Ok(None);
};
let reach = |slot: usize| {
states
.get(slot * stride + offset)
.ok_or_else(|| Error::internal(format!("group {slot} has no state for call {offset}")))
};
let Some(route) = reach(*first)?.route(ty) else {
return Ok(None);
};
let mut valid = vec![true; at.len()];
let data = match route {
Route::Whole => {
let mut answers: Vec<i128> = Vec::with_capacity(at.len());
for (row, &slot) in at.iter().enumerate() {
match reach(slot)?.answer() {
Some(Answer::Whole(total)) => answers.push(total),
Some(Answer::Null) => {
valid[row] = false;
answers.push(0);
}
_ => return Ok(None),
}
}
narrow(&answers, &valid, ty)?
}
Route::Double | Route::Float => {
let mut answers: Vec<f64> = Vec::with_capacity(at.len());
for (row, &slot) in at.iter().enumerate() {
match reach(slot)?.answer() {
Some(Answer::Real(answer)) => answers.push(answer),
Some(Answer::Null) => {
valid[row] = false;
answers.push(0.0);
}
_ => return Ok(None),
}
}
if route == Route::Float {
#[expect(
clippy::cast_possible_truncation,
reason = "a declared FLOAT result is a FLOAT"
)]
Data::Float32(answers.iter().map(|&answer| answer as f32).collect())
} else {
Data::Float64(answers.into())
}
}
};
let vector = Vector::flat(ty.clone(), data)?;
Ok(Some(vector.with_validity(Validity::from_run(&valid))))
}
fn narrow(answers: &[i128], valid: &[bool], ty: &LogicalType) -> Result<Data> {
macro_rules! narrowed {
($variant:ident, $native:ty) => {{
let mut out: Vec<$native> = Vec::with_capacity(answers.len());
for (row, &total) in answers.iter().enumerate() {
if !valid[row] {
out.push(0);
continue;
}
out.push(<$native>::try_from(total).map_err(|_| {
Error::out_of_range(format!("a sum of {total} does not fit in {ty}"))
})?);
}
Data::$variant(out.into())
}};
}
Ok(match ty.physical() {
PhysicalType::Int8 => narrowed!(Int8, i8),
PhysicalType::Int16 => narrowed!(Int16, i16),
PhysicalType::Int32 => narrowed!(Int32, i32),
PhysicalType::Int64 => narrowed!(Int64, i64),
PhysicalType::Int128 => Data::Int128(answers.to_vec().into()),
PhysicalType::UInt8 => narrowed!(UInt8, u8),
PhysicalType::UInt16 => narrowed!(UInt16, u16),
PhysicalType::UInt32 => narrowed!(UInt32, u32),
PhysicalType::UInt64 => narrowed!(UInt64, u64),
PhysicalType::UInt128 => narrowed!(UInt128, u128),
other => {
return Err(Error::internal(format!(
"a whole aggregate answer cannot be written as {other:?}"
)));
}
})
}
pub fn update_scattered(
states: &mut [Accumulator],
slots: &[usize],
stride: usize,
offset: usize,
input: Option<&Vector>,
rows: usize,
) -> Result<()> {
if states.is_empty() {
return Ok(());
}
if slots.len() < rows {
return Err(Error::internal(format!(
"an aggregate handed {rows} rows and {} slots",
slots.len()
)));
}
let Some(first) = states.get(offset) else {
return Err(Error::internal(format!(
"an aggregate at {offset} of {} accumulators",
states.len()
)));
};
let kind = first.kind();
let extreme = match first.state {
State::Extreme { least, .. } => Some(least),
_ => None,
};
let into = Where { slots: &slots[..rows], stride, offset };
if kind == Kind::CountStar {
if few(states, into, rows, Live::All, Feed::Counted, |_| 0)? {
return Ok(());
}
for row in 0..rows {
let Some(index) = into.index(row) else { continue };
if let State::Counted { count, .. } = &mut states[index].state {
*count += 1;
}
}
return Ok(());
}
let Some(input) = input else {
return Err(Error::internal("an aggregate over 0 arguments".to_string()));
};
if input.len() < rows {
return Err(Error::internal(format!(
"an aggregate handed {rows} rows and a vector of {}",
input.len()
)));
}
let nulls = nulls_of(input);
if matches!(nulls, Validity::AllInvalid) {
return Ok(());
}
if let Some(least) = extreme {
if ranked_extremes(states, into, input, rows, &nulls, least)? {
return Ok(());
}
}
if extreme.is_some()
&& input.logical_type() == &LogicalType::Varchar
&& matches!(input.form(), Form::Flat | Form::Dictionary | Form::StringView | Form::Rle)
{
for row in 0..rows {
if !nulls.is_valid(row) {
continue;
}
let Some(index) = into.index(row) else { continue };
let bytes = input.try_bytes_at(row)?.ok_or_else(|| {
Error::internal("a valid varchar row had no borrowed text".to_string())
})?;
let State::Extreme { held, least } = &mut states[index].state else {
return Err(Error::internal("a string extreme into another state".to_string()));
};
match held {
Some(current) => {
let Value::Varchar(previous) = current.settle()? else {
return Err(Error::internal(
"a varchar extreme held another type".to_string(),
));
};
let better = if *least {
bytes < previous.as_bytes()
} else {
bytes > previous.as_bytes()
};
if better {
previous.clear();
previous.push_str(utf8(bytes)?);
}
}
None => {
let text = Value::Varchar(utf8(bytes)?.to_owned());
*held = Some(Box::new(Extremum::Held(text)));
}
}
}
return Ok(());
}
let Some(first) = states.get(offset) else {
return Err(Error::internal(format!(
"an aggregate at {offset} of {} accumulators",
states.len()
)));
};
let feed = feed_of(first, input.logical_type());
if let Some(feed) = feed {
if spread(states, into, input, rows, nulls.live(), feed)? {
return Ok(());
}
}
fallback::record(Kernel::Aggregate, input.form(), input.form());
for row in 0..rows {
let Some(index) = into.index(row) else { continue };
let value = input.try_value_at(row)?;
states[index].update(std::slice::from_ref(&value))?;
}
Ok(())
}
pub fn update_runs(
states: &mut [Accumulator],
runs: &[(usize, usize)],
stride: usize,
offset: usize,
input: Option<&Vector>,
rows: usize,
) -> Result<bool> {
if runs.last().map_or(0, |&(_, end)| end) != rows {
return Err(Error::internal(format!("runs that do not end at the {rows} rows given")));
}
let Some(first) = states.get(offset) else {
return Ok(false);
};
let group = |slot: usize| (slot != NOWHERE).then(|| slot * stride + offset);
if first.kind() == Kind::CountStar {
count_runs(states, runs, group);
return Ok(true);
}
let Some(input) = input else { return Ok(false) };
if input.form() != Form::Flat
|| input.len() < rows
|| !matches!(nulls_of(input), Validity::AllValid)
{
return Ok(false);
}
let Some(feed) = feed_of(first, input.logical_type()) else {
return Ok(false);
};
if matches!(feed, Feed::Counted) {
count_runs(states, runs, group);
return Ok(true);
}
let Some(data) = input.data().filter(|data| data.len() >= rows) else {
return Ok(false);
};
macro_rules! each {
($(($variant:ident, $native:ty, $zero:expr)),+ $(,)?) => {
match data {
$(Data::$variant(values) => {
let values = &values.as_slice()[..rows];
let mut start = 0;
for &(slot, end) in runs {
let run = &values[start..end];
let length = end - start;
start = end;
let Some(index) = group(slot) else { continue };
match (&mut states[index].state, feed) {
(
State::Whole { total, seen, .. }
| State::Scaled { total, seen, .. },
Feed::Total,
) => {
let sum = run_total(run).ok_or_else(overflowed)?;
*total = total.checked_add(sum).ok_or_else(overflowed)?;
*seen = true;
}
(
State::Mean { total, seen, exact, scale: held, .. },
Feed::Whole { scale },
) => {
*held = scale;
*seen += length as i64;
let sum = run_total(run).filter(|_| *exact);
match sum.and_then(|sum| total.checked_add(sum)) {
Some(sum) => *total = sum,
None => {
for &value in run {
let number = i128::from(value);
match total.checked_add(number).filter(|_| *exact) {
Some(sum) => *total = sum,
None => widened(total, exact, number),
}
}
}
}
}
_ => return Err(Error::internal(
"a run into a state its feed does not fit".to_string(),
)),
}
}
})+
_ => return Ok(false),
}
};
}
match feed {
Feed::Total | Feed::Whole { .. } => {}
Feed::Counted | Feed::Real { .. } | Feed::Extreme(_) => return Ok(false),
}
rudb_vector::for_each_layout!(exact, each);
Ok(true)
}
fn count_runs(
states: &mut [Accumulator],
runs: &[(usize, usize)],
group: impl Fn(usize) -> Option<usize>,
) {
let mut start = 0;
for &(slot, end) in runs {
if let Some(index) = group(slot) {
if let State::Counted { count, .. } = &mut states[index].state {
*count += (end - start) as i64;
}
}
start = end;
}
}
fn run_total<T: Copy + Into<i128>>(run: &[T]) -> Option<i128> {
if size_of::<T>() < size_of::<i128>() {
Some(run.iter().map(|&value| value.into()).sum())
} else {
run.iter().try_fold(0_i128, |sum, &value| sum.checked_add(value.into()))
}
}
fn ranked_extremes(
states: &mut [Accumulator],
into: Where<'_>,
input: &Vector,
rows: usize,
nulls: &Validity,
least: bool,
) -> Result<bool> {
let Some((codes, dictionary)) = input.shared_dictionary_parts() else { return Ok(false) };
let Some(ranks) = dictionary.code_ranks() else { return Ok(false) };
if codes.len() < rows {
return Ok(false);
}
for (row, &code) in codes.iter().enumerate().take(rows) {
if !nulls.is_valid(row) {
continue;
}
let Some(index) = into.index(row) else { continue };
let Some(&rank) = ranks.get(code as usize) else { return Ok(false) };
let State::Extreme { held, .. } = &mut states[index].state else {
return Err(Error::internal("a ranked extreme into another state".to_string()));
};
match held {
Some(current) => current.offer(dictionary, code, rank, least)?,
None => {
let kept = Extremum::Ranked { dictionary: dictionary.clone(), code, rank };
*held = Some(Box::new(kept));
}
}
}
Ok(true)
}
fn ranked_extreme(
state: &mut State,
input: &Vector,
rows: usize,
nulls: &Validity,
least: bool,
) -> Result<bool> {
let Some((codes, dictionary)) = input.shared_dictionary_parts() else { return Ok(false) };
let Some(ranks) = dictionary.code_ranks() else { return Ok(false) };
let Some(codes) = codes.get(..rows) else { return Ok(false) };
let mut winner: Option<(u32, u32)> = None;
for (row, &code) in codes.iter().enumerate() {
if !nulls.is_valid(row) {
continue;
}
let Some(&rank) = ranks.get(code as usize) else { return Ok(false) };
if winner.is_none_or(|(_, held)| if least { rank < held } else { rank > held }) {
winner = Some((code, rank));
}
}
let State::Extreme { held, .. } = state else {
return Err(Error::internal("a ranked extreme into another state".to_string()));
};
if let Some((code, rank)) = winner {
match held {
Some(current) => current.offer(dictionary, code, rank, least)?,
None => {
let kept = Extremum::Ranked { dictionary: dictionary.clone(), code, rank };
*held = Some(Box::new(kept));
}
}
}
Ok(true)
}
pub fn settle_extremes(
states: &mut [Accumulator],
slots: Option<&[usize]>,
groups: usize,
stride: usize,
) -> Result<()> {
type Wanted = (usize, u32, usize);
let mut dictionaries: Vec<Arc<Vector>> = Vec::new();
let mut wanted: Vec<Wanted> = Vec::new();
let emitted = slots.map_or(groups, <[usize]>::len);
for index in 0..emitted {
let slot = slots.map_or(index, |slots| slots[index]);
for call in 0..stride {
let at = slot * stride + call;
let Some(state) = states.get(at) else {
return Err(Error::internal("an extreme to settle is out of range".to_string()));
};
let State::Extreme { held: Some(held), .. } = &state.state else { continue };
let Extremum::Ranked { dictionary, code, .. } = held.as_ref() else { continue };
let which = match dictionaries.iter().position(|kept| Arc::ptr_eq(kept, dictionary)) {
Some(found) => found,
None => {
dictionaries.push(Arc::clone(dictionary));
dictionaries.len() - 1
}
};
wanted.push((which, *code, at));
}
}
if wanted.is_empty() {
return Ok(());
}
wanted.sort_unstable();
let mut start = 0;
while start < wanted.len() {
let mut end = start;
while end < wanted.len() && wanted[end].0 == wanted[start].0 {
end += 1;
}
let dictionary = Arc::clone(&dictionaries[wanted[start].0]);
settle_swept(states, &dictionary, &wanted[start..end])?;
start = end;
}
Ok(())
}
fn settle_swept(
states: &mut [Accumulator],
dictionary: &Vector,
wanted: &[(usize, u32, usize)],
) -> Result<()> {
let mut at = 0;
let mut found: Vec<(usize, Value)> = Vec::new();
while at < wanted.len() {
let first = wanted[at].1 as usize;
let mut cursor = at;
found.clear();
dictionary.sweep_text(first, dictionary.len(), &mut |index: usize, text: &[u8]| {
while cursor < wanted.len() && wanted[cursor].1 as usize == index {
found.push((wanted[cursor].2, dictionary.value_of(text)));
cursor += 1;
}
Ok(())
})?;
if cursor <= at {
return Err(Error::internal(
"a dictionary sweep passed the code it began at".to_string(),
));
}
for (slot, value) in found.drain(..) {
let Some(state) = states.get_mut(slot) else {
return Err(Error::internal("an extreme to settle is out of range".to_string()));
};
let State::Extreme { held: Some(held), .. } = &mut state.state else { continue };
**held = Extremum::Held(value);
}
at = cursor;
}
Ok(())
}
fn utf8(bytes: &[u8]) -> Result<&str> {
std::str::from_utf8(bytes)
.map_err(|error| Error::conversion(format!("invalid UTF-8 in VARCHAR: {error}")))
}
#[derive(Clone, Copy)]
struct Where<'w> {
slots: &'w [usize],
stride: usize,
offset: usize,
}
impl Where<'_> {
fn index(self, row: usize) -> Option<usize> {
let slot = self.slots[row];
(slot != NOWHERE).then(|| slot * self.stride + self.offset)
}
}
#[derive(Clone, Copy)]
enum Feed {
Counted,
Total,
Whole { scale: u8 },
Real { scale: u8 },
Extreme(bool),
}
fn feed_of(first: &Accumulator, ty: &LogicalType) -> Option<Feed> {
match (&first.state, ty) {
(State::Counted { .. }, _) => Some(Feed::Counted),
(State::Whole { .. }, ty) if ty.is_integer() => Some(Feed::Total),
(State::Whole { .. }, _) => None,
(State::Scaled { scale, .. }, LogicalType::Decimal { scale: held, .. })
if held == scale =>
{
Some(Feed::Total)
}
(State::Scaled { .. }, _) => None,
(State::Mean { .. }, ty) if ty.is_integer() => Some(Feed::Whole { scale: 0 }),
(State::Mean { .. }, LogicalType::Decimal { scale, .. }) => {
Some(Feed::Whole { scale: *scale })
}
(State::Mean { .. } | State::Real { .. }, ty) if addable(ty) => {
Some(Feed::Real { scale: decimal_scale(ty) })
}
(State::Mean { .. } | State::Real { .. }, _) => None,
(State::Extreme { .. }, ty)
if ty.is_integer()
|| matches!(
ty,
LogicalType::Decimal { .. }
| LogicalType::Date
| LogicalType::Time
| LogicalType::Timestamp
) =>
{
Some(Feed::Extreme(first.kind() == Kind::Min))
}
(State::Extreme { .. }, _) => None,
}
}
macro_rules! live_rows {
($nulls:expr, $rows:expr, |$row:ident| $body:block) => {
match $nulls {
Live::None => {}
Live::All => {
for $row in 0..$rows $body
}
Live::Mask(mask) => {
let words = mask.words();
let covered = $rows.min(words.len().saturating_mul(u64::BITS as usize));
for $row in 0..covered {
if words[$row / 64] >> ($row % 64) & 1 == 1 $body
}
}
}
};
}
const FEW: usize = 256;
fn few<V: Fn(usize) -> i128>(
states: &mut [Accumulator],
into: Where<'_>,
rows: usize,
nulls: Live<'_>,
feed: Feed,
value: V,
) -> Result<bool> {
let groups = states.len().checked_div(into.stride).unwrap_or(usize::MAX);
if groups > FEW
|| groups.saturating_mul(4) > rows
|| !matches!(feed, Feed::Counted | Feed::Total | Feed::Whole { .. })
{
return Ok(false);
}
let mut totals = [0_i128; FEW];
let mut counts = [0_i64; FEW];
live_rows!(nulls, rows, |row| {
let slot = into.slots[row];
if slot == NOWHERE {
continue;
}
totals[slot] = totals[slot].wrapping_add(value(row));
counts[slot] += 1;
});
for (slot, (&count, &number)) in counts.iter().zip(&totals).take(groups).enumerate() {
if count == 0 {
continue;
}
let index = slot * into.stride + into.offset;
match (&mut states[index].state, feed) {
(State::Counted { count: held, .. }, Feed::Counted) => *held += count,
(State::Whole { total, seen, .. } | State::Scaled { total, seen, .. }, Feed::Total) => {
*total = total.checked_add(number).ok_or_else(overflowed)?;
*seen = true;
}
(State::Mean { total, seen, exact, scale: held, .. }, Feed::Whole { scale }) => {
*held = scale;
match total.checked_add(number).filter(|_| *exact) {
Some(sum) => *total = sum,
None => widened(total, exact, number),
}
*seen += count;
}
_ => return Err(Error::internal("a total per group into another state".to_string())),
}
}
Ok(true)
}
fn spread(
states: &mut [Accumulator],
into: Where<'_>,
input: &Vector,
rows: usize,
nulls: Live<'_>,
feed: Feed,
) -> Result<bool> {
if matches!(feed, Feed::Counted) {
if few(states, into, rows, nulls, feed, |_| 0)? {
return Ok(true);
}
live_rows!(nulls, rows, |row| {
let Some(index) = into.index(row) else { continue };
if let State::Counted { count, .. } = &mut states[index].state {
*count += 1;
}
});
return Ok(true);
}
match input.form() {
Form::Flat => {
let Some(data) = input.data() else { return Ok(false) };
if data.len() < rows {
return Ok(false);
}
let run = Run { input, data, rows, nulls };
scatter(states, into, &run, identity, feed)
}
Form::Dictionary | Form::Rle => {
let Some((codes, values)) = input.positions() else { return Ok(false) };
if codes.len() < rows {
return Ok(false);
}
let codes = &codes[..rows];
let Some(data) = values.data() else {
let Some(packed) = values.packed_parts() else { return Ok(false) };
return packed_into(
states,
into,
input,
&packed,
|index| codes[index] as usize,
rows,
nulls,
feed,
);
};
let run = Run { input, data, rows, nulls };
scatter(states, into, &run, |index| codes[index] as usize, feed)
}
Form::BitPacked => {
let Some(packed) = input.packed_parts() else { return Ok(false) };
packed_into(states, into, input, &packed, identity, rows, nulls, feed)
}
_ => Ok(false),
}
}
struct Run<'r> {
input: &'r Vector,
data: &'r Data,
rows: usize,
nulls: Live<'r>,
}
fn scatter<M: Fn(usize) -> usize>(
states: &mut [Accumulator],
into: Where<'_>,
run: &Run<'_>,
at: M,
feed: Feed,
) -> Result<bool> {
match feed {
Feed::Counted => Ok(true),
Feed::Total => total_into(states, into, run, at),
Feed::Whole { scale } => mean_into(states, into, run, at, scale),
Feed::Real { scale } => real_into(states, into, run, at, scale),
Feed::Extreme(least) => extreme_into(states, into, run, at, least),
}
}
fn total_into<M: Fn(usize) -> usize>(
states: &mut [Accumulator],
into: Where<'_>,
run: &Run<'_>,
at: M,
) -> Result<bool> {
macro_rules! each {
($(($variant:ident, $native:ty, $zero:expr)),+ $(,)?) => {
match run.data {
$(Data::$variant(values) => {
let values = values.as_slice();
if size_of::<$native>() < 16
&& few(states, into, run.rows, run.nulls, Feed::Total, |row| {
i128::from(values[at(row)])
})?
{
return Ok(true);
}
live_rows!(run.nulls, run.rows, |row| {
let Some(index) = into.index(row) else { continue };
let (State::Whole { total, seen, .. }
| State::Scaled { total, seen, .. }) = &mut states[index].state
else {
return Err(Error::internal("an exact total into another".to_string()));
};
*total = total
.checked_add(i128::from(values[at(row)]))
.ok_or_else(overflowed)?;
*seen = true;
});
})+
_ => return Ok(false),
}
};
}
rudb_vector::for_each_layout!(exact, each);
Ok(true)
}
fn mean_into<M: Fn(usize) -> usize>(
states: &mut [Accumulator],
into: Where<'_>,
run: &Run<'_>,
at: M,
scale: u8,
) -> Result<bool> {
macro_rules! each {
($(($variant:ident, $native:ty, $zero:expr)),+ $(,)?) => {
match run.data {
$(Data::$variant(values) => {
let values = values.as_slice();
if size_of::<$native>() < 16
&& few(states, into, run.rows, run.nulls, Feed::Whole { scale }, |row| {
i128::from(values[at(row)])
})?
{
return Ok(true);
}
live_rows!(run.nulls, run.rows, |row| {
let Some(index) = into.index(row) else { continue };
let number = i128::from(values[at(row)]);
let State::Mean { total, seen, exact, scale: held, .. } =
&mut states[index].state
else {
return Err(Error::internal("a mean into another".to_string()));
};
*held = scale;
match total.checked_add(number).filter(|_| *exact) {
Some(sum) => *total = sum,
None => widened(total, exact, number),
}
*seen += 1;
});
})+
_ => return Ok(false),
}
};
}
rudb_vector::for_each_layout!(exact, each);
Ok(true)
}
#[expect(
clippy::cast_precision_loss,
reason = "a wide integer past 2^53 losing digits is what a double is, and this is the float path"
)]
#[expect(clippy::too_many_arguments, reason = "the flat path's Run plus the packing it replaces")]
fn packed_into<M: Fn(usize) -> usize>(
states: &mut [Accumulator],
into: Where<'_>,
input: &Vector,
packed: &rudb_vector::Packed<'_>,
at: M,
rows: usize,
nulls: Live<'_>,
feed: Feed,
) -> Result<bool> {
let wide = input.logical_type().physical() == PhysicalType::UInt128;
let scale = decimal_scale(input.logical_type());
let base = packed.base();
if !wide
&& i64::try_from(base).is_ok()
&& few(states, into, rows, nulls, feed, |row| base + i128::from(packed.code(at(row))))?
{
return Ok(true);
}
match feed {
Feed::Counted => Ok(true),
Feed::Total => {
if wide {
return Ok(false);
}
live_rows!(nulls, rows, |row| {
let Some(index) = into.index(row) else { continue };
let (State::Whole { total, seen, .. } | State::Scaled { total, seen, .. }) =
&mut states[index].state
else {
return Err(Error::internal("an exact total into another".to_string()));
};
*total = total
.checked_add(base + i128::from(packed.code(at(row))))
.ok_or_else(overflowed)?;
*seen = true;
});
Ok(true)
}
Feed::Whole { scale } => {
if wide {
return Ok(false);
}
live_rows!(nulls, rows, |row| {
let Some(index) = into.index(row) else { continue };
let number = base + i128::from(packed.code(at(row)));
let State::Mean { total, seen, exact, scale: held, .. } = &mut states[index].state
else {
return Err(Error::internal("a mean into another".to_string()));
};
*held = scale;
match total.checked_add(number).filter(|_| *exact) {
Some(sum) => *total = sum,
None => widened(total, exact, number),
}
*seen += 1;
});
Ok(true)
}
Feed::Real { scale } => {
let factor = pow10(scale) as f64;
let scaled = scale != 0;
live_rows!(nulls, rows, |row| {
let Some(index) = into.index(row) else { continue };
let number = (base + i128::from(packed.code(at(row)))) as f64;
fold_real(&mut states[index], if scaled { number / factor } else { number });
});
Ok(true)
}
Feed::Extreme(least) => {
if wide {
return Ok(false);
}
live_rows!(nulls, rows, |row| {
let Some(index) = into.index(row) else { continue };
let number = base + i128::from(packed.code(at(row)));
let State::Extreme { held, .. } = &mut states[index].state else {
return Err(Error::internal("an extreme into a total".to_string()));
};
let replace = match held {
None => true,
Some(current) => {
let current: &Value = current.settle()?;
let against = mark(current, scale).ok_or_else(|| not_narrow(current))?;
if least { number < against } else { number > against }
}
};
if replace {
let value = input.try_value_at(row)?;
*held = Some(Box::new(Extremum::Held(value)));
}
});
Ok(true)
}
}
}
#[cold]
fn widened(total: &mut i128, exact: &mut bool, number: i128) {
let real = if *exact { exactly(*total) } else { mean_real(*total) };
*total = mean_bits(real + exactly(number));
*exact = false;
}
#[expect(
clippy::cast_precision_loss,
reason = "a wide integer past 2^53 losing digits is what a double is, and this is the float path"
)]
fn real_into<M: Fn(usize) -> usize>(
states: &mut [Accumulator],
into: Where<'_>,
run: &Run<'_>,
at: M,
scale: u8,
) -> Result<bool> {
let factor = pow10(scale) as f64;
let scaled = scale != 0;
macro_rules! each {
($(($variant:ident, $native:ty, $zero:expr)),+ $(,)?) => {
match run.data {
$(Data::$variant(values) => each!(@run values, |number| number as f64),)+
Data::Float32(values) => each!(@run values, f64::from),
Data::Float64(values) => each!(@run values, |number: f64| number),
_ => return Ok(false),
}
};
(@run $values:expr, $convert:expr) => {{
let values = $values.as_slice();
let convert = $convert;
live_rows!(run.nulls, run.rows, |row| {
let Some(index) = into.index(row) else { continue };
let number = convert(values[at(row)]);
fold_real(&mut states[index], if scaled { number / factor } else { number });
});
}};
}
rudb_vector::for_each_layout!(integer, each);
Ok(true)
}
fn fold_real(into: &mut Accumulator, number: f64) {
match &mut into.state {
State::Real { total, seen, .. } => {
*total += number;
*seen += 1;
}
State::Mean { total, seen, exact, .. } => {
let real = if *exact { exactly(*total) } else { mean_real(*total) };
*total = mean_bits(real + number);
*exact = false;
*seen += 1;
}
_ => {}
}
}
fn extreme_into<M: Fn(usize) -> usize>(
states: &mut [Accumulator],
into: Where<'_>,
run: &Run<'_>,
at: M,
least: bool,
) -> Result<bool> {
let scale = decimal_scale(run.input.logical_type());
macro_rules! each {
($(($variant:ident, $native:ty, $zero:expr)),+ $(,)?) => {
match run.data {
$(Data::$variant(values) => {
let values = values.as_slice();
live_rows!(run.nulls, run.rows, |row| {
let Some(index) = into.index(row) else { continue };
let number = i128::from(values[at(row)]);
let State::Extreme { held, .. } = &mut states[index].state else {
return Err(Error::internal("an extreme into a total".to_string()));
};
let replace = match held {
None => true,
Some(current) => {
let current: &Value = current.settle()?;
let against =
mark(current, scale).ok_or_else(|| not_narrow(current))?;
if least { number < against } else { number > against }
}
};
if replace {
let value = run.input.try_value_at(row)?;
*held = Some(Box::new(Extremum::Held(value)));
}
});
})+
_ => return Ok(false),
}
};
}
rudb_vector::for_each_layout!(exact, each);
Ok(true)
}
fn not_narrow(value: &Value) -> Error {
Error::not_implemented(format!("summing a {}", value.logical_type()))
}
fn mark(value: &Value, scale: u8) -> Option<i128> {
match *value {
Value::Decimal { unscaled, scale: held, .. } if held == scale => Some(unscaled),
Value::Decimal { .. } => None,
Value::Date(days) => Some(i128::from(days)),
Value::Time(micros) | Value::Timestamp(micros) => Some(i128::from(micros)),
_ => integral(value),
}
}
#[expect(
clippy::cast_precision_loss,
reason = "a total past 2^53 rounding once here is the definition of a double result"
)]
fn exactly(total: i128) -> f64 {
total as f64
}
fn mean_bits(total: f64) -> i128 {
i128::from(total.to_bits())
}
fn mean_real(bits: i128) -> f64 {
f64::from_bits(bits as u64)
}
fn approximate_or_error(value: &Value) -> Result<f64> {
crate::number::approximate(value).ok_or_else(|| not_narrow(value))
}
fn at_scale(value: &Value, scale: u8) -> Option<i128> {
match *value {
Value::Decimal { unscaled, scale: held, .. } => rescale(unscaled, held, scale),
_ => integral(value).and_then(|whole| whole.checked_mul(pow10(scale))),
}
}
fn overflowed() -> Error {
Error::out_of_range("Overflow in the running total of a sum".to_string())
}
fn overlong() -> Error {
Error::out_of_range("more rows in one vector than a count can hold".to_string())
}
fn addable(ty: &LogicalType) -> bool {
ty.is_integer()
|| matches!(ty, LogicalType::Decimal { .. } | LogicalType::Float | LogicalType::Double)
}
fn decimal_scale(ty: &LogicalType) -> u8 {
match *ty {
LogicalType::Decimal { scale, .. } => scale,
_ => 0,
}
}
#[derive(Clone, Copy)]
enum Want {
Whole,
Real { scale: u8, from: f64 },
Extreme(bool),
}
enum Contribution {
Whole(i128),
Real { total: f64, seen: i64 },
Extreme(Option<usize>),
}
fn gather(input: &Vector, rows: usize, nulls: &Validity, want: Want) -> Option<Contribution> {
match input.form() {
Form::Flat => {
let data = input.data()?;
if data.len() < rows {
return None;
}
collect::<true, _>(data, identity, rows, nulls, want)
}
Form::Dictionary | Form::Rle => {
let (codes, values) = input.positions()?;
let codes = codes.get(..rows)?;
let Some(data) = values.data() else {
if let Some(packed) = values.packed_parts() {
return from_packed(&packed, |row| codes[row] as usize, rows, nulls, want);
}
return match want {
Want::Extreme(least) => {
extreme_bytes(values, codes, nulls, least).map(Contribution::Extreme)
}
_ => None,
};
};
if let (Want::Whole, Validity::AllValid) = (want, nulls) {
if let Some(total) = tally(data, codes) {
return Some(Contribution::Whole(total));
}
}
collect::<false, _>(data, |index| codes[index] as usize, rows, nulls, want)
}
Form::BitPacked => from_packed(&input.packed_parts()?, identity, rows, nulls, want),
_ => None,
}
}
fn from_packed<M: Fn(usize) -> usize>(
packed: &rudb_vector::Packed<'_>,
at: M,
rows: usize,
nulls: &Validity,
want: Want,
) -> Option<Contribution> {
match want {
Want::Whole => {
let mut total = 0_i128;
for row in 0..rows {
if nulls.is_valid(row) {
total += packed.base() + i128::from(packed.code(at(row)));
}
}
Some(Contribution::Whole(total))
}
Want::Real { scale, from } => {
let factor = pow10(scale) as f64;
let scaled = scale != 0;
let mut total = from;
let mut seen = 0_i64;
for row in 0..rows {
if nulls.is_valid(row) {
let number = (packed.base() + i128::from(packed.code(at(row)))) as f64;
total += if scaled { number / factor } else { number };
seen += 1;
}
}
Some(Contribution::Real { total, seen })
}
Want::Extreme(least) => {
let mut found: Option<(usize, u64)> = None;
for row in 0..rows {
if !nulls.is_valid(row) {
continue;
}
let code = packed.code(at(row));
if found.is_none_or(|(_, held)| if least { code < held } else { code > held }) {
found = Some((row, code));
}
}
Some(Contribution::Extreme(found.map(|(row, _)| row)))
}
}
}
fn extreme_bytes(
values: &Vector,
codes: &[u32],
nulls: &Validity,
least: bool,
) -> Option<Option<usize>> {
if !matches!(values.logical_type(), LogicalType::Varchar | LogicalType::Blob) {
return None;
}
if let Some(ranks) = values.code_ranks() {
return extreme_ranked(ranks, codes, nulls, least);
}
let mut winner: Option<(usize, u32)> = None;
let mut best: Vec<u8> = Vec::new();
for (row, &code) in codes.iter().enumerate() {
if !nulls.is_valid(row) {
continue;
}
if let Some((_, held)) = winner {
if held == code {
continue;
}
}
let candidate = values.try_bytes_at(code as usize).ok()??;
let ahead = match winner {
None => true,
Some(_) => {
let ordering = candidate.cmp(best.as_slice());
if least { ordering.is_lt() } else { ordering.is_gt() }
}
};
if ahead {
best.clear();
best.extend_from_slice(candidate);
winner = Some((row, code));
}
}
Some(winner.map(|(row, _)| row))
}
fn extreme_ranked(
ranks: &[u32],
codes: &[u32],
nulls: &Validity,
least: bool,
) -> Option<Option<usize>> {
let mut winner: Option<(usize, u32)> = None;
for (row, &code) in codes.iter().enumerate() {
if !nulls.is_valid(row) {
continue;
}
let &rank = ranks.get(code as usize)?;
if winner.is_none_or(|(_, held)| if least { rank < held } else { rank > held }) {
winner = Some((row, rank));
}
}
Some(winner.map(|(row, _)| row))
}
const TALLY_LIMIT: usize = 256;
fn tally(data: &Data, codes: &[u32]) -> Option<i128> {
match data.len() {
0..=8 => tally_into::<8>(data, codes),
9..=32 => tally_into::<32>(data, codes),
33..=128 => tally_into::<128>(data, codes),
129..=TALLY_LIMIT => tally_into::<TALLY_LIMIT>(data, codes),
_ => None,
}
}
fn tally_into<const SLOTS: usize>(data: &Data, codes: &[u32]) -> Option<i128> {
macro_rules! padded {
($(($variant:ident, $native:ty, $zero:expr)),+ $(,)?) => {
match data {
$(Data::$variant(values) => padded!(@run values, $zero),)+
_ => return None,
}
};
(@run $values:expr, $zero:expr) => {{
let values = $values.as_slice();
if values.len() > SLOTS {
return None;
}
let mut table = [$zero; SLOTS];
table[..values.len()].copy_from_slice(values);
let mut total: i128 = 0;
for &code in codes {
total += i128::from(table[code as usize & (SLOTS - 1)]);
}
total
}};
}
Some(rudb_vector::for_each_layout!(narrow, padded))
}
fn straight<const DIRECT: bool, T>(values: &[T], rows: usize) -> Option<&[T]> {
if DIRECT { values.get(..rows) } else { None }
}
fn collect<const DIRECT: bool, M: Fn(usize) -> usize>(
data: &Data,
at: M,
rows: usize,
nulls: &Validity,
want: Want,
) -> Option<Contribution> {
match want {
Want::Whole => whole_sum::<DIRECT, M>(data, at, rows, nulls).map(Contribution::Whole),
Want::Real { scale, from } => real_sum::<DIRECT, M>(data, at, rows, nulls, scale, from),
Want::Extreme(least) => {
extreme::<DIRECT, M>(data, at, rows, nulls, least).map(Contribution::Extreme)
}
}
}
fn whole_sum<const DIRECT: bool, M: Fn(usize) -> usize>(
data: &Data,
at: M,
rows: usize,
nulls: &Validity,
) -> Option<i128> {
macro_rules! summed {
($(($variant:ident, $native:ty, $zero:expr)),+ $(,)?) => {
match data {
$(Data::$variant(values) => summed!(@run values),)+
Data::Int128(values) => wide_sum::<DIRECT, _>(values.as_slice(), &at, rows, nulls)?,
_ => return None,
}
};
(@run $values:expr) => {{
let values = $values.as_slice();
let run = straight::<DIRECT, _>(values, rows);
let mut total: i128 = 0;
match nulls {
Validity::AllValid => match run {
Some(run) => {
for &value in run {
total += i128::from(value);
}
}
None => {
for index in 0..rows {
total += i128::from(values[at(index)]);
}
}
},
Validity::AllInvalid => {}
Validity::Mask(mask) => {
for start in (0..rows).step_by(64) {
let word = mask.word(start / 64);
for index in start..(start + 64).min(rows) {
let number = match run {
Some(run) => i128::from(run[index]),
None => i128::from(values[at(index)]),
};
total += if word >> (index - start) & 1 == 1 { number } else { 0 };
}
}
}
}
total
}};
}
Some(rudb_vector::for_each_layout!(narrow, summed))
}
fn wide_sum<const DIRECT: bool, M: Fn(usize) -> usize>(
values: &[i128],
at: M,
rows: usize,
nulls: &Validity,
) -> Option<i128> {
let run = straight::<DIRECT, _>(values, rows);
let mut total: i128 = 0;
for index in 0..rows {
if !nulls.is_valid(index) {
continue;
}
let number = match run {
Some(run) => run[index],
None => values[at(index)],
};
total = total.checked_add(number)?;
}
Some(total)
}
#[expect(
clippy::cast_precision_loss,
reason = "a wide integer past 2^53 losing digits is what a double is, and this is the float path"
)]
fn real_sum<const DIRECT: bool, M: Fn(usize) -> usize>(
data: &Data,
at: M,
rows: usize,
nulls: &Validity,
scale: u8,
from: f64,
) -> Option<Contribution> {
let factor = pow10(scale) as f64;
let scaled = scale != 0;
let all = i64::try_from(rows).ok()?;
macro_rules! added {
($(($variant:ident, $native:ty, $zero:expr)),+ $(,)?) => {
match data {
$(Data::$variant(values) => added!(@run values, |number| number as f64),)+
Data::Float32(values) => added!(@run values, f64::from),
Data::Float64(values) => added!(@run values, |number: f64| number),
_ => return None,
}
};
(@run $values:expr, $convert:expr) => {{
let values = $values.as_slice();
let convert = $convert;
let run = straight::<DIRECT, _>(values, rows);
let mut total = from;
let mut seen: i64 = 0;
match nulls {
Validity::AllValid => {
match run {
Some(run) => {
for &value in run {
let number = convert(value);
total += if scaled { number / factor } else { number };
}
}
None => {
for index in 0..rows {
let number = convert(values[at(index)]);
total += if scaled { number / factor } else { number };
}
}
}
seen = all;
}
Validity::AllInvalid => {}
Validity::Mask(mask) => {
for start in (0..rows).step_by(64) {
let word = mask.word(start / 64);
for index in start..(start + 64).min(rows) {
if word >> (index - start) & 1 == 0 {
continue;
}
let number = match run {
Some(run) => convert(run[index]),
None => convert(values[at(index)]),
};
total += if scaled { number / factor } else { number };
seen += 1;
}
}
}
}
(total, seen)
}};
}
let (total, seen) = rudb_vector::for_each_layout!(integer, added);
Some(Contribution::Real { total, seen })
}
fn extreme<const DIRECT: bool, M: Fn(usize) -> usize>(
data: &Data,
at: M,
rows: usize,
nulls: &Validity,
least: bool,
) -> Option<Option<usize>> {
macro_rules! best {
($(($variant:ident, $native:ty, $zero:expr)),+ $(,)?) => {
match data {
$(Data::$variant(values) => best!(@run values),)+
_ => return None,
}
};
(@run $values:expr) => {{
let values = $values.as_slice();
let run = straight::<DIRECT, _>(values, rows);
let mut held = usize::MAX;
let mut mark: i128 = 0;
match nulls {
Validity::AllValid => match run {
Some(run) if !run.is_empty() => {
mark = i128::from(run[0]);
held = 0;
for (index, &value) in run.iter().enumerate().skip(1) {
let number = i128::from(value);
let win = if least { number < mark } else { number > mark };
if win {
mark = number;
held = index;
}
}
}
Some(_) => {}
None => {
if rows > 0 {
mark = i128::from(values[at(0)]);
held = 0;
for index in 1..rows {
let number = i128::from(values[at(index)]);
let win = if least { number < mark } else { number > mark };
if win {
mark = number;
held = index;
}
}
}
}
},
Validity::AllInvalid => {}
Validity::Mask(mask) => {
for start in (0..rows).step_by(64) {
let word = mask.word(start / 64);
for index in start..(start + 64).min(rows) {
if word >> (index - start) & 1 == 0 {
continue;
}
let number = match run {
Some(run) => i128::from(run[index]),
None => i128::from(values[at(index)]),
};
let win = if least { number < mark } else { number > mark };
if held == usize::MAX || win {
mark = number;
held = index;
}
}
}
}
}
(held != usize::MAX).then_some(held)
}};
}
Some(rudb_vector::for_each_layout!(exact, best))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_grouped_accumulator_does_not_carry_a_full_logical_type() {
assert!(size_of::<Accumulator>() <= 32, "{} bytes", size_of::<Accumulator>());
}
fn run(name: &str, returns: &LogicalType, rows: &[Value]) -> Value {
let mut accumulator = Accumulator::new(name, returns).expect("a known aggregate");
for row in rows {
accumulator.update(std::slice::from_ref(row)).expect("accumulates");
}
accumulator.finish().expect("finishes")
}
#[test]
fn count_star_counts_rows_and_count_counts_values() {
let mut stars = Accumulator::new("count_star", &LogicalType::BigInt).expect("known");
for _ in 0..3 {
stars.update(&[]).expect("no arguments");
}
assert_eq!(stars.finish().expect("finishes"), Value::BigInt(3));
let counted = run(
"count",
&LogicalType::BigInt,
&[Value::Integer(1), Value::Null, Value::Integer(3)],
);
assert_eq!(counted, Value::BigInt(2));
}
#[test]
fn a_sum_of_nothing_is_null_and_a_count_of_nothing_is_zero() {
assert_eq!(run("sum", &LogicalType::HugeInt, &[]), Value::Null);
assert_eq!(run("sum", &LogicalType::HugeInt, &[Value::Null]), Value::Null);
assert_eq!(run("count", &LogicalType::BigInt, &[]), Value::BigInt(0));
assert_eq!(run("count_star", &LogicalType::BigInt, &[]), Value::BigInt(0));
}
#[test]
fn a_sum_of_integers_accumulates_wider_than_it_reads() {
let rows = vec![Value::Integer(i32::MAX); 4];
let total = run("sum", &LogicalType::HugeInt, &rows);
assert_eq!(total, Value::HugeInt(i128::from(i32::MAX) * 4));
}
#[test]
fn an_average_divides_by_the_rows_it_saw_rather_than_the_rows_there_were() {
let average =
run("avg", &LogicalType::Double, &[Value::Integer(1), Value::Null, Value::Integer(3)]);
assert_eq!(average, Value::Double(2.0));
}
const WIDE: [i64; 4] = [435090932899640449, 435090932899640450, 1000003, 999999999999999999];
fn wide_mean() -> f64 {
exactly(WIDE.iter().map(|&number| i128::from(number)).sum()) / 4.0
}
fn wide_values() -> Vec<Value> {
WIDE.iter().map(|&number| Value::BigInt(number)).collect()
}
#[test]
fn an_average_of_whole_numbers_adds_them_up_exactly_and_divides_once() {
let mut running = 0.0_f64;
for value in wide_values() {
running += crate::number::approximate(&value).expect("a number");
}
assert_ne!(running / 4.0, wide_mean(), "the two ways of averaging have to differ here");
assert_eq!(run("avg", &LogicalType::Double, &wide_values()), Value::Double(wide_mean()));
}
#[test]
fn the_vector_path_averages_whole_numbers_exactly_as_well() {
let values = wide_values();
let vector = Vector::from_values(LogicalType::BigInt, &values).expect("a vector of these");
let mut accumulator = Accumulator::new("avg", &LogicalType::Double).expect("a known one");
accumulator.update_run(std::slice::from_ref(&vector), values.len()).expect("folds them in");
assert_eq!(accumulator.finish().expect("finishes"), Value::Double(wide_mean()));
}
#[test]
fn an_average_of_doubles_is_the_running_total_the_float_path_produces() {
let rows = [Value::Double(1e17), Value::Double(1.0), Value::Double(3.0)];
let mut running = 0.0_f64;
for value in &rows {
running += crate::number::approximate(value).expect("a number");
}
assert_eq!(run("avg", &LogicalType::Double, &rows), Value::Double(running / 3.0));
}
#[test]
fn min_and_max_skip_nulls_and_keep_the_value_rather_than_a_number() {
let smallest = run(
"min",
&LogicalType::Varchar,
&[Value::Varchar("b".into()), Value::Null, Value::Varchar("a".into())],
);
assert_eq!(smallest, Value::Varchar("a".into()));
let largest = run(
"max",
&LogicalType::Integer,
&[Value::Integer(1), Value::Integer(7), Value::Integer(3)],
);
assert_eq!(largest, Value::Integer(7));
}
#[test]
fn a_decimal_sums_at_its_own_scale() {
let ty = LogicalType::decimal(10, 2).expect("a legal decimal");
let total = run(
"sum",
&ty,
&[
Value::Decimal { unscaled: 250, width: 10, scale: 2 },
Value::Decimal { unscaled: 125, width: 10, scale: 2 },
],
);
assert_eq!(total, Value::Decimal { unscaled: 375, width: 10, scale: 2 });
}
#[test]
fn an_aggregate_nobody_has_written_says_which_one() {
let error = Accumulator::new("median", &LogicalType::Double)
.expect_err("median is not written yet");
assert!(error.message().contains("the median aggregate"), "{error}");
}
fn row_at_a_time(name: &str, returns: &LogicalType, batches: &[Vector]) -> Result<Value> {
let mut accumulator = Accumulator::new(name, returns)?;
for batch in batches {
for row in 0..batch.len() {
let value = batch.value_at(row);
accumulator.update(std::slice::from_ref(&value))?;
}
}
accumulator.finish()
}
fn a_vector_at_a_time(name: &str, returns: &LogicalType, batches: &[Vector]) -> Result<Value> {
let mut accumulator = Accumulator::new(name, returns)?;
for batch in batches {
accumulator.update_run(std::slice::from_ref(batch), batch.len())?;
}
accumulator.finish()
}
fn agrees(name: &str, returns: &LogicalType, batches: &[Vector], note: &str) {
let slow = row_at_a_time(name, returns, batches);
let fast = a_vector_at_a_time(name, returns, batches);
match (slow, fast) {
(Ok(slow), Ok(fast)) => assert_eq!(slow, fast, "{note}"),
(Err(slow), Err(fast)) => {
assert_eq!(slow.message(), fast.message(), "{note}");
}
(slow, fast) => {
panic!(
"{note}: one path answered and the other did not, {slow:?} against {fast:?}"
);
}
}
}
struct Rng(u64);
impl Rng {
fn next(&mut self) -> u64 {
self.0 ^= self.0 << 13;
self.0 ^= self.0 >> 7;
self.0 ^= self.0 << 17;
self.0
}
}
fn small(rng: &mut Rng) -> i64 {
(rng.next() % 201) as i64 - 100
}
fn sample(ty: &LogicalType, rng: &mut Rng) -> Value {
let number = small(rng);
let positive = number.unsigned_abs();
match *ty {
LogicalType::TinyInt => Value::TinyInt(number as i8),
LogicalType::SmallInt => Value::SmallInt(number as i16),
LogicalType::Integer => Value::Integer(number as i32),
LogicalType::BigInt => Value::BigInt(number),
LogicalType::HugeInt => Value::HugeInt(i128::from(number)),
LogicalType::UTinyInt => Value::UTinyInt(positive as u8),
LogicalType::USmallInt => Value::USmallInt(positive as u16),
LogicalType::UInteger => Value::UInteger(positive as u32),
LogicalType::UBigInt => Value::UBigInt(positive),
LogicalType::Float => Value::Float(number as f32 / 8.0),
LogicalType::Double => Value::Double(number as f64 / 8.0),
LogicalType::Decimal { width, scale } => {
Value::Decimal { unscaled: i128::from(number) * 7, width, scale }
}
LogicalType::Date => Value::Date(number as i32),
LogicalType::Time => Value::Time(positive as i64),
LogicalType::Timestamp => Value::Timestamp(number),
LogicalType::Varchar => Value::Varchar(format!("w{number}")),
_ => panic!("no sample for {ty}"),
}
}
fn flat(ty: &LogicalType, rows: usize, nulls: usize, rng: &mut Rng) -> Vector {
let values: Vec<Value> = (0..rows)
.map(
|index| {
if nulls > 0 && index % nulls == 0 { Value::Null } else { sample(ty, rng) }
},
)
.collect();
Vector::from_values(ty.clone(), &values).expect("a vector of this type")
}
fn returns_of(name: &str, ty: &LogicalType) -> LogicalType {
match name {
"count" | "count_star" => LogicalType::BigInt,
"avg" => LogicalType::Double,
"min" | "max" => ty.clone(),
_ => match *ty {
LogicalType::Decimal { scale, .. } => {
LogicalType::decimal(rudb_common::MAX_DECIMAL_WIDTH, scale)
.expect("the widest decimal at this scale is legal")
}
LogicalType::Float | LogicalType::Double => LogicalType::Double,
_ => LogicalType::HugeInt,
},
}
}
#[test]
fn every_aggregate_over_every_type_agrees_with_the_row_at_a_time_path() {
let mut rng = Rng(0x5eed_ca11_ab1e_0003);
let types = [
LogicalType::TinyInt,
LogicalType::SmallInt,
LogicalType::Integer,
LogicalType::BigInt,
LogicalType::HugeInt,
LogicalType::UTinyInt,
LogicalType::USmallInt,
LogicalType::UInteger,
LogicalType::UBigInt,
LogicalType::Float,
LogicalType::Double,
LogicalType::decimal(9, 2).expect("a legal decimal"),
LogicalType::decimal(18, 4).expect("a legal decimal"),
LogicalType::decimal(30, 6).expect("a legal decimal"),
LogicalType::Date,
LogicalType::Time,
LogicalType::Timestamp,
LogicalType::Varchar,
];
for ty in &types {
for name in ["count_star", "count", "sum", "avg", "min", "max"] {
let returns = returns_of(name, ty);
for nulls in [0_usize, 4, 1] {
let first = flat(ty, 97, nulls, &mut rng);
let second = flat(ty, 64, nulls, &mut rng);
let note = format!("{name} over {ty}, flat, one null in {nulls}");
agrees(name, &returns, &[first.clone(), second.clone()], ¬e);
let codes: Vec<u32> = (0..97).map(|index| (index % 13) as u32).collect();
let coded = Vector::dictionary(codes, first.clone()).expect("in range");
let note = format!("{name} over {ty}, dictionary, one null in {nulls}");
agrees(name, &returns, &[coded, second.clone()], ¬e);
let ends: Vec<u32> = (1..=8).map(|run| (run * 13).min(97)).collect();
let runs = Vector::runs(ends, first.slice(0, 8).expect("eight values"))
.expect("one value for each run");
let note = format!("{name} over {ty}, runs, one null in {nulls}");
agrees(name, &returns, &[runs, second], ¬e);
}
}
}
}
const STRIDE: usize = 3;
const OFFSET: usize = 1;
fn deal(rows: usize, groups: usize) -> Vec<usize> {
(0..rows).map(|row| if row % 11 == 5 { NOWHERE } else { (row * 7 + 3) % groups }).collect()
}
fn group_at_a_time(
name: &str,
returns: &LogicalType,
batches: &[(Vector, Vec<usize>)],
groups: usize,
reads: bool,
) -> Result<Vec<Value>> {
let mut states = Vec::new();
for _ in 0..groups {
states.push(Accumulator::new(name, returns)?);
}
for (batch, slots) in batches {
for (row, &slot) in slots.iter().enumerate() {
if slot == NOWHERE {
continue;
}
if reads {
let value = batch.value_at(row);
states[slot].update(std::slice::from_ref(&value))?;
} else {
states[slot].update(&[])?;
}
}
}
states.iter().map(Accumulator::finish).collect()
}
fn group_at_once(
name: &str,
returns: &LogicalType,
batches: &[(Vector, Vec<usize>)],
groups: usize,
reads: bool,
) -> Result<Vec<Value>> {
let mut states = Vec::new();
for _ in 0..groups * STRIDE {
states.push(Accumulator::new(name, returns)?);
}
for (batch, slots) in batches {
let input = reads.then_some(batch);
update_scattered(&mut states, slots, STRIDE, OFFSET, input, slots.len())?;
}
(0..groups).map(|group| states[group * STRIDE + OFFSET].finish()).collect()
}
fn scattered_states(
name: &str,
returns: &LogicalType,
batches: &[(Vector, Vec<usize>)],
groups: usize,
reads: bool,
) -> Result<Vec<Accumulator>> {
let mut states = Vec::new();
for _ in 0..groups * STRIDE {
states.push(Accumulator::new(name, returns)?);
}
for (batch, slots) in batches {
let input = reads.then_some(batch);
update_scattered(&mut states, slots, STRIDE, OFFSET, input, slots.len())?;
}
Ok(states)
}
#[test]
fn the_run_at_a_time_finish_answers_what_the_value_at_a_time_finish_answers() {
let mut rng = Rng(0x5eed_ca11_ab1e_00f1);
let groups = 5;
let types = [
LogicalType::TinyInt,
LogicalType::Integer,
LogicalType::BigInt,
LogicalType::HugeInt,
LogicalType::UBigInt,
LogicalType::Float,
LogicalType::Double,
LogicalType::decimal(9, 2).expect("a legal decimal"),
LogicalType::decimal(30, 6).expect("a legal decimal"),
LogicalType::Varchar,
];
for ty in &types {
for name in ["count_star", "count", "sum", "avg", "min", "max"] {
let returns = returns_of(name, ty);
let reads = name != "count_star";
for nulls in [0_usize, 4, 1] {
let batch = flat(ty, 97, nulls, &mut rng);
let slots = deal(batch.len(), groups);
let dealt = vec![(batch, slots)];
let note = format!("{name} over {ty}, one null in {nulls}");
let Ok(states) = scattered_states(name, &returns, &dealt, groups, reads) else {
continue;
};
let at: Vec<usize> = (0..groups).collect();
let slow: Vec<Value> = at
.iter()
.map(|&group| states[group * STRIDE + OFFSET].finish())
.collect::<Result<_>>()
.expect("the value at a time finish answers for every shape here");
let fast = finish_run(&states, &at, STRIDE, OFFSET, &returns)
.expect("no total here is out of range");
let owns = matches!(name, "min" | "max");
assert_eq!(fast.is_none(), owns, "{note}: taken when it should not be");
let Some(fast) = fast else { continue };
assert_eq!(fast.len(), groups, "{note}");
assert_eq!(fast.logical_type(), &returns, "{note}");
for (group, slow) in slow.iter().enumerate() {
assert_eq!(&fast.value_at(group), slow, "{note}, group {group}");
}
}
}
}
}
#[test]
fn the_run_at_a_time_finish_follows_the_slots_it_is_given() {
let mut rng = Rng(0x5eed_ca11_ab1e_00f2);
let groups = 5;
let ty = LogicalType::BigInt;
let returns = returns_of("sum", &ty);
let batch = flat(&ty, 97, 4, &mut rng);
let slots = deal(batch.len(), groups);
let states = scattered_states("sum", &returns, &[(batch, slots)], groups, true)
.expect("a sum over BIGINT builds");
let at = [3_usize, 0, 4];
let fast = finish_run(&states, &at, STRIDE, OFFSET, &returns)
.expect("no total here is out of range")
.expect("a sum over BIGINT is a shape the run at a time finish takes");
for (row, &group) in at.iter().enumerate() {
let slow = states[group * STRIDE + OFFSET].finish().expect("the sum finishes");
assert_eq!(fast.value_at(row), slow, "row {row} is group {group}");
}
}
#[test]
fn every_aggregate_scattered_into_groups_agrees_with_one_accumulator_per_group() {
scattered_into(5, 0x5eed_ca11_ab1e_0061);
scattered_into(FEW + 3, 0x5eed_ca11_ab1e_0062);
}
fn scattered_into(groups: usize, seed: u64) {
let mut rng = Rng(seed);
let types = [
LogicalType::TinyInt,
LogicalType::SmallInt,
LogicalType::Integer,
LogicalType::BigInt,
LogicalType::HugeInt,
LogicalType::UTinyInt,
LogicalType::USmallInt,
LogicalType::UInteger,
LogicalType::UBigInt,
LogicalType::Float,
LogicalType::Double,
LogicalType::decimal(9, 2).expect("a legal decimal"),
LogicalType::decimal(18, 4).expect("a legal decimal"),
LogicalType::decimal(30, 6).expect("a legal decimal"),
LogicalType::Date,
LogicalType::Time,
LogicalType::Timestamp,
LogicalType::Varchar,
];
for ty in &types {
for name in ["count_star", "count", "sum", "avg", "min", "max"] {
let returns = returns_of(name, ty);
let reads = name != "count_star";
for nulls in [0_usize, 4, 1] {
let first = flat(ty, 97, nulls, &mut rng);
let second = flat(ty, 64, nulls, &mut rng);
let codes: Vec<u32> = (0..97).map(|index| (index % 13) as u32).collect();
let coded =
Vector::dictionary(codes.clone(), first.clone()).expect("codes in range");
let packed = first.bit_packed().expect("packs or hands the vector back");
let over_packed =
Vector::dictionary(codes, packed.clone()).expect("codes in range");
for (shape, batches) in [
("flat", vec![first.clone(), second.clone()]),
("dictionary", vec![coded, second.clone()]),
("packed", vec![packed, second.clone()]),
("dictionary over packed", vec![over_packed, second.clone()]),
] {
let dealt: Vec<(Vector, Vec<usize>)> = batches
.into_iter()
.map(|batch| {
let slots = deal(batch.len(), groups);
(batch, slots)
})
.collect();
let note = format!("{name} over {ty}, {shape}, one null in {nulls}");
let slow = group_at_a_time(name, &returns, &dealt, groups, reads);
let fast = group_at_once(name, &returns, &dealt, groups, reads);
match (slow, fast) {
(Ok(slow), Ok(fast)) => assert_eq!(slow, fast, "{note}"),
(Err(slow), Err(fast)) => {
assert_eq!(slow.message(), fast.message(), "{note}");
}
(slow, fast) => panic!(
"{note}: one path answered and the other did not, \
{slow:?} against {fast:?}"
),
}
}
}
}
}
}
#[test]
fn a_run_at_a_time_agrees_with_a_row_at_a_time() {
let mut rng = Rng(0x5eed_0f00_2c0d_0028);
let groups = 4;
let types = [
LogicalType::TinyInt,
LogicalType::Integer,
LogicalType::BigInt,
LogicalType::HugeInt,
LogicalType::UBigInt,
LogicalType::Double,
LogicalType::decimal(18, 4).expect("a legal decimal"),
LogicalType::decimal(30, 6).expect("a legal decimal"),
LogicalType::Date,
LogicalType::Varchar,
];
let slots: Vec<usize> = [(2, 30), (0, 11), (NOWHERE, 9), (3, 1), (0, 20), (1, 26)]
.iter()
.flat_map(|&(slot, length)| std::iter::repeat_n(slot, length))
.collect();
let mut runs: Vec<(usize, usize)> = Vec::new();
for (row, &slot) in slots.iter().enumerate() {
match runs.last_mut() {
Some((last, end)) if *last == slot => *end = row + 1,
_ => runs.push((slot, row + 1)),
}
}
for ty in &types {
for name in ["count_star", "count", "sum", "avg", "min", "max"] {
let returns = returns_of(name, ty);
let reads = name != "count_star";
for nulls in [0_usize, 4] {
let batch = flat(ty, slots.len(), nulls, &mut rng);
let note = format!("{name} over {ty}, one null in {nulls}");
let dealt = vec![(batch.clone(), slots.clone())];
let slow = group_at_a_time(name, &returns, &dealt, groups, reads);
let mut states = Vec::new();
for _ in 0..groups * STRIDE {
states.push(Accumulator::new(name, &returns).expect("known"));
}
let input = reads.then_some(&batch);
let took = update_runs(&mut states, &runs, STRIDE, OFFSET, input, slots.len());
let taken = took.as_ref().ok().copied();
let fast = took.and_then(|took| {
if !took {
update_scattered(
&mut states,
&slots,
STRIDE,
OFFSET,
input,
slots.len(),
)?;
}
(0..groups)
.map(|group| states[group * STRIDE + OFFSET].finish())
.collect::<Result<Vec<_>>>()
});
match (slow, fast) {
(Ok(slow), Ok(fast)) => assert_eq!(slow, fast, "{note}"),
(Err(slow), Err(fast)) => {
assert_eq!(slow.message(), fast.message(), "{note}");
}
(slow, fast) => panic!("{note}: {slow:?} against {fast:?}"),
}
let exact = ty.is_integer() || matches!(ty, LogicalType::Decimal { .. });
let covered = name == "count_star"
|| (nulls == 0
&& (name == "count" || (exact && matches!(name, "sum" | "avg"))));
if covered {
assert_eq!(taken, Some(true), "{note} is taken a run at a time");
}
}
}
}
}
#[test]
fn a_row_that_belongs_to_no_group_is_counted_by_nobody() {
let column = Vector::from_values(
LogicalType::Integer,
&[Value::Integer(1), Value::Integer(2), Value::Integer(3), Value::Integer(4)],
)
.expect("a vector of integers");
let mut states = vec![Accumulator::new("sum", &LogicalType::HugeInt).expect("known"); 2];
let slots = [0, NOWHERE, 1, NOWHERE];
update_scattered(&mut states, &slots, 1, 0, Some(&column), 4).expect("folds them in");
assert_eq!(states[0].finish().expect("finishes"), Value::HugeInt(1));
assert_eq!(states[1].finish().expect("finishes"), Value::HugeInt(3));
}
#[test]
fn the_shapes_a_grouped_query_is_made_of_stay_off_the_row_at_a_time_path() {
let numbers = Vector::from_values(
LogicalType::Integer,
&[Value::Integer(1), Value::Integer(2), Value::Integer(3)],
)
.expect("a vector of integers");
let words = Vector::from_values(
LogicalType::Varchar,
&[Value::Varchar("a".into()), Value::Varchar("b".into()), Value::Varchar("c".into())],
)
.expect("a vector of strings");
let slots = [0_usize, 1, 0];
for (name, returns, column) in [
("count_star", LogicalType::BigInt, None),
("count", LogicalType::BigInt, Some(&numbers)),
("sum", LogicalType::HugeInt, Some(&numbers)),
("avg", LogicalType::Double, Some(&numbers)),
("min", LogicalType::Integer, Some(&numbers)),
("max", LogicalType::Integer, Some(&numbers)),
] {
fallback::reset();
let mut states = vec![Accumulator::new(name, &returns).expect("known"); 2];
update_scattered(&mut states, &slots, 1, 0, column, 3).expect("folds them in");
assert_eq!(
fallback::count(Kernel::Aggregate, Form::Flat, Form::Flat),
0,
"{name} over an integer column took the row at a time path"
);
}
fallback::reset();
let mut states = vec![Accumulator::new("min", &LogicalType::Varchar).expect("known"); 2];
update_scattered(&mut states, &slots, 1, 0, Some(&words), 3).expect("folds them in");
assert_eq!(states[0].finish().expect("finishes"), Value::Varchar("a".into()));
assert_eq!(fallback::count(Kernel::Aggregate, Form::Flat, Form::Flat), 0);
for (ty, values, least) in [
(
LogicalType::Date,
vec![Value::Date(3), Value::Date(1), Value::Date(2)],
Value::Date(2),
),
(
LogicalType::Time,
vec![Value::Time(30), Value::Time(10), Value::Time(20)],
Value::Time(20),
),
(
LogicalType::Timestamp,
vec![Value::Timestamp(30), Value::Timestamp(10), Value::Timestamp(20)],
Value::Timestamp(20),
),
] {
let column = Vector::from_values(ty.clone(), &values).expect("a vector of this type");
for name in ["min", "max"] {
fallback::reset();
let mut states = vec![Accumulator::new(name, &ty).expect("known"); 2];
update_scattered(&mut states, &slots, 1, 0, Some(&column), 3)
.expect("folds them in");
assert_eq!(
fallback::count(Kernel::Aggregate, Form::Flat, Form::Flat),
0,
"{name} over a {ty} took the row at a time path"
);
}
let mut states = vec![Accumulator::new("min", &ty).expect("known"); 2];
update_scattered(&mut states, &slots, 1, 0, Some(&column), 3).expect("folds them in");
assert_eq!(states[0].finish().expect("finishes"), least);
}
}
#[test]
fn a_packed_run_and_a_dictionary_over_one_do_not_reach_the_row_at_a_time_path() {
fallback::reset();
let ty = LogicalType::decimal(15, 2).expect("a legal decimal");
let values: Vec<Value> = (0..24)
.map(|row| {
if row % 7 == 0 {
Value::Null
} else {
Value::Decimal { unscaled: 900 + row * 13, width: 15, scale: 2 }
}
})
.collect();
let packed = Vector::from_values(ty.clone(), &values)
.expect("a flat decimal")
.bit_packed()
.expect("a three hundred wide range packs");
assert_eq!(packed.form(), Form::BitPacked);
let codes: Vec<u32> = (0..64).map(|row| ((row * 5) % 24) as u32).collect();
let over = Vector::dictionary(codes, packed.clone()).expect("codes are in range");
assert_eq!(over.form(), Form::Dictionary);
for (name, returns) in [
("count", LogicalType::BigInt),
("sum", LogicalType::decimal(38, 2).expect("a legal decimal")),
("avg", LogicalType::Double),
("min", ty.clone()),
("max", ty.clone()),
] {
agrees(name, &returns, std::slice::from_ref(&packed), name);
agrees(name, &returns, std::slice::from_ref(&over), name);
}
assert_eq!(fallback::count(Kernel::Aggregate, Form::BitPacked, Form::BitPacked), 0);
assert_eq!(fallback::count(Kernel::Aggregate, Form::Dictionary, Form::Dictionary), 0);
fallback::reset();
}
#[test]
fn a_packed_column_scattered_into_groups_does_not_reach_the_row_at_a_time_path() {
let ty = LogicalType::decimal(15, 2).expect("a legal decimal");
let values: Vec<Value> = (0..48)
.map(|row| {
if row % 7 == 0 {
Value::Null
} else {
Value::Decimal { unscaled: 900 + row * 13, width: 15, scale: 2 }
}
})
.collect();
let packed = Vector::from_values(ty.clone(), &values)
.expect("a flat decimal")
.bit_packed()
.expect("a six hundred wide range packs");
assert_eq!(packed.form(), Form::BitPacked);
let codes: Vec<u32> = (0..48).map(|row| ((row * 5) % 48) as u32).collect();
let over = Vector::dictionary(codes, packed.clone()).expect("codes are in range");
let whole = Vector::from_values(
LogicalType::Integer,
&(0..48).map(|row| Value::Integer(500 + (row * 7) % 29)).collect::<Vec<_>>(),
)
.expect("a flat integer column")
.bit_packed()
.expect("a range of twenty nine packs");
assert_eq!(whole.form(), Form::BitPacked);
let slots: Vec<usize> = (0..48).map(|row| row % 3).collect();
for (name, returns, column) in [
("count", LogicalType::BigInt, &packed),
("sum", LogicalType::decimal(38, 2).expect("a legal decimal"), &packed),
("avg", LogicalType::Double, &packed),
("count", LogicalType::BigInt, &over),
("sum", LogicalType::decimal(38, 2).expect("a legal decimal"), &over),
("avg", LogicalType::Double, &over),
("min", LogicalType::Integer, &whole),
("max", LogicalType::Integer, &whole),
("min", ty.clone(), &packed),
("max", ty.clone(), &packed),
("min", ty.clone(), &over),
("max", ty.clone(), &over),
] {
fallback::reset();
let mut states = vec![Accumulator::new(name, &returns).expect("known"); 3];
update_scattered(&mut states, &slots, 1, 0, Some(column), 48).expect("folds them in");
let form = column.form();
assert_eq!(
fallback::count(Kernel::Aggregate, form, form),
0,
"{name} over a {form:?} column took the row at a time path"
);
for (group, state) in states.iter().enumerate() {
let mut one = Accumulator::new(name, &returns).expect("known");
let mine: Vec<Value> = (0..48)
.filter(|row| slots[*row] == group)
.map(|row| column.try_value_at(row).expect("a value"))
.collect();
for value in &mine {
one.update(std::slice::from_ref(value)).expect("folds one in");
}
assert_eq!(
state.clone().finish().expect("finishes"),
one.finish().expect("finishes"),
"{name} over group {group} of a {form:?} column"
);
}
}
fallback::reset();
}
#[test]
fn a_wide_decimal_totals_off_the_row_at_a_time_path_and_still_raises_on_overflow() {
fallback::reset();
let ty = LogicalType::decimal(34, 2).expect("a legal decimal");
let sum = LogicalType::decimal(38, 2).expect("a legal decimal");
let values: Vec<Value> = (0..40)
.map(|row| {
if row % 9 == 0 {
Value::Null
} else {
Value::Decimal {
unscaled: i128::from(row) * 1_000_000_000_000_000_000_000 + 7,
width: 34,
scale: 2,
}
}
})
.collect();
let column = Vector::from_values(ty.clone(), &values).expect("a flat wide decimal");
for (name, returns) in [
("sum", sum.clone()),
("min", ty.clone()),
("max", ty.clone()),
("avg", LogicalType::Double),
] {
agrees(name, &returns, std::slice::from_ref(&column), name);
}
let slots: Vec<usize> = (0..40).map(|row| row % 3).collect();
let mut states = vec![Accumulator::new("sum", &sum).expect("known"); 3];
update_scattered(&mut states, &slots, 1, 0, Some(&column), 40).expect("folds them in");
for (group, state) in states.iter().enumerate() {
let mut one = Accumulator::new("sum", &sum).expect("known");
for row in (0..40).filter(|row| slots[*row] == group) {
one.update(std::slice::from_ref(&values[row])).expect("folds one in");
}
assert_eq!(
state.clone().finish().expect("finishes"),
one.finish().expect("finishes"),
"the sum of group {group}"
);
}
assert_eq!(fallback::count(Kernel::Aggregate, Form::Flat, Form::Flat), 0);
let huge = Value::Decimal { unscaled: i128::MAX - 1, width: 34, scale: 2 };
let brims = Vector::from_values(ty.clone(), &[huge.clone(), huge.clone()]).expect("two");
let mut ungrouped = Accumulator::new("sum", &sum).expect("known");
let raised = ungrouped.update_run(std::slice::from_ref(&brims), 2);
assert!(raised.is_err(), "a total that does not fit answered anyway");
let mut grouped = vec![Accumulator::new("sum", &sum).expect("known"); 1];
let one_group = vec![0_usize; 2];
let scattered = update_scattered(&mut grouped, &one_group, 1, 0, Some(&brims), 2);
assert!(scattered.is_err(), "a total that does not fit answered anyway");
fallback::reset();
}
#[test]
fn a_sum_over_runs_takes_the_same_loop_a_dictionary_takes() {
fallback::reset();
let values = Vector::from_values(
LogicalType::Integer,
&[Value::Integer(5), Value::Null, Value::Integer(7)],
)
.expect("a vector of integers");
let runs = Vector::runs(vec![4, 6, 10], values).expect("one value for each run");
assert_eq!(runs.form(), Form::Rle);
let mut summing =
Accumulator::new("sum", &LogicalType::HugeInt).expect("a known aggregate");
summing.update_run(std::slice::from_ref(&runs), 10).expect("sums");
assert_eq!(summing.finish().expect("finishes"), Value::HugeInt(48));
assert_eq!(fallback::count(Kernel::Aggregate, Form::Rle, Form::Rle), 0);
assert_eq!(fallback::count(Kernel::Aggregate, Form::Flat, Form::Flat), 0);
fallback::reset();
}
#[test]
fn a_sum_of_numbers_stays_off_the_row_at_a_time_path_and_a_sum_of_strings_does_not() {
fallback::reset();
let numbers = Vector::from_values(
LogicalType::Integer,
&[Value::Integer(1), Value::Integer(2), Value::Integer(3)],
)
.expect("a vector of integers");
let mut summing =
Accumulator::new("sum", &LogicalType::HugeInt).expect("a known aggregate");
summing.update_run(std::slice::from_ref(&numbers), 3).expect("sums");
assert_eq!(summing.finish().expect("finishes"), Value::HugeInt(6));
assert_eq!(fallback::count(Kernel::Aggregate, Form::Flat, Form::Flat), 0);
let words = Vector::from_values(
LogicalType::Varchar,
&[Value::Varchar("a".into()), Value::Null, Value::Varchar("b".into())],
)
.expect("a vector of strings");
let mut counting = Accumulator::new("count", &LogicalType::BigInt).expect("a known one");
counting.update_run(std::slice::from_ref(&words), 3).expect("counts");
assert_eq!(counting.finish().expect("finishes"), Value::BigInt(2));
assert_eq!(fallback::count(Kernel::Aggregate, Form::Flat, Form::Flat), 0);
let mut wrong = Accumulator::new("sum", &LogicalType::HugeInt).expect("a known aggregate");
let error =
wrong.update_run(std::slice::from_ref(&words), 3).expect_err("cannot sum those");
assert!(error.message().contains("summing a"), "{error}");
assert_eq!(fallback::count(Kernel::Aggregate, Form::Flat, Form::Flat), 1);
fallback::reset();
}
#[test]
fn a_floating_point_sum_carries_the_running_total_into_the_next_vector() {
let first =
Vector::from_values(LogicalType::Double, &[Value::Double(1.0e16)]).expect("a vector");
let second = Vector::from_values(LogicalType::Double, &vec![Value::Double(1.0); 8])
.expect("a vector");
let batches = [first, second];
let slow = row_at_a_time("sum", &LogicalType::Double, &batches).expect("sums");
let fast = a_vector_at_a_time("sum", &LogicalType::Double, &batches).expect("sums");
assert_eq!(slow, fast);
assert_eq!(slow, Value::Double(1.0e16));
assert_ne!(1.0e16 + 8.0, 1.0e16);
}
#[test]
fn a_null_behind_a_dictionary_code_is_skipped_by_every_aggregate() {
let values = Vector::from_values(
LogicalType::Integer,
&[Value::Null, Value::Integer(5), Value::Integer(9)],
)
.expect("a vector of integers");
let coded = Vector::dictionary(vec![0, 1, 0, 2, 0], values).expect("codes are in range");
let batch = std::slice::from_ref(&coded);
assert_eq!(
a_vector_at_a_time("count", &LogicalType::BigInt, batch).expect("counts"),
Value::BigInt(2)
);
assert_eq!(
a_vector_at_a_time("sum", &LogicalType::HugeInt, batch).expect("sums"),
Value::HugeInt(14)
);
assert_eq!(
a_vector_at_a_time("min", &LogicalType::Integer, batch).expect("finds one"),
Value::Integer(5)
);
}
#[derive(Debug)]
struct Filed(Vec<Vec<u8>>);
impl rudb_vector::TextSource for Filed {
fn len(&self) -> usize {
self.0.len()
}
fn bytes_at(&self, index: usize) -> Result<Option<&[u8]>> {
Ok(self.0.get(index).map(Vec::as_slice))
}
fn footprint(&self) -> usize {
self.0.iter().map(Vec::len).sum()
}
}
#[test]
fn an_extreme_over_a_dictionary_that_keeps_its_bytes_in_a_file_is_decided_on_the_bytes() {
let source = Arc::new(Filed(vec![b"pear".to_vec(), b"apple".to_vec(), b"plum".to_vec()]));
let values = Vector::external_text(LogicalType::Varchar, source).expect("three values");
let coded = Vector::dictionary(vec![0, 2, 1, 2, 0], values).expect("codes are in range");
let batch = std::slice::from_ref(&coded);
assert_eq!(
a_vector_at_a_time("min", &LogicalType::Varchar, batch).expect("finds one"),
Value::Varchar("apple".into())
);
assert_eq!(
a_vector_at_a_time("max", &LogicalType::Varchar, batch).expect("finds one"),
Value::Varchar("plum".into())
);
let smallest = gather(&coded, 5, &Validity::AllValid, Want::Extreme(true));
assert!(matches!(smallest, Some(Contribution::Extreme(Some(2)))));
let largest = gather(&coded, 5, &Validity::AllValid, Want::Extreme(false));
assert!(matches!(largest, Some(Contribution::Extreme(Some(1)))));
}
#[derive(Debug)]
struct Sorted {
values: Vec<Vec<u8>>,
order: Vec<u32>,
ranked: std::sync::OnceLock<Option<Vec<u32>>>,
reads: std::sync::atomic::AtomicUsize,
}
impl Sorted {
fn over(words: &[&str]) -> Arc<Self> {
let values: Vec<Vec<u8>> = words.iter().map(|word| word.as_bytes().to_vec()).collect();
let mut order: Vec<u32> = (0..values.len() as u32).collect();
order.sort_by(|&left, &right| values[left as usize].cmp(&values[right as usize]));
Arc::new(Self {
values,
order,
ranked: std::sync::OnceLock::new(),
reads: std::sync::atomic::AtomicUsize::new(0),
})
}
fn reads(&self) -> usize {
self.reads.load(std::sync::atomic::Ordering::Relaxed)
}
}
impl rudb_vector::TextSource for Sorted {
fn len(&self) -> usize {
self.values.len()
}
fn bytes_at(&self, index: usize) -> Result<Option<&[u8]>> {
self.reads.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
Ok(self.values.get(index).map(Vec::as_slice))
}
fn footprint(&self) -> usize {
self.values.iter().map(Vec::len).sum()
}
fn ranks(&self) -> Option<usize> {
Some(self.order.len())
}
fn compare_rank(&self, rank: usize, wanted: &[u8]) -> Result<std::cmp::Ordering> {
Ok(self.values[self.order[rank] as usize].as_slice().cmp(wanted))
}
fn code_at_rank(&self, rank: usize) -> Result<u32> {
Ok(self.order[rank])
}
fn code_ranks(&self) -> Option<&[u32]> {
self.ranked
.get_or_init(|| {
let mut ranks = vec![0; self.order.len()];
for (rank, &code) in self.order.iter().enumerate() {
ranks[code as usize] = rank as u32;
}
Some(ranks)
})
.as_deref()
}
}
#[test]
fn an_ungrouped_extreme_over_a_sorted_dictionary_reads_one_value_however_many_vectors_it_saw() {
let source = Sorted::over(&["pear", "apple", "plum", "fig"]);
let handed: Arc<dyn rudb_vector::TextSource> = source.clone();
let values =
Arc::new(Vector::external_text(LogicalType::Varchar, handed).expect("four values"));
for (name, wanted) in [("min", "apple"), ("max", "plum")] {
source.reads.store(0, std::sync::atomic::Ordering::Relaxed);
let mut accumulator =
Accumulator::new(name, &LogicalType::Varchar).expect("a known one");
for codes in [vec![0_u32, 2, 3], vec![1, 0, 2], vec![3, 3, 0]] {
let rows = codes.len();
let coded = Vector::stable_dictionary(codes, Arc::clone(&values))
.expect("codes are in range");
accumulator
.update_run(std::slice::from_ref(&coded), rows)
.expect("folds a vector in");
}
assert_eq!(
accumulator.finish().expect("an extreme"),
Value::Varchar(wanted.into()),
"the {name} of three vectors"
);
assert_eq!(source.reads(), 1, "the {name} read the payload once");
}
}
#[test]
fn a_run_shorter_than_the_vector_totals_only_the_rows_it_was_asked_for() {
let rows: Vec<Value> = (1..=10).map(Value::Integer).collect();
let vector = Vector::from_values(LogicalType::Integer, &rows).expect("a vector");
let batch = std::slice::from_ref(&vector);
for count in 0..=10usize {
let mut accumulator =
Accumulator::new("sum", &LogicalType::HugeInt).expect("a known one");
accumulator.update_run(batch, count).expect("totals");
let wanted = (count * (count + 1) / 2) as i128;
let got = accumulator.finish().expect("a total");
if count == 0 {
assert_eq!(got, Value::Null, "no rows is no total");
} else {
assert_eq!(got, Value::HugeInt(wanted), "the first {count} rows");
}
}
}
#[test]
fn a_dictionary_read_through_a_padded_copy_totals_what_the_same_rows_total_laid_out_flat() {
for distinct in [1usize, 2, 7, 255, TALLY_LIMIT, TALLY_LIMIT + 1, TALLY_LIMIT * 3] {
let entries: Vec<Value> =
(0..distinct).map(|slot| Value::Integer(slot as i32 * 7 - 11)).collect();
let values = Vector::from_values(LogicalType::Integer, &entries).expect("a dictionary");
let codes: Vec<u32> = (0..1500u32).map(|row| row * 13 % distinct as u32).collect();
let flat: Vec<Value> =
codes.iter().map(|&code| entries[code as usize].clone()).collect();
let coded = Vector::dictionary(codes, values).expect("codes are in range");
let mut counted = Accumulator::new("sum", &LogicalType::HugeInt).expect("a known one");
counted.update_run(std::slice::from_ref(&coded), 1500).expect("totals");
let laid_out = Vector::from_values(LogicalType::Integer, &flat).expect("a vector");
let mut gathered = Accumulator::new("sum", &LogicalType::HugeInt).expect("a known one");
gathered.update_run(std::slice::from_ref(&laid_out), 1500).expect("totals");
assert_eq!(
counted.finish().expect("a total"),
gathered.finish().expect("a total"),
"a dictionary of {distinct} entries"
);
}
}
#[test]
fn a_padded_dictionary_stops_at_the_rows_it_was_asked_for() {
let entries = [Value::Integer(1), Value::Integer(100)];
let values = Vector::from_values(LogicalType::Integer, &entries).expect("a dictionary");
let coded = Vector::dictionary(vec![0, 0, 0, 1, 1], values).expect("codes are in range");
let mut accumulator = Accumulator::new("sum", &LogicalType::HugeInt).expect("a known one");
accumulator.update_run(std::slice::from_ref(&coded), 3).expect("totals");
assert_eq!(
accumulator.finish().expect("a total"),
Value::HugeInt(3),
"only the three ones"
);
}
#[test]
fn a_mean_of_a_decimal_column_adds_exactly_and_divides_once() {
let ty = LogicalType::Decimal { width: 15, scale: 2 };
let rows: Vec<Value> = (0..10_000)
.map(|row: i64| Value::Decimal {
unscaled: i128::from(row % 97 + 3),
width: 15,
scale: 2,
})
.collect();
let total: i128 = (0..10_000i64).map(|row| i128::from(row % 97 + 3)).sum();
#[expect(clippy::cast_precision_loss, reason = "the test is about which double comes out")]
let exact = total as f64 / (rows.len() as f64 * 100.0);
assert_eq!(run("avg", &LogicalType::Double, &rows), Value::Double(exact));
#[expect(clippy::cast_precision_loss, reason = "the test is about which double comes out")]
let drifted = rows
.iter()
.map(|row| match row {
Value::Decimal { unscaled, .. } => *unscaled as f64 / 100.0,
other => unreachable!("{other:?}"),
})
.sum::<f64>()
/ rows.len() as f64;
assert_ne!(drifted, exact, "the naive order has to round differently");
let vector = Vector::from_values(ty, &rows).expect("a decimal vector");
let mut whole = Accumulator::new("avg", &LogicalType::Double).expect("a known one");
whole.update_run(std::slice::from_ref(&vector), rows.len()).expect("totals");
assert_eq!(whole.finish().expect("finishes"), Value::Double(exact), "one vector");
for cut in [1, 3_000, 9_999] {
let mut first = Accumulator::new("avg", &LogicalType::Double).expect("a known one");
let mut rest = Accumulator::new("avg", &LogicalType::Double).expect("a known one");
for row in &rows[..cut] {
first.update(std::slice::from_ref(row)).expect("accumulates");
}
for row in &rows[cut..] {
rest.update(std::slice::from_ref(row)).expect("accumulates");
}
first.combine(&rest).expect("two halves of one column");
assert_eq!(first.finish().expect("finishes"), Value::Double(exact), "split at {cut}");
}
}
#[test]
fn a_total_of_hugeints_that_does_not_fit_goes_the_row_at_a_time_way_and_overflows() {
fallback::reset();
let rows = vec![Value::HugeInt(i128::MAX); 2];
let vector = Vector::from_values(LogicalType::HugeInt, &rows).expect("a vector");
let mut accumulator = Accumulator::new("sum", &LogicalType::HugeInt).expect("a known one");
let error =
accumulator.update_run(std::slice::from_ref(&vector), 2).expect_err("overflows");
assert!(error.message().contains("Overflow in the running total"), "{error}");
assert_eq!(fallback::count(Kernel::Aggregate, Form::Flat, Form::Flat), 1);
fallback::reset();
}
}