use std::marker::PhantomData;
use std::sync::Arc;
use arrow::datatypes::SchemaRef;
use arrow::record_batch::RecordBatch;
use datafusion_common::Result;
use datafusion_common::assert_or_internal_err;
use datafusion_execution::memory_pool::proxy::VecAllocExt;
use datafusion_expr::EmitTo;
use crate::InputOrderMode;
use crate::PhysicalExpr;
use crate::aggregates::group_values::{GroupByMetrics, GroupValues, new_group_values};
use crate::aggregates::grouped_hash_stream::create_group_accumulator;
use crate::aggregates::order::GroupOrdering;
use crate::aggregates::{
AggregateExec, AggregateMode, PhysicalGroupBy, aggregate_expressions,
evaluate_group_by,
};
use super::common::{AggregateAccumulator, EvaluatedAggregateBatch};
pub(in crate::aggregates) struct OrderedAggregateTable<OrderedAggrMode> {
pub(super) output_schema: SchemaRef,
pub(super) state_schema: SchemaRef,
pub(super) batch_size: usize,
pub(super) group_by_metrics: GroupByMetrics,
pub(super) buffer: OrderedAggregateTableBuffer,
_mode: PhantomData<OrderedAggrMode>,
}
pub(super) struct OrderedAggregateTableBuffer {
pub(super) group_by: Arc<PhysicalGroupBy>,
pub(super) group_ordering: GroupOrdering,
pub(super) group_values: Box<dyn GroupValues>,
pub(super) group_indices: Vec<usize>,
pub(super) accumulators: Vec<AggregateAccumulator>,
}
impl<AggrMode> OrderedAggregateTable<AggrMode> {
#[expect(
clippy::too_many_arguments,
reason = "keeps ordered partial and final table construction explicit"
)]
pub(super) fn new_for_mode(
agg: &AggregateExec,
input_schema: &SchemaRef,
output_schema: SchemaRef,
state_schema: SchemaRef,
batch_size: usize,
input_order_mode: &InputOrderMode,
aggregate_mode: &AggregateMode,
filters: Vec<Option<Arc<dyn PhysicalExpr>>>,
group_by_metrics: GroupByMetrics,
) -> Result<Self> {
assert_or_internal_err!(
batch_size > 0,
"OrderedAggregateTable requires config batch_size >= 1"
);
let group_ordering = GroupOrdering::try_new(input_order_mode)?;
let group_schema = agg.group_by.group_schema(input_schema)?;
let group_values = new_group_values(group_schema, &group_ordering)?;
let aggregate_arguments = aggregate_expressions(
&agg.aggr_expr,
aggregate_mode,
agg.group_by.num_group_exprs(),
)?;
let accumulators = agg
.aggr_expr
.iter()
.zip(aggregate_arguments)
.zip(filters)
.map(|((agg_expr, arguments), filter)| {
let accumulator = create_group_accumulator(agg_expr)?;
Ok(AggregateAccumulator::new(
Arc::clone(agg_expr),
arguments,
filter,
accumulator,
))
})
.collect::<Result<_>>()?;
Ok(Self {
output_schema,
state_schema,
batch_size,
group_by_metrics,
buffer: OrderedAggregateTableBuffer {
group_by: Arc::clone(&agg.group_by),
group_ordering,
group_values,
group_indices: vec![],
accumulators,
},
_mode: PhantomData,
})
}
pub(super) fn evaluate_batch(
&self,
batch: &RecordBatch,
) -> Result<EvaluatedAggregateBatch> {
let timer = self.group_by_metrics.time_calculating_group_ids.timer();
let grouping_set_args = evaluate_group_by(&self.buffer.group_by, batch)?;
drop(timer);
let timer = self.group_by_metrics.aggregate_arguments_time.timer();
let accumulator_args = self
.buffer
.accumulators
.iter()
.map(|acc| acc.evaluate_acc_args(batch))
.collect::<Result<Vec<_>>>()?;
drop(timer);
Ok(EvaluatedAggregateBatch {
grouping_set_args,
accumulator_args,
})
}
pub(in crate::aggregates) fn input_done(&mut self) {
self.buffer.group_ordering.input_done();
}
pub(in crate::aggregates) fn group_ordering(&self) -> &GroupOrdering {
&self.buffer.group_ordering
}
pub(in crate::aggregates) fn num_groups(&self) -> usize {
self.buffer.group_values.len()
}
pub(in crate::aggregates) fn is_empty(&self) -> bool {
self.num_groups() == 0
}
pub(in crate::aggregates) fn memory_size(&self) -> usize {
self.buffer
.accumulators
.iter()
.map(|acc| acc.size())
.sum::<usize>()
+ self.buffer.group_values.size()
+ self.buffer.group_ordering.size()
+ self.buffer.group_indices.allocated_size()
}
pub(in crate::aggregates) fn group_by_metrics(&self) -> GroupByMetrics {
self.group_by_metrics.clone()
}
pub(in crate::aggregates) fn take_state_batch(
&mut self,
) -> Result<Option<RecordBatch>> {
if self.buffer.group_values.is_empty() {
return Ok(None);
}
let mut output = self.buffer.group_values.emit(EmitTo::All)?;
for acc in &mut self.buffer.accumulators {
output.extend(acc.state(EmitTo::All)?);
}
let batch = RecordBatch::try_new(Arc::clone(&self.state_schema), output)?;
debug_assert!(batch.num_rows() > 0);
self.buffer.group_values.clear_shrink(0);
self.buffer.group_indices.clear();
self.buffer.group_indices.shrink_to_fit();
self.buffer.group_ordering.reset();
Ok(Some(batch))
}
pub(super) fn clamp_emit_to(
&self,
group_count: usize,
emit_to: EmitTo,
) -> (EmitTo, bool) {
match emit_to {
EmitTo::First(n) => (EmitTo::First(n.min(self.batch_size)), true),
EmitTo::All if group_count <= self.batch_size => (EmitTo::All, false),
EmitTo::All => (EmitTo::First(self.batch_size), false),
}
}
pub(super) fn aggregate_evaluated_batch(
&mut self,
evaluated_batch: &EvaluatedAggregateBatch,
is_final: bool,
) -> Result<()> {
for group_values in &evaluated_batch.grouping_set_args {
let starting_num_groups = self.buffer.group_values.len();
self.buffer
.group_values
.intern(group_values, &mut self.buffer.group_indices)?;
let total_num_groups = self.buffer.group_values.len();
if total_num_groups > starting_num_groups {
self.buffer.group_ordering.new_groups(
group_values,
&self.buffer.group_indices,
total_num_groups,
)?;
}
let timer = self.group_by_metrics.aggregation_time.timer();
for (acc, values) in self
.buffer
.accumulators
.iter_mut()
.zip(evaluated_batch.accumulator_args.iter())
{
if is_final {
acc.merge_batch(
values,
&self.buffer.group_indices,
total_num_groups,
)?;
} else {
acc.update_batch(
values,
&self.buffer.group_indices,
total_num_groups,
)?;
}
}
drop(timer);
}
Ok(())
}
pub(super) fn next_output_batch_for_mode(
&mut self,
is_final: bool,
) -> Result<Option<RecordBatch>> {
if self.buffer.group_values.is_empty() {
return Ok(None);
}
let Some(emit_to) = self.buffer.group_ordering.emit_to() else {
return Ok(None);
};
let (emit_to, should_remove_groups) =
self.clamp_emit_to(self.buffer.group_values.len(), emit_to);
let timer = self.group_by_metrics.emitting_time.timer();
let mut output = self.buffer.group_values.emit(emit_to)?;
if should_remove_groups {
match emit_to {
EmitTo::First(n) => self.buffer.group_ordering.remove_groups(n),
EmitTo::All => {}
}
}
for acc in &mut self.buffer.accumulators {
if is_final {
output.push(acc.evaluate(emit_to)?);
} else {
output.extend(acc.state(emit_to)?);
}
}
drop(timer);
let batch = RecordBatch::try_new(Arc::clone(&self.output_schema), output)?;
debug_assert!(batch.num_rows() > 0);
Ok(Some(batch))
}
}