use super::avi::{avi_batch, AviBake};
use crate::algebra::{Accumulator, GroupOutKey, ReduceShape};
use crate::repr::Batch;
use crate::schema::SchemaDescriptor;
use gnitz_wire::AggDescriptor;
use gnitz_wire::AggFunc;
pub struct ReducePlan {
pub(super) shape: ReduceShape,
pub seeds_ground: bool,
pub(super) cardinality: usize,
pub(super) avi: Option<AviBake>,
}
impl ReducePlan {
pub fn from_wire(
input: &SchemaDescriptor,
group_cols: &[u32],
aggs: &[AggDescriptor],
seeds_ground: bool,
) -> Result<Self, String> {
Self::new(input, group_cols, aggs, cardinality(aggs)?, seeds_ground)
}
pub fn partial(input: &SchemaDescriptor, aggs: &[AggDescriptor]) -> Result<Option<Self>, String> {
let plan = Self::from_wire(input, &[], aggs, false)?;
Ok(plan.is_exact_linear().then_some(plan))
}
pub fn combine(partials: &SchemaDescriptor, aggs: &[AggDescriptor], seeds_ground: bool) -> Result<Self, String> {
let merged: Vec<AggDescriptor> = aggs
.iter()
.zip(1..)
.map(|(d, col_idx)| AggDescriptor { col_idx, agg_op: d.agg_op.merge_op() })
.collect();
let plan = Self::new(partials, &[], &merged, cardinality(aggs)?, seeds_ground)?;
debug_assert_eq!(plan.shape.output_schema, *partials);
debug_assert!(plan.is_exact_linear(), "only exact linear partials are split off");
Ok(plan)
}
fn new(
input: &SchemaDescriptor,
group_cols: &[u32],
aggs: &[AggDescriptor],
cardinality: usize,
seeds_ground: bool,
) -> Result<Self, String> {
debug_assert!(
!seeds_ground || group_cols.is_empty(),
"the ground row carries no group columns"
);
let (key, prefix) = GroupOutKey::new(input, group_cols, group_cols.iter().copied())?;
let shape = ReduceShape::new(input, key, prefix, aggs)?;
let avi = AviBake::new(input, group_cols, &shape.acc_template)?;
Ok(ReducePlan { shape, seeds_ground, cardinality, avi })
}
pub fn output_schema(&self) -> &SchemaDescriptor {
&self.shape.output_schema
}
pub fn index_schema(&self) -> Option<&SchemaDescriptor> {
self.avi.as_ref().map(|bake| &bake.schema)
}
pub fn index_batch(&self, delta: &Batch) -> Option<Batch> {
self.avi.as_ref().map(|bake| avi_batch(delta, bake))
}
pub fn is_exact_linear(&self) -> bool {
self.shape.acc_template.iter().all(Accumulator::is_exact_linear)
}
}
fn cardinality(aggs: &[AggDescriptor]) -> Result<usize, String> {
aggs.iter()
.position(|d| d.agg_op == AggFunc::Count)
.ok_or_else(|| "reduce: a circuit reduce needs a COUNT(*)".to_string())
}