use core::f64;
use std::collections::BTreeMap;
use anyhow::Result;
use reblessive::tree::Stk;
use surrealdb_types::ToSql;
use crate::catalog::aggregation::{self, AggregateFields, AggregationAnalysis, AggregationStat};
use crate::ctx::FrozenContext;
use crate::dbs::plan::Explanation;
use crate::dbs::store::MemoryCollector;
use crate::dbs::{Options, Statement};
use crate::doc::CursorDoc;
use crate::expr::{FlowResultExt as _, Idiom};
use crate::idx::planner::RecordStrategy;
use crate::val::{Number, TryFloatDiv, Value};
#[derive(Debug)]
pub struct GroupCollector {
analysis: AggregationAnalysis,
omit: Vec<Idiom>,
exprs_buffer: Vec<Value>,
group_buffer: Vec<Value>,
results: BTreeMap<Vec<Value>, Vec<AggregationStat>>,
}
impl GroupCollector {
pub fn new(stm: &Statement<'_>) -> Result<Self> {
let Some(fields) = stm.expr() else {
fail!("Tried to group a statement without a selector");
};
let Some(groups) = stm.group() else {
fail!("Tried to group a statement without a group");
};
let analysis = AggregationAnalysis::analyze_fields_groups(fields, groups, false)?;
let omit = stm.omit().to_vec();
Ok(GroupCollector {
analysis,
omit,
exprs_buffer: Vec::new(),
group_buffer: Vec::new(),
results: BTreeMap::new(),
})
}
pub fn len(&self) -> usize {
self.results.len()
}
pub(super) fn explain(&self, exp: &mut Explanation) {
let aggr_agrs = self
.analysis
.aggregate_arguments
.iter()
.enumerate()
.map(|(idx, x)| (format!("expr{idx}"), Value::from(x.to_sql())))
.collect::<Value>();
let group_expr = self
.analysis
.group_expressions
.iter()
.enumerate()
.map(|(idx, x)| (format!("_g{idx}"), Value::from(x.to_sql())))
.collect::<Value>();
let selector = match &self.analysis.fields {
AggregateFields::Value(expr) => Value::from(expr.to_sql()),
AggregateFields::Fields(items) => {
items.iter().map(|(k, v)| (k.to_sql(), Value::from(v.to_sql()))).collect()
}
};
let aggregates = self
.analysis
.aggregations
.iter()
.enumerate()
.map(|(idx, x)| {
let res = match x {
aggregation::Aggregation::Count => "Count".to_string(),
aggregation::Aggregation::CountValue(x) => format!("CountValue(expr{x})"),
aggregation::Aggregation::NumberMax(x) => format!("NumberMax(expr{x})"),
aggregation::Aggregation::NumberMin(x) => format!("NumberMin(expr{x})"),
aggregation::Aggregation::Sum(x) => format!("Sum(expr{x})"),
aggregation::Aggregation::Mean(x) => format!("Mean(expr{x})"),
aggregation::Aggregation::StdDev(x) => format!("StdDev(expr{x})"),
aggregation::Aggregation::Variance(x) => format!("Variance(expr{x})"),
aggregation::Aggregation::DatetimeMax(x) => format!("DatetimeMax(expr{x})"),
aggregation::Aggregation::DatetimeMin(x) => format!("DatetimeMin(expr{x})"),
aggregation::Aggregation::Accumulate(x) => format!("Accumulate(expr{x})"),
};
(format!("_a{idx}"), Value::from(res))
})
.collect();
exp.add_collector(
"Group",
vec![
("Aggregate expressions", aggr_agrs),
("Group expressions", group_expr),
("Aggregations", aggregates),
("Select expression", selector),
],
);
}
pub async fn push(
&mut self,
stk: &mut Stk,
ctx: &FrozenContext,
opt: &Options,
rs: RecordStrategy,
obj: Value,
) -> Result<()> {
let doc = obj.into();
self.group_buffer.clear();
for g in self.analysis.group_expressions.iter() {
let v = stk.run(|stk| g.compute(stk, ctx, opt, Some(&doc))).await.catch_return()?;
self.group_buffer.push(v);
}
let aggragates = if let Some(x) = self.results.get_mut(&self.group_buffer) {
x
} else {
self.results
.entry(self.group_buffer.clone())
.or_insert_with(|| self.analysis.aggregations.iter().map(|x| x.to_stat()).collect())
};
if let RecordStrategy::Count = rs {
let Value::Number(n) = doc.doc.as_ref() else {
fail!("Value for Count RecordStrategy was not a number");
};
for a in aggragates.iter_mut() {
if let AggregationStat::Count {
count,
} = a
{
*count = n.as_int();
}
}
} else {
self.exprs_buffer.clear();
for v in self.analysis.aggregate_arguments.iter() {
let v = stk.run(|stk| v.compute(stk, ctx, opt, Some(&doc))).await.catch_return()?;
self.exprs_buffer.push(v);
}
aggregation::add_to_aggregation_stats(&self.exprs_buffer, aggragates)?;
}
Ok(())
}
pub(super) async fn output(
&mut self,
stk: &mut Stk,
ctx: &FrozenContext,
opt: &Options,
) -> Result<MemoryCollector> {
let mut collector = MemoryCollector::default();
let mut field_buffer = String::new();
let mut doc: CursorDoc = Value::empty_object().into();
for (group, result) in std::mem::take(&mut self.results) {
let Value::Object(doc_obj) = doc.doc.to_mut() else {
unreachable!()
};
for (idx, a) in result.into_iter().enumerate() {
field_buffer.clear();
aggregation::write_aggregate_field_name(&mut field_buffer, idx);
let value = match a {
AggregationStat::Count {
count,
}
| AggregationStat::CountValue {
count,
..
} => Value::from(Number::from(count)),
AggregationStat::NumberMax {
max,
..
} => max.into(),
AggregationStat::NumberMin {
min,
..
} => min.into(),
AggregationStat::Sum {
sum,
..
} => sum.into(),
AggregationStat::Mean {
sum,
count,
..
} => sum.try_float_div(count.into()).unwrap_or(f64::NAN.into()).into(),
AggregationStat::TimeMax {
max,
..
} => max.into(),
AggregationStat::TimeMin {
min,
..
} => min.into(),
AggregationStat::Accumulate {
values,
..
} => values.into(),
AggregationStat::StdDev {
sum,
sum_of_squares,
count,
..
} => {
let num = if count == 0 {
Number::from(f64::NAN)
} else if count == 1 {
Number::from(0.0)
} else {
let mean = sum / Number::from(count);
let variance =
(sum_of_squares - (sum * mean)) / Number::from(count - 1);
if variance == Number::from(0.0) {
Number::from(0.0)
} else {
variance.sqrt()
}
};
num.into()
}
AggregationStat::Variance {
sum,
sum_of_squares,
count,
..
} => {
let num = if count == 0 {
Number::from(f64::NAN)
} else if count == 1 {
Number::from(0.0)
} else {
let mean = sum / Number::from(count);
(sum_of_squares - (sum * mean)) / Number::from(count - 1)
};
num.into()
}
};
if let Some(x) = doc_obj.get_mut(&field_buffer) {
*x = value;
} else {
doc_obj.insert(field_buffer.clone(), value);
}
}
for (idx, g) in group.into_iter().enumerate() {
field_buffer.clear();
aggregation::write_group_field_name(&mut field_buffer, idx);
if let Some(x) = doc_obj.get_mut(&field_buffer) {
*x = g;
} else {
doc_obj.insert(field_buffer.clone(), g);
}
}
match &self.analysis.fields {
AggregateFields::Value(expr) => {
let mut res = stk
.run(|stk| expr.compute(stk, ctx, opt, Some(&doc)))
.await
.catch_return()?;
for field in &self.omit {
res.del(stk, ctx, opt, field).await?;
}
collector.push(res);
}
AggregateFields::Fields(items) => {
let mut obj = Value::empty_object();
for (name, expr) in items {
let res = stk
.run(|stk| expr.compute(stk, ctx, opt, Some(&doc)))
.await
.catch_return()?;
obj.set(stk, ctx, opt, name.as_ref(), res).await?;
}
for field in &self.omit {
obj.del(stk, ctx, opt, field).await?;
}
collector.push(obj);
}
}
}
Ok(collector)
}
}