1use std::pin::Pin;
21use std::sync::Arc;
22use std::task::{Context, Poll};
23
24#[cfg(feature = "proto")]
25use super::proto::{decode_physical_window_expr, encode_physical_window_expr};
26use super::utils::create_schema;
27use crate::execution_plan::{CardinalityEffect, EmissionType};
28use crate::metrics::{BaselineMetrics, ExecutionPlanMetricsSet, MetricsSet};
29use crate::statistics::{ChildStats, StatisticsArgs};
30use crate::stream::EmptyRecordBatchStream;
31use crate::windows::{
32 calc_requirements, get_ordered_partition_by_indices, get_partition_by_sort_exprs,
33 window_equivalence_properties,
34};
35use crate::{
36 ChildrenPropertiesMode, ColumnStatistics, DisplayAs, DisplayFormatType, Distribution,
37 ExecutionPlan, ExecutionPlanProperties, InputDistributionRequirements, PhysicalExpr,
38 PlanProperties, RecordBatchStream, ReplaceChildrenOptions, SendableRecordBatchStream,
39 Statistics, WindowExpr, validate_child_count,
40};
41
42use arrow::array::ArrayRef;
43use arrow::compute::{concat, concat_batches};
44use arrow::datatypes::SchemaRef;
45use arrow::error::ArrowError;
46use arrow::record_batch::RecordBatch;
47use datafusion_common::stats::Precision;
48use datafusion_common::tree_node::TreeNodeRecursion;
49use datafusion_common::utils::{evaluate_partition_ranges, transpose};
50use datafusion_common::{Result, assert_eq_or_internal_err};
51use datafusion_execution::TaskContext;
52use datafusion_physical_expr_common::sort_expr::{
53 OrderingRequirements, PhysicalSortExpr,
54};
55
56use futures::{Stream, StreamExt, ready};
57
58#[derive(Debug, Clone)]
60pub struct WindowAggExec {
61 pub(crate) input: Arc<dyn ExecutionPlan>,
63 window_expr: Vec<Arc<dyn WindowExpr>>,
65 schema: SchemaRef,
67 metrics: ExecutionPlanMetricsSet,
69 ordered_partition_by_indices: Vec<usize>,
72 cache: Arc<PlanProperties>,
74 can_repartition: bool,
76}
77
78impl WindowAggExec {
79 pub fn try_new(
81 window_expr: Vec<Arc<dyn WindowExpr>>,
82 input: Arc<dyn ExecutionPlan>,
83 can_repartition: bool,
84 ) -> Result<Self> {
85 let schema = create_schema(&input.schema(), &window_expr)?;
86 let schema = Arc::new(schema);
87
88 let ordered_partition_by_indices =
89 get_ordered_partition_by_indices(window_expr[0].partition_by(), &input)?;
90 let cache = Self::compute_properties(&schema, &input, &window_expr)?;
91 Ok(Self {
92 input,
93 window_expr,
94 schema,
95 metrics: ExecutionPlanMetricsSet::new(),
96 ordered_partition_by_indices,
97 cache: Arc::new(cache),
98 can_repartition,
99 })
100 }
101
102 pub fn window_expr(&self) -> &[Arc<dyn WindowExpr>] {
104 &self.window_expr
105 }
106
107 pub fn input(&self) -> &Arc<dyn ExecutionPlan> {
109 &self.input
110 }
111
112 pub fn partition_by_sort_keys(&self) -> Result<Vec<PhysicalSortExpr>> {
118 let partition_by = self.window_expr()[0].partition_by();
119 get_partition_by_sort_exprs(
120 &self.input,
121 partition_by,
122 &self.ordered_partition_by_indices,
123 )
124 }
125
126 fn compute_properties(
128 schema: &SchemaRef,
129 input: &Arc<dyn ExecutionPlan>,
130 window_exprs: &[Arc<dyn WindowExpr>],
131 ) -> Result<PlanProperties> {
132 let eq_properties = window_equivalence_properties(schema, input, window_exprs)?;
134
135 let output_partitioning = input.output_partitioning().clone();
139
140 Ok(PlanProperties::new(
142 eq_properties,
143 output_partitioning,
144 EmissionType::Final,
146 input.boundedness(),
147 ))
148 }
149
150 pub fn partition_keys(&self) -> Vec<Arc<dyn PhysicalExpr>> {
151 if !self.can_repartition {
152 vec![]
153 } else {
154 let all_partition_keys = self
155 .window_expr()
156 .iter()
157 .map(|expr| expr.partition_by().to_vec())
158 .collect::<Vec<_>>();
159
160 all_partition_keys
161 .into_iter()
162 .min_by_key(|s| s.len())
163 .unwrap_or_else(Vec::new)
164 }
165 }
166}
167
168impl DisplayAs for WindowAggExec {
169 fn fmt_as(
170 &self,
171 t: DisplayFormatType,
172 f: &mut std::fmt::Formatter,
173 ) -> std::fmt::Result {
174 match t {
175 DisplayFormatType::Default | DisplayFormatType::Verbose => {
176 write!(f, "WindowAggExec: ")?;
177 let g: Vec<String> = self
178 .window_expr
179 .iter()
180 .map(|e| {
181 format!(
182 "{}: {:?}, frame: {:?}",
183 e.name().to_owned(),
184 e.field(),
185 e.get_window_frame()
186 )
187 })
188 .collect();
189 write!(f, "wdw=[{}]", g.join(", "))?;
190 }
191 DisplayFormatType::TreeRender => {
192 let g: Vec<String> = self
193 .window_expr
194 .iter()
195 .map(|e| e.name().to_owned().to_string())
196 .collect();
197 writeln!(f, "select_list={}", g.join(", "))?;
198 }
199 }
200 Ok(())
201 }
202}
203
204impl ExecutionPlan for WindowAggExec {
205 fn name(&self) -> &'static str {
206 "WindowAggExec"
207 }
208
209 fn properties(&self) -> &Arc<PlanProperties> {
211 &self.cache
212 }
213
214 fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
215 vec![&self.input]
216 }
217
218 fn apply_expressions(
219 &self,
220 f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> Result<TreeNodeRecursion>,
221 ) -> Result<TreeNodeRecursion> {
222 let expressions = self.window_expr.iter().flat_map(|window_expr| {
223 let expressions = window_expr.all_expressions();
224 expressions
225 .args
226 .into_iter()
227 .chain(expressions.partition_by_exprs)
228 .chain(expressions.order_by_exprs)
229 });
230 crate::apply_expression_roots(expressions, f)
231 }
232
233 fn maintains_input_order(&self) -> Vec<bool> {
234 vec![true]
235 }
236
237 fn required_input_ordering(&self) -> Vec<Option<OrderingRequirements>> {
238 let partition_bys = self.window_expr()[0].partition_by();
239 let order_keys = self.window_expr()[0].order_by();
240 if self.ordered_partition_by_indices.len() < partition_bys.len() {
241 vec![calc_requirements(partition_bys, order_keys)]
242 } else {
243 let partition_bys = self
244 .ordered_partition_by_indices
245 .iter()
246 .map(|idx| &partition_bys[*idx]);
247 vec![calc_requirements(partition_bys, order_keys)]
248 }
249 }
250
251 fn required_input_distribution(&self) -> Vec<Distribution> {
252 self.input_distribution_requirements().into_per_child()
253 }
254
255 fn input_distribution_requirements(&self) -> InputDistributionRequirements {
256 if self.partition_keys().is_empty() {
257 InputDistributionRequirements::new(vec![Distribution::SinglePartition])
258 } else {
259 InputDistributionRequirements::new(vec![Distribution::KeyPartitioned(
260 self.partition_keys(),
261 )])
262 }
263 }
264
265 fn replace_children(
266 self: Arc<Self>,
267 mut children: Vec<Arc<dyn ExecutionPlan>>,
268 options: ReplaceChildrenOptions,
269 ) -> Result<Arc<dyn ExecutionPlan>> {
270 validate_child_count!(self, children);
271 match options.children_properties {
272 ChildrenPropertiesMode::Keep => Ok(Arc::new(Self {
273 input: children.swap_remove(0),
274 metrics: ExecutionPlanMetricsSet::new(),
275 ..Self::clone(&*self)
276 })),
277 ChildrenPropertiesMode::Recompute => Ok(Arc::new(WindowAggExec::try_new(
278 self.window_expr.clone(),
279 children.swap_remove(0),
280 true,
281 )?)),
282 }
283 }
284
285 fn with_new_children(
286 self: Arc<Self>,
287 children: Vec<Arc<dyn ExecutionPlan>>,
288 ) -> Result<Arc<dyn ExecutionPlan>> {
289 self.replace_children(
290 children,
291 ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
292 )
293 }
294
295 fn with_new_children_and_same_properties(
296 self: Arc<Self>,
297 children: Vec<Arc<dyn ExecutionPlan>>,
298 ) -> Result<Arc<dyn ExecutionPlan>> {
299 self.replace_children(
300 children,
301 ReplaceChildrenOptions::new(ChildrenPropertiesMode::Keep),
302 )
303 }
304
305 fn execute(
306 &self,
307 partition: usize,
308 context: Arc<TaskContext>,
309 ) -> Result<SendableRecordBatchStream> {
310 let input = self.input.execute(partition, context)?;
311 let stream = Box::pin(WindowAggStream::new(
312 Arc::clone(&self.schema),
313 self.window_expr.clone(),
314 input,
315 BaselineMetrics::new(&self.metrics, partition),
316 self.partition_by_sort_keys()?,
317 self.ordered_partition_by_indices.clone(),
318 )?);
319 Ok(stream)
320 }
321
322 fn metrics(&self) -> Option<MetricsSet> {
323 Some(self.metrics.clone_inner())
324 }
325
326 fn child_stats_requests(&self, partition: Option<usize>) -> Vec<ChildStats> {
327 vec![ChildStats::At(partition)]
328 }
329
330 fn statistics_from_inputs(
331 &self,
332 input_stats: &[Arc<Statistics>],
333 _args: &StatisticsArgs,
334 ) -> Result<Arc<Statistics>> {
335 let input_stat = input_stats[0].as_ref().clone();
336 let win_cols = self.window_expr.len();
337 let input_cols = self.input.schema().fields().len();
338 let mut column_statistics = Vec::with_capacity(win_cols + input_cols);
340 column_statistics.extend(input_stat.column_statistics);
342 for _ in 0..win_cols {
343 column_statistics.push(ColumnStatistics::new_unknown())
344 }
345 Ok(Arc::new(Statistics {
346 num_rows: input_stat.num_rows,
347 column_statistics,
348 total_byte_size: Precision::Absent,
349 }))
350 }
351
352 fn cardinality_effect(&self) -> CardinalityEffect {
353 CardinalityEffect::Equal
354 }
355
356 #[cfg(feature = "proto")]
357 fn try_to_proto(
358 &self,
359 ctx: &crate::proto::ExecutionPlanEncodeCtx<'_>,
360 ) -> Result<Option<datafusion_proto_models::protobuf::PhysicalPlanNode>> {
361 use datafusion_proto_models::protobuf;
362
363 let Self {
367 input,
368 window_expr,
369 schema: _,
372 metrics: _,
374 ordered_partition_by_indices: _,
376 cache: _,
378 can_repartition: _,
382 } = self;
383
384 let input = ctx.encode_child(input)?;
385 let window_expr = window_expr
386 .iter()
387 .map(|expr| encode_physical_window_expr(expr, ctx))
388 .collect::<Result<Vec<_>>>()?;
389 let partition_keys = self
390 .partition_keys()
391 .iter()
392 .map(|expr| ctx.encode_expr(expr))
393 .collect::<Result<Vec<_>>>()?;
394
395 Ok(Some(protobuf::PhysicalPlanNode {
396 physical_plan_type: Some(
397 protobuf::physical_plan_node::PhysicalPlanType::Window(Box::new(
398 protobuf::WindowAggExecNode {
399 input: Some(Box::new(input)),
400 window_expr,
401 partition_keys,
402 input_order_mode: None,
405 },
406 )),
407 ),
408 }))
409 }
410}
411
412#[cfg(feature = "proto")]
413impl WindowAggExec {
414 pub fn try_from_proto(
421 node: &datafusion_proto_models::protobuf::PhysicalPlanNode,
422 ctx: &crate::proto::ExecutionPlanDecodeCtx<'_>,
423 ) -> Result<Arc<dyn ExecutionPlan>> {
424 use super::BoundedWindowAggExec;
425 use crate::InputOrderMode;
426 use datafusion_proto_models::protobuf;
427 use protobuf::window_agg_exec_node::InputOrderMode as ProtoInputOrderMode;
428
429 let window_agg = crate::expect_plan_variant!(
430 node,
431 protobuf::physical_plan_node::PhysicalPlanType::Window,
432 "WindowAggExec",
433 );
434 let protobuf::WindowAggExecNode {
437 input,
438 window_expr,
439 partition_keys,
440 input_order_mode,
441 } = window_agg.as_ref();
442
443 let input =
444 ctx.decode_required_child(input.as_deref(), "WindowAggExec", "input")?;
445 let input_schema = input.schema();
446 let window_expr = window_expr
447 .iter()
448 .map(|expr| decode_physical_window_expr(expr, ctx, input_schema.as_ref()))
449 .collect::<Result<Vec<_>>>()?;
450 let partition_keys = partition_keys
451 .iter()
452 .map(|expr| ctx.decode_expr(expr, input_schema.as_ref()))
453 .collect::<Result<Vec<_>>>()?;
454
455 if let Some(input_order_mode) = input_order_mode.as_ref() {
456 let input_order_mode = match input_order_mode {
457 ProtoInputOrderMode::Linear(_) => InputOrderMode::Linear,
458 ProtoInputOrderMode::PartiallySorted(
459 protobuf::PartiallySortedInputOrderMode { columns },
460 ) => InputOrderMode::PartiallySorted(
461 columns.iter().map(|column| *column as usize).collect(),
462 ),
463 ProtoInputOrderMode::Sorted(_) => InputOrderMode::Sorted,
464 };
465 Ok(Arc::new(BoundedWindowAggExec::try_new(
466 window_expr,
467 input,
468 input_order_mode,
469 !partition_keys.is_empty(),
472 )?))
473 } else {
474 Ok(Arc::new(WindowAggExec::try_new(
475 window_expr,
476 input,
477 !partition_keys.is_empty(),
479 )?))
480 }
481 }
482}
483
484fn compute_window_aggregates(
486 window_expr: &[Arc<dyn WindowExpr>],
487 batch: &RecordBatch,
488) -> Result<Vec<ArrayRef>> {
489 window_expr
490 .iter()
491 .map(|window_expr| window_expr.evaluate(batch))
492 .collect()
493}
494
495pub struct WindowAggStream {
497 schema: SchemaRef,
498 input: SendableRecordBatchStream,
499 batches: Vec<RecordBatch>,
500 finished: bool,
501 window_expr: Vec<Arc<dyn WindowExpr>>,
502 partition_by_sort_keys: Vec<PhysicalSortExpr>,
503 baseline_metrics: BaselineMetrics,
504 ordered_partition_by_indices: Vec<usize>,
505}
506
507impl WindowAggStream {
508 pub fn new(
510 schema: SchemaRef,
511 window_expr: Vec<Arc<dyn WindowExpr>>,
512 input: SendableRecordBatchStream,
513 baseline_metrics: BaselineMetrics,
514 partition_by_sort_keys: Vec<PhysicalSortExpr>,
515 ordered_partition_by_indices: Vec<usize>,
516 ) -> Result<Self> {
517 assert_eq_or_internal_err!(
519 window_expr[0].partition_by().len(),
520 ordered_partition_by_indices.len(),
521 "All partition by columns should have an ordering"
522 );
523 Ok(Self {
524 schema,
525 input,
526 batches: vec![],
527 finished: false,
528 window_expr,
529 baseline_metrics,
530 partition_by_sort_keys,
531 ordered_partition_by_indices,
532 })
533 }
534
535 fn compute_aggregates(&self) -> Result<Option<RecordBatch>> {
536 let _timer = self.baseline_metrics.elapsed_compute().timer();
538
539 let batch = concat_batches(&self.input.schema(), &self.batches)?;
540 if batch.num_rows() == 0 {
541 return Ok(None);
542 }
543
544 let partition_by_sort_keys = self
545 .ordered_partition_by_indices
546 .iter()
547 .map(|idx| self.partition_by_sort_keys[*idx].evaluate_to_sort_column(&batch))
548 .collect::<Result<Vec<_>>>()?;
549 let partition_points =
550 evaluate_partition_ranges(batch.num_rows(), &partition_by_sort_keys)?;
551
552 let mut partition_results = vec![];
553 for partition_point in partition_points {
555 let length = partition_point.end - partition_point.start;
556 partition_results.push(compute_window_aggregates(
557 &self.window_expr,
558 &batch.slice(partition_point.start, length),
559 )?)
560 }
561 let columns = transpose(partition_results)
562 .iter()
563 .map(|elems| concat(&elems.iter().map(|x| x.as_ref()).collect::<Vec<_>>()))
564 .collect::<Vec<_>>()
565 .into_iter()
566 .collect::<Result<Vec<ArrayRef>, ArrowError>>()?;
567
568 let mut batch_columns = batch.columns().to_vec();
572 batch_columns.extend_from_slice(&columns);
574 Ok(Some(RecordBatch::try_new(
575 Arc::clone(&self.schema),
576 batch_columns,
577 )?))
578 }
579}
580
581impl Stream for WindowAggStream {
582 type Item = Result<RecordBatch>;
583
584 fn poll_next(
585 mut self: Pin<&mut Self>,
586 cx: &mut Context<'_>,
587 ) -> Poll<Option<Self::Item>> {
588 let poll = self.poll_next_inner(cx);
589 self.baseline_metrics.record_poll(poll)
590 }
591}
592
593impl WindowAggStream {
594 #[inline]
595 fn poll_next_inner(
596 &mut self,
597 cx: &mut Context<'_>,
598 ) -> Poll<Option<Result<RecordBatch>>> {
599 if self.finished {
600 return Poll::Ready(None);
601 }
602
603 loop {
604 return Poll::Ready(Some(match ready!(self.input.poll_next_unpin(cx)) {
605 Some(Ok(batch)) => {
606 self.batches.push(batch);
607 continue;
608 }
609 Some(Err(e)) => Err(e),
610 None => {
611 let input_schema = self.input.schema();
614 self.input = Box::pin(EmptyRecordBatchStream::new(input_schema));
615 let Some(result) = self.compute_aggregates()? else {
616 return Poll::Ready(None);
617 };
618 self.finished = true;
619 debug_assert!(result.num_rows() > 0);
622 Ok(result)
623 }
624 }));
625 }
626 }
627}
628
629impl RecordBatchStream for WindowAggStream {
630 fn schema(&self) -> SchemaRef {
632 Arc::clone(&self.schema)
633 }
634}
635
636#[cfg(test)]
637mod tests {
638 use super::*;
639 use crate::test::TestMemoryExec;
640 use crate::windows::create_window_expr;
641 use arrow::datatypes::{DataType, Field, Schema};
642 use datafusion_common::ScalarValue;
643 use datafusion_expr::{
644 WindowFrame, WindowFrameBound, WindowFrameUnits, WindowFunctionDefinition,
645 };
646 use datafusion_functions_aggregate::count::count_udaf;
647
648 #[test]
649 fn test_window_agg_cardinality_effect() -> Result<()> {
650 let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int64, true)]));
651 let input: Arc<dyn ExecutionPlan> =
652 Arc::new(TestMemoryExec::try_new(&[], Arc::clone(&schema), None)?);
653 let args = vec![crate::expressions::col("a", &schema)?];
654 let window_expr = create_window_expr(
655 &WindowFunctionDefinition::AggregateUDF(count_udaf()),
656 "count(a)".to_string(),
657 &args,
658 &[],
659 &[],
660 Arc::new(WindowFrame::new_bounds(
661 WindowFrameUnits::Rows,
662 WindowFrameBound::Preceding(ScalarValue::UInt64(None)),
663 WindowFrameBound::CurrentRow,
664 )),
665 Arc::clone(&schema),
666 false,
667 false,
668 None,
669 )?;
670
671 let window = WindowAggExec::try_new(vec![window_expr], input, true)?;
672 assert!(matches!(
673 window.cardinality_effect(),
674 CardinalityEffect::Equal
675 ));
676 Ok(())
677 }
678}