use crate::physical_plan::aggregates::{
aggregate_expressions, create_accumulators, finalize_aggregation, AccumulatorItem,
AggregateMode,
};
use crate::physical_plan::metrics::{BaselineMetrics, RecordOutput};
use crate::physical_plan::{RecordBatchStream, SendableRecordBatchStream};
use arrow::datatypes::SchemaRef;
use arrow::record_batch::RecordBatch;
use datafusion_common::Result;
use datafusion_execution::TaskContext;
use datafusion_physical_expr::PhysicalExpr;
use futures::stream::BoxStream;
use std::borrow::Cow;
use std::sync::Arc;
use std::task::{Context, Poll};
use crate::physical_plan::filter::batch_filter;
use datafusion_execution::memory_pool::{MemoryConsumer, MemoryReservation};
use futures::stream::{Stream, StreamExt};
use super::AggregateExec;
pub(crate) struct AggregateStream {
stream: BoxStream<'static, Result<RecordBatch>>,
schema: SchemaRef,
}
struct AggregateStreamInner {
schema: SchemaRef,
mode: AggregateMode,
input: SendableRecordBatchStream,
baseline_metrics: BaselineMetrics,
aggregate_expressions: Vec<Vec<Arc<dyn PhysicalExpr>>>,
filter_expressions: Vec<Option<Arc<dyn PhysicalExpr>>>,
accumulators: Vec<AccumulatorItem>,
reservation: MemoryReservation,
finished: bool,
}
impl AggregateStream {
pub fn new(
agg: &AggregateExec,
context: Arc<TaskContext>,
partition: usize,
) -> Result<Self> {
let agg_schema = Arc::clone(&agg.schema);
let agg_filter_expr = agg.filter_expr.clone();
let baseline_metrics = BaselineMetrics::new(&agg.metrics, partition);
let input = agg.input.execute(partition, Arc::clone(&context))?;
let aggregate_expressions = aggregate_expressions(&agg.aggr_expr, &agg.mode, 0)?;
let filter_expressions = match agg.mode {
AggregateMode::Partial
| AggregateMode::Single
| AggregateMode::SinglePartitioned => agg_filter_expr,
AggregateMode::Final | AggregateMode::FinalPartitioned => {
vec![None; agg.aggr_expr.len()]
}
};
let accumulators = create_accumulators(&agg.aggr_expr)?;
let reservation = MemoryConsumer::new(format!("AggregateStream[{partition}]"))
.register(context.memory_pool());
let inner = AggregateStreamInner {
schema: Arc::clone(&agg.schema),
mode: agg.mode,
input,
baseline_metrics,
aggregate_expressions,
filter_expressions,
accumulators,
reservation,
finished: false,
};
let stream = futures::stream::unfold(inner, |mut this| async move {
if this.finished {
return None;
}
let elapsed_compute = this.baseline_metrics.elapsed_compute();
loop {
let result = match this.input.next().await {
Some(Ok(batch)) => {
let timer = elapsed_compute.timer();
let result = aggregate_batch(
&this.mode,
batch,
&mut this.accumulators,
&this.aggregate_expressions,
&this.filter_expressions,
);
timer.done();
match result
.and_then(|allocated| this.reservation.try_grow(allocated))
{
Ok(_) => continue,
Err(e) => Err(e),
}
}
Some(Err(e)) => Err(e),
None => {
this.finished = true;
let timer = this.baseline_metrics.elapsed_compute().timer();
let result = finalize_aggregation(&this.accumulators, &this.mode)
.and_then(|columns| {
RecordBatch::try_new(this.schema.clone(), columns)
.map_err(Into::into)
})
.record_output(&this.baseline_metrics);
timer.done();
result
}
};
this.finished = true;
return Some((result, this));
}
});
let stream = stream.fuse();
let stream = Box::pin(stream);
Ok(Self {
schema: agg_schema,
stream,
})
}
}
impl Stream for AggregateStream {
type Item = Result<RecordBatch>;
fn poll_next(
mut self: std::pin::Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Self::Item>> {
let this = &mut *self;
this.stream.poll_next_unpin(cx)
}
}
impl RecordBatchStream for AggregateStream {
fn schema(&self) -> SchemaRef {
self.schema.clone()
}
}
fn aggregate_batch(
mode: &AggregateMode,
batch: RecordBatch,
accumulators: &mut [AccumulatorItem],
expressions: &[Vec<Arc<dyn PhysicalExpr>>],
filters: &[Option<Arc<dyn PhysicalExpr>>],
) -> Result<usize> {
let mut allocated = 0usize;
accumulators
.iter_mut()
.zip(expressions)
.zip(filters)
.try_for_each(|((accum, expr), filter)| {
let batch = match filter {
Some(filter) => Cow::Owned(batch_filter(&batch, filter)?),
None => Cow::Borrowed(&batch),
};
let values = &expr
.iter()
.map(|e| e.evaluate(&batch))
.map(|r| r.map(|v| v.into_array(batch.num_rows())))
.collect::<Result<Vec<_>>>()?;
let size_pre = accum.size();
let res = match mode {
AggregateMode::Partial
| AggregateMode::Single
| AggregateMode::SinglePartitioned => accum.update_batch(values),
AggregateMode::Final | AggregateMode::FinalPartitioned => {
accum.merge_batch(values)
}
};
let size_post = accum.size();
allocated += size_post.saturating_sub(size_pre);
res
})?;
Ok(allocated)
}