use rudb_common::{Error, LogicalType, Result, Value};
use crate::number::{approximate, fit, integral};
#[derive(Debug, Clone)]
pub(crate) enum General {
List { element: LogicalType, values: Vec<Value> },
Pick { held: Option<Value>, pick: Pick },
Logic { held: Option<bool>, all: bool },
Bits { held: Option<i128>, op: BitOp, returns: LogicalType },
Product { total: f64, seen: bool },
Moments { count: u64, mean: f64, squared: f64, measure: Measure },
Joined { text: String, seen: bool, separator: String },
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Pick {
First,
Last,
Any,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum BitOp {
And,
Or,
Xor,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Measure {
VarSamp,
VarPop,
StddevSamp,
StddevPop,
}
impl General {
pub(crate) fn new(name: &str, returns: &LogicalType) -> Option<Self> {
let pick = |pick| Self::Pick { held: None, pick };
let bits = |op| Self::Bits { held: None, op, returns: returns.clone() };
let moments = |measure| Self::Moments { count: 0, mean: 0.0, squared: 0.0, measure };
Some(match name {
"list" => {
let element = match returns {
LogicalType::List(element) => (**element).clone(),
_ => LogicalType::Null,
};
Self::List { element, values: Vec::new() }
}
"first" => pick(Pick::First),
"last" => pick(Pick::Last),
"any_value" => pick(Pick::Any),
"bool_and" => Self::Logic { held: None, all: true },
"bool_or" => Self::Logic { held: None, all: false },
"bit_and" => bits(BitOp::And),
"bit_or" => bits(BitOp::Or),
"bit_xor" => bits(BitOp::Xor),
"product" => Self::Product { total: 1.0, seen: false },
"var_samp" => moments(Measure::VarSamp),
"var_pop" => moments(Measure::VarPop),
"stddev_samp" => moments(Measure::StddevSamp),
"stddev_pop" => moments(Measure::StddevPop),
"string_agg" => {
Self::Joined { text: String::new(), seen: false, separator: String::new() }
}
_ => return None,
})
}
pub(crate) fn update(&mut self, args: &[Value]) -> Result<()> {
let Some(value) = args.first() else {
return Err(Error::internal("an aggregate over 0 arguments".to_string()));
};
match self {
Self::List { values, .. } => values.push(value.clone()),
Self::Pick { held, pick } => match pick {
Pick::First => {
if held.is_none() {
*held = Some(value.clone());
}
}
Pick::Last => *held = Some(value.clone()),
Pick::Any => {
if held.is_none() && !value.is_null() {
*held = Some(value.clone());
}
}
},
_ if value.is_null() => {}
Self::Logic { held, all } => {
let Value::Boolean(flag) = *value else {
return Err(unexpected("bool_and", value));
};
let so_far = held.unwrap_or(*all);
*held = Some(if *all { so_far && flag } else { so_far || flag });
}
Self::Bits { held, op, .. } => {
let bits = integral(value).ok_or_else(|| unexpected("bit_and", value))?;
*held = Some(match (*held, *op) {
(None, _) => bits,
(Some(so_far), BitOp::And) => so_far & bits,
(Some(so_far), BitOp::Or) => so_far | bits,
(Some(so_far), BitOp::Xor) => so_far ^ bits,
});
}
Self::Product { total, seen } => {
*total *= approximate(value).ok_or_else(|| unexpected("product", value))?;
*seen = true;
}
Self::Moments { count, mean, squared, .. } => {
let input = approximate(value).ok_or_else(|| unexpected("stddev", value))?;
*count += 1;
#[expect(
clippy::cast_precision_loss,
reason = "the count of rows in one group is well inside the exact range"
)]
let differential = (input - *mean) / *count as f64;
let next = *mean + differential;
*squared += (input - next) * (input - *mean);
*mean = next;
}
Self::Joined { text, seen, separator: kept } => {
let separator = match args.get(1) {
None => ",",
Some(Value::Varchar(separator)) => separator,
Some(_) => return Ok(()),
};
let Value::Varchar(piece) = value else {
return Err(unexpected("string_agg", value));
};
if *seen {
text.push_str(separator);
} else {
separator.clone_into(kept);
}
text.push_str(piece);
*seen = true;
}
}
Ok(())
}
pub(crate) fn combine(&mut self, other: &Self) -> Result<()> {
match (self, other) {
(Self::List { values, .. }, Self::List { values: more, .. }) => {
values.extend(more.iter().cloned());
}
(Self::Pick { held, pick }, Self::Pick { held: theirs, .. }) => {
let take = match pick {
Pick::First | Pick::Any => held.is_none(),
Pick::Last => theirs.is_some(),
};
if take {
held.clone_from(theirs);
}
}
(Self::Logic { held, all }, Self::Logic { held: theirs, .. }) => {
*held = match (*held, *theirs) {
(Some(here), Some(there)) => {
Some(if *all { here && there } else { here || there })
}
(here, there) => here.or(there),
};
}
(Self::Bits { held, op, .. }, Self::Bits { held: theirs, .. }) => {
*held = match (*held, *theirs) {
(Some(here), Some(there)) => Some(match op {
BitOp::And => here & there,
BitOp::Or => here | there,
BitOp::Xor => here ^ there,
}),
(here, there) => here.or(there),
};
}
(Self::Product { total, seen }, Self::Product { total: theirs, seen: any }) => {
*total *= theirs;
*seen |= any;
}
(
Self::Moments { count, mean, squared, .. },
Self::Moments { count: more, mean: theirs, squared: their_squared, .. },
) => {
if *count == 0 {
(*count, *mean, *squared) = (*more, *theirs, *their_squared);
} else if *more > 0 {
#[expect(
clippy::cast_precision_loss,
reason = "the count of rows in one group is well inside the exact range"
)]
let (here, there) = (*count as f64, *more as f64);
let total = here + there;
let delta = theirs - *mean;
*squared = their_squared + *squared + delta * delta * there * here / total;
*mean = (there * theirs + here * *mean) / total;
*count += more;
}
}
(
Self::Joined { text, seen, separator },
Self::Joined { text: theirs, seen: any, separator: their_separator },
) => {
if !*seen {
text.clone_from(theirs);
separator.clone_from(their_separator);
*seen = *any;
} else if *any {
text.push_str(separator);
text.push_str(theirs);
}
}
(here, there) => {
return Err(Error::internal(format!(
"combining a {here:?} aggregate state with a {there:?} one"
)));
}
}
Ok(())
}
pub(crate) fn finish(&self) -> Result<Value> {
Ok(match self {
Self::List { values, .. } if values.is_empty() => Value::Null,
Self::List { element, values } => {
Value::List { element: element.clone(), values: values.clone() }
}
Self::Pick { held, .. } => held.clone().unwrap_or(Value::Null),
Self::Logic { held, .. } => held.map_or(Value::Null, Value::Boolean),
Self::Bits { held: None, .. } | Self::Product { seen: false, .. } => Value::Null,
Self::Bits { held: Some(bits), returns, .. } => {
let returns =
if *returns == LogicalType::Null { &LogicalType::BigInt } else { returns };
fit(*bits, returns).ok_or_else(|| {
Error::out_of_range(format!(
"a bitwise aggregate of {bits} does not fit in {returns}"
))
})?
}
Self::Product { total, .. } => Value::Double(*total),
Self::Moments { count, squared, measure, .. } => {
#[expect(
clippy::cast_precision_loss,
reason = "the count of rows in one group is well inside the exact range"
)]
let rows = *count as f64;
let sample = matches!(measure, Measure::VarSamp | Measure::StddevSamp);
let variance = match (*count, sample) {
(0, _) | (1, true) => return Ok(Value::Null),
(1, false) => 0.0,
(_, true) => squared / (rows - 1.0),
(_, false) => squared / rows,
};
match measure {
Measure::VarSamp | Measure::VarPop => Value::Double(variance),
Measure::StddevSamp | Measure::StddevPop => Value::Double(variance.sqrt()),
}
}
Self::Joined { seen: false, .. } => Value::Null,
Self::Joined { text, .. } => Value::Varchar(text.clone()),
})
}
}
fn unexpected(name: &str, value: &Value) -> Error {
Error::internal(format!("{name} was handed a {}", value.logical_type()))
}