1use std::cmp::{Ordering, min};
24use std::collections::VecDeque;
25use std::pin::Pin;
26use std::sync::Arc;
27use std::task::{Context, Poll};
28
29use super::utils::create_schema;
30use crate::metrics::{BaselineMetrics, ExecutionPlanMetricsSet, MetricsSet};
31use crate::statistics::{ChildStats, StatisticsArgs};
32use crate::stream::EmptyRecordBatchStream;
33use crate::windows::{
34 calc_requirements, get_ordered_partition_by_indices, get_partition_by_sort_exprs,
35 window_equivalence_properties,
36};
37use crate::{
38 ChildrenPropertiesMode, ColumnStatistics, DisplayAs, DisplayFormatType, Distribution,
39 ExecutionPlan, ExecutionPlanProperties, InputDistributionRequirements,
40 InputOrderMode, PlanProperties, RecordBatchStream, ReplaceChildrenOptions,
41 SendableRecordBatchStream, Statistics, WindowExpr, validate_child_count,
42};
43
44use arrow::compute::take_record_batch;
45use arrow::{
46 array::{Array, ArrayRef, RecordBatchOptions, UInt32Array, UInt32Builder},
47 compute::{concat, concat_batches, sort_to_indices, take_arrays},
48 datatypes::SchemaRef,
49 record_batch::RecordBatch,
50};
51use datafusion_common::hash_utils::create_hashes;
52use datafusion_common::stats::Precision;
53use datafusion_common::tree_node::TreeNodeRecursion;
54use datafusion_common::utils::{
55 evaluate_partition_ranges, get_at_indices, get_row_at_idx,
56};
57use datafusion_common::{
58 HashMap, Result, ScalarValue, arrow_datafusion_err, exec_datafusion_err, exec_err,
59};
60use datafusion_execution::TaskContext;
61use datafusion_expr::ColumnarValue;
62use datafusion_expr::window_state::{PartitionBatchState, WindowAggState};
63use datafusion_physical_expr::window::{
64 PartitionBatches, PartitionKey, PartitionWindowAggStates, WindowEvalContext,
65 WindowState,
66};
67use datafusion_physical_expr_common::physical_expr::PhysicalExpr;
68use datafusion_physical_expr_common::sort_expr::{
69 OrderingRequirements, PhysicalSortExpr,
70};
71
72use crate::execution_plan::CardinalityEffect;
73use datafusion_common::hash_utils::RandomState;
74use futures::stream::Stream;
75use futures::{StreamExt, ready};
76use hashbrown::hash_table::HashTable;
77use indexmap::IndexMap;
78use log::debug;
79
80pub trait WindowStateObserver: Send + Sync {
93 fn finalize_window_aggregate(
111 &self,
112 partition_idx: usize,
113 window_expr: &Arc<dyn WindowExpr>,
114 partition_key: &PartitionKey,
115 state: Vec<ScalarValue>,
116 ) -> Result<()>;
117}
118
119#[derive(Clone)]
121pub struct BoundedWindowAggExec {
122 input: Arc<dyn ExecutionPlan>,
124 window_expr: Vec<Arc<dyn WindowExpr>>,
126 schema: SchemaRef,
128 metrics: ExecutionPlanMetricsSet,
130 pub input_order_mode: InputOrderMode,
132 ordered_partition_by_indices: Vec<usize>,
139 cache: Arc<PlanProperties>,
141 can_repartition: bool,
143 state_observer: Option<Arc<dyn WindowStateObserver>>,
147}
148
149impl std::fmt::Debug for BoundedWindowAggExec {
150 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
151 f.debug_struct("BoundedWindowAggExec")
152 .field("input", &self.input)
153 .field("window_expr", &self.window_expr)
154 .field("schema", &self.schema)
155 .field("metrics", &self.metrics)
156 .field("input_order_mode", &self.input_order_mode)
157 .field(
158 "ordered_partition_by_indices",
159 &self.ordered_partition_by_indices,
160 )
161 .field("cache", &self.cache)
162 .field("can_repartition", &self.can_repartition)
163 .field(
164 "state_observer",
165 &self.state_observer.as_ref().map(|_| "..."),
166 )
167 .finish()
168 }
169}
170
171impl BoundedWindowAggExec {
172 pub fn try_new(
174 window_expr: Vec<Arc<dyn WindowExpr>>,
175 input: Arc<dyn ExecutionPlan>,
176 input_order_mode: InputOrderMode,
177 can_repartition: bool,
178 ) -> Result<Self> {
179 let schema = create_schema(&input.schema(), &window_expr)?;
180 let schema = Arc::new(schema);
181 let partition_by_exprs = window_expr[0].partition_by();
182 let ordered_partition_by_indices = match &input_order_mode {
183 InputOrderMode::Sorted => {
184 let indices = get_ordered_partition_by_indices(
185 window_expr[0].partition_by(),
186 &input,
187 )?;
188 if indices.len() == partition_by_exprs.len() {
189 indices
190 } else {
191 (0..partition_by_exprs.len()).collect::<Vec<_>>()
192 }
193 }
194 InputOrderMode::PartiallySorted(ordered_indices) => ordered_indices.clone(),
195 InputOrderMode::Linear => {
196 vec![]
197 }
198 };
199 let cache = Self::compute_properties(&input, &schema, &window_expr)?;
200 Ok(Self {
201 input,
202 window_expr,
203 schema,
204 metrics: ExecutionPlanMetricsSet::new(),
205 input_order_mode,
206 ordered_partition_by_indices,
207 cache: Arc::new(cache),
208 can_repartition,
209 state_observer: None,
210 })
211 }
212
213 pub fn with_state_observer(
224 mut self,
225 observer: Option<Arc<dyn WindowStateObserver>>,
226 ) -> Result<Self> {
227 if observer.is_some() {
228 for expr in &self.window_expr {
229 if !expr.get_window_frame().is_ever_expanding() {
230 return exec_err!(
231 "cannot install WindowStateObserver on BoundedWindowAggExec \
232 with a sliding aggregate window frame (start != \
233 UNBOUNDED PRECEDING) for `{}`; sliding accumulator state \
234 is frame-only, not the partition aggregate",
235 expr.name()
236 );
237 }
238 }
239 }
240 self.state_observer = observer;
241 Ok(self)
242 }
243
244 pub fn state_observer(&self) -> Option<&Arc<dyn WindowStateObserver>> {
250 self.state_observer.as_ref()
251 }
252
253 pub fn window_expr(&self) -> &[Arc<dyn WindowExpr>] {
255 &self.window_expr
256 }
257
258 pub fn input(&self) -> &Arc<dyn ExecutionPlan> {
260 &self.input
261 }
262
263 pub fn partition_by_sort_keys(&self) -> Result<Vec<PhysicalSortExpr>> {
269 let partition_by = self.window_expr()[0].partition_by();
270 get_partition_by_sort_exprs(
271 &self.input,
272 partition_by,
273 &self.ordered_partition_by_indices,
274 )
275 }
276
277 fn get_search_algo(&self) -> Result<Box<dyn PartitionSearcher>> {
280 let partition_by_sort_keys = self.partition_by_sort_keys()?;
281 let ordered_partition_by_indices = self.ordered_partition_by_indices.clone();
282 let input_schema = self.input().schema();
283 Ok(match &self.input_order_mode {
284 InputOrderMode::Sorted => {
285 if self.window_expr()[0].partition_by().len()
287 != ordered_partition_by_indices.len()
288 {
289 return exec_err!(
290 "All partition by columns should have an ordering in Sorted mode."
291 );
292 }
293 Box::new(SortedSearch {
294 partition_by_sort_keys,
295 ordered_partition_by_indices,
296 input_schema,
297 })
298 }
299 InputOrderMode::Linear | InputOrderMode::PartiallySorted(_) => Box::new(
300 LinearSearch::new(ordered_partition_by_indices, input_schema),
301 ),
302 })
303 }
304
305 fn compute_properties(
307 input: &Arc<dyn ExecutionPlan>,
308 schema: &SchemaRef,
309 window_exprs: &[Arc<dyn WindowExpr>],
310 ) -> Result<PlanProperties> {
311 let eq_properties = window_equivalence_properties(schema, input, window_exprs)?;
313
314 let output_partitioning = input.output_partitioning().clone();
318
319 Ok(PlanProperties::new(
321 eq_properties,
322 output_partitioning,
323 input.pipeline_behavior(),
325 input.boundedness(),
326 ))
327 }
328
329 pub fn partition_keys(&self) -> Vec<Arc<dyn PhysicalExpr>> {
330 if !self.can_repartition {
331 vec![]
332 } else {
333 let all_partition_keys = self
334 .window_expr()
335 .iter()
336 .map(|expr| expr.partition_by().to_vec())
337 .collect::<Vec<_>>();
338
339 all_partition_keys
340 .into_iter()
341 .min_by_key(|s| s.len())
342 .unwrap_or_else(Vec::new)
343 }
344 }
345
346 fn statistics_helper(&self, statistics: Statistics) -> Result<Statistics> {
347 let win_cols = self.window_expr.len();
348 let input_cols = self.input.schema().fields().len();
349 let mut column_statistics = Vec::with_capacity(win_cols + input_cols);
351 column_statistics.extend(statistics.column_statistics);
353 for _ in 0..win_cols {
354 column_statistics.push(ColumnStatistics::new_unknown())
355 }
356 Ok(Statistics {
357 num_rows: statistics.num_rows,
358 column_statistics,
359 total_byte_size: Precision::Absent,
360 })
361 }
362}
363
364impl DisplayAs for BoundedWindowAggExec {
365 fn fmt_as(
366 &self,
367 t: DisplayFormatType,
368 f: &mut std::fmt::Formatter,
369 ) -> std::fmt::Result {
370 match t {
371 DisplayFormatType::Default | DisplayFormatType::Verbose => {
372 write!(f, "BoundedWindowAggExec: ")?;
373 let g: Vec<String> = self
374 .window_expr
375 .iter()
376 .map(|e| {
377 let field = match e.field() {
378 Ok(f) => f.to_string(),
379 Err(e) => format!("{e:?}"),
380 };
381 format!(
382 "{}: {}, frame: {}",
383 e.name().to_owned(),
384 field,
385 e.get_window_frame()
386 )
387 })
388 .collect();
389 let mode = &self.input_order_mode;
390 write!(f, "wdw=[{}], mode=[{:?}]", g.join(", "), mode)?;
391 }
392 DisplayFormatType::TreeRender => {
393 let g: Vec<String> = self
394 .window_expr
395 .iter()
396 .map(|e| e.name().to_owned().to_string())
397 .collect();
398 writeln!(f, "select_list={}", g.join(", "))?;
399
400 let mode = &self.input_order_mode;
401 writeln!(f, "mode={mode:?}")?;
402 }
403 }
404 Ok(())
405 }
406}
407
408impl ExecutionPlan for BoundedWindowAggExec {
409 fn name(&self) -> &'static str {
410 "BoundedWindowAggExec"
411 }
412
413 fn properties(&self) -> &Arc<PlanProperties> {
415 &self.cache
416 }
417
418 fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
419 vec![&self.input]
420 }
421
422 fn apply_expressions(
423 &self,
424 f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> Result<TreeNodeRecursion>,
425 ) -> Result<TreeNodeRecursion> {
426 let expressions = self.window_expr.iter().flat_map(|window_expr| {
427 let expressions = window_expr.all_expressions();
428 expressions
429 .args
430 .into_iter()
431 .chain(expressions.partition_by_exprs)
432 .chain(expressions.order_by_exprs)
433 });
434 crate::apply_expression_roots(expressions, f)
435 }
436
437 fn required_input_ordering(&self) -> Vec<Option<OrderingRequirements>> {
438 let partition_bys = self.window_expr()[0].partition_by();
439 let order_keys = self.window_expr()[0].order_by();
440 let partition_bys = self
441 .ordered_partition_by_indices
442 .iter()
443 .map(|idx| &partition_bys[*idx]);
444 vec![calc_requirements(partition_bys, order_keys)]
445 }
446
447 fn required_input_distribution(&self) -> Vec<Distribution> {
448 self.input_distribution_requirements().into_per_child()
449 }
450
451 fn input_distribution_requirements(&self) -> InputDistributionRequirements {
452 if self.partition_keys().is_empty() {
453 debug!("No partition defined for BoundedWindowAggExec!!!");
454 InputDistributionRequirements::new(vec![Distribution::SinglePartition])
455 } else {
456 InputDistributionRequirements::new(vec![Distribution::KeyPartitioned(
457 self.partition_keys(),
458 )])
459 }
460 }
461
462 fn maintains_input_order(&self) -> Vec<bool> {
463 vec![true]
464 }
465
466 fn replace_children(
467 self: Arc<Self>,
468 mut children: Vec<Arc<dyn ExecutionPlan>>,
469 options: ReplaceChildrenOptions,
470 ) -> Result<Arc<dyn ExecutionPlan>> {
471 validate_child_count!(self, children);
472 match options.children_properties {
473 ChildrenPropertiesMode::Keep => Ok(Arc::new(Self {
474 input: children.swap_remove(0),
475 metrics: ExecutionPlanMetricsSet::new(),
476 ..Self::clone(&*self)
477 })),
478 ChildrenPropertiesMode::Recompute => {
479 let new = BoundedWindowAggExec::try_new(
480 self.window_expr.clone(),
481 Arc::clone(&children[0]),
482 self.input_order_mode.clone(),
483 self.can_repartition,
484 )?
485 .with_state_observer(self.state_observer.clone())?;
486 Ok(Arc::new(new))
487 }
488 }
489 }
490
491 fn with_new_children(
492 self: Arc<Self>,
493 children: Vec<Arc<dyn ExecutionPlan>>,
494 ) -> Result<Arc<dyn ExecutionPlan>> {
495 self.replace_children(
496 children,
497 ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
498 )
499 }
500
501 fn with_new_children_and_same_properties(
502 self: Arc<Self>,
503 children: Vec<Arc<dyn ExecutionPlan>>,
504 ) -> Result<Arc<dyn ExecutionPlan>> {
505 self.replace_children(
506 children,
507 ReplaceChildrenOptions::new(ChildrenPropertiesMode::Keep),
508 )
509 }
510
511 fn execute(
512 &self,
513 partition: usize,
514 context: Arc<TaskContext>,
515 ) -> Result<SendableRecordBatchStream> {
516 let input = self.input.execute(partition, context)?;
517 let search_mode = self.get_search_algo()?;
518 let stream = Box::pin(BoundedWindowAggStream::new(
519 Arc::clone(&self.schema),
520 self.window_expr.clone(),
521 input,
522 BaselineMetrics::new(&self.metrics, partition),
523 search_mode,
524 partition,
525 self.state_observer.clone(),
526 )?);
527 Ok(stream)
528 }
529
530 fn metrics(&self) -> Option<MetricsSet> {
531 Some(self.metrics.clone_inner())
532 }
533
534 fn child_stats_requests(&self, partition: Option<usize>) -> Vec<ChildStats> {
535 vec![ChildStats::At(partition)]
536 }
537
538 fn statistics_from_inputs(
539 &self,
540 input_stats: &[Arc<Statistics>],
541 _args: &StatisticsArgs,
542 ) -> Result<Arc<Statistics>> {
543 let input_stat = input_stats[0].as_ref().clone();
544 Ok(Arc::new(self.statistics_helper(input_stat)?))
545 }
546
547 fn cardinality_effect(&self) -> CardinalityEffect {
548 CardinalityEffect::Equal
549 }
550
551 #[cfg(feature = "proto")]
552 fn try_to_proto(
553 &self,
554 ctx: &crate::proto::ExecutionPlanEncodeCtx<'_>,
555 ) -> Result<Option<datafusion_proto_models::protobuf::PhysicalPlanNode>> {
556 use super::proto::encode_physical_window_expr;
557 use datafusion_proto_common::protobuf_common::EmptyMessage;
558 use datafusion_proto_models::protobuf;
559 use protobuf::window_agg_exec_node::InputOrderMode as ProtoInputOrderMode;
560
561 let Self {
565 input,
566 window_expr,
567 schema: _,
570 metrics: _,
572 input_order_mode,
573 ordered_partition_by_indices: _,
576 cache: _,
578 can_repartition: _,
582 state_observer: _,
586 } = self;
587
588 let input = ctx.encode_child(input)?;
589 let window_expr = window_expr
590 .iter()
591 .map(|expr| encode_physical_window_expr(expr, ctx))
592 .collect::<Result<Vec<_>>>()?;
593 let partition_keys = self
594 .partition_keys()
595 .iter()
596 .map(|expr| ctx.encode_expr(expr))
597 .collect::<Result<Vec<_>>>()?;
598 let input_order_mode = match input_order_mode {
601 InputOrderMode::Linear => ProtoInputOrderMode::Linear(EmptyMessage {}),
602 InputOrderMode::PartiallySorted(columns) => {
603 ProtoInputOrderMode::PartiallySorted(
604 protobuf::PartiallySortedInputOrderMode {
605 columns: columns.iter().map(|column| *column as u64).collect(),
606 },
607 )
608 }
609 InputOrderMode::Sorted => ProtoInputOrderMode::Sorted(EmptyMessage {}),
610 };
611
612 Ok(Some(protobuf::PhysicalPlanNode {
613 physical_plan_type: Some(
614 protobuf::physical_plan_node::PhysicalPlanType::Window(Box::new(
615 protobuf::WindowAggExecNode {
616 input: Some(Box::new(input)),
617 window_expr,
618 partition_keys,
619 input_order_mode: Some(input_order_mode),
620 },
621 )),
622 ),
623 }))
624 }
625}
626
627trait PartitionSearcher: Send {
630 fn calculate_out_columns(
640 &mut self,
641 input_buffer: &RecordBatch,
642 window_agg_states: &[PartitionWindowAggStates],
643 partition_buffers: &mut PartitionBatches,
644 window_expr: &[Arc<dyn WindowExpr>],
645 ) -> Result<Option<Vec<ArrayRef>>>;
646
647 fn is_mode_linear(&self) -> bool {
649 false
650 }
651
652 fn evaluate_partition_batches(
654 &mut self,
655 record_batch: &RecordBatch,
656 window_expr: &[Arc<dyn WindowExpr>],
657 ) -> Result<Vec<(PartitionKey, RecordBatch)>>;
658
659 fn prune(&mut self, _n_out: usize) {}
661
662 fn mark_partition_end(&self, partition_buffers: &mut PartitionBatches);
665
666 fn update_partition_batch(
668 &mut self,
669 input_buffer: &mut RecordBatch,
670 record_batch: RecordBatch,
671 window_expr: &[Arc<dyn WindowExpr>],
672 partition_buffers: &mut PartitionBatches,
673 ) -> Result<()> {
674 if record_batch.num_rows() == 0 {
675 return Ok(());
676 }
677 let partition_batches =
678 self.evaluate_partition_batches(&record_batch, window_expr)?;
679 for (partition_row, partition_batch) in partition_batches {
680 if let Some(partition_batch_state) = partition_buffers.get_mut(&partition_row)
681 {
682 partition_batch_state.extend(&partition_batch)?
683 } else {
684 let options = RecordBatchOptions::new()
685 .with_row_count(Some(partition_batch.num_rows()));
686 let partition_batch = RecordBatch::try_new_with_options(
691 Arc::clone(self.input_schema()),
692 partition_batch.columns().to_vec(),
693 &options,
694 )?;
695 let partition_batch_state =
696 PartitionBatchState::new_with_batch(partition_batch);
697 partition_buffers.insert(partition_row, partition_batch_state);
698 }
699 }
700
701 self.mark_partition_end(partition_buffers);
702
703 *input_buffer = if input_buffer.num_rows() == 0 {
704 record_batch
705 } else {
706 concat_batches(self.input_schema(), [input_buffer, &record_batch])?
707 };
708
709 Ok(())
710 }
711
712 fn input_schema(&self) -> &SchemaRef;
713}
714
715pub struct LinearSearch {
718 input_buffer_hashes: VecDeque<u64>,
721 random_state: RandomState,
723 ordered_partition_by_indices: Vec<usize>,
728 row_map_batch: HashTable<(u64, usize)>,
732 row_map_out: HashTable<(u64, usize, usize)>,
738 input_schema: SchemaRef,
739}
740
741impl PartitionSearcher for LinearSearch {
742 fn calculate_out_columns(
777 &mut self,
778 input_buffer: &RecordBatch,
779 window_agg_states: &[PartitionWindowAggStates],
780 partition_buffers: &mut PartitionBatches,
781 window_expr: &[Arc<dyn WindowExpr>],
782 ) -> Result<Option<Vec<ArrayRef>>> {
783 let partition_output_indices = self.calc_partition_output_indices(
784 input_buffer,
785 window_agg_states,
786 window_expr,
787 )?;
788
789 let n_window_col = window_agg_states.len();
790 let mut new_columns = vec![vec![]; n_window_col];
791 let mut all_indices = UInt32Builder::with_capacity(input_buffer.num_rows());
793 for (row, indices) in partition_output_indices {
794 let length = indices.len();
795 for (idx, window_agg_state) in window_agg_states.iter().enumerate() {
796 let partition = &window_agg_state[&row];
797 let values = Arc::clone(&partition.state.out_col.slice(0, length));
798 new_columns[idx].push(values);
799 }
800 let partition_batch_state = &mut partition_buffers[&row];
801 partition_batch_state.n_out_row = length;
803 all_indices.append_slice(&indices);
805 }
806 let all_indices = all_indices.finish();
807 if all_indices.is_empty() {
808 return Ok(None);
810 }
811
812 let new_columns = new_columns
815 .iter()
816 .map(|items| {
817 concat(&items.iter().map(|e| e.as_ref()).collect::<Vec<_>>())
818 .map_err(|e| arrow_datafusion_err!(e))
819 })
820 .collect::<Result<Vec<_>>>()?;
821 let sorted_indices = sort_to_indices(&all_indices, None, None)?;
823 take_arrays(&new_columns, &sorted_indices, None)
825 .map(Some)
826 .map_err(|e| arrow_datafusion_err!(e))
827 }
828
829 fn evaluate_partition_batches(
830 &mut self,
831 record_batch: &RecordBatch,
832 window_expr: &[Arc<dyn WindowExpr>],
833 ) -> Result<Vec<(PartitionKey, RecordBatch)>> {
834 let partition_bys =
835 evaluate_partition_by_column_values(record_batch, window_expr)?;
836 let (mut keys, permutation, bounds) =
839 self.compute_partition_permutation(&partition_bys, record_batch)?;
840 if keys.len() == 1 {
841 let key = keys.remove(0);
844 return Ok(vec![(key, record_batch.clone())]);
845 }
846 let gathered = take_record_batch(record_batch, &UInt32Array::from(permutation))?;
852 Ok(keys
853 .into_iter()
854 .zip(bounds.windows(2))
855 .map(|(key, bound)| (key, gathered.slice(bound[0], bound[1] - bound[0])))
856 .collect())
857 }
858
859 fn prune(&mut self, n_out: usize) {
860 self.input_buffer_hashes.drain(0..n_out);
862 }
863
864 fn mark_partition_end(&self, partition_buffers: &mut PartitionBatches) {
865 if !self.ordered_partition_by_indices.is_empty()
868 && let Some((last_row, _)) = partition_buffers.last()
869 {
870 let last_sorted_cols = self
871 .ordered_partition_by_indices
872 .iter()
873 .map(|idx| last_row[*idx].clone())
874 .collect::<Vec<_>>();
875 for (row, partition_batch_state) in partition_buffers.iter_mut() {
876 let sorted_cols = self
877 .ordered_partition_by_indices
878 .iter()
879 .map(|idx| &row[*idx]);
880 partition_batch_state.is_end = !sorted_cols.eq(&last_sorted_cols);
884 }
885 }
886 }
887
888 fn is_mode_linear(&self) -> bool {
889 self.ordered_partition_by_indices.is_empty()
890 }
891
892 fn input_schema(&self) -> &SchemaRef {
893 &self.input_schema
894 }
895}
896
897impl LinearSearch {
898 fn new(ordered_partition_by_indices: Vec<usize>, input_schema: SchemaRef) -> Self {
900 LinearSearch {
901 input_buffer_hashes: VecDeque::new(),
902 random_state: Default::default(),
903 ordered_partition_by_indices,
904 row_map_batch: HashTable::with_capacity(256),
905 row_map_out: HashTable::with_capacity(256),
906 input_schema,
907 }
908 }
909
910 fn compute_partition_permutation(
918 &mut self,
919 columns: &[ArrayRef],
920 batch: &RecordBatch,
921 ) -> Result<(Vec<PartitionKey>, Vec<u32>, Vec<usize>)> {
922 let num_rows = batch.num_rows();
923 let mut batch_hashes = vec![0; num_rows];
924 create_hashes(columns, &self.random_state, &mut batch_hashes)?;
925 self.input_buffer_hashes.extend(&batch_hashes);
926 self.row_map_batch.clear();
928 let mut keys: Vec<PartitionKey> = vec![];
929 let mut row_partition_ids = Vec::with_capacity(num_rows);
931 let mut counts: Vec<usize> = vec![];
933 for (hash, row_idx) in batch_hashes.into_iter().zip(0u32..) {
934 let entry = self.row_map_batch.find_mut(hash, |(_, group_idx)| {
935 let row = get_row_at_idx(columns, row_idx as usize).unwrap();
936 row == keys[*group_idx]
938 });
939 let group_idx = if let Some((_, group_idx)) = entry {
940 *group_idx
941 } else {
942 let group_idx = keys.len();
943 self.row_map_batch
944 .insert_unique(hash, (hash, group_idx), |(hash, _)| *hash);
945 keys.push(get_row_at_idx(columns, row_idx as usize)?);
946 counts.push(0);
947 group_idx
948 };
949 row_partition_ids.push(group_idx);
950 counts[group_idx] += 1;
951 }
952 let mut bounds = Vec::with_capacity(counts.len() + 1);
955 let mut total = 0;
956 bounds.push(0);
957 for count in counts {
958 total += count;
959 bounds.push(total);
960 }
961 let mut cursors: Vec<usize> = bounds[..bounds.len() - 1].to_vec();
964 let mut permutation = vec![0u32; num_rows];
965 for (row_idx, group_idx) in row_partition_ids.into_iter().enumerate() {
966 permutation[cursors[group_idx]] = row_idx as u32;
967 cursors[group_idx] += 1;
968 }
969 Ok((keys, permutation, bounds))
970 }
971
972 fn calc_partition_output_indices(
977 &mut self,
978 input_buffer: &RecordBatch,
979 window_agg_states: &[PartitionWindowAggStates],
980 window_expr: &[Arc<dyn WindowExpr>],
981 ) -> Result<Vec<(PartitionKey, Vec<u32>)>> {
982 let partition_by_columns =
983 evaluate_partition_by_column_values(input_buffer, window_expr)?;
984 self.row_map_out.clear();
986 let mut partition_indices: Vec<(PartitionKey, Vec<u32>)> = vec![];
987 for (hash, row_idx) in self.input_buffer_hashes.iter().zip(0u32..) {
988 let entry = self.row_map_out.find_mut(*hash, |(_, group_idx, _)| {
989 let row =
990 get_row_at_idx(&partition_by_columns, row_idx as usize).unwrap();
991 row == partition_indices[*group_idx].0
992 });
993 if let Some((_, group_idx, n_out)) = entry {
994 let (_, indices) = &mut partition_indices[*group_idx];
995 if indices.len() >= *n_out {
996 break;
997 }
998 indices.push(row_idx);
999 } else {
1000 let row = get_row_at_idx(&partition_by_columns, row_idx as usize)?;
1001 let min_out = window_agg_states
1002 .iter()
1003 .map(|window_agg_state| {
1004 window_agg_state
1005 .get(&row)
1006 .map(|partition| partition.state.out_col.len())
1007 .unwrap_or(0)
1008 })
1009 .min()
1010 .unwrap_or(0);
1011 if min_out == 0 {
1012 break;
1013 }
1014 self.row_map_out.insert_unique(
1015 *hash,
1016 (*hash, partition_indices.len(), min_out),
1017 |(hash, _, _)| *hash,
1018 );
1019 partition_indices.push((row, vec![row_idx]));
1020 }
1021 }
1022 Ok(partition_indices)
1023 }
1024}
1025
1026pub struct SortedSearch {
1029 partition_by_sort_keys: Vec<PhysicalSortExpr>,
1031 ordered_partition_by_indices: Vec<usize>,
1036 input_schema: SchemaRef,
1037}
1038
1039impl PartitionSearcher for SortedSearch {
1040 fn calculate_out_columns(
1042 &mut self,
1043 _input_buffer: &RecordBatch,
1044 window_agg_states: &[PartitionWindowAggStates],
1045 partition_buffers: &mut PartitionBatches,
1046 _window_expr: &[Arc<dyn WindowExpr>],
1047 ) -> Result<Option<Vec<ArrayRef>>> {
1048 let n_out = self.calculate_n_out_row(window_agg_states, partition_buffers);
1049 if n_out == 0 {
1050 Ok(None)
1051 } else {
1052 window_agg_states
1053 .iter()
1054 .map(|map| get_aggregate_result_out_column(map, n_out).map(Some))
1055 .collect()
1056 }
1057 }
1058
1059 fn evaluate_partition_batches(
1060 &mut self,
1061 record_batch: &RecordBatch,
1062 _window_expr: &[Arc<dyn WindowExpr>],
1063 ) -> Result<Vec<(PartitionKey, RecordBatch)>> {
1064 let num_rows = record_batch.num_rows();
1065 let partition_columns = self
1067 .partition_by_sort_keys
1068 .iter()
1069 .map(|elem| elem.evaluate_to_sort_column(record_batch))
1070 .collect::<Result<Vec<_>>>()?;
1071 let partition_columns_ordered =
1073 get_at_indices(&partition_columns, &self.ordered_partition_by_indices)?;
1074 let partition_points =
1075 evaluate_partition_ranges(num_rows, &partition_columns_ordered)?;
1076 let partition_bys = partition_columns
1077 .into_iter()
1078 .map(|arr| arr.values)
1079 .collect::<Vec<ArrayRef>>();
1080
1081 partition_points
1082 .iter()
1083 .map(|range| {
1084 let row = get_row_at_idx(&partition_bys, range.start)?;
1085 let len = range.end - range.start;
1086 let slice = record_batch.slice(range.start, len);
1087 Ok((row, slice))
1088 })
1089 .collect::<Result<Vec<_>>>()
1090 }
1091
1092 fn mark_partition_end(&self, partition_buffers: &mut PartitionBatches) {
1093 let n_partitions = partition_buffers.len();
1097 for (idx, (_, partition_batch_state)) in partition_buffers.iter_mut().enumerate()
1098 {
1099 partition_batch_state.is_end |= idx < n_partitions - 1;
1100 }
1101 }
1102
1103 fn input_schema(&self) -> &SchemaRef {
1104 &self.input_schema
1105 }
1106}
1107
1108impl SortedSearch {
1109 fn calculate_n_out_row(
1111 &mut self,
1112 window_agg_states: &[PartitionWindowAggStates],
1113 partition_buffers: &mut PartitionBatches,
1114 ) -> usize {
1115 let mut counts = vec![];
1118 let out_col_counts = window_agg_states.iter().map(|window_agg_state| {
1119 let mut cur_window_expr_out_result_len = 0;
1122 let mut per_partition_out_results = HashMap::new();
1126 for (row, WindowState { state, .. }) in window_agg_state.iter() {
1127 cur_window_expr_out_result_len += state.out_col.len();
1128 let count = per_partition_out_results.entry(row).or_insert(0);
1129 if *count < state.out_col.len() {
1130 *count = state.out_col.len();
1131 }
1132 if state.n_row_result_missing > 0 {
1136 break;
1137 }
1138 }
1139 counts.push(per_partition_out_results);
1140 cur_window_expr_out_result_len
1141 });
1142 argmin(out_col_counts).map_or(0, |(min_idx, minima)| {
1143 let mut slowest_partition = counts.swap_remove(min_idx);
1144 for (partition_key, partition_batch) in partition_buffers.iter_mut() {
1145 if let Some(count) = slowest_partition.remove(partition_key) {
1146 partition_batch.n_out_row = count;
1147 }
1148 }
1149 minima
1150 })
1151 }
1152}
1153
1154fn evaluate_partition_by_column_values(
1157 record_batch: &RecordBatch,
1158 window_expr: &[Arc<dyn WindowExpr>],
1159) -> Result<Vec<ArrayRef>> {
1160 window_expr[0]
1161 .partition_by()
1162 .iter()
1163 .map(|item| match item.evaluate(record_batch)? {
1164 ColumnarValue::Array(array) => Ok(array),
1165 ColumnarValue::Scalar(scalar) => {
1166 scalar.to_array_of_size(record_batch.num_rows())
1167 }
1168 })
1169 .collect()
1170}
1171
1172pub struct BoundedWindowAggStream {
1174 schema: SchemaRef,
1175 input: SendableRecordBatchStream,
1176 input_buffer: RecordBatch,
1179 partition_buffers: PartitionBatches,
1183 window_agg_states: Vec<PartitionWindowAggStates>,
1187 finished: bool,
1188 window_expr: Vec<Arc<dyn WindowExpr>>,
1189 baseline_metrics: BaselineMetrics,
1190 search_mode: Box<dyn PartitionSearcher>,
1193 most_recent_row: Option<RecordBatch>,
1211 partition_idx: usize,
1214 state_observer: Option<Arc<dyn WindowStateObserver>>,
1218}
1219
1220impl BoundedWindowAggStream {
1221 fn publish_finalized_states(
1230 &mut self,
1231 observer: &dyn WindowStateObserver,
1232 ) -> Result<()> {
1233 let partition_idx = self.partition_idx;
1234 for (expr_idx, per_expr) in self.window_agg_states.iter_mut().enumerate() {
1235 let window_expr = &self.window_expr[expr_idx];
1236 for (key, ws) in per_expr.iter_mut() {
1237 if ws.published || !ws.state.is_end {
1238 continue;
1239 }
1240 if let Some(state) = ws.aggregate_state()? {
1241 observer.finalize_window_aggregate(
1242 partition_idx,
1243 window_expr,
1244 key,
1245 state,
1246 )?;
1247 }
1248 }
1249 }
1250 Ok(())
1251 }
1252
1253 fn prune_state(&mut self, n_out: usize) -> Result<()> {
1261 self.prune_out_columns();
1263 self.prune_partition_batches();
1265 self.prune_input_batch(n_out)?;
1267 self.search_mode.prune(n_out);
1269 Ok(())
1270 }
1271}
1272
1273impl Stream for BoundedWindowAggStream {
1274 type Item = Result<RecordBatch>;
1275
1276 fn poll_next(
1277 mut self: Pin<&mut Self>,
1278 cx: &mut Context<'_>,
1279 ) -> Poll<Option<Self::Item>> {
1280 let poll = self.poll_next_inner(cx);
1281 self.baseline_metrics.record_poll(poll)
1282 }
1283}
1284
1285impl BoundedWindowAggStream {
1286 fn new(
1288 schema: SchemaRef,
1289 window_expr: Vec<Arc<dyn WindowExpr>>,
1290 input: SendableRecordBatchStream,
1291 baseline_metrics: BaselineMetrics,
1292 search_mode: Box<dyn PartitionSearcher>,
1293 partition_idx: usize,
1294 state_observer: Option<Arc<dyn WindowStateObserver>>,
1295 ) -> Result<Self> {
1296 let state = window_expr.iter().map(|_| IndexMap::default()).collect();
1297 let empty_batch = RecordBatch::new_empty(Arc::clone(&schema));
1298 Ok(Self {
1299 schema,
1300 input,
1301 input_buffer: empty_batch,
1302 partition_buffers: IndexMap::default(),
1303 window_agg_states: state,
1304 finished: false,
1305 window_expr,
1306 baseline_metrics,
1307 search_mode,
1308 most_recent_row: None,
1309 partition_idx,
1310 state_observer,
1311 })
1312 }
1313
1314 fn compute_aggregates(&mut self) -> Result<Option<RecordBatch>> {
1315 let eval_ctx = WindowEvalContext::default()
1317 .with_most_recent_row(self.most_recent_row.as_ref());
1318 for (cur_window_expr, state) in
1319 self.window_expr.iter().zip(&mut self.window_agg_states)
1320 {
1321 cur_window_expr.evaluate_stateful(
1322 &self.partition_buffers,
1323 state,
1324 &eval_ctx,
1325 )?;
1326 }
1327
1328 if let Some(observer) = self.state_observer.clone() {
1333 self.publish_finalized_states(observer.as_ref())?;
1334 }
1335
1336 let schema = Arc::clone(&self.schema);
1337 let window_expr_out = self.search_mode.calculate_out_columns(
1338 &self.input_buffer,
1339 &self.window_agg_states,
1340 &mut self.partition_buffers,
1341 &self.window_expr,
1342 )?;
1343 if let Some(window_expr_out) = window_expr_out {
1344 let n_out = window_expr_out[0].len();
1345 let columns_to_show = self
1347 .input_buffer
1348 .columns()
1349 .iter()
1350 .map(|elem| elem.slice(0, n_out))
1351 .chain(window_expr_out)
1352 .collect::<Vec<_>>();
1353 let n_generated = columns_to_show[0].len();
1354 self.prune_state(n_generated)?;
1355 Ok(Some(RecordBatch::try_new(schema, columns_to_show)?))
1356 } else {
1357 Ok(None)
1358 }
1359 }
1360
1361 #[inline]
1362 fn poll_next_inner(
1363 &mut self,
1364 cx: &mut Context<'_>,
1365 ) -> Poll<Option<Result<RecordBatch>>> {
1366 if self.finished {
1367 return Poll::Ready(None);
1368 }
1369
1370 let elapsed_compute = self.baseline_metrics.elapsed_compute().clone();
1371 match ready!(self.input.poll_next_unpin(cx)) {
1372 Some(Ok(batch)) => {
1373 let _timer = elapsed_compute.timer();
1376
1377 if self.search_mode.is_mode_linear() && batch.num_rows() > 0 {
1378 self.most_recent_row = Some(get_last_row_batch(&batch)?);
1379 }
1380 self.search_mode.update_partition_batch(
1381 &mut self.input_buffer,
1382 batch,
1383 &self.window_expr,
1384 &mut self.partition_buffers,
1385 )?;
1386 if let Some(batch) = self.compute_aggregates()? {
1387 return Poll::Ready(Some(Ok(batch)));
1388 }
1389 self.poll_next_inner(cx)
1390 }
1391 Some(Err(e)) => Poll::Ready(Some(Err(e))),
1392 None => {
1393 let _timer = elapsed_compute.timer();
1394
1395 self.finished = true;
1396 let input_schema = self.input.schema();
1399 self.input = Box::pin(EmptyRecordBatchStream::new(input_schema));
1400 for (_, partition_batch_state) in self.partition_buffers.iter_mut() {
1401 partition_batch_state.is_end = true;
1402 }
1403 if let Some(batch) = self.compute_aggregates()? {
1404 return Poll::Ready(Some(Ok(batch)));
1405 }
1406 Poll::Ready(None)
1407 }
1408 }
1409 }
1410
1411 fn prune_partition_batches(&mut self) {
1414 #[cfg(debug_assertions)]
1417 for window_agg_state in self.window_agg_states.iter() {
1418 for (partition_row, WindowState { state, .. }) in window_agg_state.iter() {
1419 debug_assert_eq!(
1420 state.is_end, self.partition_buffers[partition_row].is_end,
1421 "window state's recorded end flag is out of sync with its partition"
1422 );
1423 }
1424 }
1425
1426 self.partition_buffers
1430 .retain(|_, partition_batch_state| !partition_batch_state.is_end);
1431 for window_agg_state in self.window_agg_states.iter_mut() {
1433 window_agg_state.retain(|_, WindowState { state, .. }| !state.is_end);
1434 }
1435
1436 let mut n_prune_each_partition = HashMap::new();
1445 if let Some((first, rest)) = self.window_agg_states.split_first() {
1446 for (partition_row, WindowState { state, .. }) in first.iter() {
1448 let n_prune =
1449 min(state.window_frame_range.start, state.last_calculated_index);
1450 if n_prune > 0 {
1451 n_prune_each_partition.insert(partition_row.clone(), n_prune);
1452 }
1453 }
1454 for window_agg_state in rest {
1457 n_prune_each_partition.retain(|partition_row, current| {
1458 let Some(WindowState { state, .. }) =
1459 window_agg_state.get(partition_row)
1460 else {
1461 return false;
1462 };
1463 let n_prune =
1464 min(state.window_frame_range.start, state.last_calculated_index);
1465 *current = min(*current, n_prune);
1466 *current > 0
1467 });
1468 }
1469 }
1470
1471 for (partition_row, n_prune) in n_prune_each_partition.iter() {
1473 debug_assert!(
1474 *n_prune > 0,
1475 "prune-count map must only contain positive entries"
1476 );
1477 let pb_state = &mut self.partition_buffers[partition_row];
1478
1479 let batch = &pb_state.record_batch;
1480 pb_state.record_batch = batch.slice(*n_prune, batch.num_rows() - n_prune);
1481
1482 for window_agg_state in self.window_agg_states.iter_mut() {
1484 window_agg_state[partition_row].state.prune_state(*n_prune);
1485 }
1486 }
1487 }
1488
1489 fn prune_input_batch(&mut self, n_out: usize) -> Result<()> {
1492 let n_to_keep = self.input_buffer.num_rows() - n_out;
1494 let batch_to_keep = self
1495 .input_buffer
1496 .columns()
1497 .iter()
1498 .map(|elem| elem.slice(n_out, n_to_keep))
1499 .collect::<Vec<_>>();
1500 self.input_buffer = RecordBatch::try_new_with_options(
1501 self.input_buffer.schema(),
1502 batch_to_keep,
1503 &RecordBatchOptions::new().with_row_count(Some(n_to_keep)),
1504 )?;
1505 Ok(())
1506 }
1507
1508 fn prune_out_columns(&mut self) {
1510 for partition_window_agg_states in self.window_agg_states.iter_mut() {
1514 partition_window_agg_states
1517 .retain(|_, partition_batch_state| !partition_batch_state.state.is_end);
1518 }
1519 for (partition_key, partition_batch) in self.partition_buffers.iter_mut() {
1524 let n_emitted = partition_batch.n_out_row;
1525 if n_emitted == 0 {
1526 continue;
1527 }
1528 partition_batch.n_out_row = 0;
1529 for partition_window_agg_states in self.window_agg_states.iter_mut() {
1530 if let Some(WindowState { state, .. }) =
1531 partition_window_agg_states.get_mut(partition_key)
1532 {
1533 let out_col = &mut state.out_col;
1534 let n_to_keep = out_col.len() - n_emitted;
1535 *out_col = out_col.slice(n_emitted, n_to_keep);
1536 }
1537 }
1538 }
1539 }
1540}
1541
1542impl RecordBatchStream for BoundedWindowAggStream {
1543 fn schema(&self) -> SchemaRef {
1545 Arc::clone(&self.schema)
1546 }
1547}
1548
1549fn argmin<T: PartialOrd>(data: impl Iterator<Item = T>) -> Option<(usize, T)> {
1551 data.enumerate()
1552 .min_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(Ordering::Equal))
1553}
1554
1555fn get_aggregate_result_out_column(
1557 partition_window_agg_states: &PartitionWindowAggStates,
1558 len_to_show: usize,
1559) -> Result<ArrayRef> {
1560 let mut result = None;
1561 let mut running_length = 0;
1562 let mut batches_to_concat = vec![];
1563 for (
1565 _,
1566 WindowState {
1567 state: WindowAggState { out_col, .. },
1568 ..
1569 },
1570 ) in partition_window_agg_states
1571 {
1572 if running_length < len_to_show {
1573 let n_to_use = min(len_to_show - running_length, out_col.len());
1574 let slice_to_use = if n_to_use == out_col.len() {
1575 Arc::clone(out_col)
1577 } else {
1578 out_col.slice(0, n_to_use)
1579 };
1580 batches_to_concat.push(slice_to_use);
1581 running_length += n_to_use;
1582 } else {
1583 break;
1584 }
1585 }
1586
1587 if !batches_to_concat.is_empty() {
1588 let array_refs: Vec<&dyn Array> =
1589 batches_to_concat.iter().map(|a| a.as_ref()).collect();
1590 result = Some(concat(&array_refs)?);
1591 }
1592
1593 if running_length != len_to_show {
1594 return exec_err!(
1595 "Generated row number should be {len_to_show}, it is {running_length}"
1596 );
1597 }
1598 result.ok_or_else(|| exec_datafusion_err!("Should contain something"))
1599}
1600
1601pub(crate) fn get_last_row_batch(batch: &RecordBatch) -> Result<RecordBatch> {
1603 if batch.num_rows() == 0 {
1604 return exec_err!("Latest batch should have at least 1 row");
1605 }
1606 Ok(batch.slice(batch.num_rows() - 1, 1))
1607}
1608
1609#[cfg(test)]
1610mod tests {
1611 use std::pin::Pin;
1612 use std::sync::Arc;
1613 use std::task::{Context, Poll};
1614 use std::time::Duration;
1615
1616 use crate::common::collect;
1617 use crate::execution_plan::CardinalityEffect;
1618 use crate::expressions::PhysicalSortExpr;
1619 use crate::projection::{ProjectionExec, ProjectionExpr};
1620 use crate::streaming::{PartitionStream, StreamingTableExec};
1621 use crate::test::TestMemoryExec;
1622 use crate::windows::bounded_window_agg_exec::WindowStateObserver;
1623 use crate::windows::{
1624 BoundedWindowAggExec, InputOrderMode, create_udwf_window_expr, create_window_expr,
1625 };
1626 use crate::{ExecutionPlan, WindowExpr, displayable, execute_stream};
1627
1628 use arrow::array::{
1629 RecordBatch,
1630 builder::{Int64Builder, UInt64Builder},
1631 };
1632 use arrow::compute::SortOptions;
1633 use arrow::datatypes::{DataType, Field, Schema, SchemaRef};
1634 use datafusion_common::test_util::batches_to_string;
1635 use datafusion_common::{Result, ScalarValue, exec_datafusion_err};
1636 use datafusion_execution::config::SessionConfig;
1637 use datafusion_execution::{
1638 RecordBatchStream, SendableRecordBatchStream, TaskContext,
1639 };
1640 use datafusion_expr::{
1641 WindowFrame, WindowFrameBound, WindowFrameUnits, WindowFunctionDefinition,
1642 };
1643 use datafusion_functions_aggregate::count::count_udaf;
1644 use datafusion_functions_aggregate::sum::sum_udaf;
1645 use datafusion_functions_window::nth_value::last_value_udwf;
1646 use datafusion_functions_window::nth_value::nth_value_udwf;
1647 use datafusion_physical_expr::expressions::{Column, Literal, col};
1648 use datafusion_physical_expr::window::{PartitionKey, StandardWindowExpr};
1649 use datafusion_physical_expr::{LexOrdering, PhysicalExpr};
1650
1651 use futures::future::Shared;
1652 use futures::{FutureExt, Stream, StreamExt, pin_mut, ready};
1653 use insta::assert_snapshot;
1654 use itertools::Itertools;
1655 use tokio::time::timeout;
1656
1657 #[derive(Debug, Clone)]
1658 struct TestStreamPartition {
1659 schema: SchemaRef,
1660 batches: Vec<RecordBatch>,
1661 idx: usize,
1662 state: PolingState,
1663 sleep_duration: Duration,
1664 send_exit: bool,
1665 }
1666
1667 impl PartitionStream for TestStreamPartition {
1668 fn schema(&self) -> &SchemaRef {
1669 &self.schema
1670 }
1671
1672 fn execute(&self, _ctx: Arc<TaskContext>) -> SendableRecordBatchStream {
1673 Box::pin(self.clone())
1676 }
1677 }
1678
1679 impl Stream for TestStreamPartition {
1680 type Item = Result<RecordBatch>;
1681
1682 fn poll_next(
1683 mut self: Pin<&mut Self>,
1684 cx: &mut Context<'_>,
1685 ) -> Poll<Option<Self::Item>> {
1686 self.poll_next_inner(cx)
1687 }
1688 }
1689
1690 #[derive(Debug, Clone)]
1691 enum PolingState {
1692 Sleep(Shared<futures::future::BoxFuture<'static, ()>>),
1693 BatchReturn,
1694 }
1695
1696 impl TestStreamPartition {
1697 fn poll_next_inner(
1698 self: &mut Pin<&mut Self>,
1699 cx: &mut Context<'_>,
1700 ) -> Poll<Option<Result<RecordBatch>>> {
1701 loop {
1702 match &mut self.state {
1703 PolingState::BatchReturn => {
1704 let f = tokio::time::sleep(self.sleep_duration).boxed().shared();
1706 self.state = PolingState::Sleep(f);
1707 let input_batch = if let Some(batch) =
1708 self.batches.clone().get(self.idx)
1709 {
1710 batch.clone()
1711 } else if self.send_exit {
1712 return Poll::Ready(None);
1714 } else {
1715 let f =
1717 tokio::time::sleep(self.sleep_duration).boxed().shared();
1718 self.state = PolingState::Sleep(f);
1719 continue;
1720 };
1721 self.idx += 1;
1722 return Poll::Ready(Some(Ok(input_batch)));
1723 }
1724 PolingState::Sleep(future) => {
1725 pin_mut!(future);
1726 ready!(future.poll_unpin(cx));
1727 self.state = PolingState::BatchReturn;
1728 }
1729 }
1730 }
1731 }
1732 }
1733
1734 impl RecordBatchStream for TestStreamPartition {
1735 fn schema(&self) -> SchemaRef {
1736 Arc::clone(&self.schema)
1737 }
1738 }
1739
1740 fn bounded_window_exec_pb_latent_range(
1741 input: Arc<dyn ExecutionPlan>,
1742 n_future_range: usize,
1743 hash: &str,
1744 order_by: &str,
1745 ) -> Result<Arc<dyn ExecutionPlan>> {
1746 let schema = input.schema();
1747 let window_fn = WindowFunctionDefinition::AggregateUDF(count_udaf());
1748 let col_expr =
1749 Arc::new(Column::new(schema.fields[0].name(), 0)) as Arc<dyn PhysicalExpr>;
1750 let args = vec![col_expr];
1751 let partitionby_exprs = vec![col(hash, &schema)?];
1752 let orderby_exprs = vec![PhysicalSortExpr {
1753 expr: col(order_by, &schema)?,
1754 options: SortOptions::default(),
1755 }];
1756 let window_frame = WindowFrame::new_bounds(
1757 WindowFrameUnits::Range,
1758 WindowFrameBound::CurrentRow,
1759 WindowFrameBound::Following(ScalarValue::UInt64(Some(n_future_range as u64))),
1760 );
1761 let fn_name = format!(
1762 "{window_fn}({args:?}) PARTITION BY: [{partitionby_exprs:?}], ORDER BY: [{orderby_exprs:?}]"
1763 );
1764 let input_order_mode = InputOrderMode::Linear;
1765 Ok(Arc::new(BoundedWindowAggExec::try_new(
1766 vec![create_window_expr(
1767 &window_fn,
1768 fn_name,
1769 &args,
1770 &partitionby_exprs,
1771 &orderby_exprs,
1772 Arc::new(window_frame),
1773 input.schema(),
1774 false,
1775 false,
1776 None,
1777 )?],
1778 input,
1779 input_order_mode,
1780 true,
1781 )?))
1782 }
1783
1784 fn projection_exec(input: Arc<dyn ExecutionPlan>) -> Result<Arc<dyn ExecutionPlan>> {
1785 let schema = input.schema();
1786 let exprs = input
1787 .schema()
1788 .fields
1789 .iter()
1790 .enumerate()
1791 .map(|(idx, field)| {
1792 let name = if field.name().len() > 20 {
1793 format!("col_{idx}")
1794 } else {
1795 field.name().clone()
1796 };
1797 let expr = col(field.name(), &schema).unwrap();
1798 (expr, name)
1799 })
1800 .collect::<Vec<_>>();
1801 let proj_exprs: Vec<ProjectionExpr> = exprs
1802 .into_iter()
1803 .map(|(expr, alias)| ProjectionExpr { expr, alias })
1804 .collect();
1805 Ok(Arc::new(ProjectionExec::try_new(proj_exprs, input)?))
1806 }
1807
1808 fn task_context_helper() -> TaskContext {
1809 let task_ctx = TaskContext::default();
1810 let session_config = SessionConfig::new()
1812 .with_batch_size(1)
1813 .with_target_partitions(2)
1814 .with_round_robin_repartition(false);
1815 task_ctx.with_session_config(session_config)
1816 }
1817
1818 fn task_context() -> Arc<TaskContext> {
1819 Arc::new(task_context_helper())
1820 }
1821
1822 pub async fn collect_stream(
1823 mut stream: SendableRecordBatchStream,
1824 results: &mut Vec<RecordBatch>,
1825 ) -> Result<()> {
1826 while let Some(item) = stream.next().await {
1827 results.push(item?);
1828 }
1829 Ok(())
1830 }
1831
1832 pub async fn collect_with_timeout(
1834 plan: Arc<dyn ExecutionPlan>,
1835 context: Arc<TaskContext>,
1836 timeout_duration: Duration,
1837 ) -> Result<Vec<RecordBatch>> {
1838 let stream = execute_stream(plan, context)?;
1839 let mut results = vec![];
1840
1841 if timeout(timeout_duration, collect_stream(stream, &mut results))
1843 .await
1844 .is_ok()
1845 {
1846 return Err(exec_datafusion_err!("shouldn't have completed"));
1847 };
1848
1849 Ok(results)
1850 }
1851
1852 fn test_schema() -> SchemaRef {
1853 Arc::new(Schema::new(vec![
1854 Field::new("sn", DataType::UInt64, true),
1855 Field::new("hash", DataType::Int64, true),
1856 ]))
1857 }
1858
1859 fn schema_orders(schema: &SchemaRef) -> Result<Vec<LexOrdering>> {
1860 let orderings = vec![
1861 [PhysicalSortExpr {
1862 expr: col("sn", schema)?,
1863 options: SortOptions {
1864 descending: false,
1865 nulls_first: false,
1866 },
1867 }]
1868 .into(),
1869 ];
1870 Ok(orderings)
1871 }
1872
1873 fn is_integer_division_safe(lhs: usize, rhs: usize) -> bool {
1874 let res = lhs / rhs;
1875 res * rhs == lhs
1876 }
1877 fn generate_batches(
1878 schema: &SchemaRef,
1879 n_row: usize,
1880 n_chunk: usize,
1881 ) -> Result<Vec<RecordBatch>> {
1882 let mut batches = vec![];
1883 assert!(n_row > 0);
1884 assert!(n_chunk > 0);
1885 assert!(is_integer_division_safe(n_row, n_chunk));
1886 let hash_replicate = 4;
1887
1888 let chunks = (0..n_row)
1889 .chunks(n_chunk)
1890 .into_iter()
1891 .map(|elem| elem.into_iter().collect::<Vec<_>>())
1892 .collect::<Vec<_>>();
1893
1894 for sn_values in chunks {
1896 let mut sn1_array = UInt64Builder::with_capacity(sn_values.len());
1897 let mut hash_array = Int64Builder::with_capacity(sn_values.len());
1898
1899 for sn in sn_values {
1900 sn1_array.append_value(sn as u64);
1901 let hash_value = (2 - (sn / hash_replicate)) as i64;
1902 hash_array.append_value(hash_value);
1903 }
1904
1905 let batch = RecordBatch::try_new(
1906 Arc::clone(schema),
1907 vec![Arc::new(sn1_array.finish()), Arc::new(hash_array.finish())],
1908 )?;
1909 batches.push(batch);
1910 }
1911 Ok(batches)
1912 }
1913
1914 fn generate_never_ending_source(
1915 n_rows: usize,
1916 chunk_length: usize,
1917 n_partition: usize,
1918 is_infinite: bool,
1919 send_exit: bool,
1920 per_batch_wait_duration_in_millis: u64,
1921 ) -> Result<Arc<dyn ExecutionPlan>> {
1922 assert!(n_partition > 0);
1923
1924 let schema = test_schema();
1928 let orderings = schema_orders(&schema)?;
1929
1930 let per_batch_wait_duration =
1932 Duration::from_millis(per_batch_wait_duration_in_millis);
1933
1934 let batches = generate_batches(&schema, n_rows, chunk_length)?;
1935
1936 let partitions = vec![
1938 Arc::new(TestStreamPartition {
1939 schema: Arc::clone(&schema),
1940 batches,
1941 idx: 0,
1942 state: PolingState::BatchReturn,
1943 sleep_duration: per_batch_wait_duration,
1944 send_exit,
1945 }) as _;
1946 n_partition
1947 ];
1948 let source = Arc::new(StreamingTableExec::try_new(
1949 Arc::clone(&schema),
1950 partitions,
1951 None,
1952 orderings,
1953 is_infinite,
1954 None,
1955 )?) as _;
1956 Ok(source)
1957 }
1958
1959 #[tokio::test]
1965 async fn test_window_nth_value_bounded_memoize() -> Result<()> {
1966 let config = SessionConfig::new().with_target_partitions(1);
1967 let task_ctx = Arc::new(TaskContext::default().with_session_config(config));
1968
1969 let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, false)]));
1970 let batch = RecordBatch::try_new(
1972 Arc::clone(&schema),
1973 vec![Arc::new(arrow::array::Int32Array::from(vec![1, 2, 3]))],
1974 )?;
1975
1976 let memory_exec = TestMemoryExec::try_new_exec(
1977 &[vec![batch.clone(), batch.clone(), batch.clone()]],
1978 Arc::clone(&schema),
1979 None,
1980 )?;
1981 let col_a = col("a", &schema)?;
1982 let nth_value_func1 = create_udwf_window_expr(
1983 &nth_value_udwf(),
1984 &[
1985 Arc::clone(&col_a),
1986 Arc::new(Literal::new(ScalarValue::Int32(Some(1)))),
1987 ],
1988 &schema,
1989 "nth_value(-1)".to_string(),
1990 false,
1991 )?
1992 .reverse_expr()
1993 .unwrap();
1994 let nth_value_func2 = create_udwf_window_expr(
1995 &nth_value_udwf(),
1996 &[
1997 Arc::clone(&col_a),
1998 Arc::new(Literal::new(ScalarValue::Int32(Some(2)))),
1999 ],
2000 &schema,
2001 "nth_value(-2)".to_string(),
2002 false,
2003 )?
2004 .reverse_expr()
2005 .unwrap();
2006
2007 let last_value_func = create_udwf_window_expr(
2008 &last_value_udwf(),
2009 &[Arc::clone(&col_a)],
2010 &schema,
2011 "last".to_string(),
2012 false,
2013 )?;
2014
2015 let window_exprs = vec![
2016 Arc::new(StandardWindowExpr::new(
2018 last_value_func,
2019 &[],
2020 &[],
2021 Arc::new(WindowFrame::new_bounds(
2022 WindowFrameUnits::Rows,
2023 WindowFrameBound::Preceding(ScalarValue::UInt64(None)),
2024 WindowFrameBound::CurrentRow,
2025 )),
2026 )) as _,
2027 Arc::new(StandardWindowExpr::new(
2029 nth_value_func1,
2030 &[],
2031 &[],
2032 Arc::new(WindowFrame::new_bounds(
2033 WindowFrameUnits::Rows,
2034 WindowFrameBound::Preceding(ScalarValue::UInt64(None)),
2035 WindowFrameBound::CurrentRow,
2036 )),
2037 )) as _,
2038 Arc::new(StandardWindowExpr::new(
2040 nth_value_func2,
2041 &[],
2042 &[],
2043 Arc::new(WindowFrame::new_bounds(
2044 WindowFrameUnits::Rows,
2045 WindowFrameBound::Preceding(ScalarValue::UInt64(None)),
2046 WindowFrameBound::CurrentRow,
2047 )),
2048 )) as _,
2049 ];
2050 let physical_plan = BoundedWindowAggExec::try_new(
2051 window_exprs,
2052 memory_exec,
2053 InputOrderMode::Sorted,
2054 true,
2055 )
2056 .map(|e| Arc::new(e) as Arc<dyn ExecutionPlan>)?;
2057
2058 let batches = collect(physical_plan.execute(0, task_ctx)?).await?;
2059
2060 assert_snapshot!(displayable(physical_plan.as_ref()).indent(true), @r#"
2062 BoundedWindowAggExec: wdw=[last: Field { "last": nullable Int32 }, frame: ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW, nth_value(-1): Field { "nth_value(-1)": nullable Int32 }, frame: ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW, nth_value(-2): Field { "nth_value(-2)": nullable Int32 }, frame: ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW], mode=[Sorted]
2063 DataSourceExec: partitions=1, partition_sizes=[3]
2064 "#);
2065
2066 assert_snapshot!(batches_to_string(&batches), @r"
2067 +---+------+---------------+---------------+
2068 | a | last | nth_value(-1) | nth_value(-2) |
2069 +---+------+---------------+---------------+
2070 | 1 | 1 | 1 | |
2071 | 2 | 2 | 2 | 1 |
2072 | 3 | 3 | 3 | 2 |
2073 | 1 | 1 | 1 | 3 |
2074 | 2 | 2 | 2 | 1 |
2075 | 3 | 3 | 3 | 2 |
2076 | 1 | 1 | 1 | 3 |
2077 | 2 | 2 | 2 | 1 |
2078 | 3 | 3 | 3 | 2 |
2079 +---+------+---------------+---------------+
2080 ");
2081 Ok(())
2082 }
2083
2084 #[tokio::test]
2093 async fn bounded_window_linear_quiet_partition_resume() -> Result<()> {
2094 let schema = Arc::new(Schema::new(vec![
2095 Field::new("pk", DataType::UInt64, false),
2096 Field::new("ts", DataType::UInt64, false),
2097 ]));
2098 let make_batch = |rows: &[(u64, u64)]| -> Result<RecordBatch> {
2099 let mut pk = UInt64Builder::with_capacity(rows.len());
2100 let mut ts = UInt64Builder::with_capacity(rows.len());
2101 for (p, t) in rows {
2102 pk.append_value(*p);
2103 ts.append_value(*t);
2104 }
2105 Ok(RecordBatch::try_new(
2106 Arc::clone(&schema),
2107 vec![Arc::new(pk.finish()), Arc::new(ts.finish())],
2108 )?)
2109 };
2110 let batches = vec![
2112 make_batch(&[(0, 0), (0, 1), (1, 2)])?,
2113 make_batch(&[(1, 3), (1, 4)])?,
2114 make_batch(&[(1, 5)])?,
2115 make_batch(&[(0, 6), (1, 7)])?,
2116 ];
2117 let memory_exec =
2118 TestMemoryExec::try_new_exec(&[batches], Arc::clone(&schema), None)?;
2119
2120 let partition_by = vec![col("pk", &schema)?];
2121 let order_by = [PhysicalSortExpr {
2122 expr: col("ts", &schema)?,
2123 options: SortOptions::default(),
2124 }];
2125 let count_expr = create_window_expr(
2128 &WindowFunctionDefinition::AggregateUDF(count_udaf()),
2129 "count".to_string(),
2130 &[col("ts", &schema)?],
2131 &partition_by,
2132 &order_by,
2133 Arc::new(WindowFrame::new_bounds(
2134 WindowFrameUnits::Rows,
2135 WindowFrameBound::Preceding(ScalarValue::UInt64(None)),
2136 WindowFrameBound::CurrentRow,
2137 )),
2138 Arc::clone(&schema),
2139 false,
2140 false,
2141 None,
2142 )?;
2143 let sum_expr = create_window_expr(
2144 &WindowFunctionDefinition::AggregateUDF(sum_udaf()),
2145 "sum".to_string(),
2146 &[col("ts", &schema)?],
2147 &partition_by,
2148 &order_by,
2149 Arc::new(WindowFrame::new_bounds(
2150 WindowFrameUnits::Rows,
2151 WindowFrameBound::Preceding(ScalarValue::UInt64(Some(1))),
2152 WindowFrameBound::CurrentRow,
2153 )),
2154 Arc::clone(&schema),
2155 false,
2156 false,
2157 None,
2158 )?;
2159 let physical_plan = BoundedWindowAggExec::try_new(
2160 vec![count_expr, sum_expr],
2161 memory_exec,
2162 InputOrderMode::Linear,
2163 true,
2164 )
2165 .map(|e| Arc::new(e) as Arc<dyn ExecutionPlan>)?;
2166
2167 let batches = collect(physical_plan.execute(0, task_context())?).await?;
2168
2169 assert_snapshot!(batches_to_string(&batches), @r"
2170 +----+----+-------+-----+
2171 | pk | ts | count | sum |
2172 +----+----+-------+-----+
2173 | 0 | 0 | 1 | 0 |
2174 | 0 | 1 | 2 | 1 |
2175 | 1 | 2 | 1 | 2 |
2176 | 1 | 3 | 2 | 5 |
2177 | 1 | 4 | 3 | 7 |
2178 | 1 | 5 | 4 | 9 |
2179 | 0 | 6 | 3 | 7 |
2180 | 1 | 7 | 5 | 12 |
2181 +----+----+-------+-----+
2182 ");
2183 Ok(())
2184 }
2185
2186 #[tokio::test]
2264 async fn bounded_window_exec_linear_mode_range_information() -> Result<()> {
2265 let n_rows = 10;
2266 let chunk_length = 2;
2267 let n_future_range = 1;
2268
2269 let timeout_duration = Duration::from_millis(2000);
2270
2271 let source =
2272 generate_never_ending_source(n_rows, chunk_length, 1, true, false, 5)?;
2273
2274 let window =
2275 bounded_window_exec_pb_latent_range(source, n_future_range, "hash", "sn")?;
2276
2277 let plan = projection_exec(window)?;
2278
2279 assert_snapshot!(displayable(plan.as_ref()).indent(true), @r#"
2281 ProjectionExec: expr=[sn@0 as sn, hash@1 as hash, count([Column { name: "sn", index: 0 }]) PARTITION BY: [[Column { name: "hash", index: 1 }]], ORDER BY: [[PhysicalSortExpr { expr: Column { name: "sn", index: 0 }, options: SortOptions { descending: false, nulls_first: true } }]]@2 as col_2]
2282 BoundedWindowAggExec: wdw=[count([Column { name: "sn", index: 0 }]) PARTITION BY: [[Column { name: "hash", index: 1 }]], ORDER BY: [[PhysicalSortExpr { expr: Column { name: "sn", index: 0 }, options: SortOptions { descending: false, nulls_first: true } }]]: Field { "count([Column { name: \"sn\", index: 0 }]) PARTITION BY: [[Column { name: \"hash\", index: 1 }]], ORDER BY: [[PhysicalSortExpr { expr: Column { name: \"sn\", index: 0 }, options: SortOptions { descending: false, nulls_first: true } }]]": Int64 }, frame: RANGE BETWEEN CURRENT ROW AND 1 FOLLOWING], mode=[Linear]
2283 StreamingTableExec: partition_sizes=1, projection=[sn, hash], infinite_source=true, output_ordering=[sn@0 ASC NULLS LAST]
2284 "#);
2285
2286 let task_ctx = task_context();
2287 let batches = collect_with_timeout(plan, task_ctx, timeout_duration).await?;
2288
2289 assert_snapshot!(batches_to_string(&batches), @r"
2290 +----+------+-------+
2291 | sn | hash | col_2 |
2292 +----+------+-------+
2293 | 0 | 2 | 2 |
2294 | 1 | 2 | 2 |
2295 | 2 | 2 | 2 |
2296 | 3 | 2 | 1 |
2297 | 4 | 1 | 2 |
2298 | 5 | 1 | 2 |
2299 | 6 | 1 | 2 |
2300 | 7 | 1 | 1 |
2301 +----+------+-------+
2302 ");
2303
2304 Ok(())
2305 }
2306
2307 type Observation = (usize, PartitionKey, Vec<ScalarValue>);
2308
2309 struct RecordingObserver {
2312 sink: Arc<std::sync::Mutex<Vec<Observation>>>,
2313 }
2314
2315 impl WindowStateObserver for RecordingObserver {
2316 fn finalize_window_aggregate(
2317 &self,
2318 partition_idx: usize,
2319 _window_expr: &Arc<dyn WindowExpr>,
2320 partition_key: &PartitionKey,
2321 state: Vec<ScalarValue>,
2322 ) -> Result<()> {
2323 self.sink
2324 .lock()
2325 .unwrap()
2326 .push((partition_idx, partition_key.clone(), state));
2327 Ok(())
2328 }
2329 }
2330
2331 fn build_partition_close_plan(frame: WindowFrame) -> Result<BoundedWindowAggExec> {
2336 let schema = test_schema();
2337
2338 let mut sn_b = UInt64Builder::with_capacity(6);
2339 let mut hash_b = Int64Builder::with_capacity(6);
2340 for (sn, hash) in [(1u64, 1i64), (2, 1), (3, 1), (4, 2), (5, 2), (6, 2)] {
2341 sn_b.append_value(sn);
2342 hash_b.append_value(hash);
2343 }
2344 let batch = RecordBatch::try_new(
2345 Arc::clone(&schema),
2346 vec![Arc::new(sn_b.finish()), Arc::new(hash_b.finish())],
2347 )?;
2348 let ordering: LexOrdering = [
2349 PhysicalSortExpr {
2350 expr: col("hash", &schema)?,
2351 options: SortOptions::default(),
2352 },
2353 PhysicalSortExpr {
2354 expr: col("sn", &schema)?,
2355 options: SortOptions::default(),
2356 },
2357 ]
2358 .into();
2359 let source_raw =
2360 TestMemoryExec::try_new(&[vec![batch]], Arc::clone(&schema), None)?
2361 .try_with_sort_information(vec![ordering])?;
2362 let source: Arc<dyn ExecutionPlan> =
2363 Arc::new(TestMemoryExec::update_cache(&Arc::new(source_raw)));
2364
2365 let expr = create_window_expr(
2366 &WindowFunctionDefinition::AggregateUDF(count_udaf()),
2367 "cnt".to_string(),
2368 &[col("sn", &schema)?],
2369 &[col("hash", &schema)?],
2370 &[PhysicalSortExpr {
2371 expr: col("sn", &schema)?,
2372 options: SortOptions::default(),
2373 }],
2374 Arc::new(frame),
2375 source.schema(),
2376 false,
2377 false,
2378 None,
2379 )?;
2380
2381 BoundedWindowAggExec::try_new(vec![expr], source, InputOrderMode::Sorted, false)
2382 }
2383
2384 #[tokio::test]
2391 async fn test_state_observer_rejects_sliding_frame() -> Result<()> {
2392 use std::sync::Mutex;
2398
2399 let plan = build_partition_close_plan(WindowFrame::new_bounds(
2400 WindowFrameUnits::Rows,
2401 WindowFrameBound::CurrentRow,
2402 WindowFrameBound::Following(ScalarValue::UInt64(None)),
2403 ))?;
2404 let observer: Arc<dyn WindowStateObserver> = Arc::new(RecordingObserver {
2405 sink: Arc::new(Mutex::new(vec![])),
2406 });
2407 let err = plan.with_state_observer(Some(observer)).unwrap_err();
2408 let msg = err.to_string();
2409 assert!(
2410 msg.contains("sliding aggregate window frame"),
2411 "expected sliding-frame rejection, got: {msg}"
2412 );
2413 Ok(())
2414 }
2415
2416 #[tokio::test]
2417 async fn test_finalized_state_observer_fires_on_causal_frame() -> Result<()> {
2418 use std::sync::Mutex;
2423
2424 let task_ctx = Arc::new(TaskContext::default());
2425 let plan = build_partition_close_plan(WindowFrame::new_bounds(
2426 WindowFrameUnits::Rows,
2427 WindowFrameBound::Preceding(ScalarValue::UInt64(None)),
2428 WindowFrameBound::CurrentRow,
2429 ))?;
2430
2431 let observations: Arc<Mutex<Vec<Observation>>> = Arc::new(Mutex::new(vec![]));
2432 let observer: Arc<dyn WindowStateObserver> = Arc::new(RecordingObserver {
2433 sink: Arc::clone(&observations),
2434 });
2435 let plan = plan.with_state_observer(Some(observer))?;
2436
2437 let _ = collect(Arc::new(plan).execute(0, task_ctx)?).await?;
2438
2439 let observed: Vec<(usize, i64, Vec<ScalarValue>)> = observations
2442 .lock()
2443 .unwrap()
2444 .iter()
2445 .map(|(idx, key, state)| {
2446 let hash = match &key[0] {
2447 ScalarValue::Int64(Some(v)) => *v,
2448 other => panic!("unexpected partition-key element: {other:?}"),
2449 };
2450 (*idx, hash, state.clone())
2451 })
2452 .collect();
2453 assert_eq!(
2454 observed,
2455 vec![
2456 (0, 1, vec![ScalarValue::Int64(Some(3))]),
2457 (0, 2, vec![ScalarValue::Int64(Some(3))]),
2458 ]
2459 );
2460 Ok(())
2461 }
2462
2463 #[tokio::test]
2464 async fn test_finalized_state_observer_fires_exactly_once_across_batches()
2465 -> Result<()> {
2466 use std::sync::Mutex;
2487
2488 let task_ctx = Arc::new(TaskContext::default());
2489 let schema = test_schema();
2490
2491 let make_batch = |rows: &[(u64, i64)]| -> Result<RecordBatch> {
2493 let mut sn_b = UInt64Builder::with_capacity(rows.len());
2494 let mut hash_b = Int64Builder::with_capacity(rows.len());
2495 for &(sn, hash) in rows {
2496 sn_b.append_value(sn);
2497 hash_b.append_value(hash);
2498 }
2499 Ok(RecordBatch::try_new(
2500 Arc::clone(&schema),
2501 vec![Arc::new(sn_b.finish()), Arc::new(hash_b.finish())],
2502 )?)
2503 };
2504 let batch1 = make_batch(&[(1, 1), (2, 1)])?;
2505 let batch2 = make_batch(&[(3, 2), (4, 2), (5, 3), (6, 3)])?;
2506
2507 let ordering: LexOrdering = [
2508 PhysicalSortExpr {
2509 expr: col("hash", &schema)?,
2510 options: SortOptions::default(),
2511 },
2512 PhysicalSortExpr {
2513 expr: col("sn", &schema)?,
2514 options: SortOptions::default(),
2515 },
2516 ]
2517 .into();
2518 let source_raw =
2519 TestMemoryExec::try_new(&[vec![batch1, batch2]], Arc::clone(&schema), None)?
2520 .try_with_sort_information(vec![ordering])?;
2521 let source: Arc<dyn ExecutionPlan> =
2522 Arc::new(TestMemoryExec::update_cache(&Arc::new(source_raw)));
2523
2524 let expr = create_window_expr(
2525 &WindowFunctionDefinition::AggregateUDF(count_udaf()),
2526 "cnt".to_string(),
2527 &[col("sn", &schema)?],
2528 &[col("hash", &schema)?],
2529 &[PhysicalSortExpr {
2530 expr: col("sn", &schema)?,
2531 options: SortOptions::default(),
2532 }],
2533 Arc::new(WindowFrame::new_bounds(
2534 WindowFrameUnits::Rows,
2535 WindowFrameBound::Preceding(ScalarValue::UInt64(None)),
2536 WindowFrameBound::CurrentRow,
2537 )),
2538 source.schema(),
2539 false,
2540 false,
2541 None,
2542 )?;
2543
2544 let observations: Arc<Mutex<Vec<Observation>>> = Arc::new(Mutex::new(vec![]));
2545 let observer: Arc<dyn WindowStateObserver> = Arc::new(RecordingObserver {
2546 sink: Arc::clone(&observations),
2547 });
2548
2549 let plan = BoundedWindowAggExec::try_new(
2550 vec![expr],
2551 source,
2552 InputOrderMode::Sorted,
2553 false,
2554 )?
2555 .with_state_observer(Some(observer))?;
2556
2557 let _ = collect(Arc::new(plan).execute(0, task_ctx)?).await?;
2558
2559 let fired: Vec<i64> = observations
2560 .lock()
2561 .unwrap()
2562 .iter()
2563 .map(|(_, key, _)| match &key[0] {
2564 ScalarValue::Int64(Some(v)) => *v,
2565 other => panic!("unexpected partition-key element: {other:?}"),
2566 })
2567 .collect();
2568 assert_eq!(fired, vec![1, 2, 3]);
2572 Ok(())
2573 }
2574
2575 async fn run_running_sum_task(
2581 input: &[u64],
2582 task_ctx: Arc<TaskContext>,
2583 ) -> Result<(Vec<u64>, u64)> {
2584 use arrow::array::UInt64Array;
2585 use datafusion_functions_aggregate::sum::sum_udaf;
2586 use std::sync::Mutex;
2587
2588 struct RunningSumObserver {
2592 sink: Arc<Mutex<Option<u64>>>,
2593 }
2594
2595 impl WindowStateObserver for RunningSumObserver {
2596 fn finalize_window_aggregate(
2597 &self,
2598 _partition_idx: usize,
2599 _window_expr: &Arc<dyn WindowExpr>,
2600 partition_key: &PartitionKey,
2601 state: Vec<ScalarValue>,
2602 ) -> Result<()> {
2603 assert!(
2604 partition_key.is_empty(),
2605 "empty PartitionKey for no-PARTITION-BY plan"
2606 );
2607 let total = match &state[0] {
2608 ScalarValue::UInt64(Some(v)) => *v,
2609 ScalarValue::Int64(Some(v)) => *v as u64,
2610 other => panic!("unexpected sum state element: {other:?}"),
2611 };
2612 let prev = self.sink.lock().unwrap().replace(total);
2613 assert!(prev.is_none(), "observer must fire exactly once per task");
2614 Ok(())
2615 }
2616 }
2617
2618 let schema = test_schema();
2619 let mut sn_b = UInt64Builder::with_capacity(input.len());
2620 let mut hash_b = Int64Builder::with_capacity(input.len());
2621 for &sn in input {
2622 sn_b.append_value(sn);
2623 hash_b.append_value(0);
2624 }
2625 let batch = RecordBatch::try_new(
2626 Arc::clone(&schema),
2627 vec![Arc::new(sn_b.finish()), Arc::new(hash_b.finish())],
2628 )?;
2629 let ordering: LexOrdering = [PhysicalSortExpr {
2630 expr: col("sn", &schema)?,
2631 options: SortOptions::default(),
2632 }]
2633 .into();
2634 let source_raw =
2635 TestMemoryExec::try_new(&[vec![batch]], Arc::clone(&schema), None)?
2636 .try_with_sort_information(vec![ordering])?;
2637 let source: Arc<dyn ExecutionPlan> =
2638 Arc::new(TestMemoryExec::update_cache(&Arc::new(source_raw)));
2639
2640 let window_fn = WindowFunctionDefinition::AggregateUDF(sum_udaf());
2641 let args = vec![col("sn", &schema)?];
2642 let partition_by: Vec<Arc<dyn PhysicalExpr>> = vec![];
2643 let order_by = vec![PhysicalSortExpr {
2644 expr: col("sn", &schema)?,
2645 options: SortOptions::default(),
2646 }];
2647 let frame = WindowFrame::new_bounds(
2648 WindowFrameUnits::Rows,
2649 WindowFrameBound::Preceding(ScalarValue::UInt64(None)),
2650 WindowFrameBound::CurrentRow,
2651 );
2652 let expr = create_window_expr(
2653 &window_fn,
2654 "running_sum".to_string(),
2655 &args,
2656 &partition_by,
2657 &order_by,
2658 Arc::new(frame),
2659 source.schema(),
2660 false,
2661 false,
2662 None,
2663 )?;
2664
2665 let total_sink: Arc<Mutex<Option<u64>>> = Arc::new(Mutex::new(None));
2666 let observer: Arc<dyn WindowStateObserver> = Arc::new(RunningSumObserver {
2667 sink: Arc::clone(&total_sink),
2668 });
2669
2670 let plan = BoundedWindowAggExec::try_new(
2671 vec![expr],
2672 source,
2673 InputOrderMode::Sorted,
2674 false,
2675 )?
2676 .with_state_observer(Some(observer))?;
2677 let batches = collect(Arc::new(plan).execute(0, task_ctx)?).await?;
2678
2679 let mut out = Vec::with_capacity(input.len());
2680 for batch in &batches {
2681 let col = batch
2682 .column_by_name("running_sum")
2683 .expect("running_sum column present");
2684 let arr = col
2685 .as_any()
2686 .downcast_ref::<UInt64Array>()
2687 .expect("SUM(UInt64) → UInt64Array");
2688 for i in 0..arr.len() {
2689 out.push(arr.value(i));
2690 }
2691 }
2692 let total = total_sink
2693 .lock()
2694 .unwrap()
2695 .expect("observer must have fired at EOS");
2696 Ok((out, total))
2697 }
2698
2699 async fn run_approx_distinct_task(
2703 input: &[u64],
2704 task_ctx: Arc<TaskContext>,
2705 ) -> Result<Vec<ScalarValue>> {
2706 use datafusion_functions_aggregate::approx_distinct::approx_distinct_udaf;
2707 use std::sync::Mutex;
2708
2709 struct ApproxDistinctObserver {
2713 sink: Arc<Mutex<Option<Vec<ScalarValue>>>>,
2714 }
2715
2716 impl WindowStateObserver for ApproxDistinctObserver {
2717 fn finalize_window_aggregate(
2718 &self,
2719 _partition_idx: usize,
2720 _window_expr: &Arc<dyn WindowExpr>,
2721 partition_key: &PartitionKey,
2722 state: Vec<ScalarValue>,
2723 ) -> Result<()> {
2724 assert!(
2725 partition_key.is_empty(),
2726 "empty PartitionKey for no-PARTITION-BY plan"
2727 );
2728 let prev = self.sink.lock().unwrap().replace(state);
2729 assert!(prev.is_none(), "observer must fire exactly once per task");
2730 Ok(())
2731 }
2732 }
2733
2734 let schema = test_schema();
2735 let mut sn_b = UInt64Builder::with_capacity(input.len());
2736 let mut hash_b = Int64Builder::with_capacity(input.len());
2737 for &sn in input {
2738 sn_b.append_value(sn);
2739 hash_b.append_value(0);
2740 }
2741 let batch = RecordBatch::try_new(
2742 Arc::clone(&schema),
2743 vec![Arc::new(sn_b.finish()), Arc::new(hash_b.finish())],
2744 )?;
2745 let ordering: LexOrdering = [PhysicalSortExpr {
2746 expr: col("sn", &schema)?,
2747 options: SortOptions::default(),
2748 }]
2749 .into();
2750 let source_raw =
2751 TestMemoryExec::try_new(&[vec![batch]], Arc::clone(&schema), None)?
2752 .try_with_sort_information(vec![ordering])?;
2753 let source: Arc<dyn ExecutionPlan> =
2754 Arc::new(TestMemoryExec::update_cache(&Arc::new(source_raw)));
2755
2756 let expr = create_window_expr(
2757 &WindowFunctionDefinition::AggregateUDF(approx_distinct_udaf()),
2758 "approx_distinct_sn".to_string(),
2759 &[col("sn", &schema)?],
2760 &[],
2761 &[PhysicalSortExpr {
2762 expr: col("sn", &schema)?,
2763 options: SortOptions::default(),
2764 }],
2765 Arc::new(WindowFrame::new_bounds(
2766 WindowFrameUnits::Rows,
2767 WindowFrameBound::Preceding(ScalarValue::UInt64(None)),
2768 WindowFrameBound::CurrentRow,
2769 )),
2770 source.schema(),
2771 false,
2772 false,
2773 None,
2774 )?;
2775
2776 let state_sink: Arc<Mutex<Option<Vec<ScalarValue>>>> = Arc::new(Mutex::new(None));
2777 let observer: Arc<dyn WindowStateObserver> = Arc::new(ApproxDistinctObserver {
2778 sink: Arc::clone(&state_sink),
2779 });
2780
2781 let plan = BoundedWindowAggExec::try_new(
2782 vec![expr],
2783 source,
2784 InputOrderMode::Sorted,
2785 false,
2786 )?
2787 .with_state_observer(Some(observer))?;
2788 let _ = collect(Arc::new(plan).execute(0, task_ctx)?).await?;
2789
2790 state_sink
2791 .lock()
2792 .unwrap()
2793 .take()
2794 .ok_or_else(|| exec_datafusion_err!("observer never fired"))
2795 }
2796
2797 #[tokio::test]
2798 async fn test_prefix_scan_across_tasks_matches_single_bwag() -> Result<()> {
2799 let task_ctx = Arc::new(TaskContext::default());
2805
2806 let (task1_out, task1_total) =
2808 run_running_sum_task(&[1, 1, 2, 2, 3, 3, 4, 4], Arc::clone(&task_ctx))
2809 .await?;
2810 let (task2_out, task2_total) =
2811 run_running_sum_task(&[5, 5, 6, 6, 7, 7, 8, 8], Arc::clone(&task_ctx))
2812 .await?;
2813
2814 assert_eq!(task1_out, vec![1, 2, 4, 6, 9, 12, 16, 20]);
2816 assert_eq!(task1_total, 20);
2817 assert_eq!(task2_out, vec![5, 10, 16, 22, 29, 36, 44, 52]);
2818 assert_eq!(task2_total, 52);
2819
2820 let carry_ins = [0u64, task1_total];
2823
2824 let task1_final: Vec<u64> = task1_out.iter().map(|v| v + carry_ins[0]).collect();
2826 let task2_final: Vec<u64> = task2_out.iter().map(|v| v + carry_ins[1]).collect();
2827 let parallel_result: Vec<u64> = task1_final
2828 .iter()
2829 .chain(task2_final.iter())
2830 .copied()
2831 .collect();
2832
2833 let (single_result, single_total) = run_running_sum_task(
2835 &[1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8],
2836 task_ctx,
2837 )
2838 .await?;
2839
2840 assert_eq!(
2841 parallel_result, single_result,
2842 "two-task prefix-scan must match single-BWAG oracle"
2843 );
2844 assert_eq!(
2846 single_result,
2847 vec![1, 2, 4, 6, 9, 12, 16, 20, 25, 30, 36, 42, 49, 56, 64, 72]
2848 );
2849 assert_eq!(single_total, 72);
2850 Ok(())
2851 }
2852
2853 #[tokio::test]
2854 async fn test_prefix_merge_across_tasks_approx_distinct() -> Result<()> {
2855 use arrow::array::{ArrayRef, BinaryArray};
2864 use arrow::datatypes::FieldRef;
2865 use datafusion_expr::function::AccumulatorArgs;
2866 use datafusion_functions_aggregate::approx_distinct::approx_distinct_udaf;
2867
2868 let task_ctx = Arc::new(TaskContext::default());
2869
2870 let state1 =
2873 run_approx_distinct_task(&[1, 1, 2, 3], Arc::clone(&task_ctx)).await?;
2874 let state2 = run_approx_distinct_task(&[3, 4, 5], Arc::clone(&task_ctx)).await?;
2875 let state_single =
2876 run_approx_distinct_task(&[1, 1, 2, 3, 3, 4, 5], Arc::clone(&task_ctx))
2877 .await?;
2878
2879 assert_eq!(state1.len(), 1, "single state field");
2881 assert_eq!(state2.len(), 1, "single state field");
2882 assert_eq!(state_single.len(), 1, "single state field");
2883
2884 fn evaluate_merged(states: &[&ScalarValue]) -> Result<ScalarValue> {
2887 let udaf = approx_distinct_udaf();
2888 let input_schema =
2889 Arc::new(Schema::new(vec![Field::new("sn", DataType::UInt64, true)]));
2890 let return_field: FieldRef =
2891 Arc::new(Field::new("approx_distinct_sn", DataType::UInt64, true));
2892 let expr_field: FieldRef = Arc::new(Field::new("sn", DataType::UInt64, true));
2893 let physical_col: Arc<dyn PhysicalExpr> = col("sn", &input_schema)?;
2894 let args = AccumulatorArgs {
2895 return_field: Arc::clone(&return_field),
2896 schema: &input_schema,
2897 ignore_nulls: false,
2898 order_bys: &[],
2899 is_reversed: false,
2900 name: "approx_distinct",
2901 is_distinct: false,
2902 exprs: std::slice::from_ref(&physical_col),
2903 expr_fields: std::slice::from_ref(&expr_field),
2904 };
2905 let mut acc = udaf.accumulator(args)?;
2906 let byte_slices: Vec<&[u8]> = states
2907 .iter()
2908 .map(|s| match s {
2909 ScalarValue::Binary(Some(v)) => v.as_slice(),
2910 other => panic!("expected Binary state, got {other:?}"),
2911 })
2912 .collect();
2913 let bin: ArrayRef = Arc::new(BinaryArray::from_iter_values(byte_slices));
2914 acc.merge_batch(std::slice::from_ref(&bin))?;
2915 acc.evaluate()
2916 }
2917
2918 let merged = evaluate_merged(&[&state1[0], &state2[0]])?;
2919 let oracle = evaluate_merged(&[&state_single[0]])?;
2920
2921 assert_eq!(
2922 merged, oracle,
2923 "merged task states must match single-BWAG oracle — parallel prefix-merge contract"
2924 );
2925 assert_eq!(merged, ScalarValue::UInt64(Some(5)));
2927 Ok(())
2928 }
2929
2930 #[test]
2931 fn test_bounded_window_agg_cardinality_effect() -> Result<()> {
2932 let schema = test_schema();
2933 let input: Arc<dyn ExecutionPlan> =
2934 Arc::new(TestMemoryExec::try_new(&[], Arc::clone(&schema), None)?);
2935 let plan = bounded_window_exec_pb_latent_range(input, 1, "hash", "sn")?;
2936 let plan = plan
2937 .downcast_ref::<BoundedWindowAggExec>()
2938 .expect("expected BoundedWindowAggExec");
2939
2940 assert!(matches!(
2941 plan.cardinality_effect(),
2942 CardinalityEffect::Equal
2943 ));
2944 Ok(())
2945 }
2946
2947 #[test]
2952 fn test_linear_search_evaluate_partition_batches() -> Result<()> {
2953 use super::{LinearSearch, PartitionSearcher};
2954 use arrow::array::{Int32Array, Int64Array};
2955
2956 let schema = Arc::new(Schema::new(vec![
2957 Field::new("a", DataType::Int32, true),
2958 Field::new("b", DataType::Int64, false),
2959 ]));
2960 let window_expr = create_window_expr(
2961 &WindowFunctionDefinition::AggregateUDF(count_udaf()),
2962 "count".to_string(),
2963 &[col("b", &schema)?],
2964 &[col("a", &schema)?],
2965 &[],
2966 Arc::new(WindowFrame::new(None)),
2967 Arc::clone(&schema),
2968 false,
2969 false,
2970 None,
2971 )?;
2972 let mut searcher = LinearSearch::new(vec![], Arc::clone(&schema));
2973
2974 let batch = RecordBatch::try_new(
2975 Arc::clone(&schema),
2976 vec![
2977 Arc::new(Int32Array::from(vec![
2978 Some(1),
2979 Some(2),
2980 Some(1),
2981 None,
2982 Some(2),
2983 Some(1),
2984 ])),
2985 Arc::new(Int64Array::from(vec![10, 20, 11, 30, 21, 12])),
2986 ],
2987 )?;
2988 let result =
2989 searcher.evaluate_partition_batches(&batch, &[Arc::clone(&window_expr)])?;
2990 assert_eq!(result.len(), 3);
2991 let expected = [
2992 (
2993 ScalarValue::Int32(Some(1)),
2994 vec![Some(1); 3],
2995 vec![10i64, 11, 12],
2996 ),
2997 (ScalarValue::Int32(Some(2)), vec![Some(2); 2], vec![20, 21]),
2998 (ScalarValue::Int32(None), vec![None], vec![30]),
2999 ];
3000 for ((key, partition_batch), (exp_key, exp_a, exp_b)) in
3001 result.iter().zip(expected)
3002 {
3003 assert_eq!(key, &vec![exp_key]);
3004 let exp_batch = RecordBatch::try_new(
3005 Arc::clone(&schema),
3006 vec![
3007 Arc::new(Int32Array::from(exp_a)),
3008 Arc::new(Int64Array::from(exp_b)),
3009 ],
3010 )?;
3011 assert_eq!(partition_batch, &exp_batch);
3012 }
3013
3014 let single = RecordBatch::try_new(
3015 Arc::clone(&schema),
3016 vec![
3017 Arc::new(Int32Array::from(vec![Some(7), Some(7)])),
3018 Arc::new(Int64Array::from(vec![70, 71])),
3019 ],
3020 )?;
3021 let result = searcher.evaluate_partition_batches(&single, &[window_expr])?;
3022 assert_eq!(result.len(), 1);
3023 assert_eq!(result[0].0, vec![ScalarValue::Int32(Some(7))]);
3024 assert_eq!(result[0].1, single);
3025 assert!(Arc::ptr_eq(result[0].1.column(0), single.column(0)));
3028 Ok(())
3029 }
3030}