1#![expect(rustdoc::private_intra_doc_links)]
144
145use std::borrow::Cow;
146use std::sync::Arc;
147
148use super::{DisplayAs, ExecutionPlanProperties, PlanProperties};
149use crate::aggregates::{
150 aggregate_stream::AggregateStream,
151 grouped_hash_stream::GroupedHashAggregateStream,
152 grouped_topk_stream::GroupedTopKAggregateStream,
153 hash_stream::{FinalHashAggregateStream, PartialHashAggregateStream},
154 ordered_final_stream::OrderedFinalAggregateStream,
155 ordered_partial_stream::OrderedPartialAggregateStream,
156 partial_reduce_stream::PartialReduceHashAggregateStream,
157 single_stream::SingleHashAggregateStream,
158};
159use crate::execution_plan::{
160 CardinalityEffect, EmissionType, plan_contains_expression_id,
161};
162use crate::filter_pushdown::{
163 ChildFilterDescription, ChildPushdownResult, FilterDescription, FilterPushdownPhase,
164 FilterPushdownPropagation,
165};
166use crate::metrics::{ExecutionPlanMetricsSet, MetricsSet};
167use crate::statistics::{ChildStats, StatisticsArgs};
168use crate::{ChildrenPropertiesMode, ReplaceChildrenOptions, validate_child_count};
169use crate::{
170 DisplayFormatType, Distribution, ExecutionPlan, InputDistributionRequirements,
171 InputOrderMode, SendableRecordBatchStream, Statistics,
172};
173use datafusion_common::config::ConfigOptions;
174use parking_lot::Mutex;
175use std::collections::{HashMap, HashSet};
176
177use arrow::array::{ArrayRef, UInt8Array, UInt16Array, UInt32Array, UInt64Array};
178use arrow::datatypes::{DataType, Field, Schema, SchemaRef};
179use arrow::record_batch::RecordBatch;
180use arrow_schema::FieldRef;
181use datafusion_common::stats::Precision;
182use datafusion_common::tree_node::TreeNodeRecursion;
183use datafusion_common::{
184 ColumnStatistics, Constraint, Constraints, Result, ScalarValue,
185 assert_eq_or_internal_err, internal_err, not_impl_err,
186};
187use datafusion_execution::TaskContext;
188use datafusion_execution::memory_pool::MemoryLimit;
189use datafusion_expr::{Accumulator, Aggregate};
190use datafusion_physical_expr::aggregate::AggregateFunctionExpr;
191use datafusion_physical_expr::equivalence::ProjectionMapping;
192use datafusion_physical_expr::expressions::{Column, DynamicFilterPhysicalExpr, lit};
193use datafusion_physical_expr::{
194 ConstExpr, EquivalenceProperties, physical_exprs_contains,
195};
196use datafusion_physical_expr_common::physical_expr::{PhysicalExpr, fmt_sql};
197use datafusion_physical_expr_common::sort_expr::{
198 LexOrdering, LexRequirement, OrderingRequirements, PhysicalSortRequirement,
199};
200
201use datafusion_expr::utils::AggregateOrderSensitivity;
202use datafusion_physical_expr_common::utils::evaluate_expressions_to_arrays;
203use itertools::Itertools;
204use topk::hash_table::is_supported_hash_key_type;
205use topk::heap::is_supported_heap_type;
206
207mod aggregate_hash_table;
208mod aggregate_stream;
209pub mod group_values;
210mod grouped_hash_stream;
211mod grouped_topk_stream;
212mod hash_stream;
213pub mod order;
214mod ordered_final_stream;
215mod ordered_partial_stream;
216mod partial_reduce_stream;
217mod single_stream;
218mod skip_partial;
219mod topk;
220
221pub fn topk_types_supported(key_type: &DataType, value_type: &DataType) -> bool {
229 is_supported_hash_key_type(key_type) && is_supported_heap_type(value_type)
230}
231
232const AGGREGATION_HASH_SEED: datafusion_common::hash_utils::RandomState =
234 datafusion_common::hash_utils::RandomState::with_seed(15395726432021054657);
236
237#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
243pub enum AggregateInputMode {
244 Raw,
247 Partial,
250}
251
252#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
258pub enum AggregateOutputMode {
259 Partial,
262 Final,
265}
266
267#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
286pub enum AggregateMode {
287 Partial,
294 Final,
310 FinalPartitioned,
319 Single,
327 SinglePartitioned,
336 PartialReduce,
359}
360
361impl AggregateMode {
362 pub fn input_mode(&self) -> AggregateInputMode {
368 match self {
369 AggregateMode::Partial
370 | AggregateMode::Single
371 | AggregateMode::SinglePartitioned => AggregateInputMode::Raw,
372 AggregateMode::Final
373 | AggregateMode::FinalPartitioned
374 | AggregateMode::PartialReduce => AggregateInputMode::Partial,
375 }
376 }
377
378 pub fn output_mode(&self) -> AggregateOutputMode {
384 match self {
385 AggregateMode::Final
386 | AggregateMode::FinalPartitioned
387 | AggregateMode::Single
388 | AggregateMode::SinglePartitioned => AggregateOutputMode::Final,
389 AggregateMode::Partial | AggregateMode::PartialReduce => {
390 AggregateOutputMode::Partial
391 }
392 }
393 }
394}
395
396#[derive(Clone, Debug, Default)]
415pub struct PhysicalGroupBy {
416 expr: Vec<(Arc<dyn PhysicalExpr>, String)>,
418 null_expr: Vec<(Arc<dyn PhysicalExpr>, String)>,
420 groups: Vec<Vec<bool>>,
425 has_grouping_set: bool,
428}
429
430impl PhysicalGroupBy {
431 pub fn new(
433 expr: Vec<(Arc<dyn PhysicalExpr>, String)>,
434 null_expr: Vec<(Arc<dyn PhysicalExpr>, String)>,
435 groups: Vec<Vec<bool>>,
436 has_grouping_set: bool,
437 ) -> Self {
438 Self {
439 expr,
440 null_expr,
441 groups,
442 has_grouping_set,
443 }
444 }
445
446 pub fn new_single(expr: Vec<(Arc<dyn PhysicalExpr>, String)>) -> Self {
449 let num_exprs = expr.len();
450 Self {
451 expr,
452 null_expr: vec![],
453 groups: vec![vec![false; num_exprs]],
454 has_grouping_set: false,
455 }
456 }
457
458 pub fn exprs_nullable(&self) -> Vec<bool> {
460 let mut exprs_nullable = vec![false; self.expr.len()];
461 for group in self.groups.iter() {
462 group.iter().enumerate().for_each(|(index, is_null)| {
463 if *is_null {
464 exprs_nullable[index] = true;
465 }
466 })
467 }
468 exprs_nullable
469 }
470
471 pub fn is_true_no_grouping(&self) -> bool {
473 self.is_empty() && !self.has_grouping_set
474 }
475
476 pub fn expr(&self) -> &[(Arc<dyn PhysicalExpr>, String)] {
478 &self.expr
479 }
480
481 pub fn null_expr(&self) -> &[(Arc<dyn PhysicalExpr>, String)] {
483 &self.null_expr
484 }
485
486 pub fn groups(&self) -> &[Vec<bool>] {
488 &self.groups
489 }
490
491 pub fn has_grouping_set(&self) -> bool {
493 self.has_grouping_set
494 }
495
496 pub fn is_empty(&self) -> bool {
498 self.expr.is_empty()
499 }
500
501 pub fn is_single(&self) -> bool {
504 !self.has_grouping_set
505 }
506
507 pub fn input_exprs(&self) -> Vec<Arc<dyn PhysicalExpr>> {
509 self.expr
510 .iter()
511 .map(|(expr, _alias)| Arc::clone(expr))
512 .collect()
513 }
514
515 fn num_output_exprs(&self) -> usize {
517 let mut num_exprs = self.expr.len();
518 if self.has_grouping_set {
519 num_exprs += 1
520 }
521 num_exprs
522 }
523
524 pub fn output_exprs(&self) -> Vec<Arc<dyn PhysicalExpr>> {
526 let num_output_exprs = self.num_output_exprs();
527 let mut output_exprs = Vec::with_capacity(num_output_exprs);
528 output_exprs.extend(
529 self.expr
530 .iter()
531 .enumerate()
532 .take(num_output_exprs)
533 .map(|(index, (_, name))| Arc::new(Column::new(name, index)) as _),
534 );
535 if self.has_grouping_set {
536 output_exprs.push(Arc::new(Column::new(
537 Aggregate::INTERNAL_GROUPING_ID,
538 self.expr.len(),
539 )) as _);
540 }
541 output_exprs
542 }
543
544 pub fn num_group_exprs(&self) -> usize {
546 self.expr.len() + usize::from(self.has_grouping_set)
547 }
548
549 fn grouping_id_data_type(&self) -> DataType {
555 Aggregate::grouping_id_type(self.expr.len(), max_duplicate_ordinal(&self.groups))
556 }
557
558 pub fn group_schema(&self, schema: &Schema) -> Result<SchemaRef> {
559 Ok(Arc::new(Schema::new(self.group_fields(schema)?)))
560 }
561
562 fn group_fields(&self, input_schema: &Schema) -> Result<Vec<FieldRef>> {
564 let mut fields = Vec::with_capacity(self.num_group_exprs());
565 for ((expr, name), group_expr_nullable) in
566 self.expr.iter().zip(self.exprs_nullable())
567 {
568 fields.push(
569 Field::new(
570 name,
571 expr.data_type(input_schema)?,
572 group_expr_nullable || expr.nullable(input_schema)?,
573 )
574 .with_metadata(expr.return_field(input_schema)?.metadata().clone())
575 .into(),
576 );
577 }
578 if self.has_grouping_set {
579 fields.push(
580 Field::new(
581 Aggregate::INTERNAL_GROUPING_ID,
582 self.grouping_id_data_type(),
583 false,
584 )
585 .into(),
586 );
587 }
588 Ok(fields)
589 }
590
591 fn output_fields(&self, input_schema: &Schema) -> Result<Vec<FieldRef>> {
596 let mut fields = self.group_fields(input_schema)?;
597 fields.truncate(self.num_output_exprs());
598 Ok(fields)
599 }
600
601 pub fn as_final(&self) -> PhysicalGroupBy {
604 let expr: Vec<_> =
605 self.output_exprs()
606 .into_iter()
607 .zip(
608 self.expr.iter().map(|t| t.1.clone()).chain(std::iter::once(
609 Aggregate::INTERNAL_GROUPING_ID.to_owned(),
610 )),
611 )
612 .collect();
613 let num_exprs = expr.len();
614 let groups = if self.expr.is_empty() && !self.has_grouping_set {
615 vec![]
617 } else {
618 vec![vec![false; num_exprs]]
619 };
620 Self {
621 expr,
622 null_expr: vec![],
623 groups,
624 has_grouping_set: false,
625 }
626 }
627}
628
629impl PartialEq for PhysicalGroupBy {
630 fn eq(&self, other: &PhysicalGroupBy) -> bool {
631 self.expr.len() == other.expr.len()
632 && self
633 .expr
634 .iter()
635 .zip(other.expr.iter())
636 .all(|((expr1, name1), (expr2, name2))| expr1.eq(expr2) && name1 == name2)
637 && self.null_expr.len() == other.null_expr.len()
638 && self
639 .null_expr
640 .iter()
641 .zip(other.null_expr.iter())
642 .all(|((expr1, name1), (expr2, name2))| expr1.eq(expr2) && name1 == name2)
643 && self.groups == other.groups
644 && self.has_grouping_set == other.has_grouping_set
645 }
646}
647
648#[expect(clippy::large_enum_variant)]
659enum StreamType {
660 AggregateStream(AggregateStream),
663 PartialHash(PartialHashAggregateStream),
666 PartialReduceHash(PartialReduceHashAggregateStream),
669 FinalHash(FinalHashAggregateStream),
672 SingleHash(SingleHashAggregateStream),
675 OrderedPartialAggregate(OrderedPartialAggregateStream),
677 OrderedFinalAggregate(OrderedFinalAggregateStream),
679 GroupedHash(GroupedHashAggregateStream),
688 GroupedPriorityQueue(GroupedTopKAggregateStream),
694}
695
696impl From<StreamType> for SendableRecordBatchStream {
697 fn from(stream: StreamType) -> Self {
698 match stream {
699 StreamType::AggregateStream(stream) => Box::pin(stream),
700 StreamType::PartialHash(stream) => Box::pin(stream),
701 StreamType::PartialReduceHash(stream) => Box::pin(stream),
702 StreamType::FinalHash(stream) => Box::pin(stream),
703 StreamType::SingleHash(stream) => Box::pin(stream),
704 StreamType::OrderedPartialAggregate(stream) => stream.into_stream(),
705 StreamType::OrderedFinalAggregate(stream) => Box::pin(stream),
706 StreamType::GroupedHash(stream) => Box::pin(stream),
707 StreamType::GroupedPriorityQueue(stream) => Box::pin(stream),
708 }
709 }
710}
711
712#[derive(Debug, Clone)]
756struct AggrDynFilter {
757 filter: Arc<DynamicFilterPhysicalExpr>,
760 supported_accumulators_info: Vec<PerAccumulatorDynFilter>,
768}
769
770#[derive(Debug, Clone)]
775struct PerAccumulatorDynFilter {
776 aggr_type: DynamicFilterAggregateType,
777 aggr_index: usize,
783 shared_bound: Arc<Mutex<ScalarValue>>,
785}
786
787#[derive(Debug, Clone)]
789enum DynamicFilterAggregateType {
790 Min,
791 Max,
792}
793
794#[derive(Debug, Clone, Copy, PartialEq, Eq)]
796pub struct LimitOptions {
797 pub limit: usize,
799 pub descending: Option<bool>,
802}
803
804impl LimitOptions {
805 pub fn new(limit: usize) -> Self {
807 Self {
808 limit,
809 descending: None,
810 }
811 }
812
813 pub fn new_with_order(limit: usize, descending: bool) -> Self {
815 Self {
816 limit,
817 descending: Some(descending),
818 }
819 }
820
821 pub fn limit(&self) -> usize {
822 self.limit
823 }
824
825 pub fn descending(&self) -> Option<bool> {
826 self.descending
827 }
828}
829
830#[derive(Debug, Clone)]
832pub struct AggregateExec {
833 mode: AggregateMode,
835 group_by: Arc<PhysicalGroupBy>,
838 aggr_expr: Arc<[Arc<AggregateFunctionExpr>]>,
841 filter_expr: Arc<[Option<Arc<dyn PhysicalExpr>>]>,
844 limit_options: Option<LimitOptions>,
846 pub input: Arc<dyn ExecutionPlan>,
848 schema: SchemaRef,
851 pub input_schema: SchemaRef,
857 metrics: ExecutionPlanMetricsSet,
859 required_input_ordering: Option<OrderingRequirements>,
860 input_order_mode: InputOrderMode,
862 cache: Arc<PlanProperties>,
863 dynamic_filter: Option<Arc<AggrDynFilter>>,
870}
871
872impl AggregateExec {
873 pub fn with_new_aggr_exprs(
877 &self,
878 aggr_expr: impl Into<Arc<[Arc<AggregateFunctionExpr>]>>,
879 ) -> Self {
880 Self {
881 aggr_expr: aggr_expr.into(),
882 required_input_ordering: self.required_input_ordering.clone(),
884 metrics: ExecutionPlanMetricsSet::new(),
885 input_order_mode: self.input_order_mode.clone(),
886 cache: Arc::clone(&self.cache),
887 mode: self.mode,
888 group_by: Arc::clone(&self.group_by),
889 filter_expr: Arc::clone(&self.filter_expr),
890 limit_options: self.limit_options,
891 input: Arc::clone(&self.input),
892 schema: Arc::clone(&self.schema),
893 input_schema: Arc::clone(&self.input_schema),
894 dynamic_filter: self.dynamic_filter.clone(),
895 }
896 }
897
898 pub fn with_new_limit_options(&self, limit_options: Option<LimitOptions>) -> Self {
900 Self {
901 limit_options,
902 required_input_ordering: self.required_input_ordering.clone(),
904 metrics: ExecutionPlanMetricsSet::new(),
905 input_order_mode: self.input_order_mode.clone(),
906 cache: Arc::clone(&self.cache),
907 mode: self.mode,
908 group_by: Arc::clone(&self.group_by),
909 aggr_expr: Arc::clone(&self.aggr_expr),
910 filter_expr: Arc::clone(&self.filter_expr),
911 input: Arc::clone(&self.input),
912 schema: Arc::clone(&self.schema),
913 input_schema: Arc::clone(&self.input_schema),
914 dynamic_filter: self.dynamic_filter.clone(),
915 }
916 }
917
918 pub fn cache(&self) -> &PlanProperties {
919 &self.cache
920 }
921
922 pub fn try_new(
924 mode: AggregateMode,
925 group_by: impl Into<Arc<PhysicalGroupBy>>,
926 aggr_expr: Vec<Arc<AggregateFunctionExpr>>,
927 filter_expr: Vec<Option<Arc<dyn PhysicalExpr>>>,
928 input: Arc<dyn ExecutionPlan>,
929 input_schema: SchemaRef,
930 ) -> Result<Self> {
931 let group_by = group_by.into();
932 let schema = create_schema(&input.schema(), &group_by, &aggr_expr, mode)?;
933
934 let schema = Arc::new(schema);
935 AggregateExec::try_new_with_schema(
936 mode,
937 group_by,
938 aggr_expr,
939 filter_expr,
940 input,
941 input_schema,
942 schema,
943 )
944 }
945
946 fn try_new_with_schema(
955 mode: AggregateMode,
956 group_by: impl Into<Arc<PhysicalGroupBy>>,
957 mut aggr_expr: Vec<Arc<AggregateFunctionExpr>>,
958 filter_expr: impl Into<Arc<[Option<Arc<dyn PhysicalExpr>>]>>,
959 input: Arc<dyn ExecutionPlan>,
960 input_schema: SchemaRef,
961 schema: SchemaRef,
962 ) -> Result<Self> {
963 let group_by = group_by.into();
964 let filter_expr = filter_expr.into();
965
966 assert_eq_or_internal_err!(
968 aggr_expr.len(),
969 filter_expr.len(),
970 "Inconsistent aggregate expr: {:?} and filter expr: {:?} for AggregateExec, their size should match",
971 aggr_expr,
972 filter_expr
973 );
974
975 let input_eq_properties = input.equivalence_properties();
976 let groupby_exprs = group_by.input_exprs();
978 let (new_sort_exprs, indices) =
983 input_eq_properties.find_longest_permutation(&groupby_exprs)?;
984
985 let mut new_requirements = new_sort_exprs
986 .into_iter()
987 .map(PhysicalSortRequirement::from)
988 .collect::<Vec<_>>();
989
990 let req = get_finer_aggregate_exprs_requirement(
991 &mut aggr_expr,
992 &group_by,
993 input_eq_properties,
994 &mode,
995 )?;
996 new_requirements.extend(req);
997
998 let required_input_ordering =
999 LexRequirement::new(new_requirements).map(OrderingRequirements::new_soft);
1000
1001 let indices: Vec<usize> = indices
1007 .into_iter()
1008 .filter(|idx| group_by.groups.iter().all(|group| !group[*idx]))
1009 .collect();
1010
1011 let input_order_mode = if indices.len() == groupby_exprs.len()
1012 && !indices.is_empty()
1013 && group_by.groups.len() == 1
1014 {
1015 InputOrderMode::Sorted
1016 } else if !indices.is_empty() {
1017 InputOrderMode::PartiallySorted(indices)
1018 } else {
1019 InputOrderMode::Linear
1020 };
1021
1022 let group_expr_mapping =
1024 ProjectionMapping::try_new(group_by.expr.clone(), &input.schema())?;
1025
1026 let cache = Self::compute_properties(
1027 &input,
1028 Arc::clone(&schema),
1029 &group_expr_mapping,
1030 group_by.is_true_no_grouping(),
1031 &mode,
1032 &input_order_mode,
1033 aggr_expr.as_ref(),
1034 )?;
1035
1036 let mut exec = AggregateExec {
1037 mode,
1038 group_by,
1039 aggr_expr: aggr_expr.into(),
1040 filter_expr,
1041 input,
1042 schema,
1043 input_schema,
1044 metrics: ExecutionPlanMetricsSet::new(),
1045 required_input_ordering,
1046 limit_options: None,
1047 input_order_mode,
1048 cache: Arc::new(cache),
1049 dynamic_filter: None,
1050 };
1051
1052 exec.init_dynamic_filter();
1053
1054 Ok(exec)
1055 }
1056
1057 pub fn mode(&self) -> &AggregateMode {
1059 &self.mode
1060 }
1061
1062 pub fn with_limit_options(mut self, limit_options: Option<LimitOptions>) -> Self {
1064 self.limit_options = limit_options;
1065 self
1066 }
1067
1068 pub fn limit_options(&self) -> Option<LimitOptions> {
1070 self.limit_options
1071 }
1072
1073 pub fn group_expr(&self) -> &PhysicalGroupBy {
1075 &self.group_by
1076 }
1077
1078 pub fn output_group_expr(&self) -> Vec<Arc<dyn PhysicalExpr>> {
1080 self.group_by.output_exprs()
1081 }
1082
1083 pub fn aggr_expr(&self) -> &[Arc<AggregateFunctionExpr>] {
1085 &self.aggr_expr
1086 }
1087
1088 pub fn filter_expr(&self) -> &[Option<Arc<dyn PhysicalExpr>>] {
1090 &self.filter_expr
1091 }
1092
1093 #[deprecated(
1095 since = "55.0.0",
1096 note = "Use ExecutionPlan::dynamic_expressions_produced instead"
1097 )]
1098 pub fn dynamic_filter_expr(&self) -> Option<&Arc<DynamicFilterPhysicalExpr>> {
1099 self.dynamic_filter.as_ref().map(|df| &df.filter)
1100 }
1101
1102 pub fn with_dynamic_filter_expr(
1106 mut self,
1107 filter: Arc<DynamicFilterPhysicalExpr>,
1108 ) -> Result<Self> {
1109 let Some(dyn_filter) = self.dynamic_filter.as_ref() else {
1112 return internal_err!("Aggregate does not support dynamic filtering");
1113 };
1114
1115 let cols = self.cols_for_dynamic_filter(&dyn_filter.supported_accumulators_info);
1117 if cols.len() != filter.children().len() {
1118 return internal_err!(
1119 "Dynamic filter expression is incompatible with aggregate due to mismatched number of columns"
1120 );
1121 }
1122 for (col, child) in cols.iter().zip(filter.children()) {
1123 if !col.eq(child) {
1124 return internal_err!(
1125 "Dynamic filter expression is incompatible with aggregate due to mismatched column references {col} != {child}"
1126 );
1127 }
1128 }
1129
1130 self.dynamic_filter = Some(Arc::new(AggrDynFilter {
1132 filter,
1133 supported_accumulators_info: dyn_filter.supported_accumulators_info.clone(),
1134 }));
1135 Ok(self)
1136 }
1137
1138 pub fn input(&self) -> &Arc<dyn ExecutionPlan> {
1140 &self.input
1141 }
1142
1143 pub fn input_schema(&self) -> SchemaRef {
1145 Arc::clone(&self.input_schema)
1146 }
1147
1148 fn execute_typed(
1151 &self,
1152 partition: usize,
1153 context: &Arc<TaskContext>,
1154 ) -> Result<StreamType> {
1155 if self.group_by.is_true_no_grouping() {
1156 return Ok(StreamType::AggregateStream(AggregateStream::new(
1157 self, context, partition,
1158 )?));
1159 }
1160
1161 if let Some(config) = self.limit_options
1163 && !self.is_unordered_unfiltered_group_by_distinct()
1164 {
1165 return Ok(StreamType::GroupedPriorityQueue(
1166 GroupedTopKAggregateStream::new(self, context, partition, config.limit)?,
1167 ));
1168 }
1169
1170 if context
1184 .session_config()
1185 .options()
1186 .execution
1187 .enable_migration_aggregate
1188 {
1189 if self.should_use_ordered_partial_aggregate_stream(context) {
1190 return Ok(StreamType::OrderedPartialAggregate(
1191 OrderedPartialAggregateStream::new(self, context, partition)?,
1192 ));
1193 }
1194
1195 if self.should_use_partial_hash_stream(context) {
1196 return Ok(StreamType::PartialHash(PartialHashAggregateStream::new(
1197 self, context, partition,
1198 )?));
1199 }
1200
1201 if self.should_use_partial_reduce_hash_stream(context) {
1202 return Ok(StreamType::PartialReduceHash(
1203 PartialReduceHashAggregateStream::new(self, context, partition)?,
1204 ));
1205 }
1206
1207 if self.should_use_ordered_final_aggregate_stream(context) {
1208 return Ok(StreamType::OrderedFinalAggregate(
1209 OrderedFinalAggregateStream::new(self, context, partition)?,
1210 ));
1211 }
1212
1213 if self.should_use_final_hash_stream(context) {
1214 return Ok(StreamType::FinalHash(FinalHashAggregateStream::new(
1215 self, context, partition,
1216 )?));
1217 }
1218
1219 if self.should_use_single_hash_stream(context) {
1220 return Ok(StreamType::SingleHash(SingleHashAggregateStream::new(
1221 self, context, partition,
1222 )?));
1223 }
1224 }
1225
1226 Ok(StreamType::GroupedHash(GroupedHashAggregateStream::new(
1228 self, context, partition,
1229 )?))
1230 }
1231
1232 fn should_use_partial_hash_stream(&self, _context: &TaskContext) -> bool {
1233 self.mode == AggregateMode::Partial
1234 && self.input_order_mode == InputOrderMode::Linear
1235 && !self.group_by.is_true_no_grouping()
1236 && self.group_by.is_single()
1237 && self.limit_options_supported_by_hash_stream()
1238 }
1239
1240 fn should_use_ordered_partial_aggregate_stream(
1241 &self,
1242 _context: &TaskContext,
1243 ) -> bool {
1244 self.mode == AggregateMode::Partial
1245 && self.input_order_mode != InputOrderMode::Linear
1246 && !self.group_by.is_true_no_grouping()
1247 && self.group_by.is_single()
1248 && self.limit_options_supported_by_hash_stream()
1249 }
1250
1251 fn should_use_final_hash_stream(&self, _context: &TaskContext) -> bool {
1252 matches!(
1253 self.mode,
1254 AggregateMode::Final | AggregateMode::FinalPartitioned
1255 ) && self.limit_options_supported_by_hash_stream()
1256 && self.input_order_mode == InputOrderMode::Linear
1257 && !self.group_by.is_true_no_grouping()
1258 && self.group_by.is_single()
1259 }
1260
1261 fn should_use_partial_reduce_hash_stream(&self, context: &TaskContext) -> bool {
1262 if matches!(context.memory_pool().memory_limit(), MemoryLimit::Finite(_)) {
1264 return false;
1265 }
1266
1267 self.mode == AggregateMode::PartialReduce
1268 && self.limit_options.is_none()
1269 && self.input_order_mode == InputOrderMode::Linear
1270 && !self.group_by.is_true_no_grouping()
1271 && self.group_by.is_single()
1272 }
1273
1274 fn should_use_single_hash_stream(&self, _context: &TaskContext) -> bool {
1275 matches!(
1276 self.mode,
1277 AggregateMode::Single | AggregateMode::SinglePartitioned
1278 ) && self.limit_options.is_none()
1279 && self.input_order_mode == InputOrderMode::Linear
1280 && !self.group_by.is_true_no_grouping()
1281 && self.group_by.is_single()
1282 }
1283
1284 fn should_use_ordered_final_aggregate_stream(&self, _context: &TaskContext) -> bool {
1285 matches!(
1286 self.mode,
1287 AggregateMode::Final | AggregateMode::FinalPartitioned
1288 ) && self.limit_options_supported_by_hash_stream()
1289 && self.input_order_mode != InputOrderMode::Linear
1290 && !self.group_by.is_true_no_grouping()
1291 && self.group_by.is_single()
1292 }
1293
1294 fn limit_options_supported_by_hash_stream(&self) -> bool {
1296 self.limit_options.is_none() || self.is_unordered_unfiltered_group_by_distinct()
1297 }
1298
1299 pub fn get_minmax_desc(&self) -> Option<(FieldRef, bool)> {
1301 let agg_expr = self.aggr_expr.iter().exactly_one().ok()?;
1302 agg_expr.get_minmax_desc()
1303 }
1304
1305 pub fn is_unordered_unfiltered_group_by_distinct(&self) -> bool {
1310 if self
1311 .limit_options()
1312 .and_then(|config| config.descending)
1313 .is_some()
1314 {
1315 return false;
1316 }
1317 if self.group_expr().is_empty() && !self.group_expr().has_grouping_set() {
1319 return false;
1320 }
1321 if !self.aggr_expr().is_empty() {
1323 return false;
1324 }
1325 if self.filter_expr().iter().any(|e| e.is_some()) {
1328 return false;
1329 }
1330 if !self.aggr_expr().iter().all(|e| e.order_bys().is_empty()) {
1332 return false;
1333 }
1334 if self.properties().output_ordering().is_some() {
1336 return false;
1337 }
1338 if let Some(requirement) = self.required_input_ordering().swap_remove(0) {
1340 return matches!(requirement, OrderingRequirements::Hard(_));
1341 }
1342 true
1343 }
1344
1345 pub fn compute_properties(
1347 input: &Arc<dyn ExecutionPlan>,
1348 schema: SchemaRef,
1349 group_expr_mapping: &ProjectionMapping,
1350 is_true_no_grouping: bool,
1351 mode: &AggregateMode,
1352 input_order_mode: &InputOrderMode,
1353 aggr_exprs: &[Arc<AggregateFunctionExpr>],
1354 ) -> Result<PlanProperties> {
1355 let mut eq_properties = input
1357 .equivalence_properties()
1358 .project(group_expr_mapping, schema);
1359
1360 if is_true_no_grouping {
1366 let new_constants = aggr_exprs.iter().enumerate().map(|(idx, func)| {
1367 let column = Arc::new(Column::new(func.name(), idx));
1368 ConstExpr::from(column as Arc<dyn PhysicalExpr>)
1369 });
1370 eq_properties.add_constants(new_constants)?;
1371 }
1372
1373 let mut constraints = eq_properties.constraints().to_vec();
1376 let new_constraint = Constraint::Unique(
1377 group_expr_mapping
1378 .iter()
1379 .flat_map(|(_, target_cols)| {
1380 target_cols.iter().flat_map(|(expr, _)| {
1381 expr.downcast_ref::<Column>().map(|c| c.index())
1382 })
1383 })
1384 .collect(),
1385 );
1386 constraints.push(new_constraint);
1387 eq_properties =
1388 eq_properties.with_constraints(Constraints::new_unverified(constraints));
1389
1390 let input_partitioning = input.output_partitioning().clone();
1392 let output_partitioning = match mode.input_mode() {
1393 AggregateInputMode::Raw => {
1394 let input_eq_properties = input.equivalence_properties();
1398 input_partitioning.project(group_expr_mapping, input_eq_properties)
1399 }
1400 AggregateInputMode::Partial => input_partitioning.clone(),
1401 };
1402
1403 let emission_type = if *input_order_mode == InputOrderMode::Linear {
1405 EmissionType::Final
1406 } else {
1407 input.pipeline_behavior()
1408 };
1409
1410 Ok(PlanProperties::new(
1411 eq_properties,
1412 output_partitioning,
1413 emission_type,
1414 input.boundedness(),
1415 ))
1416 }
1417
1418 pub fn input_order_mode(&self) -> &InputOrderMode {
1419 &self.input_order_mode
1420 }
1421
1422 fn statistics_inner(
1467 &self,
1468 child_statistics: &Statistics,
1469 partition: Option<usize>,
1470 ) -> Result<Statistics> {
1471 let column_statistics = {
1478 let mut column_statistics = Statistics::unknown_column(&self.schema());
1480
1481 for (idx, (expr, _)) in self.group_by.expr.iter().enumerate() {
1482 if let Some(col) = expr.downcast_ref::<Column>() {
1483 let child_col_stats =
1484 &child_statistics.column_statistics[col.index()];
1485 column_statistics[idx].max_value = child_col_stats.max_value.clone();
1486 column_statistics[idx].min_value = child_col_stats.min_value.clone();
1487 column_statistics[idx].distinct_count =
1488 child_col_stats.distinct_count;
1489 }
1490 }
1491
1492 column_statistics
1493 };
1494 match self.exact_output_rows_without_group_exprs(partition) {
1495 Some(output_rows) => {
1496 let total_byte_size =
1497 Self::calculate_scaled_byte_size(child_statistics, output_rows);
1498
1499 Ok(Statistics {
1500 num_rows: Precision::Exact(output_rows),
1501 column_statistics,
1502 total_byte_size,
1503 })
1504 }
1505 None => {
1506 let num_rows = self.estimate_num_rows(child_statistics, partition);
1507 let column_statistics = self.nullify_group_columns_for_empty_input(
1508 column_statistics,
1509 child_statistics,
1510 &num_rows,
1511 );
1512
1513 let total_byte_size = num_rows
1514 .get_value()
1515 .and_then(|&output_rows| {
1516 Self::calculate_scaled_byte_size(child_statistics, output_rows)
1517 .get_value()
1518 .map(|&bytes| Precision::Inexact(bytes))
1519 })
1520 .unwrap_or(Precision::Absent);
1521
1522 Ok(Statistics {
1523 num_rows,
1524 column_statistics,
1525 total_byte_size,
1526 })
1527 }
1528 }
1529 }
1530
1531 fn exact_output_rows_without_group_exprs(
1545 &self,
1546 partition: Option<usize>,
1547 ) -> Option<usize> {
1548 let logical_rows = self.logical_rows_without_group_exprs()?;
1549
1550 Some(self.scale_logical_rows(logical_rows, partition))
1551 }
1552
1553 fn scale_logical_rows(&self, logical_rows: usize, partition: Option<usize>) -> usize {
1556 match (self.mode.output_mode(), partition) {
1557 (AggregateOutputMode::Final, _) => logical_rows,
1558 (AggregateOutputMode::Partial, Some(_)) => logical_rows,
1559 (AggregateOutputMode::Partial, None) => {
1560 logical_rows * self.cache.output_partitioning().partition_count()
1561 }
1562 }
1563 }
1564
1565 fn output_rows_for_empty_input(&self, partition: Option<usize>) -> usize {
1571 let empty_grouping_sets = self
1572 .group_by
1573 .groups
1574 .iter()
1575 .filter(|nulls| nulls.iter().all(|is_null| *is_null))
1576 .count();
1577
1578 self.scale_logical_rows(empty_grouping_sets, partition)
1579 }
1580
1581 fn nullify_group_columns_for_empty_input(
1593 &self,
1594 mut column_statistics: Vec<ColumnStatistics>,
1595 child_statistics: &Statistics,
1596 num_rows: &Precision<usize>,
1597 ) -> Vec<ColumnStatistics> {
1598 let empty_input = child_statistics.num_rows.get_value() == Some(&0);
1599 let emits_rows = num_rows.get_value().is_some_and(|&rows| rows > 0);
1600 if !empty_input || !emits_rows {
1601 return column_statistics;
1602 }
1603
1604 let schema = self.schema();
1605 for (idx, column_stats) in column_statistics
1606 .iter_mut()
1607 .take(self.group_by.expr.len())
1608 .enumerate()
1609 {
1610 let typed_null = ScalarValue::try_from(schema.field(idx).data_type())
1611 .unwrap_or(ScalarValue::Null);
1612 let mut null_bound = Precision::Exact(typed_null);
1613 if matches!(num_rows, Precision::Inexact(_)) {
1614 null_bound = null_bound.to_inexact();
1615 }
1616 column_stats.min_value = null_bound.clone();
1617 column_stats.max_value = null_bound;
1618 column_stats.distinct_count = num_rows.map(|_| 0);
1619 column_stats.null_count = *num_rows;
1620 }
1621
1622 column_statistics
1623 }
1624
1625 fn logical_rows_without_group_exprs(&self) -> Option<usize> {
1633 if self.group_by.is_true_no_grouping() {
1634 Some(1)
1635 } else if self.group_by.expr.is_empty() {
1636 Some(self.group_by.groups.len())
1637 } else {
1638 None
1639 }
1640 }
1641
1642 fn estimate_num_rows(
1645 &self,
1646 child_statistics: &Statistics,
1647 partition: Option<usize>,
1648 ) -> Precision<usize> {
1649 let ndv = if !self.group_by.expr.is_empty() {
1650 self.compute_group_ndv(child_statistics)
1651 } else {
1652 None
1653 };
1654 let limit = self.limit_options.as_ref().map(|lo| lo.limit);
1655
1656 if let Some(&value) = child_statistics.num_rows.get_value() {
1657 if value > 1 {
1658 let mut num_rows = child_statistics.num_rows.to_inexact();
1659 if let Some(ndv) = ndv {
1660 num_rows = num_rows.map(|n| n.min(ndv));
1661 }
1662 if let Some(limit) = limit {
1663 num_rows = num_rows.map(|n| n.min(limit));
1664 }
1665 num_rows
1666 } else if value == 0 {
1667 child_statistics
1670 .num_rows
1671 .map(|_| self.output_rows_for_empty_input(partition))
1672 } else {
1673 let grouping_set_num = self.group_by.groups.len();
1674 let mut num_rows =
1675 child_statistics.num_rows.map(|x| x * grouping_set_num);
1676 if let Some(limit) = limit {
1677 num_rows = num_rows.map(|n| n.min(limit));
1678 }
1679 num_rows
1680 }
1681 } else {
1682 match (ndv, limit) {
1683 (Some(n), Some(l)) => Precision::Inexact(n.min(l)),
1684 (Some(n), None) => Precision::Inexact(n),
1685 (None, Some(l)) => Precision::Inexact(l),
1686 (None, None) => Precision::Absent,
1687 }
1688 }
1689 }
1690
1691 fn compute_group_ndv(&self, child_statistics: &Statistics) -> Option<usize> {
1709 let mut total: usize = 0;
1710 for group_mask in &self.group_by.groups {
1711 let mut set_product: usize = 1;
1712 for (j, (expr, _)) in self.group_by.expr.iter().enumerate() {
1713 if group_mask[j] {
1714 continue;
1715 }
1716 let col = expr.downcast_ref::<Column>()?;
1717 let col_stats = &child_statistics.column_statistics[col.index()];
1718 let ndv = *col_stats.distinct_count.get_value()?;
1719 let null_adjustment = match col_stats.null_count.get_value() {
1720 Some(&n) if n > 0 => 1usize,
1721 _ => 0,
1722 };
1723 set_product = set_product
1724 .saturating_mul(ndv.saturating_add(null_adjustment).max(1));
1725 }
1726 total = total.saturating_add(set_product);
1727 }
1728 Some(total)
1729 }
1730
1731 fn init_dynamic_filter(&mut self) {
1735 if (!self.group_by.is_empty()) || (self.mode != AggregateMode::Partial) {
1736 debug_assert!(
1737 self.dynamic_filter.is_none(),
1738 "The current operator node does not support dynamic filter"
1739 );
1740 return;
1741 }
1742
1743 if self.dynamic_filter.is_some() {
1745 return;
1746 }
1747
1748 let mut aggr_dyn_filters = Vec::new();
1752 let mut all_cols: Vec<Arc<dyn PhysicalExpr>> = Vec::new();
1756 for (i, aggr_expr) in self.aggr_expr.iter().enumerate() {
1757 let fun_name = aggr_expr.fun().name();
1759 let aggr_type = if fun_name.eq_ignore_ascii_case("min") {
1762 DynamicFilterAggregateType::Min
1763 } else if fun_name.eq_ignore_ascii_case("max") {
1764 DynamicFilterAggregateType::Max
1765 } else {
1766 return;
1767 };
1768
1769 if let [arg] = aggr_expr.expressions().as_slice()
1771 && arg.is::<Column>()
1772 {
1773 all_cols.push(Arc::clone(arg));
1774 aggr_dyn_filters.push(PerAccumulatorDynFilter {
1775 aggr_type,
1776 aggr_index: i,
1777 shared_bound: Arc::new(Mutex::new(ScalarValue::Null)),
1778 });
1779 }
1780 }
1781
1782 if !aggr_dyn_filters.is_empty() {
1783 self.dynamic_filter = Some(Arc::new(AggrDynFilter {
1784 filter: Arc::new(DynamicFilterPhysicalExpr::new(all_cols, lit(true))),
1785 supported_accumulators_info: aggr_dyn_filters,
1786 }))
1787 }
1788 }
1789
1790 fn cols_for_dynamic_filter(
1792 &self,
1793 supported_accumulators_info: &[PerAccumulatorDynFilter],
1794 ) -> Vec<Arc<dyn PhysicalExpr>> {
1795 let all_cols: Vec<Arc<dyn PhysicalExpr>> = supported_accumulators_info
1796 .iter()
1797 .filter_map(|info| {
1798 if let [arg] = &self.aggr_expr[info.aggr_index].expressions().as_slice()
1801 && arg.is::<Column>()
1802 {
1803 return Some(Arc::clone(arg));
1804 }
1805 None
1806 })
1807 .collect();
1808 debug_assert!(all_cols.len() == supported_accumulators_info.len());
1809 all_cols
1810 }
1811
1812 #[inline]
1818 fn calculate_scaled_byte_size(
1819 input_stats: &Statistics,
1820 target_row_count: usize,
1821 ) -> Precision<usize> {
1822 match (
1823 input_stats.num_rows.get_value(),
1824 input_stats.total_byte_size.get_value(),
1825 ) {
1826 (Some(&input_rows), Some(&input_bytes)) if input_rows > 0 => {
1827 let bytes_per_row = input_bytes as f64 / input_rows as f64;
1828 let scaled_bytes =
1829 (bytes_per_row * target_row_count as f64).ceil() as usize;
1830 Precision::Inexact(scaled_bytes)
1831 }
1832 _ => Precision::Absent,
1833 }
1834 }
1835}
1836
1837impl DisplayAs for AggregateExec {
1838 fn fmt_as(
1839 &self,
1840 t: DisplayFormatType,
1841 f: &mut std::fmt::Formatter,
1842 ) -> std::fmt::Result {
1843 match t {
1844 DisplayFormatType::Default | DisplayFormatType::Verbose => {
1845 let format_expr_with_alias =
1846 |(e, alias): &(Arc<dyn PhysicalExpr>, String)| -> String {
1847 let e = e.to_string();
1848 if &e != alias {
1849 format!("{e} as {alias}")
1850 } else {
1851 e
1852 }
1853 };
1854
1855 write!(f, "AggregateExec: mode={:?}", self.mode)?;
1856 let g: Vec<String> = if self.group_by.is_single() {
1857 self.group_by
1858 .expr
1859 .iter()
1860 .map(format_expr_with_alias)
1861 .collect()
1862 } else {
1863 self.group_by
1864 .groups
1865 .iter()
1866 .map(|group| {
1867 let terms = group
1868 .iter()
1869 .enumerate()
1870 .map(|(idx, is_null)| {
1871 if *is_null {
1872 format_expr_with_alias(
1873 &self.group_by.null_expr[idx],
1874 )
1875 } else {
1876 format_expr_with_alias(&self.group_by.expr[idx])
1877 }
1878 })
1879 .collect::<Vec<String>>()
1880 .join(", ");
1881 format!("({terms})")
1882 })
1883 .collect()
1884 };
1885
1886 write!(f, ", gby=[{}]", g.join(", "))?;
1887
1888 let a: Vec<String> = self
1889 .aggr_expr
1890 .iter()
1891 .map(|agg| format_aggregate_exec_expr(agg).to_string())
1892 .collect();
1893 write!(f, ", aggr=[{}]", a.join(", "))?;
1894 if let Some(config) = self.limit_options {
1895 write!(f, ", lim=[{}]", config.limit)?;
1896 }
1897
1898 if self.input_order_mode != InputOrderMode::Linear {
1899 write!(f, ", ordering_mode={:?}", self.input_order_mode)?;
1900 }
1901 }
1902 DisplayFormatType::TreeRender => {
1903 let format_expr_with_alias =
1904 |(e, alias): &(Arc<dyn PhysicalExpr>, String)| -> String {
1905 let expr_sql = fmt_sql(e.as_ref()).to_string();
1906 if &expr_sql != alias {
1907 format!("{expr_sql} as {alias}")
1908 } else {
1909 expr_sql
1910 }
1911 };
1912
1913 let g: Vec<String> = if self.group_by.is_single() {
1914 self.group_by
1915 .expr
1916 .iter()
1917 .map(format_expr_with_alias)
1918 .collect()
1919 } else {
1920 self.group_by
1921 .groups
1922 .iter()
1923 .map(|group| {
1924 let terms = group
1925 .iter()
1926 .enumerate()
1927 .map(|(idx, is_null)| {
1928 if *is_null {
1929 format_expr_with_alias(
1930 &self.group_by.null_expr[idx],
1931 )
1932 } else {
1933 format_expr_with_alias(&self.group_by.expr[idx])
1934 }
1935 })
1936 .collect::<Vec<String>>()
1937 .join(", ");
1938 format!("({terms})")
1939 })
1940 .collect()
1941 };
1942 let a: Vec<String> = self
1943 .aggr_expr
1944 .iter()
1945 .map(|agg| format_tree_aggregate_expr(agg).to_string())
1946 .collect();
1947 writeln!(f, "mode={:?}", self.mode)?;
1948 if !g.is_empty() {
1949 writeln!(f, "group_by={}", g.join(", "))?;
1950 }
1951 if !a.is_empty() {
1952 writeln!(f, "aggr={}", a.join(", "))?;
1953 }
1954 if let Some(config) = self.limit_options {
1955 writeln!(f, "limit={}", config.limit)?;
1956 }
1957 }
1958 }
1959 Ok(())
1960 }
1961}
1962
1963fn format_aggregate_exec_expr(agg: &AggregateFunctionExpr) -> Cow<'_, str> {
1964 match agg.human_display_alias() {
1965 Some(_) => format_human_display(agg.human_display(), agg.human_display_alias())
1966 .unwrap_or_else(|| Cow::Borrowed(agg.name())),
1967 None => Cow::Borrowed(agg.name()),
1968 }
1969}
1970
1971fn format_tree_aggregate_expr(agg: &AggregateFunctionExpr) -> Cow<'_, str> {
1972 format_human_display(agg.human_display(), agg.human_display_alias())
1973 .unwrap_or_else(|| Cow::Borrowed(agg.name()))
1974}
1975
1976fn format_human_display<'a>(
1977 human_display: Option<&'a str>,
1978 alias: Option<&'a str>,
1979) -> Option<Cow<'a, str>> {
1980 human_display.map(|human_display| match alias {
1981 Some(alias) => Cow::Owned(format!("{human_display} as {alias}")),
1982 None => Cow::Borrowed(human_display),
1983 })
1984}
1985
1986impl ExecutionPlan for AggregateExec {
1987 fn name(&self) -> &'static str {
1988 "AggregateExec"
1989 }
1990
1991 fn properties(&self) -> &Arc<PlanProperties> {
1993 &self.cache
1994 }
1995
1996 fn required_input_distribution(&self) -> Vec<Distribution> {
1997 self.input_distribution_requirements().into_per_child()
1998 }
1999
2000 fn input_distribution_requirements(&self) -> InputDistributionRequirements {
2001 InputDistributionRequirements::new(match &self.mode {
2002 AggregateMode::Partial | AggregateMode::PartialReduce => {
2003 vec![Distribution::UnspecifiedDistribution]
2004 }
2005 AggregateMode::FinalPartitioned | AggregateMode::SinglePartitioned => {
2006 vec![Distribution::KeyPartitioned(self.group_by.input_exprs())]
2007 }
2008 AggregateMode::Final | AggregateMode::Single => {
2009 vec![Distribution::SinglePartition]
2010 }
2011 })
2012 }
2013
2014 fn required_input_ordering(&self) -> Vec<Option<OrderingRequirements>> {
2015 vec![self.required_input_ordering.clone()]
2016 }
2017
2018 fn maintains_input_order(&self) -> Vec<bool> {
2028 vec![self.input_order_mode != InputOrderMode::Linear]
2029 }
2030
2031 fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
2032 vec![&self.input]
2033 }
2034
2035 fn replace_children(
2036 self: Arc<Self>,
2037 mut children: Vec<Arc<dyn ExecutionPlan>>,
2038 options: ReplaceChildrenOptions,
2039 ) -> Result<Arc<dyn ExecutionPlan>> {
2040 validate_child_count!(self, children);
2041 match options.children_properties {
2042 ChildrenPropertiesMode::Keep => Ok(Arc::new(Self {
2043 input: children.swap_remove(0),
2044 metrics: ExecutionPlanMetricsSet::new(),
2045 ..Self::clone(&*self)
2046 })),
2047 ChildrenPropertiesMode::Recompute => {
2048 let mut me = AggregateExec::try_new_with_schema(
2049 self.mode,
2050 Arc::clone(&self.group_by),
2051 self.aggr_expr.to_vec(),
2052 Arc::clone(&self.filter_expr),
2053 Arc::clone(&children[0]),
2054 Arc::clone(&self.input_schema),
2055 Arc::clone(&self.schema),
2056 )?;
2057 me.limit_options = self.limit_options;
2058 me.dynamic_filter.clone_from(&self.dynamic_filter);
2059 Ok(Arc::new(me))
2060 }
2061 }
2062 }
2063
2064 fn with_new_children(
2065 self: Arc<Self>,
2066 children: Vec<Arc<dyn ExecutionPlan>>,
2067 ) -> Result<Arc<dyn ExecutionPlan>> {
2068 self.replace_children(
2069 children,
2070 ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
2071 )
2072 }
2073
2074 fn apply_expressions(
2075 &self,
2076 f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> Result<TreeNodeRecursion>,
2077 ) -> Result<TreeNodeRecursion> {
2078 let group_by = self.group_by.input_exprs();
2079 let aggregates = self.aggr_expr.iter().flat_map(|aggr| {
2080 let expressions = aggr.all_expressions();
2081 expressions
2082 .args
2083 .into_iter()
2084 .chain(expressions.order_by_exprs)
2085 });
2086 let filters = self.filter_expr.iter().flatten().cloned();
2087 let dynamic_filter = self.dynamic_filter.iter().map(|dynamic_filter| {
2088 Arc::<DynamicFilterPhysicalExpr>::clone(&dynamic_filter.filter)
2089 as Arc<dyn PhysicalExpr>
2090 });
2091 crate::apply_expression_roots(
2092 group_by
2093 .into_iter()
2094 .chain(aggregates)
2095 .chain(filters)
2096 .chain(dynamic_filter),
2097 f,
2098 )
2099 }
2100
2101 fn dynamic_expressions_produced(&self) -> Vec<Arc<dyn PhysicalExpr>> {
2102 self.dynamic_filter
2103 .iter()
2104 .map(|dynamic_filter| {
2105 Arc::<DynamicFilterPhysicalExpr>::clone(&dynamic_filter.filter)
2106 as Arc<dyn PhysicalExpr>
2107 })
2108 .collect()
2109 }
2110
2111 fn with_new_children_and_same_properties(
2112 self: Arc<Self>,
2113 children: Vec<Arc<dyn ExecutionPlan>>,
2114 ) -> Result<Arc<dyn ExecutionPlan>> {
2115 self.replace_children(
2116 children,
2117 ReplaceChildrenOptions::new(ChildrenPropertiesMode::Keep),
2118 )
2119 }
2120
2121 fn execute(
2122 &self,
2123 partition: usize,
2124 context: Arc<TaskContext>,
2125 ) -> Result<SendableRecordBatchStream> {
2126 self.execute_typed(partition, &context)
2127 .map(|stream| stream.into())
2128 }
2129
2130 fn metrics(&self) -> Option<MetricsSet> {
2131 Some(self.metrics.clone_inner())
2132 }
2133
2134 fn child_stats_requests(&self, partition: Option<usize>) -> Vec<ChildStats> {
2135 vec![ChildStats::At(partition)]
2136 }
2137
2138 fn statistics_from_inputs(
2139 &self,
2140 input_stats: &[Arc<Statistics>],
2141 args: &StatisticsArgs,
2142 ) -> Result<Arc<Statistics>> {
2143 let child_statistics = Arc::clone(&input_stats[0]);
2144 Ok(Arc::new(
2145 self.statistics_inner(&child_statistics, args.partition())?,
2146 ))
2147 }
2148
2149 fn cardinality_effect(&self) -> CardinalityEffect {
2150 CardinalityEffect::LowerEqual
2151 }
2152
2153 fn gather_filters_for_pushdown(
2156 &self,
2157 phase: FilterPushdownPhase,
2158 parent_filters: Vec<Arc<dyn PhysicalExpr>>,
2159 config: &ConfigOptions,
2160 ) -> Result<FilterDescription> {
2161 let mut allowed_indices: HashSet<usize> =
2173 (0..self.group_by.expr().len()).collect();
2174 for null_mask in self.group_by.groups() {
2175 allowed_indices.retain(|idx| null_mask.get(*idx) != Some(&true));
2176 }
2177
2178 let child = self.children()[0];
2179 let may_emit_on_empty_input = self.group_by.is_true_no_grouping()
2184 || self
2185 .group_by
2186 .groups()
2187 .iter()
2188 .any(|null_mask| null_mask.iter().all(|is_null| *is_null));
2189 let mut child_desc = if may_emit_on_empty_input {
2190 ChildFilterDescription::all_unsupported(&parent_filters)
2191 } else {
2192 ChildFilterDescription::from_child_with_allowed_indices(
2193 &parent_filters,
2194 allowed_indices,
2195 child,
2196 )?
2197 };
2198
2199 if phase == FilterPushdownPhase::Post
2201 && config.optimizer.enable_aggregate_dynamic_filter_pushdown
2202 && let Some(self_dyn_filter) = &self.dynamic_filter
2203 {
2204 let dyn_filter = Arc::clone(&self_dyn_filter.filter);
2205 child_desc = child_desc.with_self_filter(dyn_filter);
2206 }
2207
2208 Ok(FilterDescription::new().with_child(child_desc))
2209 }
2210
2211 fn handle_child_pushdown_result(
2214 &self,
2215 phase: FilterPushdownPhase,
2216 child_pushdown_result: ChildPushdownResult,
2217 _config: &ConfigOptions,
2218 ) -> Result<FilterPushdownPropagation<Arc<dyn ExecutionPlan>>> {
2219 let mut result = FilterPushdownPropagation::if_any(child_pushdown_result.clone());
2220
2221 if phase == FilterPushdownPhase::Post
2224 && let Some(dyn_filter) = &self.dynamic_filter
2225 {
2226 let child_accepts_dyn_filter = dyn_filter
2227 .filter
2228 .expression_id()
2229 .map(|id| plan_contains_expression_id(&self.input, id))
2230 .transpose()?
2231 .unwrap_or(false);
2232
2233 if !child_accepts_dyn_filter {
2234 let mut new_node = self.clone();
2237 new_node.dynamic_filter = None;
2238
2239 result = result
2240 .with_updated_node(Arc::new(new_node) as Arc<dyn ExecutionPlan>);
2241 }
2242 }
2243
2244 Ok(result)
2245 }
2246
2247 #[cfg(feature = "proto")]
2248 fn try_to_proto(
2249 &self,
2250 ctx: &crate::proto::ExecutionPlanEncodeCtx<'_>,
2251 ) -> Result<Option<datafusion_proto_models::protobuf::PhysicalPlanNode>> {
2252 use datafusion_proto_models::protobuf;
2253
2254 let Self {
2258 mode,
2259 group_by,
2260 aggr_expr,
2261 filter_expr,
2262 limit_options,
2263 input,
2264 schema: _,
2267 input_schema,
2268 metrics: _,
2270 required_input_ordering: _,
2272 input_order_mode: _,
2274 cache: _,
2276 dynamic_filter,
2277 } = self;
2278
2279 let input = ctx.encode_child(input)?;
2280 let group_expr =
2281 ctx.encode_expressions(group_by.expr().iter().map(|(expr, _)| expr))?;
2282 let group_expr_name = group_by
2283 .expr()
2284 .iter()
2285 .map(|(_, name)| name.to_owned())
2286 .collect();
2287 let null_expr =
2288 ctx.encode_expressions(group_by.null_expr().iter().map(|(expr, _)| expr))?;
2289 let groups = group_by.groups().iter().flatten().copied().collect();
2290 let aggr_expr_name = aggr_expr
2291 .iter()
2292 .map(|expr| expr.name().to_string())
2293 .collect();
2294 let aggr_expr = aggr_expr
2295 .iter()
2296 .map(|expr| encode_aggregate_expr(expr, ctx))
2297 .collect::<Result<Vec<_>>>()?;
2298 let filter_expr = filter_expr
2299 .iter()
2300 .map(|filter| {
2301 Ok(protobuf::MaybeFilter {
2302 expr: filter
2303 .as_ref()
2304 .map(|expr| ctx.encode_expr(expr))
2305 .transpose()?,
2306 })
2307 })
2308 .collect::<Result<Vec<_>>>()?;
2309 let mode = match mode {
2312 AggregateMode::Partial => protobuf::AggregateMode::Partial,
2313 AggregateMode::Final => protobuf::AggregateMode::Final,
2314 AggregateMode::FinalPartitioned => protobuf::AggregateMode::FinalPartitioned,
2315 AggregateMode::Single => protobuf::AggregateMode::Single,
2316 AggregateMode::SinglePartitioned => {
2317 protobuf::AggregateMode::SinglePartitioned
2318 }
2319 AggregateMode::PartialReduce => protobuf::AggregateMode::PartialReduce,
2320 };
2321 let limit = limit_options.map(|options| protobuf::AggLimit {
2322 limit: options.limit() as u64,
2323 descending: options.descending(),
2324 });
2325 let dynamic_filter = match dynamic_filter {
2328 Some(dynamic_filter) => {
2329 let expr: Arc<dyn PhysicalExpr> =
2330 Arc::clone(&dynamic_filter.filter) as Arc<dyn PhysicalExpr>;
2331 Some(ctx.encode_expr(&expr)?)
2332 }
2333 None => None,
2334 };
2335
2336 Ok(Some(protobuf::PhysicalPlanNode {
2337 physical_plan_type: Some(
2338 protobuf::physical_plan_node::PhysicalPlanType::Aggregate(Box::new(
2339 protobuf::AggregateExecNode {
2340 group_expr,
2341 group_expr_name,
2342 aggr_expr,
2343 filter_expr,
2344 aggr_expr_name,
2345 mode: mode as i32,
2346 input: Some(Box::new(input)),
2347 input_schema: Some(input_schema.as_ref().try_into()?),
2348 null_expr,
2349 groups,
2350 limit,
2351 has_grouping_set: group_by.has_grouping_set(),
2352 dynamic_filter,
2353 schema: Some(self.schema.as_ref().try_into()?),
2354 },
2355 )),
2356 ),
2357 }))
2358 }
2359}
2360
2361#[cfg(feature = "proto")]
2364const HUMAN_DISPLAY_ALIAS_PREFIX: &str = "\u{1f}datafusion_human_display_alias_v1:";
2365
2366#[cfg(feature = "proto")]
2367fn encode_human_display_alias(human_display: &str, alias: &str) -> String {
2368 format!(
2369 "{HUMAN_DISPLAY_ALIAS_PREFIX}{}:{alias}{human_display}",
2370 alias.len()
2371 )
2372}
2373
2374#[cfg(feature = "proto")]
2375fn split_human_display_alias<'a>(
2376 human_display: &'a str,
2377 name: &'a str,
2378) -> (&'a str, Option<&'a str>) {
2379 if let Some(encoded) = human_display.strip_prefix(HUMAN_DISPLAY_ALIAS_PREFIX)
2380 && let Some((alias_len, encoded)) = encoded.split_once(':')
2381 && let Ok(alias_len) = alias_len.parse::<usize>()
2382 && let Some(alias) = encoded.get(..alias_len)
2383 && let Some(human_display) = encoded.get(alias_len..)
2384 && alias == name
2385 && !human_display.is_empty()
2386 {
2387 return (human_display, Some(alias));
2388 }
2389
2390 (human_display, None)
2391}
2392
2393#[cfg(feature = "proto")]
2394fn encode_aggregate_expr(
2395 aggr_expr: &Arc<AggregateFunctionExpr>,
2396 ctx: &crate::proto::ExecutionPlanEncodeCtx<'_>,
2397) -> Result<datafusion_proto_models::protobuf::PhysicalExprNode> {
2398 use datafusion_proto_models::protobuf;
2399
2400 let expressions = aggr_expr.expressions();
2401 let expr = ctx.encode_expressions(expressions.iter())?;
2402 let ordering_req =
2403 datafusion_physical_expr_common::sort_expr::sort_exprs_try_to_proto(
2404 aggr_expr.order_bys(),
2405 &ctx.expr_ctx(),
2406 )?;
2407 let name = aggr_expr.fun().name().to_string();
2408 let fun_definition = ctx.encode_udaf(aggr_expr.fun())?;
2410 let human_display = match (aggr_expr.human_display(), aggr_expr.human_display_alias())
2411 {
2412 (Some(display), Some(alias)) => encode_human_display_alias(display, alias),
2413 (Some(display), None) => display.to_string(),
2414 (None, _) => String::new(),
2415 };
2416
2417 Ok(protobuf::PhysicalExprNode {
2418 expr_id: None,
2419 expr_type: Some(protobuf::physical_expr_node::ExprType::AggregateExpr(
2420 protobuf::PhysicalAggregateExprNode {
2421 aggregate_function: Some(
2422 protobuf::physical_aggregate_expr_node::AggregateFunction::UserDefinedAggrFunction(name),
2423 ),
2424 expr,
2425 ordering_req,
2426 distinct: aggr_expr.is_distinct(),
2427 ignore_nulls: aggr_expr.ignore_nulls(),
2428 fun_definition,
2429 human_display,
2430 is_reversed: aggr_expr.is_reversed(),
2431 },
2432 )),
2433 })
2434}
2435
2436#[cfg(feature = "proto")]
2437impl AggregateExec {
2438 pub fn try_from_proto(
2444 node: &datafusion_proto_models::protobuf::PhysicalPlanNode,
2445 ctx: &crate::proto::ExecutionPlanDecodeCtx<'_>,
2446 ) -> Result<Arc<dyn ExecutionPlan>> {
2447 use datafusion_physical_expr::aggregate::AggregateExprBuilder;
2448 use datafusion_proto_models::protobuf;
2449 use protobuf::physical_aggregate_expr_node::AggregateFunction;
2450 use protobuf::physical_expr_node::ExprType;
2451
2452 let hash_agg = crate::expect_plan_variant!(
2453 node,
2454 protobuf::physical_plan_node::PhysicalPlanType::Aggregate,
2455 "AggregateExec",
2456 );
2457 let protobuf::AggregateExecNode {
2460 group_expr,
2461 aggr_expr,
2462 mode,
2463 input,
2464 group_expr_name,
2465 aggr_expr_name,
2466 input_schema,
2467 null_expr,
2468 groups,
2469 filter_expr,
2470 limit,
2471 has_grouping_set,
2472 dynamic_filter,
2473 schema,
2474 } = hash_agg.as_ref();
2475
2476 let input =
2477 ctx.decode_required_child(input.as_deref(), "AggregateExec", "input")?;
2478 let mode = protobuf::AggregateMode::try_from(*mode).map_err(|_| {
2481 datafusion_common::internal_datafusion_err!(
2482 "Received an AggregateNode message with unknown AggregateMode {mode}"
2483 )
2484 })?;
2485 let mode = match mode {
2486 protobuf::AggregateMode::Partial => AggregateMode::Partial,
2487 protobuf::AggregateMode::Final => AggregateMode::Final,
2488 protobuf::AggregateMode::FinalPartitioned => AggregateMode::FinalPartitioned,
2489 protobuf::AggregateMode::Single => AggregateMode::Single,
2490 protobuf::AggregateMode::SinglePartitioned => {
2491 AggregateMode::SinglePartitioned
2492 }
2493 protobuf::AggregateMode::PartialReduce => AggregateMode::PartialReduce,
2494 };
2495 let num_expr = group_expr.len();
2496 let child_schema = input.schema();
2498 let group_expr = group_expr
2499 .iter()
2500 .zip(group_expr_name.iter())
2501 .map(|(expr, name)| {
2502 Ok((
2503 ctx.decode_expr(expr, child_schema.as_ref())?,
2504 name.to_string(),
2505 ))
2506 })
2507 .collect::<Result<Vec<_>>>()?;
2508 let null_expr = null_expr
2509 .iter()
2510 .zip(group_expr_name.iter())
2511 .map(|(expr, name)| {
2512 Ok((
2513 ctx.decode_expr(expr, child_schema.as_ref())?,
2514 name.to_string(),
2515 ))
2516 })
2517 .collect::<Result<Vec<_>>>()?;
2518 let groups = if groups.is_empty() {
2519 vec![]
2520 } else {
2521 groups
2522 .chunks(num_expr)
2523 .map(|group| group.to_vec())
2524 .collect()
2525 };
2526 let input_schema = input_schema.as_ref().ok_or_else(|| {
2529 datafusion_common::internal_datafusion_err!(
2530 "input_schema in AggregateNode is missing."
2531 )
2532 })?;
2533 let input_schema: SchemaRef = SchemaRef::new(input_schema.try_into()?);
2534 let filter_expr = filter_expr
2535 .iter()
2536 .map(|filter| {
2537 filter
2538 .expr
2539 .as_ref()
2540 .map(|expr| ctx.decode_expr(expr, input_schema.as_ref()))
2541 .transpose()
2542 })
2543 .collect::<Result<Vec<_>>>()?;
2544 let aggr_expr = aggr_expr
2545 .iter()
2546 .zip(aggr_expr_name.iter())
2547 .map(|(expr, name)| {
2548 let expr_type = expr.expr_type.as_ref().ok_or_else(|| {
2549 datafusion_common::internal_datafusion_err!(
2550 "Unexpected empty aggregate physical expression"
2551 )
2552 })?;
2553 let ExprType::AggregateExpr(aggregate) = expr_type else {
2554 return internal_err!(
2555 "Invalid aggregate expression for AggregateExec"
2556 );
2557 };
2558 let args = aggregate
2559 .expr
2560 .iter()
2561 .map(|expr| ctx.decode_expr(expr, input_schema.as_ref()))
2562 .collect::<Result<Vec<_>>>()?;
2563 let order_by =
2564 datafusion_physical_expr_common::sort_expr::sort_exprs_try_from_proto(
2565 &aggregate.ordering_req,
2566 &ctx.expr_ctx(input_schema.as_ref()),
2567 )?;
2568 let Some(AggregateFunction::UserDefinedAggrFunction(udaf_name)) =
2569 aggregate.aggregate_function.as_ref()
2570 else {
2571 return internal_err!(
2572 "Invalid AggregateExpr, missing aggregate_function"
2573 );
2574 };
2575 let udaf =
2578 ctx.decode_udaf(udaf_name, aggregate.fun_definition.as_deref())?;
2579 let (human_display, human_display_alias) =
2580 split_human_display_alias(&aggregate.human_display, name);
2581 let builder = AggregateExprBuilder::new(udaf, args)
2582 .schema(Arc::clone(&input_schema))
2583 .alias(name)
2584 .with_ignore_nulls(aggregate.ignore_nulls)
2585 .with_distinct(aggregate.distinct)
2586 .order_by(order_by)
2587 .with_reversed(aggregate.is_reversed)
2588 .human_display(human_display);
2589 let builder = if let Some(alias) = human_display_alias {
2590 builder.human_display_alias(alias)
2591 } else {
2592 builder
2593 };
2594 builder.build().map(Arc::new)
2595 })
2596 .collect::<Result<Vec<_>>>()?;
2597 let group_by =
2598 PhysicalGroupBy::new(group_expr, null_expr, groups, *has_grouping_set);
2599 let aggregate = if let Some(schema) = schema {
2600 let schema = SchemaRef::new(schema.try_into()?);
2601 AggregateExec::try_new_with_schema(
2602 mode,
2603 group_by,
2604 aggr_expr,
2605 filter_expr,
2606 input,
2607 Arc::clone(&input_schema),
2608 schema,
2609 )
2610 } else {
2611 AggregateExec::try_new(
2612 mode,
2613 group_by,
2614 aggr_expr,
2615 filter_expr,
2616 input,
2617 Arc::clone(&input_schema),
2618 )
2619 }?;
2620 let aggregate = if let Some(limit) = limit {
2621 let options = match limit.descending {
2622 Some(descending) => {
2623 LimitOptions::new_with_order(limit.limit as usize, descending)
2624 }
2625 None => LimitOptions::new(limit.limit as usize),
2626 };
2627 aggregate.with_limit_options(Some(options))
2628 } else {
2629 aggregate
2630 };
2631 let aggregate = if let Some(dynamic_filter) = dynamic_filter {
2632 let dynamic_filter =
2633 ctx.decode_expr(dynamic_filter, input_schema.as_ref())?;
2634 let dynamic_filter = (dynamic_filter
2635 as Arc<dyn std::any::Any + Send + Sync>)
2636 .downcast::<DynamicFilterPhysicalExpr>()
2637 .map_err(|_| {
2638 datafusion_common::internal_datafusion_err!(
2639 "AggregateExec dynamic_filter did not decode to a DynamicFilterPhysicalExpr"
2640 )
2641 })?;
2642 aggregate.with_dynamic_filter_expr(dynamic_filter)?
2643 } else {
2644 let mut aggregate = aggregate;
2645 aggregate.dynamic_filter = None;
2646 aggregate
2647 };
2648
2649 Ok(Arc::new(aggregate))
2650 }
2651}
2652
2653fn create_schema(
2656 input_schema: &Schema,
2657 group_by: &PhysicalGroupBy,
2658 aggr_expr: &[Arc<AggregateFunctionExpr>],
2659 mode: AggregateMode,
2660) -> Result<Schema> {
2661 let mut fields = Vec::with_capacity(group_by.num_output_exprs() + aggr_expr.len());
2662 fields.extend(group_by.output_fields(input_schema)?);
2663
2664 match mode.output_mode() {
2665 AggregateOutputMode::Final => {
2666 for expr in aggr_expr {
2668 fields.push(expr.field())
2669 }
2670 }
2671 AggregateOutputMode::Partial => {
2672 for expr in aggr_expr {
2674 fields.extend(expr.state_fields()?.iter().cloned());
2675 }
2676 }
2677 }
2678
2679 Ok(Schema::new_with_metadata(
2680 fields,
2681 input_schema.metadata().clone(),
2682 ))
2683}
2684
2685fn get_aggregate_expr_req(
2706 aggr_expr: &AggregateFunctionExpr,
2707 group_by: &PhysicalGroupBy,
2708 agg_mode: &AggregateMode,
2709 include_soft_requirement: bool,
2710) -> Option<LexOrdering> {
2711 if agg_mode.input_mode() == AggregateInputMode::Partial {
2715 return None;
2716 }
2717
2718 match aggr_expr.order_sensitivity() {
2719 AggregateOrderSensitivity::Insensitive => return None,
2720 AggregateOrderSensitivity::HardRequirement => {}
2721 AggregateOrderSensitivity::SoftRequirement => {
2722 if !include_soft_requirement {
2723 return None;
2724 }
2725 }
2726 AggregateOrderSensitivity::Beneficial => return None,
2727 }
2728
2729 let mut sort_exprs = aggr_expr.order_bys().to_vec();
2730 if group_by.is_single() {
2736 let physical_exprs = group_by.input_exprs();
2740 sort_exprs.retain(|sort_expr| {
2741 !physical_exprs_contains(&physical_exprs, &sort_expr.expr)
2742 });
2743 }
2744 LexOrdering::new(sort_exprs)
2745}
2746
2747pub fn concat_slices<T: Clone>(lhs: &[T], rhs: &[T]) -> Vec<T> {
2749 [lhs, rhs].concat()
2750}
2751
2752fn determine_finer(
2756 current: &Option<LexOrdering>,
2757 candidate: &LexOrdering,
2758) -> Option<bool> {
2759 if let Some(ordering) = current {
2760 candidate.partial_cmp(ordering).map(|cmp| cmp.is_gt())
2761 } else {
2762 Some(true)
2763 }
2764}
2765
2766pub fn get_finer_aggregate_exprs_requirement(
2787 aggr_exprs: &mut [Arc<AggregateFunctionExpr>],
2788 group_by: &PhysicalGroupBy,
2789 eq_properties: &EquivalenceProperties,
2790 agg_mode: &AggregateMode,
2791) -> Result<Vec<PhysicalSortRequirement>> {
2792 let mut requirement = None;
2793
2794 for include_soft_requirement in [false, true] {
2798 for aggr_expr in aggr_exprs.iter_mut() {
2799 let Some(aggr_req) = get_aggregate_expr_req(
2800 aggr_expr,
2801 group_by,
2802 agg_mode,
2803 include_soft_requirement,
2804 )
2805 .and_then(|o| eq_properties.normalize_sort_exprs(o)) else {
2806 continue;
2809 };
2810 let forward_finer = determine_finer(&requirement, &aggr_req);
2815 if let Some(finer) = forward_finer {
2816 if !finer {
2817 continue;
2818 } else if eq_properties.ordering_satisfy(aggr_req.clone())? {
2819 requirement = Some(aggr_req);
2820 continue;
2821 }
2822 }
2823 if let Some(reverse_aggr_expr) = aggr_expr.reverse_expr() {
2824 let Some(rev_aggr_req) = get_aggregate_expr_req(
2825 &reverse_aggr_expr,
2826 group_by,
2827 agg_mode,
2828 include_soft_requirement,
2829 )
2830 .and_then(|o| eq_properties.normalize_sort_exprs(o)) else {
2831 *aggr_expr = Arc::new(reverse_aggr_expr);
2834 continue;
2835 };
2836 if let Some(finer) = determine_finer(&requirement, &rev_aggr_req) {
2842 if !finer {
2843 *aggr_expr = Arc::new(reverse_aggr_expr);
2844 } else if eq_properties.ordering_satisfy(rev_aggr_req.clone())? {
2845 *aggr_expr = Arc::new(reverse_aggr_expr);
2846 requirement = Some(rev_aggr_req);
2847 } else {
2848 requirement = Some(aggr_req);
2849 }
2850 } else if forward_finer.is_some() {
2851 requirement = Some(aggr_req);
2852 } else {
2853 if !include_soft_requirement {
2858 return not_impl_err!(
2859 "Conflicting ordering requirements in aggregate functions is not supported"
2860 );
2861 }
2862 }
2863 }
2864 }
2865 }
2866
2867 Ok(requirement.map_or_else(Vec::new, |o| o.into_iter().map(Into::into).collect()))
2868}
2869
2870pub fn aggregate_expressions(
2876 aggr_expr: &[Arc<AggregateFunctionExpr>],
2877 mode: &AggregateMode,
2878 col_idx_base: usize,
2879) -> Result<Vec<Vec<Arc<dyn PhysicalExpr>>>> {
2880 match mode.input_mode() {
2881 AggregateInputMode::Raw => Ok(aggr_expr
2882 .iter()
2883 .map(|agg| {
2884 let mut result = agg.expressions();
2885 result.extend(agg.order_bys().iter().map(|item| Arc::clone(&item.expr)));
2889 result
2890 })
2891 .collect()),
2892 AggregateInputMode::Partial => {
2893 let mut col_idx_base = col_idx_base;
2895 aggr_expr
2896 .iter()
2897 .map(|agg| {
2898 let exprs = merge_expressions(col_idx_base, agg)?;
2899 col_idx_base += exprs.len();
2900 Ok(exprs)
2901 })
2902 .collect()
2903 }
2904 }
2905}
2906
2907fn merge_expressions(
2912 index_base: usize,
2913 expr: &AggregateFunctionExpr,
2914) -> Result<Vec<Arc<dyn PhysicalExpr>>> {
2915 expr.state_fields().map(|fields| {
2916 fields
2917 .iter()
2918 .enumerate()
2919 .map(|(idx, f)| Arc::new(Column::new(f.name(), index_base + idx)) as _)
2920 .collect()
2921 })
2922}
2923
2924pub type AccumulatorItem = Box<dyn Accumulator>;
2925
2926pub fn create_accumulators(
2927 aggr_expr: &[Arc<AggregateFunctionExpr>],
2928) -> Result<Vec<AccumulatorItem>> {
2929 aggr_expr
2930 .iter()
2931 .map(|expr| expr.create_accumulator())
2932 .collect()
2933}
2934
2935pub fn finalize_aggregation(
2938 accumulators: &mut [AccumulatorItem],
2939 mode: &AggregateMode,
2940) -> Result<Vec<ArrayRef>> {
2941 match mode.output_mode() {
2942 AggregateOutputMode::Final => {
2943 accumulators
2945 .iter_mut()
2946 .map(|accumulator| accumulator.evaluate().and_then(|v| v.to_array()))
2947 .collect()
2948 }
2949 AggregateOutputMode::Partial => {
2950 accumulators
2952 .iter_mut()
2953 .map(|accumulator| {
2954 accumulator.state().and_then(|e| {
2955 e.iter()
2956 .map(|v| v.to_array())
2957 .collect::<Result<Vec<ArrayRef>>>()
2958 })
2959 })
2960 .flatten_ok()
2961 .collect()
2962 }
2963 }
2964}
2965
2966pub fn evaluate_many(
2968 expr: &[Vec<Arc<dyn PhysicalExpr>>],
2969 batch: &RecordBatch,
2970) -> Result<Vec<Vec<ArrayRef>>> {
2971 expr.iter()
2972 .map(|expr| evaluate_expressions_to_arrays(expr, batch))
2973 .collect()
2974}
2975
2976fn evaluate_optional(
2977 expr: &[Option<Arc<dyn PhysicalExpr>>],
2978 batch: &RecordBatch,
2979) -> Result<Vec<Option<ArrayRef>>> {
2980 expr.iter()
2981 .map(|expr| {
2982 expr.as_ref()
2983 .map(|expr| {
2984 expr.evaluate(batch)
2985 .and_then(|v| v.into_array(batch.num_rows()))
2986 })
2987 .transpose()
2988 })
2989 .collect()
2990}
2991
2992pub(crate) fn group_id_array(
3008 group: &[bool],
3009 ordinal: usize,
3010 max_ordinal: usize,
3011 num_rows: usize,
3012) -> Result<ArrayRef> {
3013 let n = group.len();
3014 if n > 64 {
3015 return not_impl_err!(
3016 "Grouping sets with more than 64 columns are not supported"
3017 );
3018 }
3019 let ordinal_bits = usize::BITS as usize - max_ordinal.leading_zeros() as usize;
3020 let total_bits = n + ordinal_bits;
3021 if total_bits > 64 {
3022 return not_impl_err!(
3023 "Grouping sets with {n} columns and a maximum duplicate ordinal of \
3024 {max_ordinal} require {total_bits} bits, which exceeds 64"
3025 );
3026 }
3027 let semantic_id = group.iter().fold(0u64, |acc, &is_null| {
3028 (acc << 1) | if is_null { 1 } else { 0 }
3029 });
3030 let full_id = semantic_id | ((ordinal as u64) << n);
3031 if total_bits <= 8 {
3032 Ok(Arc::new(UInt8Array::from(vec![full_id as u8; num_rows])))
3033 } else if total_bits <= 16 {
3034 Ok(Arc::new(UInt16Array::from(vec![full_id as u16; num_rows])))
3035 } else if total_bits <= 32 {
3036 Ok(Arc::new(UInt32Array::from(vec![full_id as u32; num_rows])))
3037 } else {
3038 Ok(Arc::new(UInt64Array::from(vec![full_id; num_rows])))
3039 }
3040}
3041
3042pub(crate) fn max_duplicate_ordinal(groups: &[Vec<bool>]) -> usize {
3050 let mut counts: HashMap<&[bool], usize> = HashMap::new();
3051 for group in groups {
3052 *counts.entry(group).or_insert(0) += 1;
3053 }
3054 counts.into_values().max().unwrap_or(0).saturating_sub(1)
3055}
3056
3057pub fn evaluate_group_by(
3094 group_by: &PhysicalGroupBy,
3095 batch: &RecordBatch,
3096) -> Result<Vec<Vec<ArrayRef>>> {
3097 let max_ordinal = max_duplicate_ordinal(&group_by.groups);
3098 let mut ordinal_per_pattern: HashMap<&[bool], usize> = HashMap::new();
3099 let exprs = evaluate_expressions_to_arrays(
3100 group_by.expr.iter().map(|(expr, _)| expr),
3101 batch,
3102 )?;
3103 let null_exprs = evaluate_expressions_to_arrays(
3104 group_by.null_expr.iter().map(|(expr, _)| expr),
3105 batch,
3106 )?;
3107
3108 group_by
3109 .groups
3110 .iter()
3111 .map(|group| {
3112 let ordinal = ordinal_per_pattern.entry(group).or_insert(0);
3113 let current_ordinal = *ordinal;
3114 *ordinal += 1;
3115
3116 let mut group_values = Vec::with_capacity(group_by.num_group_exprs());
3117 group_values.extend(group.iter().enumerate().map(|(idx, is_null)| {
3118 if *is_null {
3119 Arc::clone(&null_exprs[idx])
3120 } else {
3121 Arc::clone(&exprs[idx])
3122 }
3123 }));
3124 if !group_by.is_single() {
3125 group_values.push(group_id_array(
3126 group,
3127 current_ordinal,
3128 max_ordinal,
3129 batch.num_rows(),
3130 )?);
3131 }
3132 Ok(group_values)
3133 })
3134 .collect()
3135}
3136
3137#[cfg(test)]
3138mod tests {
3139 use std::task::{Context, Poll};
3140
3141 use super::*;
3142 use crate::RecordBatchStream;
3143 use crate::coalesce_partitions::CoalescePartitionsExec;
3144 use crate::common;
3145 use crate::common::collect;
3146 use crate::empty::EmptyExec;
3147 use crate::execution_plan::Boundedness;
3148 use crate::expressions::col;
3149 use crate::filter::FilterExecBuilder;
3150 use crate::metrics::MetricValue;
3151 use crate::statistics::{StatisticsArgs, StatisticsContext};
3152 use crate::test::TestMemoryExec;
3153 use crate::test::assert_is_pending;
3154 use crate::test::exec::{
3155 BlockingExec, StatisticsExec, assert_strong_count_converges_to_zero,
3156 };
3157
3158 use arrow::array::{
3159 BooleanArray, DictionaryArray, Float32Array, Float64Array, Int32Array,
3160 Int64Array, StructArray, UInt32Array, UInt64Array,
3161 };
3162 use arrow::compute::{SortOptions, concat_batches};
3163 use arrow::datatypes::Int32Type;
3164 use datafusion_common::test_util::{batches_to_sort_string, batches_to_string};
3165 use datafusion_common::{DataFusionError, internal_err};
3166 use datafusion_execution::config::SessionConfig;
3167 use datafusion_execution::memory_pool::FairSpillPool;
3168 use datafusion_execution::runtime_env::RuntimeEnvBuilder;
3169 use datafusion_expr::function::{AccumulatorArgs, StateFieldsArgs};
3170 use datafusion_expr::{
3171 Accumulator, AggregateUDF, AggregateUDFImpl, EmitTo, GroupsAccumulator,
3172 Signature, Volatility,
3173 };
3174 use datafusion_functions_aggregate::approx_percentile_cont::approx_percentile_cont_udaf;
3175 use datafusion_functions_aggregate::array_agg::array_agg_udaf;
3176 use datafusion_functions_aggregate::average::avg_udaf;
3177 use datafusion_functions_aggregate::count::count_udaf;
3178 use datafusion_functions_aggregate::first_last::{first_value_udaf, last_value_udaf};
3179 use datafusion_functions_aggregate::median::median_udaf;
3180 use datafusion_functions_aggregate::min_max::min_udaf;
3181 use datafusion_functions_aggregate::sum::sum_udaf;
3182 use datafusion_physical_expr::Partitioning;
3183 use datafusion_physical_expr::PhysicalSortExpr;
3184 use datafusion_physical_expr::aggregate::AggregateExprBuilder;
3185 use datafusion_physical_expr::expressions::{Literal, NotExpr};
3186
3187 use crate::projection::ProjectionExec;
3188 use crate::repartition::RepartitionExec;
3189 use datafusion_physical_expr::projection::ProjectionExpr;
3190 use futures::{FutureExt, Stream, StreamExt};
3191 use insta::{allow_duplicates, assert_snapshot};
3192
3193 #[cfg(feature = "proto")]
3194 #[test]
3195 fn split_human_display_alias_ignores_mismatched_alias() {
3196 let encoded = encode_human_display_alias("sum(value)", "revenue");
3197
3198 assert_eq!(
3199 split_human_display_alias(&encoded, "other"),
3200 (encoded.as_str(), None)
3201 );
3202 }
3203
3204 #[cfg(feature = "proto")]
3205 #[test]
3206 fn split_human_display_alias_keeps_malformed_prefix_literal() {
3207 let display = format!("{HUMAN_DISPLAY_ALIAS_PREFIX}not-an-encoding");
3208
3209 assert_eq!(
3210 split_human_display_alias(&display, "agg"),
3211 (display.as_str(), None)
3212 );
3213 }
3214
3215 fn create_test_schema() -> Result<SchemaRef> {
3217 let a = Field::new("a", DataType::Int32, true);
3218 let b = Field::new("b", DataType::Int32, true);
3219 let c = Field::new("c", DataType::Int32, true);
3220 let d = Field::new("d", DataType::Int32, true);
3221 let e = Field::new("e", DataType::Int32, true);
3222 let schema = Arc::new(Schema::new(vec![a, b, c, d, e]));
3223
3224 Ok(schema)
3225 }
3226
3227 fn some_data() -> (Arc<Schema>, Vec<RecordBatch>) {
3229 let schema = Arc::new(Schema::new(vec![
3231 Field::new("a", DataType::UInt32, false),
3232 Field::new("b", DataType::Float64, false),
3233 ]));
3234
3235 (
3237 Arc::clone(&schema),
3238 vec![
3239 RecordBatch::try_new(
3240 Arc::clone(&schema),
3241 vec![
3242 Arc::new(UInt32Array::from(vec![2, 3, 4, 4])),
3243 Arc::new(Float64Array::from(vec![1.0, 2.0, 3.0, 4.0])),
3244 ],
3245 )
3246 .unwrap(),
3247 RecordBatch::try_new(
3248 schema,
3249 vec![
3250 Arc::new(UInt32Array::from(vec![2, 3, 3, 4])),
3251 Arc::new(Float64Array::from(vec![1.0, 2.0, 3.0, 4.0])),
3252 ],
3253 )
3254 .unwrap(),
3255 ],
3256 )
3257 }
3258
3259 fn some_data_v2() -> (Arc<Schema>, Vec<RecordBatch>) {
3261 let schema = Arc::new(Schema::new(vec![
3263 Field::new("a", DataType::UInt32, false),
3264 Field::new("b", DataType::Float64, false),
3265 ]));
3266
3267 (
3272 Arc::clone(&schema),
3273 vec![
3274 RecordBatch::try_new(
3275 Arc::clone(&schema),
3276 vec![
3277 Arc::new(UInt32Array::from(vec![2, 3, 4, 4])),
3278 Arc::new(Float64Array::from(vec![1.0, 2.0, 3.0, 4.0])),
3279 ],
3280 )
3281 .unwrap(),
3282 RecordBatch::try_new(
3283 Arc::clone(&schema),
3284 vec![
3285 Arc::new(UInt32Array::from(vec![2, 3, 3, 4])),
3286 Arc::new(Float64Array::from(vec![0.0, 1.0, 2.0, 3.0])),
3287 ],
3288 )
3289 .unwrap(),
3290 RecordBatch::try_new(
3291 Arc::clone(&schema),
3292 vec![
3293 Arc::new(UInt32Array::from(vec![2, 3, 3, 4])),
3294 Arc::new(Float64Array::from(vec![3.0, 4.0, 5.0, 6.0])),
3295 ],
3296 )
3297 .unwrap(),
3298 RecordBatch::try_new(
3299 schema,
3300 vec![
3301 Arc::new(UInt32Array::from(vec![2, 3, 3, 4])),
3302 Arc::new(Float64Array::from(vec![2.0, 3.0, 4.0, 5.0])),
3303 ],
3304 )
3305 .unwrap(),
3306 ],
3307 )
3308 }
3309
3310 fn new_spill_ctx(batch_size: usize, max_memory: usize) -> Arc<TaskContext> {
3311 let session_config = SessionConfig::new().with_batch_size(batch_size);
3312 let runtime = RuntimeEnvBuilder::new()
3313 .with_memory_pool(Arc::new(FairSpillPool::new(max_memory)))
3314 .build_arc()
3315 .unwrap();
3316 let task_ctx = TaskContext::default()
3317 .with_session_config(session_config)
3318 .with_runtime(runtime);
3319 Arc::new(task_ctx)
3320 }
3321
3322 fn migrated_hash_session_config(batch_size: usize) -> SessionConfig {
3323 SessionConfig::new()
3324 .with_batch_size(batch_size)
3325 .set_bool("datafusion.execution.enable_migration_aggregate", true)
3326 }
3327
3328 fn new_migrated_hash_ctx(batch_size: usize) -> Arc<TaskContext> {
3329 Arc::new(
3330 TaskContext::default()
3331 .with_session_config(migrated_hash_session_config(batch_size)),
3332 )
3333 }
3334
3335 fn new_finite_memory_migrated_hash_ctx(
3336 batch_size: usize,
3337 max_memory: usize,
3338 ) -> Result<Arc<TaskContext>> {
3339 let runtime = RuntimeEnvBuilder::default()
3340 .with_memory_limit(max_memory, 1.0)
3341 .build_arc()?;
3342
3343 Ok(Arc::new(
3344 TaskContext::default()
3345 .with_runtime(runtime)
3346 .with_session_config(migrated_hash_session_config(batch_size)),
3347 ))
3348 }
3349
3350 async fn check_grouping_sets(
3351 input: Arc<dyn ExecutionPlan>,
3352 spill: bool,
3353 ) -> Result<()> {
3354 let input_schema = input.schema();
3355
3356 let grouping_set = PhysicalGroupBy::new(
3357 vec![
3358 (col("a", &input_schema)?, "a".to_string()),
3359 (col("b", &input_schema)?, "b".to_string()),
3360 ],
3361 vec![
3362 (lit(ScalarValue::UInt32(None)), "a".to_string()),
3363 (lit(ScalarValue::Float64(None)), "b".to_string()),
3364 ],
3365 vec![
3366 vec![false, true], vec![true, false], vec![false, false], ],
3370 true,
3371 );
3372
3373 let aggregates = vec![Arc::new(
3374 AggregateExprBuilder::new(count_udaf(), vec![lit(1i8)])
3375 .schema(Arc::clone(&input_schema))
3376 .alias("COUNT(1)")
3377 .build()?,
3378 )];
3379
3380 let task_ctx = if spill {
3381 new_spill_ctx(4, 500)
3383 } else {
3384 Arc::new(TaskContext::default())
3385 };
3386
3387 let partial_aggregate = Arc::new(AggregateExec::try_new(
3388 AggregateMode::Partial,
3389 grouping_set.clone(),
3390 aggregates.clone(),
3391 vec![None],
3392 input,
3393 Arc::clone(&input_schema),
3394 )?);
3395
3396 let result =
3397 collect(partial_aggregate.execute(0, Arc::clone(&task_ctx))?).await?;
3398
3399 if spill {
3400 allow_duplicates! {
3403 assert_snapshot!(batches_to_sort_string(&result),
3404 @r"
3405 +---+-----+---------------+-----------------+
3406 | a | b | __grouping_id | COUNT(1)[count] |
3407 +---+-----+---------------+-----------------+
3408 | | 1.0 | 2 | 1 |
3409 | | 1.0 | 2 | 1 |
3410 | | 2.0 | 2 | 1 |
3411 | | 2.0 | 2 | 1 |
3412 | | 3.0 | 2 | 1 |
3413 | | 3.0 | 2 | 1 |
3414 | | 4.0 | 2 | 1 |
3415 | | 4.0 | 2 | 1 |
3416 | 2 | | 1 | 1 |
3417 | 2 | | 1 | 1 |
3418 | 2 | 1.0 | 0 | 1 |
3419 | 2 | 1.0 | 0 | 1 |
3420 | 3 | | 1 | 1 |
3421 | 3 | | 1 | 2 |
3422 | 3 | 2.0 | 0 | 2 |
3423 | 3 | 3.0 | 0 | 1 |
3424 | 4 | | 1 | 1 |
3425 | 4 | | 1 | 2 |
3426 | 4 | 3.0 | 0 | 1 |
3427 | 4 | 4.0 | 0 | 2 |
3428 +---+-----+---------------+-----------------+
3429 "
3430 );
3431 }
3432 } else {
3433 allow_duplicates! {
3434 assert_snapshot!(batches_to_sort_string(&result),
3435 @r"
3436 +---+-----+---------------+-----------------+
3437 | a | b | __grouping_id | COUNT(1)[count] |
3438 +---+-----+---------------+-----------------+
3439 | | 1.0 | 2 | 2 |
3440 | | 2.0 | 2 | 2 |
3441 | | 3.0 | 2 | 2 |
3442 | | 4.0 | 2 | 2 |
3443 | 2 | | 1 | 2 |
3444 | 2 | 1.0 | 0 | 2 |
3445 | 3 | | 1 | 3 |
3446 | 3 | 2.0 | 0 | 2 |
3447 | 3 | 3.0 | 0 | 1 |
3448 | 4 | | 1 | 3 |
3449 | 4 | 3.0 | 0 | 1 |
3450 | 4 | 4.0 | 0 | 2 |
3451 +---+-----+---------------+-----------------+
3452 "
3453 );
3454 }
3455 };
3456
3457 let merge = Arc::new(CoalescePartitionsExec::new(partial_aggregate));
3458
3459 let final_grouping_set = grouping_set.as_final();
3460
3461 let task_ctx = if spill {
3462 new_spill_ctx(4, 3160)
3463 } else {
3464 task_ctx
3465 };
3466
3467 let merged_aggregate = Arc::new(AggregateExec::try_new(
3468 AggregateMode::Final,
3469 final_grouping_set,
3470 aggregates,
3471 vec![None],
3472 merge,
3473 input_schema,
3474 )?);
3475
3476 let result = collect(merged_aggregate.execute(0, Arc::clone(&task_ctx))?).await?;
3477 let batch = concat_batches(&result[0].schema(), &result)?;
3478 assert_eq!(batch.num_columns(), 4);
3479 assert_eq!(batch.num_rows(), 12);
3480
3481 allow_duplicates! {
3482 assert_snapshot!(
3483 batches_to_sort_string(&result),
3484 @r"
3485 +---+-----+---------------+----------+
3486 | a | b | __grouping_id | COUNT(1) |
3487 +---+-----+---------------+----------+
3488 | | 1.0 | 2 | 2 |
3489 | | 2.0 | 2 | 2 |
3490 | | 3.0 | 2 | 2 |
3491 | | 4.0 | 2 | 2 |
3492 | 2 | | 1 | 2 |
3493 | 2 | 1.0 | 0 | 2 |
3494 | 3 | | 1 | 3 |
3495 | 3 | 2.0 | 0 | 2 |
3496 | 3 | 3.0 | 0 | 1 |
3497 | 4 | | 1 | 3 |
3498 | 4 | 3.0 | 0 | 1 |
3499 | 4 | 4.0 | 0 | 2 |
3500 +---+-----+---------------+----------+
3501 "
3502 );
3503 }
3504
3505 let metrics = merged_aggregate.metrics().unwrap();
3506 let output_rows = metrics.output_rows().unwrap();
3507 assert_eq!(12, output_rows);
3508
3509 Ok(())
3510 }
3511
3512 async fn check_aggregates(input: Arc<dyn ExecutionPlan>, spill: bool) -> Result<()> {
3514 let input_schema = input.schema();
3515
3516 let grouping_set = PhysicalGroupBy::new(
3517 vec![(col("a", &input_schema)?, "a".to_string())],
3518 vec![],
3519 vec![vec![false]],
3520 false,
3521 );
3522
3523 let aggregates: Vec<Arc<AggregateFunctionExpr>> = vec![Arc::new(
3524 AggregateExprBuilder::new(avg_udaf(), vec![col("b", &input_schema)?])
3525 .schema(Arc::clone(&input_schema))
3526 .alias("AVG(b)")
3527 .build()?,
3528 )];
3529
3530 let task_ctx = if spill {
3531 new_spill_ctx(2, 1600)
3533 } else {
3534 Arc::new(TaskContext::default())
3535 };
3536
3537 let partial_aggregate = Arc::new(AggregateExec::try_new(
3538 AggregateMode::Partial,
3539 grouping_set.clone(),
3540 aggregates.clone(),
3541 vec![None],
3542 input,
3543 Arc::clone(&input_schema),
3544 )?);
3545
3546 let result =
3547 collect(partial_aggregate.execute(0, Arc::clone(&task_ctx))?).await?;
3548
3549 if spill {
3550 allow_duplicates! {
3551 assert_snapshot!(batches_to_sort_string(&result), @r"
3552 +---+---------------+-------------+
3553 | a | AVG(b)[count] | AVG(b)[sum] |
3554 +---+---------------+-------------+
3555 | 2 | 1 | 1.0 |
3556 | 2 | 1 | 1.0 |
3557 | 3 | 1 | 2.0 |
3558 | 3 | 2 | 5.0 |
3559 | 4 | 1 | 4.0 |
3560 | 4 | 2 | 7.0 |
3561 +---+---------------+-------------+
3562 ");
3563 }
3564 } else {
3565 allow_duplicates! {
3566 assert_snapshot!(batches_to_sort_string(&result), @r"
3567 +---+---------------+-------------+
3568 | a | AVG(b)[count] | AVG(b)[sum] |
3569 +---+---------------+-------------+
3570 | 2 | 2 | 2.0 |
3571 | 3 | 3 | 7.0 |
3572 | 4 | 3 | 11.0 |
3573 +---+---------------+-------------+
3574 ");
3575 }
3576 };
3577
3578 let merge = Arc::new(CoalescePartitionsExec::new(partial_aggregate));
3579
3580 let final_grouping_set = grouping_set.as_final();
3581
3582 let merged_aggregate = Arc::new(AggregateExec::try_new(
3583 AggregateMode::Final,
3584 final_grouping_set,
3585 aggregates,
3586 vec![None],
3587 merge,
3588 input_schema,
3589 )?);
3590
3591 let final_stats = StatisticsContext::new()
3593 .compute(merged_aggregate.as_ref(), &StatisticsArgs::new())?;
3594 assert!(final_stats.total_byte_size.get_value().is_some());
3595
3596 let task_ctx = if spill {
3597 new_spill_ctx(2, 4640)
3599 } else {
3600 Arc::clone(&task_ctx)
3601 };
3602 let result = collect(merged_aggregate.execute(0, task_ctx)?).await?;
3603 let batch = concat_batches(&result[0].schema(), &result)?;
3604 assert_eq!(batch.num_columns(), 2);
3605 assert_eq!(batch.num_rows(), 3);
3606
3607 allow_duplicates! {
3608 assert_snapshot!(batches_to_sort_string(&result), @r"
3609 +---+--------------------+
3610 | a | AVG(b) |
3611 +---+--------------------+
3612 | 2 | 1.0 |
3613 | 3 | 2.3333333333333335 |
3614 | 4 | 3.6666666666666665 |
3615 +---+--------------------+
3616 ");
3617 }
3620
3621 let metrics = merged_aggregate.metrics().unwrap();
3622 let output_rows = metrics.output_rows().unwrap();
3623 let spill_count = metrics.spill_count().unwrap();
3624 let spilled_bytes = metrics.spilled_bytes().unwrap();
3625 let spilled_rows = metrics.spilled_rows().unwrap();
3626
3627 assert_eq!(3, output_rows);
3628 if spill {
3629 assert!(spill_count > 0);
3630 assert!(spilled_bytes > 0);
3631 assert!(spilled_rows > 0);
3632 } else {
3633 assert_eq!(0, spill_count);
3634 assert_eq!(0, spilled_bytes);
3635 assert_eq!(0, spilled_rows);
3636 }
3637
3638 Ok(())
3639 }
3640
3641 #[derive(Debug)]
3644 struct TestYieldingExec {
3645 pub yield_first: bool,
3647 cache: Arc<PlanProperties>,
3648 }
3649
3650 impl TestYieldingExec {
3651 fn new(yield_first: bool) -> Self {
3652 let schema = some_data().0;
3653 let cache = Self::compute_properties(schema);
3654 Self {
3655 yield_first,
3656 cache: Arc::new(cache),
3657 }
3658 }
3659
3660 fn compute_properties(schema: SchemaRef) -> PlanProperties {
3662 PlanProperties::new(
3663 EquivalenceProperties::new(schema),
3664 Partitioning::UnknownPartitioning(1),
3665 EmissionType::Incremental,
3666 Boundedness::Bounded,
3667 )
3668 }
3669 }
3670
3671 impl DisplayAs for TestYieldingExec {
3672 fn fmt_as(
3673 &self,
3674 t: DisplayFormatType,
3675 f: &mut std::fmt::Formatter,
3676 ) -> std::fmt::Result {
3677 match t {
3678 DisplayFormatType::Default | DisplayFormatType::Verbose => {
3679 write!(f, "TestYieldingExec")
3680 }
3681 DisplayFormatType::TreeRender => {
3682 write!(f, "")
3684 }
3685 }
3686 }
3687 }
3688
3689 impl ExecutionPlan for TestYieldingExec {
3690 fn name(&self) -> &'static str {
3691 "TestYieldingExec"
3692 }
3693
3694 fn properties(&self) -> &Arc<PlanProperties> {
3695 &self.cache
3696 }
3697
3698 fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
3699 vec![]
3700 }
3701
3702 fn replace_children(
3703 self: Arc<Self>,
3704 _: Vec<Arc<dyn ExecutionPlan>>,
3705 _: ReplaceChildrenOptions,
3706 ) -> Result<Arc<dyn ExecutionPlan>> {
3707 internal_err!("Children cannot be replaced in {self:?}")
3708 }
3709
3710 fn with_new_children(
3711 self: Arc<Self>,
3712 children: Vec<Arc<dyn ExecutionPlan>>,
3713 ) -> Result<Arc<dyn ExecutionPlan>> {
3714 self.replace_children(
3715 children,
3716 ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
3717 )
3718 }
3719
3720 fn apply_expressions(
3721 &self,
3722 _f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> Result<TreeNodeRecursion>,
3723 ) -> Result<TreeNodeRecursion> {
3724 Ok(TreeNodeRecursion::Continue)
3725 }
3726
3727 fn execute(
3728 &self,
3729 _partition: usize,
3730 _context: Arc<TaskContext>,
3731 ) -> Result<SendableRecordBatchStream> {
3732 let stream = if self.yield_first {
3733 TestYieldingStream::New
3734 } else {
3735 TestYieldingStream::Yielded
3736 };
3737
3738 Ok(Box::pin(stream))
3739 }
3740
3741 fn statistics_from_inputs(
3742 &self,
3743 _input_stats: &[Arc<Statistics>],
3744 args: &StatisticsArgs,
3745 ) -> Result<Arc<Statistics>> {
3746 if args.partition().is_some() {
3747 return Ok(Arc::new(Statistics::new_unknown(self.schema().as_ref())));
3748 }
3749 let (_, batches) = some_data();
3750 Ok(Arc::new(common::compute_record_batch_statistics(
3751 &[batches],
3752 &self.schema(),
3753 None,
3754 )))
3755 }
3756 }
3757
3758 enum TestYieldingStream {
3760 New,
3761 Yielded,
3762 ReturnedBatch1,
3763 ReturnedBatch2,
3764 }
3765
3766 impl Stream for TestYieldingStream {
3767 type Item = Result<RecordBatch>;
3768
3769 fn poll_next(
3770 mut self: std::pin::Pin<&mut Self>,
3771 cx: &mut Context<'_>,
3772 ) -> Poll<Option<Self::Item>> {
3773 match &*self {
3774 TestYieldingStream::New => {
3775 *(self.as_mut()) = TestYieldingStream::Yielded;
3776 cx.waker().wake_by_ref();
3777 Poll::Pending
3778 }
3779 TestYieldingStream::Yielded => {
3780 *(self.as_mut()) = TestYieldingStream::ReturnedBatch1;
3781 Poll::Ready(Some(Ok(some_data().1[0].clone())))
3782 }
3783 TestYieldingStream::ReturnedBatch1 => {
3784 *(self.as_mut()) = TestYieldingStream::ReturnedBatch2;
3785 Poll::Ready(Some(Ok(some_data().1[1].clone())))
3786 }
3787 TestYieldingStream::ReturnedBatch2 => Poll::Ready(None),
3788 }
3789 }
3790 }
3791
3792 impl RecordBatchStream for TestYieldingStream {
3793 fn schema(&self) -> SchemaRef {
3794 some_data().0
3795 }
3796 }
3797
3798 #[tokio::test]
3801 async fn aggregate_source_not_yielding() -> Result<()> {
3802 let input: Arc<dyn ExecutionPlan> = Arc::new(TestYieldingExec::new(false));
3803
3804 check_aggregates(input, false).await
3805 }
3806
3807 #[tokio::test]
3808 async fn aggregate_grouping_sets_source_not_yielding() -> Result<()> {
3809 let input: Arc<dyn ExecutionPlan> = Arc::new(TestYieldingExec::new(false));
3810
3811 check_grouping_sets(input, false).await
3812 }
3813
3814 #[tokio::test]
3815 async fn aggregate_source_with_yielding() -> Result<()> {
3816 let input: Arc<dyn ExecutionPlan> = Arc::new(TestYieldingExec::new(true));
3817
3818 check_aggregates(input, false).await
3819 }
3820
3821 #[tokio::test]
3822 async fn aggregate_grouping_sets_with_yielding() -> Result<()> {
3823 let input: Arc<dyn ExecutionPlan> = Arc::new(TestYieldingExec::new(true));
3824
3825 check_grouping_sets(input, false).await
3826 }
3827
3828 #[tokio::test]
3829 async fn aggregate_source_not_yielding_with_spill() -> Result<()> {
3830 let input: Arc<dyn ExecutionPlan> = Arc::new(TestYieldingExec::new(false));
3831
3832 check_aggregates(input, true).await
3833 }
3834
3835 #[tokio::test]
3836 async fn aggregate_grouping_sets_source_not_yielding_with_spill() -> Result<()> {
3837 let input: Arc<dyn ExecutionPlan> = Arc::new(TestYieldingExec::new(false));
3838
3839 check_grouping_sets(input, true).await
3840 }
3841
3842 #[tokio::test]
3843 async fn aggregate_source_with_yielding_with_spill() -> Result<()> {
3844 let input: Arc<dyn ExecutionPlan> = Arc::new(TestYieldingExec::new(true));
3845
3846 check_aggregates(input, true).await
3847 }
3848
3849 #[tokio::test]
3850 async fn aggregate_grouping_sets_with_yielding_with_spill() -> Result<()> {
3851 let input: Arc<dyn ExecutionPlan> = Arc::new(TestYieldingExec::new(true));
3852
3853 check_grouping_sets(input, true).await
3854 }
3855
3856 fn test_median_agg_expr(schema: SchemaRef) -> Result<AggregateFunctionExpr> {
3858 AggregateExprBuilder::new(median_udaf(), vec![col("a", &schema)?])
3859 .schema(schema)
3860 .alias("MEDIAN(a)")
3861 .build()
3862 }
3863
3864 #[tokio::test]
3865 async fn test_oom() -> Result<()> {
3866 let input: Arc<dyn ExecutionPlan> = Arc::new(TestYieldingExec::new(true));
3867 let input_schema = input.schema();
3868
3869 let runtime = RuntimeEnvBuilder::new()
3870 .with_memory_limit(1, 1.0)
3871 .build_arc()?;
3872 let task_ctx = TaskContext::default().with_runtime(runtime);
3873 let task_ctx = Arc::new(task_ctx);
3874
3875 let groups_none = PhysicalGroupBy::default();
3876 let groups_some = PhysicalGroupBy::new(
3877 vec![(col("a", &input_schema)?, "a".to_string())],
3878 vec![],
3879 vec![vec![false]],
3880 false,
3881 );
3882
3883 let aggregates_v0: Vec<Arc<AggregateFunctionExpr>> =
3885 vec![Arc::new(test_median_agg_expr(Arc::clone(&input_schema))?)];
3886
3887 let aggregates_v2: Vec<Arc<AggregateFunctionExpr>> = vec![Arc::new(
3889 AggregateExprBuilder::new(avg_udaf(), vec![col("b", &input_schema)?])
3890 .schema(Arc::clone(&input_schema))
3891 .alias("AVG(b)")
3892 .build()?,
3893 )];
3894
3895 for (version, groups, aggregates) in [
3896 (0, groups_none, aggregates_v0),
3897 (2, groups_some, aggregates_v2),
3898 ] {
3899 let n_aggr = aggregates.len();
3900 let partial_aggregate = Arc::new(AggregateExec::try_new(
3901 AggregateMode::Single,
3902 groups,
3903 aggregates,
3904 vec![None; n_aggr],
3905 Arc::clone(&input),
3906 Arc::clone(&input_schema),
3907 )?);
3908
3909 let stream = partial_aggregate.execute_typed(0, &task_ctx)?;
3910
3911 match version {
3913 0 => {
3914 assert!(matches!(stream, StreamType::AggregateStream(_)));
3915 }
3916 1 => {
3917 assert!(matches!(stream, StreamType::GroupedHash(_)));
3918 }
3919 2 => {
3920 assert!(matches!(stream, StreamType::SingleHash(_)));
3921 }
3922 _ => panic!("Unknown version: {version}"),
3923 }
3924
3925 let stream: SendableRecordBatchStream = stream.into();
3926 let err = collect(stream).await.unwrap_err();
3927
3928 let err = err.find_root();
3930 assert!(
3931 matches!(err, DataFusionError::ResourcesExhausted(_)),
3932 "Wrong error type: {err}",
3933 );
3934 }
3935
3936 Ok(())
3937 }
3938
3939 #[tokio::test]
3940 async fn partial_grouped_aggregate_uses_raw_partial_stream() -> Result<()> {
3941 let (schema, batches) = some_data();
3942 let input = TestMemoryExec::try_new_exec(&[batches], Arc::clone(&schema), None)?;
3943 let group_by =
3944 PhysicalGroupBy::new_single(vec![(col("a", &schema)?, "a".to_string())]);
3945 let udaf = Arc::new(AggregateUDF::from(InputTypeAssertingUdaf::new(
3946 vec![DataType::Float64],
3947 vec![DataType::Int32],
3948 DataType::Int64,
3949 )));
3950 let aggregates: Vec<Arc<AggregateFunctionExpr>> = vec![Arc::new(
3951 AggregateExprBuilder::new(udaf, vec![col("b", &schema)?])
3952 .schema(Arc::clone(&schema))
3953 .alias("input_type_asserting(b)")
3954 .build()?,
3955 )];
3956
3957 let partial_aggregate = Arc::new(AggregateExec::try_new(
3958 AggregateMode::Partial,
3959 group_by.clone(),
3960 aggregates.clone(),
3961 vec![None],
3962 input,
3963 Arc::clone(&schema),
3964 )?);
3965 let task_ctx = Arc::new(
3966 TaskContext::default().with_session_config(
3967 SessionConfig::new()
3968 .with_batch_size(2)
3969 .set_bool("datafusion.execution.enable_migration_aggregate", true),
3970 ),
3971 );
3972
3973 let partial_stream = partial_aggregate.execute_typed(0, &task_ctx)?;
3974 assert!(matches!(partial_stream, StreamType::PartialHash(_)));
3975
3976 let fallback_task_ctx = Arc::new(
3977 TaskContext::default().with_session_config(
3978 SessionConfig::new()
3979 .with_batch_size(2)
3980 .set_bool("datafusion.execution.enable_migration_aggregate", false),
3981 ),
3982 );
3983 let stream = partial_aggregate.execute_typed(0, &fallback_task_ctx)?;
3984 assert!(matches!(stream, StreamType::GroupedHash(_)));
3985
3986 let stream: SendableRecordBatchStream = partial_stream.into();
3987 let batches = collect(stream).await?;
3988 assert_eq!(
3989 batches
3990 .iter()
3991 .map(RecordBatch::num_rows)
3992 .collect::<Vec<_>>(),
3993 vec![2, 1]
3994 );
3995 assert_eq!(batches.iter().map(RecordBatch::num_rows).sum::<usize>(), 3);
3996
3997 let merge = Arc::new(CoalescePartitionsExec::new(partial_aggregate));
3998 let final_aggregate = AggregateExec::try_new(
3999 AggregateMode::Final,
4000 group_by.as_final(),
4001 aggregates,
4002 vec![None],
4003 merge,
4004 Arc::clone(&schema),
4005 )?;
4006
4007 let final_stream = final_aggregate.execute_typed(0, &task_ctx)?;
4008 assert!(matches!(final_stream, StreamType::FinalHash(_)));
4009
4010 let stream = final_aggregate.execute_typed(0, &fallback_task_ctx)?;
4011 assert!(matches!(stream, StreamType::GroupedHash(_)));
4012
4013 let stream: SendableRecordBatchStream = final_stream.into();
4014 let batches = collect(stream).await?;
4015 assert_eq!(
4016 batches
4017 .iter()
4018 .map(RecordBatch::num_rows)
4019 .collect::<Vec<_>>(),
4020 vec![2, 1]
4021 );
4022 assert_eq!(batches.iter().map(RecordBatch::num_rows).sum::<usize>(), 3);
4023
4024 Ok(())
4025 }
4026
4027 #[tokio::test]
4028 async fn partial_grouped_aggregate_materializes_before_slicing() -> Result<()> {
4029 let schema = Arc::new(Schema::new(vec![
4030 Field::new("key", DataType::Int32, false),
4031 Field::new("value", DataType::Int32, false),
4032 ]));
4033 let input_batches = vec![RecordBatch::try_new(
4034 Arc::clone(&schema),
4035 vec![
4036 Arc::new(Int32Array::from(vec![1, 2, 3])),
4037 Arc::new(Int32Array::from(vec![10, 20, 30])),
4038 ],
4039 )?];
4040 let input =
4041 TestMemoryExec::try_new_exec(&[input_batches], Arc::clone(&schema), None)?;
4042 let group_by =
4043 PhysicalGroupBy::new_single(vec![(col("key", &schema)?, "key".to_string())]);
4044 let udaf = Arc::new(AggregateUDF::from(NoFirstEmitUdaf::new()));
4045 let aggregates: Vec<Arc<AggregateFunctionExpr>> = vec![Arc::new(
4046 AggregateExprBuilder::new(udaf, vec![col("value", &schema)?])
4047 .schema(Arc::clone(&schema))
4048 .alias("no_first_emit(value)")
4049 .build()?,
4050 )];
4051 let aggregate = Arc::new(AggregateExec::try_new(
4052 AggregateMode::Partial,
4053 group_by,
4054 aggregates,
4055 vec![None],
4056 input,
4057 Arc::clone(&schema),
4058 )?);
4059 let task_ctx = Arc::new(
4060 TaskContext::default().with_session_config(
4061 SessionConfig::new()
4062 .with_batch_size(2)
4063 .set_bool("datafusion.execution.enable_migration_aggregate", true)
4064 .set(
4065 "datafusion.execution.skip_partial_aggregation_probe_ratio_threshold",
4066 &ScalarValue::Float64(Some(2.0)),
4067 ),
4068 ),
4069 );
4070
4071 let stream = aggregate.execute_typed(0, &task_ctx)?;
4072 assert!(matches!(stream, StreamType::PartialHash(_)));
4073
4074 let stream: SendableRecordBatchStream = stream.into();
4075 let batches = collect(stream).await?;
4076 assert_eq!(
4077 batches
4078 .iter()
4079 .map(RecordBatch::num_rows)
4080 .collect::<Vec<_>>(),
4081 vec![2, 1]
4082 );
4083 assert_eq!(batches.iter().map(RecordBatch::num_rows).sum::<usize>(), 3);
4084 assert_snapshot!(batches_to_sort_string(&batches), @r"
4085 +-----+-----------------------------+
4086 | key | no_first_emit(value)[count] |
4087 +-----+-----------------------------+
4088 | 1 | 1 |
4089 | 2 | 1 |
4090 | 3 | 1 |
4091 +-----+-----------------------------+
4092 ");
4093
4094 Ok(())
4095 }
4096
4097 #[tokio::test]
4098 async fn limited_distinct_aggregate_uses_migrated_hash_streams() -> Result<()> {
4099 let schema =
4100 Arc::new(Schema::new(vec![Field::new("a", DataType::UInt32, false)]));
4101 let input_batches = vec![
4102 RecordBatch::try_new(
4103 Arc::clone(&schema),
4104 vec![Arc::new(UInt32Array::from(vec![1, 2, 1]))],
4105 )?,
4106 RecordBatch::try_new(
4107 Arc::clone(&schema),
4108 vec![Arc::new(UInt32Array::from(vec![3, 4]))],
4109 )?,
4110 ];
4111 let group_by =
4112 PhysicalGroupBy::new_single(vec![(col("a", &schema)?, "a".to_string())]);
4113 let task_ctx = Arc::new(
4114 TaskContext::default().with_session_config(
4115 SessionConfig::new()
4116 .set_bool("datafusion.execution.enable_migration_aggregate", true),
4117 ),
4118 );
4119
4120 let partial_input = TestMemoryExec::try_new_exec(
4121 std::slice::from_ref(&input_batches),
4122 Arc::clone(&schema),
4123 None,
4124 )?;
4125 let partial_aggregate = Arc::new(
4126 AggregateExec::try_new(
4127 AggregateMode::Partial,
4128 group_by.clone(),
4129 vec![],
4130 vec![],
4131 partial_input,
4132 Arc::clone(&schema),
4133 )?
4134 .with_limit_options(Some(LimitOptions::new(2))),
4135 );
4136
4137 let partial_stream = partial_aggregate.execute_typed(0, &task_ctx)?;
4138 assert!(matches!(partial_stream, StreamType::PartialHash(_)));
4139 let stream: SendableRecordBatchStream = partial_stream.into();
4140 let partial_output = collect(stream).await?;
4141 assert_eq!(
4142 partial_output
4143 .iter()
4144 .map(RecordBatch::num_rows)
4145 .sum::<usize>(),
4146 2
4147 );
4148 assert_snapshot!(batches_to_sort_string(&partial_output), @r"
4149+---+
4150| a |
4151+---+
4152| 1 |
4153| 2 |
4154+---+
4155");
4156
4157 let final_input =
4158 TestMemoryExec::try_new_exec(&[input_batches], Arc::clone(&schema), None)?;
4159 let final_aggregate = Arc::new(
4160 AggregateExec::try_new(
4161 AggregateMode::Final,
4162 group_by.as_final(),
4163 vec![],
4164 vec![],
4165 final_input,
4166 Arc::clone(&schema),
4167 )?
4168 .with_limit_options(Some(LimitOptions::new(2))),
4169 );
4170
4171 let final_stream = final_aggregate.execute_typed(0, &task_ctx)?;
4172 assert!(matches!(final_stream, StreamType::FinalHash(_)));
4173 let stream: SendableRecordBatchStream = final_stream.into();
4174 let final_output = collect(stream).await?;
4175 assert_eq!(
4176 final_output
4177 .iter()
4178 .map(RecordBatch::num_rows)
4179 .sum::<usize>(),
4180 2
4181 );
4182 assert_snapshot!(batches_to_sort_string(&final_output), @r"
4183+---+
4184| a |
4185+---+
4186| 1 |
4187| 2 |
4188+---+
4189");
4190
4191 Ok(())
4192 }
4193
4194 fn single_test_aggregate() -> Result<AggregateExec> {
4195 let schema = Arc::new(Schema::new(vec![
4196 Field::new("a", DataType::UInt32, false),
4197 Field::new("b", DataType::Float64, false),
4198 ]));
4199 let input_batch = RecordBatch::try_new(
4200 Arc::clone(&schema),
4201 vec![
4202 Arc::new(UInt32Array::from(vec![1, 2, 1, 3])),
4203 Arc::new(Float64Array::from(vec![10.0, 20.0, 40.0, 30.0])),
4204 ],
4205 )?;
4206 let input = TestMemoryExec::try_new_exec(
4207 &[vec![input_batch]],
4208 Arc::clone(&schema),
4209 None,
4210 )?;
4211 let group_by =
4212 PhysicalGroupBy::new_single(vec![(col("a", &schema)?, "a".to_string())]);
4213 let aggregates: Vec<Arc<AggregateFunctionExpr>> = vec![Arc::new(
4214 AggregateExprBuilder::new(sum_udaf(), vec![col("b", &schema)?])
4215 .schema(Arc::clone(&schema))
4216 .alias("SUM(b)")
4217 .build()?,
4218 )];
4219
4220 AggregateExec::try_new(
4221 AggregateMode::Single,
4222 group_by,
4223 aggregates,
4224 vec![None],
4225 input,
4226 schema,
4227 )
4228 }
4229
4230 #[tokio::test]
4233 async fn single_aggregate_planning() -> Result<()> {
4234 let single = single_test_aggregate()?;
4235 let task_ctx = new_migrated_hash_ctx(2);
4236
4237 let stream = single.execute_typed(0, &task_ctx)?;
4238 assert!(matches!(stream, StreamType::SingleHash(_)));
4239 let stream: SendableRecordBatchStream = stream.into();
4240 let output = collect(stream).await?;
4241 assert_eq!(output.iter().map(RecordBatch::num_rows).sum::<usize>(), 3);
4242 assert_snapshot!(batches_to_sort_string(&output), @r"
4243+---+--------+
4244| a | SUM(b) |
4245+---+--------+
4246| 1 | 50.0 |
4247| 2 | 20.0 |
4248| 3 | 30.0 |
4249+---+--------+
4250");
4251
4252 Ok(())
4253 }
4254
4255 #[tokio::test]
4257 async fn single_aggregate_with_memory_limit_planning() -> Result<()> {
4258 let single = single_test_aggregate()?;
4259 let task_ctx = new_finite_memory_migrated_hash_ctx(2, 1024 * 1024)?;
4260
4261 let stream = single.execute_typed(0, &task_ctx)?;
4262 assert!(matches!(stream, StreamType::SingleHash(_)));
4263
4264 Ok(())
4265 }
4266
4267 fn partial_reduce_test_aggregate() -> Result<AggregateExec> {
4268 let schema = Arc::new(Schema::new(vec![
4269 Field::new("a", DataType::UInt32, false),
4270 Field::new("b", DataType::Float64, false),
4271 ]));
4272 let group_by =
4273 PhysicalGroupBy::new_single(vec![(col("a", &schema)?, "a".to_string())]);
4274 let aggregates: Vec<Arc<AggregateFunctionExpr>> = vec![Arc::new(
4275 AggregateExprBuilder::new(sum_udaf(), vec![col("b", &schema)?])
4276 .schema(Arc::clone(&schema))
4277 .alias("SUM(b)")
4278 .build()?,
4279 )];
4280
4281 let empty_input =
4282 TestMemoryExec::try_new_exec(&[vec![]], Arc::clone(&schema), None)?;
4283 let partial = AggregateExec::try_new(
4284 AggregateMode::Partial,
4285 group_by.clone(),
4286 aggregates.clone(),
4287 vec![None],
4288 empty_input,
4289 Arc::clone(&schema),
4290 )?;
4291 let partial_schema = partial.schema();
4292 let partial_state_batch = RecordBatch::try_new(
4293 Arc::clone(&partial_schema),
4294 vec![
4295 Arc::new(UInt32Array::from(vec![1, 2, 1, 3])),
4296 Arc::new(Float64Array::from(vec![10.0, 20.0, 40.0, 30.0])),
4297 ],
4298 )?;
4299 let partial_reduce_input = TestMemoryExec::try_new_exec(
4300 &[vec![partial_state_batch]],
4301 Arc::clone(&partial_schema),
4302 None,
4303 )?;
4304
4305 AggregateExec::try_new(
4306 AggregateMode::PartialReduce,
4307 group_by,
4308 aggregates,
4309 vec![None],
4310 partial_reduce_input,
4311 partial_schema,
4312 )
4313 }
4314
4315 #[tokio::test]
4318 async fn partial_reduce_aggregate_planning() -> Result<()> {
4319 let partial_reduce = partial_reduce_test_aggregate()?;
4320 let task_ctx = Arc::new(
4321 TaskContext::default().with_session_config(
4322 SessionConfig::new()
4323 .set_bool("datafusion.execution.enable_migration_aggregate", true),
4324 ),
4325 );
4326
4327 let stream = partial_reduce.execute_typed(0, &task_ctx)?;
4328 assert!(matches!(stream, StreamType::PartialReduceHash(_)));
4329 let stream: SendableRecordBatchStream = stream.into();
4330 let output = collect(stream).await?;
4331 assert_eq!(output.iter().map(RecordBatch::num_rows).sum::<usize>(), 3);
4332
4333 Ok(())
4334 }
4335
4336 #[tokio::test]
4339 async fn partial_reduce_aggregate_with_memory_limit_planning() -> Result<()> {
4340 let partial_reduce = partial_reduce_test_aggregate()?;
4341 let runtime = RuntimeEnvBuilder::new()
4342 .with_memory_limit(1, 1.0)
4343 .build_arc()?;
4344 let task_ctx =
4345 Arc::new(
4346 TaskContext::default()
4347 .with_session_config(SessionConfig::new().set_bool(
4348 "datafusion.execution.enable_migration_aggregate",
4349 true,
4350 ))
4351 .with_runtime(runtime),
4352 );
4353
4354 let stream = partial_reduce.execute_typed(0, &task_ctx)?;
4355 assert!(matches!(stream, StreamType::GroupedHash(_)));
4356
4357 Ok(())
4358 }
4359
4360 #[tokio::test]
4362 async fn ordered_partial_aggregate_planning() -> Result<()> {
4363 let schema = Arc::new(Schema::new(vec![
4364 Field::new("sort_col", DataType::Int32, false),
4365 Field::new("group_col", DataType::Int32, false),
4366 Field::new("value_col", DataType::Int64, false),
4367 ]));
4368
4369 let input_batches = vec![
4370 RecordBatch::try_new(
4371 Arc::clone(&schema),
4372 vec![
4373 Arc::new(Int32Array::from(vec![1, 1, 1])),
4374 Arc::new(Int32Array::from(vec![10, 11, 10])),
4375 Arc::new(Int64Array::from(vec![1, 1, 1])),
4376 ],
4377 )?,
4378 RecordBatch::try_new(
4379 Arc::clone(&schema),
4380 vec![
4381 Arc::new(Int32Array::from(vec![2, 2])),
4382 Arc::new(Int32Array::from(vec![20, 21])),
4383 Arc::new(Int64Array::from(vec![1, 1])),
4384 ],
4385 )?,
4386 ];
4387 let ordering = LexOrdering::new([PhysicalSortExpr::new_default(Arc::new(
4388 Column::new("sort_col", 0),
4389 ))])
4390 .unwrap();
4391 let input = TestMemoryExec::try_new(&[input_batches], Arc::clone(&schema), None)?
4392 .try_with_sort_information(vec![ordering])?;
4393 let input = Arc::new(TestMemoryExec::update_cache(&Arc::new(input)));
4394
4395 let group_by = PhysicalGroupBy::new_single(vec![
4396 (col("sort_col", &schema)?, "sort_col".to_string()),
4397 (col("group_col", &schema)?, "group_col".to_string()),
4398 ]);
4399 let aggr_expr = vec![Arc::new(
4400 AggregateExprBuilder::new(count_udaf(), vec![col("value_col", &schema)?])
4401 .schema(Arc::clone(&schema))
4402 .alias("COUNT(value_col)")
4403 .build()?,
4404 )];
4405 let aggregate = AggregateExec::try_new(
4406 AggregateMode::Partial,
4407 group_by,
4408 aggr_expr,
4409 vec![None],
4410 input,
4411 Arc::clone(&schema),
4412 )?;
4413 assert!(matches!(
4414 aggregate.input_order_mode(),
4415 InputOrderMode::PartiallySorted(_)
4416 ));
4417
4418 let task_ctx = new_migrated_hash_ctx(2);
4419 let stream = aggregate.execute_typed(0, &task_ctx)?;
4420 assert!(matches!(stream, StreamType::OrderedPartialAggregate(_)));
4421
4422 let stream: SendableRecordBatchStream = stream.into();
4423 let output = collect(stream).await?;
4424 assert_snapshot!(batches_to_sort_string(&output), @r"
4425+----------+-----------+-------------------------+
4426| sort_col | group_col | COUNT(value_col)[count] |
4427+----------+-----------+-------------------------+
4428| 1 | 10 | 2 |
4429| 1 | 11 | 1 |
4430| 2 | 20 | 1 |
4431| 2 | 21 | 1 |
4432+----------+-----------+-------------------------+
4433");
4434
4435 let finite_memory_task_ctx = new_finite_memory_migrated_hash_ctx(2, 1024 * 1024)?;
4437 let stream = aggregate.execute_typed(0, &finite_memory_task_ctx)?;
4438 assert!(matches!(stream, StreamType::OrderedPartialAggregate(_)));
4439
4440 Ok(())
4441 }
4442
4443 #[tokio::test]
4445 async fn ordered_final_aggregate_planning() -> Result<()> {
4446 let schema = Arc::new(Schema::new(vec![
4447 Field::new("key", DataType::Int32, false),
4448 Field::new("value", DataType::Int64, false),
4449 ]));
4450 let group_by =
4451 PhysicalGroupBy::new_single(vec![(col("key", &schema)?, "key".to_string())]);
4452 let aggr_expr = vec![Arc::new(
4453 AggregateExprBuilder::new(count_udaf(), vec![col("value", &schema)?])
4454 .schema(Arc::clone(&schema))
4455 .alias("COUNT(value)")
4456 .build()?,
4457 )];
4458
4459 let empty_input =
4460 TestMemoryExec::try_new_exec(&[vec![]], Arc::clone(&schema), None)?;
4461 let partial_aggregate = AggregateExec::try_new(
4462 AggregateMode::Partial,
4463 group_by.clone(),
4464 aggr_expr.clone(),
4465 vec![None],
4466 empty_input,
4467 Arc::clone(&schema),
4468 )?;
4469 let partial_schema = partial_aggregate.schema();
4470 let partial_state_batch = RecordBatch::try_new(
4471 Arc::clone(&partial_schema),
4472 vec![
4473 Arc::new(Int32Array::from(vec![1, 1, 2, 3])),
4474 Arc::new(Int64Array::from(vec![2, 3, 5, 7])),
4475 ],
4476 )?;
4477 let ordering = LexOrdering::new([PhysicalSortExpr::new_default(Arc::new(
4478 Column::new("key", 0),
4479 ))])
4480 .unwrap();
4481 let final_input =
4482 TestMemoryExec::try_new(&[vec![partial_state_batch]], partial_schema, None)?
4483 .try_with_sort_information(vec![ordering])?;
4484 let final_input = Arc::new(TestMemoryExec::update_cache(&Arc::new(final_input)));
4485
4486 let final_aggregate = AggregateExec::try_new(
4487 AggregateMode::Final,
4488 group_by.as_final(),
4489 aggr_expr,
4490 vec![None],
4491 final_input,
4492 Arc::clone(&schema),
4493 )?;
4494 assert_eq!(final_aggregate.input_order_mode(), &InputOrderMode::Sorted);
4495
4496 let task_ctx = new_migrated_hash_ctx(2);
4497 let stream = final_aggregate.execute_typed(0, &task_ctx)?;
4498 assert!(matches!(stream, StreamType::OrderedFinalAggregate(_)));
4499
4500 let stream: SendableRecordBatchStream = stream.into();
4501 let output = collect(stream).await?;
4502 assert_snapshot!(batches_to_sort_string(&output), @r"
4503+-----+--------------+
4504| key | COUNT(value) |
4505+-----+--------------+
4506| 1 | 5 |
4507| 2 | 5 |
4508| 3 | 7 |
4509+-----+--------------+
4510");
4511
4512 let finite_memory_task_ctx = new_finite_memory_migrated_hash_ctx(2, 1024 * 1024)?;
4514 let stream = final_aggregate.execute_typed(0, &finite_memory_task_ctx)?;
4515 assert!(matches!(stream, StreamType::OrderedFinalAggregate(_)));
4516
4517 Ok(())
4518 }
4519
4520 #[tokio::test]
4521 async fn ordered_partial_aggregate_partially_sorted_no_emit_panic() -> Result<()> {
4522 let schema = Arc::new(Schema::new(vec![
4525 Field::new("sort_col", DataType::Int32, false),
4526 Field::new("group_col", DataType::Int32, false),
4527 Field::new("value_col", DataType::Int64, false),
4528 ]));
4529
4530 let n = 256;
4533 let batch = RecordBatch::try_new(
4534 Arc::clone(&schema),
4535 vec![
4536 Arc::new(Int32Array::from(vec![1; n])),
4537 Arc::new(Int32Array::from((0..n as i32).collect::<Vec<_>>())),
4538 Arc::new(Int64Array::from(vec![1; n])),
4539 ],
4540 )?;
4541
4542 let ordering = LexOrdering::new([PhysicalSortExpr::new_default(Arc::new(
4543 Column::new("sort_col", 0),
4544 ))])
4545 .unwrap();
4546 let input = TestMemoryExec::try_new(&[vec![batch]], Arc::clone(&schema), None)?
4547 .try_with_sort_information(vec![ordering])?;
4548 let input = Arc::new(TestMemoryExec::update_cache(&Arc::new(input)));
4549
4550 let aggregate = AggregateExec::try_new(
4551 AggregateMode::Partial,
4552 PhysicalGroupBy::new_single(vec![
4553 (col("sort_col", &schema)?, "sort_col".to_string()),
4554 (col("group_col", &schema)?, "group_col".to_string()),
4555 ]),
4556 vec![Arc::new(
4557 AggregateExprBuilder::new(count_udaf(), vec![col("value_col", &schema)?])
4558 .schema(Arc::clone(&schema))
4559 .alias("count_value")
4560 .build()?,
4561 )],
4562 vec![None],
4563 input,
4564 Arc::clone(&schema),
4565 )?;
4566 assert!(matches!(
4567 aggregate.input_order_mode(),
4568 InputOrderMode::PartiallySorted(_)
4569 ));
4570
4571 let runtime = RuntimeEnvBuilder::default()
4572 .with_memory_limit(4096, 1.0)
4573 .build_arc()?;
4574 let session_config = SessionConfig::new().with_batch_size(128).set(
4575 "datafusion.execution.skip_partial_aggregation_probe_rows_threshold",
4576 &ScalarValue::UInt64(Some(u64::MAX)),
4577 );
4578 let task_ctx = Arc::new(
4579 TaskContext::default()
4580 .with_runtime(runtime)
4581 .with_session_config(session_config),
4582 );
4583
4584 let mut stream: SendableRecordBatchStream =
4585 OrderedPartialAggregateStream::new(&aggregate, &task_ctx, 0)?.into_stream();
4586
4587 while let Some(result) = stream.next().await {
4588 if let Err(e) = result {
4589 if e.to_string().contains("Resources exhausted") {
4590 break;
4591 }
4592 return Err(e);
4593 }
4594 }
4595
4596 Ok(())
4597 }
4598
4599 #[tokio::test]
4600 async fn test_drop_cancel_without_groups() -> Result<()> {
4601 let task_ctx = Arc::new(TaskContext::default());
4602 let schema =
4603 Arc::new(Schema::new(vec![Field::new("a", DataType::Float64, true)]));
4604
4605 let groups = PhysicalGroupBy::default();
4606
4607 let aggregates: Vec<Arc<AggregateFunctionExpr>> = vec![Arc::new(
4608 AggregateExprBuilder::new(avg_udaf(), vec![col("a", &schema)?])
4609 .schema(Arc::clone(&schema))
4610 .alias("AVG(a)")
4611 .build()?,
4612 )];
4613
4614 let blocking_exec = Arc::new(BlockingExec::new(Arc::clone(&schema), 1));
4615 let refs = blocking_exec.refs();
4616 let aggregate_exec = Arc::new(AggregateExec::try_new(
4617 AggregateMode::Partial,
4618 groups.clone(),
4619 aggregates.clone(),
4620 vec![None],
4621 blocking_exec,
4622 schema,
4623 )?);
4624
4625 let fut = crate::collect(aggregate_exec, task_ctx);
4626 let mut fut = fut.boxed();
4627
4628 assert_is_pending(&mut fut);
4629 drop(fut);
4630 assert_strong_count_converges_to_zero(refs).await;
4631
4632 Ok(())
4633 }
4634
4635 #[tokio::test]
4636 async fn test_drop_cancel_with_groups() -> Result<()> {
4637 let task_ctx = Arc::new(TaskContext::default());
4638 let schema = Arc::new(Schema::new(vec![
4639 Field::new("a", DataType::Float64, true),
4640 Field::new("b", DataType::Float64, true),
4641 ]));
4642
4643 let groups =
4644 PhysicalGroupBy::new_single(vec![(col("a", &schema)?, "a".to_string())]);
4645
4646 let aggregates: Vec<Arc<AggregateFunctionExpr>> = vec![Arc::new(
4647 AggregateExprBuilder::new(avg_udaf(), vec![col("b", &schema)?])
4648 .schema(Arc::clone(&schema))
4649 .alias("AVG(b)")
4650 .build()?,
4651 )];
4652
4653 let blocking_exec = Arc::new(BlockingExec::new(Arc::clone(&schema), 1));
4654 let refs = blocking_exec.refs();
4655 let aggregate_exec = Arc::new(AggregateExec::try_new(
4656 AggregateMode::Partial,
4657 groups,
4658 aggregates.clone(),
4659 vec![None],
4660 blocking_exec,
4661 schema,
4662 )?);
4663
4664 let fut = crate::collect(aggregate_exec, task_ctx);
4665 let mut fut = fut.boxed();
4666
4667 assert_is_pending(&mut fut);
4668 drop(fut);
4669 assert_strong_count_converges_to_zero(refs).await;
4670
4671 Ok(())
4672 }
4673
4674 #[tokio::test]
4675 async fn run_first_last_multi_partitions() -> Result<()> {
4676 for is_first_acc in [false, true] {
4677 for spill in [false, true] {
4678 first_last_multi_partitions(is_first_acc, spill, 5000).await?
4679 }
4680 }
4681 Ok(())
4682 }
4683
4684 fn test_first_value_agg_expr(
4686 schema: &Schema,
4687 sort_options: SortOptions,
4688 ) -> Result<Arc<AggregateFunctionExpr>> {
4689 let order_bys = vec![PhysicalSortExpr {
4690 expr: col("b", schema)?,
4691 options: sort_options,
4692 }];
4693 let args = [col("b", schema)?];
4694
4695 AggregateExprBuilder::new(first_value_udaf(), args.to_vec())
4696 .order_by(order_bys)
4697 .schema(Arc::new(schema.clone()))
4698 .alias(String::from("first_value(b) ORDER BY [b ASC NULLS LAST]"))
4699 .build()
4700 .map(Arc::new)
4701 }
4702
4703 fn test_last_value_agg_expr(
4705 schema: &Schema,
4706 sort_options: SortOptions,
4707 ) -> Result<Arc<AggregateFunctionExpr>> {
4708 let order_bys = vec![PhysicalSortExpr {
4709 expr: col("b", schema)?,
4710 options: sort_options,
4711 }];
4712 let args = [col("b", schema)?];
4713 AggregateExprBuilder::new(last_value_udaf(), args.to_vec())
4714 .order_by(order_bys)
4715 .schema(Arc::new(schema.clone()))
4716 .alias(String::from("last_value(b) ORDER BY [b ASC NULLS LAST]"))
4717 .build()
4718 .map(Arc::new)
4719 }
4720
4721 fn first_value_agg_expr(
4722 schema: &SchemaRef,
4723 column: &str,
4724 alias: &str,
4725 human_display: Option<&str>,
4726 human_display_alias: Option<&str>,
4727 ) -> Result<AggregateFunctionExpr> {
4728 let mut builder =
4729 AggregateExprBuilder::new(first_value_udaf(), vec![col(column, schema)?])
4730 .order_by(vec![PhysicalSortExpr {
4731 expr: col(column, schema)?,
4732 options: SortOptions::new(false, false),
4733 }])
4734 .schema(Arc::clone(schema))
4735 .alias(alias);
4736
4737 if let Some(human_display) = human_display {
4738 builder = builder.human_display(human_display);
4739 }
4740 if let Some(human_display_alias) = human_display_alias {
4741 builder = builder.human_display_alias(human_display_alias);
4742 }
4743
4744 builder.build()
4745 }
4746
4747 #[test]
4748 fn test_reverse_expr_preserves_aliased_human_display() -> Result<()> {
4749 let schema = create_test_schema()?;
4750 let agg = first_value_agg_expr(
4751 &schema,
4752 "b",
4753 "agg",
4754 Some("first_value(b) ORDER BY [b ASC NULLS LAST]"),
4755 Some("agg"),
4756 )?;
4757
4758 let reversed = agg.reverse_expr().expect("expected reverse expr");
4759
4760 assert_eq!(reversed.name(), "agg");
4761 assert_eq!(reversed.human_display_alias(), Some("agg"));
4762 assert_eq!(
4763 format_tree_aggregate_expr(&reversed),
4764 "last_value(b) ORDER BY [b DESC NULLS FIRST] as agg"
4765 );
4766 assert_eq!(
4767 reversed.human_display(),
4768 Some("last_value(b) ORDER BY [b DESC NULLS FIRST]")
4769 );
4770
4771 Ok(())
4772 }
4773
4774 #[test]
4775 fn test_reverse_expr_does_not_rewrite_column_names_in_human_display() -> Result<()> {
4776 let schema = Arc::new(Schema::new(vec![Field::new(
4777 "first_value_col",
4778 DataType::Int32,
4779 true,
4780 )]));
4781 let agg = first_value_agg_expr(
4782 &schema,
4783 "first_value_col",
4784 "agg",
4785 Some(
4786 "first_value(first_value_col) ORDER BY [first_value_col ASC NULLS LAST]",
4787 ),
4788 Some("agg"),
4789 )?;
4790
4791 let reversed = agg.reverse_expr().expect("expected reverse expr");
4792
4793 assert_eq!(reversed.name(), "agg");
4794 assert_eq!(
4795 reversed.human_display(),
4796 Some(
4797 "last_value(first_value_col) ORDER BY [first_value_col DESC NULLS FIRST]"
4798 )
4799 );
4800 assert_eq!(
4801 format_tree_aggregate_expr(&reversed),
4802 "last_value(first_value_col) ORDER BY [first_value_col DESC NULLS FIRST] as agg"
4803 );
4804
4805 Ok(())
4806 }
4807
4808 #[test]
4809 fn test_empty_human_display_is_treated_as_absent() -> Result<()> {
4810 let schema = create_test_schema()?;
4811 let agg = first_value_agg_expr(&schema, "b", "agg", Some(""), None)?;
4812
4813 assert_eq!(agg.human_display(), None);
4814 assert_eq!(format_tree_aggregate_expr(&agg), "agg");
4815
4816 Ok(())
4817 }
4818
4819 #[test]
4820 fn test_human_display_alias_must_match_name() -> Result<()> {
4821 let schema = create_test_schema()?;
4822 let error = first_value_agg_expr(
4823 &schema,
4824 "b",
4825 "agg",
4826 Some("first_value(b) ORDER BY [b ASC NULLS LAST]"),
4827 Some("other_alias"),
4828 )
4829 .unwrap_err();
4830
4831 assert!(
4832 error
4833 .to_string()
4834 .contains("aggregate human_display_alias must match")
4835 );
4836
4837 Ok(())
4838 }
4839
4840 #[test]
4841 fn test_reverse_expr_preserves_non_aliased_display_path() -> Result<()> {
4842 let schema = create_test_schema()?;
4843 let agg = first_value_agg_expr(
4844 &schema,
4845 "b",
4846 "first_value(b) ORDER BY [b ASC NULLS LAST]",
4847 None,
4848 None,
4849 )?;
4850
4851 let reversed = agg.reverse_expr().expect("expected reverse expr");
4852
4853 assert_eq!(
4854 reversed.name(),
4855 "last_value(b) ORDER BY [b DESC NULLS FIRST]"
4856 );
4857 assert_eq!(reversed.human_display(), None);
4858
4859 Ok(())
4860 }
4861
4862 async fn first_last_multi_partitions(
4872 is_first_acc: bool,
4873 spill: bool,
4874 max_memory: usize,
4875 ) -> Result<()> {
4876 let task_ctx = if spill {
4877 new_spill_ctx(2, max_memory)
4878 } else {
4879 Arc::new(TaskContext::default())
4880 };
4881
4882 let (schema, data) = some_data_v2();
4883 let partition1 = data[0].clone();
4884 let partition2 = data[1].clone();
4885 let partition3 = data[2].clone();
4886 let partition4 = data[3].clone();
4887
4888 let groups =
4889 PhysicalGroupBy::new_single(vec![(col("a", &schema)?, "a".to_string())]);
4890
4891 let sort_options = SortOptions {
4892 descending: false,
4893 nulls_first: false,
4894 };
4895 let aggregates: Vec<Arc<AggregateFunctionExpr>> = if is_first_acc {
4896 vec![test_first_value_agg_expr(&schema, sort_options)?]
4897 } else {
4898 vec![test_last_value_agg_expr(&schema, sort_options)?]
4899 };
4900
4901 let memory_exec = TestMemoryExec::try_new_exec(
4902 &[
4903 vec![partition1],
4904 vec![partition2],
4905 vec![partition3],
4906 vec![partition4],
4907 ],
4908 Arc::clone(&schema),
4909 None,
4910 )?;
4911 let aggregate_exec = Arc::new(AggregateExec::try_new(
4912 AggregateMode::Partial,
4913 groups.clone(),
4914 aggregates.clone(),
4915 vec![None],
4916 memory_exec,
4917 Arc::clone(&schema),
4918 )?);
4919 let coalesce = Arc::new(CoalescePartitionsExec::new(aggregate_exec))
4920 as Arc<dyn ExecutionPlan>;
4921 let aggregate_final = Arc::new(AggregateExec::try_new(
4922 AggregateMode::Final,
4923 groups,
4924 aggregates.clone(),
4925 vec![None],
4926 coalesce,
4927 schema,
4928 )?) as Arc<dyn ExecutionPlan>;
4929
4930 let result = crate::collect(aggregate_final, task_ctx).await?;
4931 if is_first_acc {
4932 allow_duplicates! {
4933 assert_snapshot!(batches_to_string(&result), @r"
4934 +---+--------------------------------------------+
4935 | a | first_value(b) ORDER BY [b ASC NULLS LAST] |
4936 +---+--------------------------------------------+
4937 | 2 | 0.0 |
4938 | 3 | 1.0 |
4939 | 4 | 3.0 |
4940 +---+--------------------------------------------+
4941 ");
4942 }
4943 } else {
4944 allow_duplicates! {
4945 assert_snapshot!(batches_to_string(&result), @r"
4946 +---+-------------------------------------------+
4947 | a | last_value(b) ORDER BY [b ASC NULLS LAST] |
4948 +---+-------------------------------------------+
4949 | 2 | 3.0 |
4950 | 3 | 5.0 |
4951 | 4 | 6.0 |
4952 +---+-------------------------------------------+
4953 ");
4954 }
4955 };
4956 Ok(())
4957 }
4958
4959 #[tokio::test]
4960 async fn test_get_finest_requirements() -> Result<()> {
4961 let test_schema = create_test_schema()?;
4962
4963 let options = SortOptions {
4964 descending: false,
4965 nulls_first: false,
4966 };
4967 let col_a = &col("a", &test_schema)?;
4968 let col_b = &col("b", &test_schema)?;
4969 let col_c = &col("c", &test_schema)?;
4970 let mut eq_properties = EquivalenceProperties::new(Arc::clone(&test_schema));
4971 eq_properties.add_equal_conditions(Arc::clone(col_a), Arc::clone(col_b))?;
4973 let order_by_exprs = vec![
4976 vec![],
4977 vec![PhysicalSortExpr {
4978 expr: Arc::clone(col_a),
4979 options,
4980 }],
4981 vec![
4982 PhysicalSortExpr {
4983 expr: Arc::clone(col_a),
4984 options,
4985 },
4986 PhysicalSortExpr {
4987 expr: Arc::clone(col_b),
4988 options,
4989 },
4990 PhysicalSortExpr {
4991 expr: Arc::clone(col_c),
4992 options,
4993 },
4994 ],
4995 vec![
4996 PhysicalSortExpr {
4997 expr: Arc::clone(col_a),
4998 options,
4999 },
5000 PhysicalSortExpr {
5001 expr: Arc::clone(col_b),
5002 options,
5003 },
5004 ],
5005 ];
5006
5007 let common_requirement = vec![
5008 PhysicalSortRequirement::new(Arc::clone(col_a), Some(options)),
5009 PhysicalSortRequirement::new(Arc::clone(col_c), Some(options)),
5010 ];
5011 let mut aggr_exprs = order_by_exprs
5012 .into_iter()
5013 .map(|order_by_expr| {
5014 AggregateExprBuilder::new(array_agg_udaf(), vec![Arc::clone(col_a)])
5015 .alias("a")
5016 .order_by(order_by_expr)
5017 .schema(Arc::clone(&test_schema))
5018 .build()
5019 .map(Arc::new)
5020 .unwrap()
5021 })
5022 .collect::<Vec<_>>();
5023 let group_by = PhysicalGroupBy::new_single(vec![]);
5024 let result = get_finer_aggregate_exprs_requirement(
5025 &mut aggr_exprs,
5026 &group_by,
5027 &eq_properties,
5028 &AggregateMode::Partial,
5029 )?;
5030 assert_eq!(result, common_requirement);
5031 Ok(())
5032 }
5033
5034 #[test]
5035 fn test_agg_exec_same_schema() -> Result<()> {
5036 let schema = Arc::new(Schema::new(vec![
5037 Field::new("a", DataType::Float32, true),
5038 Field::new("b", DataType::Float32, true),
5039 ]));
5040
5041 let col_a = col("a", &schema)?;
5042 let option_desc = SortOptions {
5043 descending: true,
5044 nulls_first: true,
5045 };
5046 let groups = PhysicalGroupBy::new_single(vec![(col_a, "a".to_string())]);
5047
5048 let aggregates: Vec<Arc<AggregateFunctionExpr>> = vec![
5049 test_first_value_agg_expr(&schema, option_desc)?,
5050 test_last_value_agg_expr(&schema, option_desc)?,
5051 ];
5052 let blocking_exec = Arc::new(BlockingExec::new(Arc::clone(&schema), 1));
5053 let aggregate_exec = Arc::new(AggregateExec::try_new(
5054 AggregateMode::Partial,
5055 groups,
5056 aggregates,
5057 vec![None, None],
5058 Arc::clone(&blocking_exec) as Arc<dyn ExecutionPlan>,
5059 schema,
5060 )?);
5061 let new_agg = Arc::clone(&aggregate_exec).replace_children(
5062 vec![blocking_exec],
5063 ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
5064 )?;
5065 assert_eq!(new_agg.schema(), aggregate_exec.schema());
5066 Ok(())
5067 }
5068
5069 #[tokio::test]
5070 async fn test_agg_exec_group_by_const() -> Result<()> {
5071 let schema = Arc::new(Schema::new(vec![
5072 Field::new("a", DataType::Float32, true),
5073 Field::new("b", DataType::Float32, true),
5074 Field::new("const", DataType::Int32, false),
5075 ]));
5076
5077 let col_a = col("a", &schema)?;
5078 let col_b = col("b", &schema)?;
5079 let const_expr = Arc::new(Literal::new(ScalarValue::Int32(Some(1))));
5080
5081 let groups = PhysicalGroupBy::new(
5082 vec![
5083 (col_a, "a".to_string()),
5084 (col_b, "b".to_string()),
5085 (const_expr, "const".to_string()),
5086 ],
5087 vec![
5088 (
5089 Arc::new(Literal::new(ScalarValue::Float32(None))),
5090 "a".to_string(),
5091 ),
5092 (
5093 Arc::new(Literal::new(ScalarValue::Float32(None))),
5094 "b".to_string(),
5095 ),
5096 (
5097 Arc::new(Literal::new(ScalarValue::Int32(None))),
5098 "const".to_string(),
5099 ),
5100 ],
5101 vec![
5102 vec![false, true, true],
5103 vec![true, false, true],
5104 vec![true, true, false],
5105 ],
5106 true,
5107 );
5108
5109 let aggregates: Vec<Arc<AggregateFunctionExpr>> = vec![
5110 AggregateExprBuilder::new(count_udaf(), vec![lit(1)])
5111 .schema(Arc::clone(&schema))
5112 .alias("1")
5113 .build()
5114 .map(Arc::new)?,
5115 ];
5116
5117 let input_batches = (0..4)
5118 .map(|_| {
5119 let a = Arc::new(Float32Array::from(vec![0.; 8192]));
5120 let b = Arc::new(Float32Array::from(vec![0.; 8192]));
5121 let c = Arc::new(Int32Array::from(vec![1; 8192]));
5122
5123 RecordBatch::try_new(Arc::clone(&schema), vec![a, b, c]).unwrap()
5124 })
5125 .collect();
5126
5127 let input =
5128 TestMemoryExec::try_new_exec(&[input_batches], Arc::clone(&schema), None)?;
5129
5130 let aggregate_exec = Arc::new(AggregateExec::try_new(
5131 AggregateMode::Single,
5132 groups,
5133 aggregates.clone(),
5134 vec![None],
5135 input,
5136 schema,
5137 )?);
5138
5139 let output =
5140 collect(aggregate_exec.execute(0, Arc::new(TaskContext::default()))?).await?;
5141
5142 allow_duplicates! {
5143 assert_snapshot!(batches_to_sort_string(&output), @r"
5144 +-----+-----+-------+---------------+-------+
5145 | a | b | const | __grouping_id | 1 |
5146 +-----+-----+-------+---------------+-------+
5147 | | | 1 | 6 | 32768 |
5148 | | 0.0 | | 5 | 32768 |
5149 | 0.0 | | | 3 | 32768 |
5150 +-----+-----+-------+---------------+-------+
5151 ");
5152 }
5153
5154 Ok(())
5155 }
5156
5157 #[tokio::test]
5158 async fn test_agg_exec_struct_of_dicts() -> Result<()> {
5159 let batch = RecordBatch::try_new(
5160 Arc::new(Schema::new(vec![
5161 Field::new(
5162 "labels".to_string(),
5163 DataType::Struct(
5164 vec![
5165 Field::new(
5166 "a".to_string(),
5167 DataType::Dictionary(
5168 Box::new(DataType::Int32),
5169 Box::new(DataType::Utf8),
5170 ),
5171 true,
5172 ),
5173 Field::new(
5174 "b".to_string(),
5175 DataType::Dictionary(
5176 Box::new(DataType::Int32),
5177 Box::new(DataType::Utf8),
5178 ),
5179 true,
5180 ),
5181 ]
5182 .into(),
5183 ),
5184 false,
5185 ),
5186 Field::new("value", DataType::UInt64, false),
5187 ])),
5188 vec![
5189 Arc::new(StructArray::from(vec![
5190 (
5191 Arc::new(Field::new(
5192 "a".to_string(),
5193 DataType::Dictionary(
5194 Box::new(DataType::Int32),
5195 Box::new(DataType::Utf8),
5196 ),
5197 true,
5198 )),
5199 Arc::new(
5200 vec![Some("a"), None, Some("a")]
5201 .into_iter()
5202 .collect::<DictionaryArray<Int32Type>>(),
5203 ) as ArrayRef,
5204 ),
5205 (
5206 Arc::new(Field::new(
5207 "b".to_string(),
5208 DataType::Dictionary(
5209 Box::new(DataType::Int32),
5210 Box::new(DataType::Utf8),
5211 ),
5212 true,
5213 )),
5214 Arc::new(
5215 vec![Some("b"), Some("c"), Some("b")]
5216 .into_iter()
5217 .collect::<DictionaryArray<Int32Type>>(),
5218 ) as ArrayRef,
5219 ),
5220 ])),
5221 Arc::new(UInt64Array::from(vec![1, 1, 1])),
5222 ],
5223 )
5224 .expect("Failed to create RecordBatch");
5225
5226 let group_by = PhysicalGroupBy::new_single(vec![(
5227 col("labels", &batch.schema())?,
5228 "labels".to_string(),
5229 )]);
5230
5231 let aggr_expr = vec![
5232 AggregateExprBuilder::new(sum_udaf(), vec![col("value", &batch.schema())?])
5233 .schema(Arc::clone(&batch.schema()))
5234 .alias(String::from("SUM(value)"))
5235 .build()
5236 .map(Arc::new)?,
5237 ];
5238
5239 let input = TestMemoryExec::try_new_exec(
5240 &[vec![batch.clone()]],
5241 Arc::<Schema>::clone(&batch.schema()),
5242 None,
5243 )?;
5244 let aggregate_exec = Arc::new(AggregateExec::try_new(
5245 AggregateMode::FinalPartitioned,
5246 group_by,
5247 aggr_expr,
5248 vec![None],
5249 Arc::clone(&input) as Arc<dyn ExecutionPlan>,
5250 batch.schema(),
5251 )?);
5252
5253 let session_config = SessionConfig::default();
5254 let ctx = TaskContext::default().with_session_config(session_config);
5255 let output = collect(aggregate_exec.execute(0, Arc::new(ctx))?).await?;
5256
5257 allow_duplicates! {
5258 assert_snapshot!(batches_to_string(&output), @r"
5259 +--------------+------------+
5260 | labels | SUM(value) |
5261 +--------------+------------+
5262 | {a: a, b: b} | 2 |
5263 | {a: , b: c} | 1 |
5264 +--------------+------------+
5265 ");
5266 }
5267
5268 Ok(())
5269 }
5270
5271 #[tokio::test]
5274 async fn test_skip_aggregation_after_first_batch() -> Result<()> {
5275 let schema = Arc::new(Schema::new(vec![
5276 Field::new("key", DataType::Int32, true),
5277 Field::new("val", DataType::Int32, true),
5278 ]));
5279
5280 let group_by =
5281 PhysicalGroupBy::new_single(vec![(col("key", &schema)?, "key".to_string())]);
5282
5283 let aggr_expr = vec![
5284 AggregateExprBuilder::new(count_udaf(), vec![col("val", &schema)?])
5285 .schema(Arc::clone(&schema))
5286 .alias(String::from("COUNT(val)"))
5287 .build()
5288 .map(Arc::new)?,
5289 ];
5290
5291 let input_data = vec![
5292 RecordBatch::try_new(
5293 Arc::clone(&schema),
5294 vec![
5295 Arc::new(Int32Array::from(vec![1, 2, 3])),
5296 Arc::new(Int32Array::from(vec![0, 0, 0])),
5297 ],
5298 )
5299 .unwrap(),
5300 RecordBatch::try_new(
5301 Arc::clone(&schema),
5302 vec![
5303 Arc::new(Int32Array::from(vec![2, 3, 4])),
5304 Arc::new(Int32Array::from(vec![0, 0, 0])),
5305 ],
5306 )
5307 .unwrap(),
5308 ];
5309
5310 let input =
5311 TestMemoryExec::try_new_exec(&[input_data], Arc::clone(&schema), None)?;
5312 let aggregate_exec = Arc::new(AggregateExec::try_new(
5313 AggregateMode::Partial,
5314 group_by,
5315 aggr_expr,
5316 vec![None],
5317 Arc::clone(&input) as Arc<dyn ExecutionPlan>,
5318 schema,
5319 )?);
5320
5321 let mut session_config = SessionConfig::default();
5322 session_config = session_config.set(
5323 "datafusion.execution.skip_partial_aggregation_probe_rows_threshold",
5324 &ScalarValue::Int64(Some(2)),
5325 );
5326 session_config = session_config.set(
5327 "datafusion.execution.skip_partial_aggregation_probe_ratio_threshold",
5328 &ScalarValue::Float64(Some(0.1)),
5329 );
5330
5331 let ctx = Arc::new(TaskContext::default().with_session_config(session_config));
5332 let stream: SendableRecordBatchStream = Box::pin(
5333 GroupedHashAggregateStream::new(aggregate_exec.as_ref(), &ctx, 0)?,
5334 );
5335 let output = collect(stream).await?;
5336
5337 allow_duplicates! {
5338 assert_snapshot!(batches_to_string(&output), @r"
5339 +-----+-------------------+
5340 | key | COUNT(val)[count] |
5341 +-----+-------------------+
5342 | 1 | 1 |
5343 | 2 | 1 |
5344 | 3 | 1 |
5345 | 2 | 1 |
5346 | 3 | 1 |
5347 | 4 | 1 |
5348 +-----+-------------------+
5349 ");
5350 }
5351
5352 Ok(())
5353 }
5354
5355 #[tokio::test]
5358 async fn test_skip_aggregation_after_threshold() -> Result<()> {
5359 let schema = Arc::new(Schema::new(vec![
5360 Field::new("key", DataType::Int32, true),
5361 Field::new("val", DataType::Int32, true),
5362 ]));
5363
5364 let group_by =
5365 PhysicalGroupBy::new_single(vec![(col("key", &schema)?, "key".to_string())]);
5366
5367 let aggr_expr = vec![
5368 AggregateExprBuilder::new(count_udaf(), vec![col("val", &schema)?])
5369 .schema(Arc::clone(&schema))
5370 .alias(String::from("COUNT(val)"))
5371 .build()
5372 .map(Arc::new)?,
5373 ];
5374
5375 let input_data = vec![
5376 RecordBatch::try_new(
5377 Arc::clone(&schema),
5378 vec![
5379 Arc::new(Int32Array::from(vec![1, 2, 3])),
5380 Arc::new(Int32Array::from(vec![0, 0, 0])),
5381 ],
5382 )
5383 .unwrap(),
5384 RecordBatch::try_new(
5385 Arc::clone(&schema),
5386 vec![
5387 Arc::new(Int32Array::from(vec![2, 3, 4])),
5388 Arc::new(Int32Array::from(vec![0, 0, 0])),
5389 ],
5390 )
5391 .unwrap(),
5392 RecordBatch::try_new(
5393 Arc::clone(&schema),
5394 vec![
5395 Arc::new(Int32Array::from(vec![2, 3, 4])),
5396 Arc::new(Int32Array::from(vec![0, 0, 0])),
5397 ],
5398 )
5399 .unwrap(),
5400 ];
5401
5402 let input =
5403 TestMemoryExec::try_new_exec(&[input_data], Arc::clone(&schema), None)?;
5404 let aggregate_exec = Arc::new(AggregateExec::try_new(
5405 AggregateMode::Partial,
5406 group_by,
5407 aggr_expr,
5408 vec![None],
5409 Arc::clone(&input) as Arc<dyn ExecutionPlan>,
5410 schema,
5411 )?);
5412
5413 let mut session_config = SessionConfig::default();
5414 session_config = session_config.set(
5415 "datafusion.execution.skip_partial_aggregation_probe_rows_threshold",
5416 &ScalarValue::Int64(Some(5)),
5417 );
5418 session_config = session_config.set(
5419 "datafusion.execution.skip_partial_aggregation_probe_ratio_threshold",
5420 &ScalarValue::Float64(Some(0.1)),
5421 );
5422
5423 let ctx = Arc::new(TaskContext::default().with_session_config(session_config));
5424 let stream: SendableRecordBatchStream = Box::pin(
5425 GroupedHashAggregateStream::new(aggregate_exec.as_ref(), &ctx, 0)?,
5426 );
5427 let output = collect(stream).await?;
5428
5429 allow_duplicates! {
5430 assert_snapshot!(batches_to_string(&output), @r"
5431 +-----+-------------------+
5432 | key | COUNT(val)[count] |
5433 +-----+-------------------+
5434 | 1 | 1 |
5435 | 2 | 2 |
5436 | 3 | 2 |
5437 | 4 | 1 |
5438 | 2 | 1 |
5439 | 3 | 1 |
5440 | 4 | 1 |
5441 +-----+-------------------+
5442 ");
5443 }
5444
5445 Ok(())
5446 }
5447
5448 #[tokio::test]
5449 async fn test_partial_hash_stream_skip_aggregation_after_first_batch() -> Result<()> {
5450 let schema = Arc::new(Schema::new(vec![
5451 Field::new("key", DataType::Int32, true),
5452 Field::new("val", DataType::Int32, true),
5453 ]));
5454
5455 let group_by =
5456 PhysicalGroupBy::new_single(vec![(col("key", &schema)?, "key".to_string())]);
5457
5458 let aggr_expr = vec![
5459 AggregateExprBuilder::new(count_udaf(), vec![col("val", &schema)?])
5460 .schema(Arc::clone(&schema))
5461 .alias(String::from("COUNT(val)"))
5462 .build()
5463 .map(Arc::new)?,
5464 ];
5465
5466 let input_data = vec![
5467 RecordBatch::try_new(
5468 Arc::clone(&schema),
5469 vec![
5470 Arc::new(Int32Array::from(vec![1, 2, 3])),
5471 Arc::new(Int32Array::from(vec![0, 0, 0])),
5472 ],
5473 )
5474 .unwrap(),
5475 RecordBatch::try_new(
5476 Arc::clone(&schema),
5477 vec![
5478 Arc::new(Int32Array::from(vec![2, 3, 4])),
5479 Arc::new(Int32Array::from(vec![0, 0, 0])),
5480 ],
5481 )
5482 .unwrap(),
5483 ];
5484
5485 let input =
5486 TestMemoryExec::try_new_exec(&[input_data], Arc::clone(&schema), None)?;
5487 let aggregate_exec = Arc::new(AggregateExec::try_new(
5488 AggregateMode::Partial,
5489 group_by,
5490 aggr_expr,
5491 vec![None],
5492 Arc::clone(&input) as Arc<dyn ExecutionPlan>,
5493 schema,
5494 )?);
5495
5496 let session_config = SessionConfig::default()
5497 .set_bool("datafusion.execution.enable_migration_aggregate", true)
5498 .set(
5499 "datafusion.execution.skip_partial_aggregation_probe_rows_threshold",
5500 &ScalarValue::Int64(Some(2)),
5501 )
5502 .set(
5503 "datafusion.execution.skip_partial_aggregation_probe_ratio_threshold",
5504 &ScalarValue::Float64(Some(0.1)),
5505 );
5506
5507 let ctx = Arc::new(TaskContext::default().with_session_config(session_config));
5508 let output = collect(aggregate_exec.execute(0, Arc::clone(&ctx))?).await?;
5509
5510 allow_duplicates! {
5511 assert_snapshot!(batches_to_sort_string(&output), @r"
5512 +-----+-------------------+
5513 | key | COUNT(val)[count] |
5514 +-----+-------------------+
5515 | 1 | 1 |
5516 | 2 | 1 |
5517 | 2 | 1 |
5518 | 3 | 1 |
5519 | 3 | 1 |
5520 | 4 | 1 |
5521 +-----+-------------------+
5522 ");
5523 }
5524
5525 let metrics = aggregate_exec.metrics().unwrap();
5526 let skipped_rows = metrics
5527 .sum_by_name("skipped_aggregation_rows")
5528 .map(|m| m.as_usize())
5529 .unwrap_or(0);
5530 assert_eq!(skipped_rows, 3);
5531
5532 Ok(())
5533 }
5534
5535 #[tokio::test]
5536 async fn test_partial_hash_stream_skip_aggregation_after_threshold() -> Result<()> {
5537 let schema = Arc::new(Schema::new(vec![
5538 Field::new("key", DataType::Int32, true),
5539 Field::new("val", DataType::Int32, true),
5540 ]));
5541
5542 let group_by =
5543 PhysicalGroupBy::new_single(vec![(col("key", &schema)?, "key".to_string())]);
5544
5545 let aggr_expr = vec![
5546 AggregateExprBuilder::new(count_udaf(), vec![col("val", &schema)?])
5547 .schema(Arc::clone(&schema))
5548 .alias(String::from("COUNT(val)"))
5549 .build()
5550 .map(Arc::new)?,
5551 ];
5552
5553 let input_data = vec![
5554 RecordBatch::try_new(
5555 Arc::clone(&schema),
5556 vec![
5557 Arc::new(Int32Array::from(vec![1, 2, 3])),
5558 Arc::new(Int32Array::from(vec![0, 0, 0])),
5559 ],
5560 )
5561 .unwrap(),
5562 RecordBatch::try_new(
5563 Arc::clone(&schema),
5564 vec![
5565 Arc::new(Int32Array::from(vec![2, 3, 4])),
5566 Arc::new(Int32Array::from(vec![0, 0, 0])),
5567 ],
5568 )
5569 .unwrap(),
5570 RecordBatch::try_new(
5571 Arc::clone(&schema),
5572 vec![
5573 Arc::new(Int32Array::from(vec![2, 3, 4])),
5574 Arc::new(Int32Array::from(vec![0, 0, 0])),
5575 ],
5576 )
5577 .unwrap(),
5578 ];
5579
5580 let input =
5581 TestMemoryExec::try_new_exec(&[input_data], Arc::clone(&schema), None)?;
5582 let aggregate_exec = Arc::new(AggregateExec::try_new(
5583 AggregateMode::Partial,
5584 group_by,
5585 aggr_expr,
5586 vec![None],
5587 Arc::clone(&input) as Arc<dyn ExecutionPlan>,
5588 schema,
5589 )?);
5590
5591 let session_config = SessionConfig::default()
5592 .set_bool("datafusion.execution.enable_migration_aggregate", true)
5593 .set(
5594 "datafusion.execution.skip_partial_aggregation_probe_rows_threshold",
5595 &ScalarValue::Int64(Some(5)),
5596 )
5597 .set(
5598 "datafusion.execution.skip_partial_aggregation_probe_ratio_threshold",
5599 &ScalarValue::Float64(Some(0.1)),
5600 );
5601
5602 let ctx = Arc::new(TaskContext::default().with_session_config(session_config));
5603 let output = collect(aggregate_exec.execute(0, Arc::clone(&ctx))?).await?;
5604
5605 allow_duplicates! {
5606 assert_snapshot!(batches_to_sort_string(&output), @r"
5607 +-----+-------------------+
5608 | key | COUNT(val)[count] |
5609 +-----+-------------------+
5610 | 1 | 1 |
5611 | 2 | 1 |
5612 | 2 | 2 |
5613 | 3 | 1 |
5614 | 3 | 2 |
5615 | 4 | 1 |
5616 | 4 | 1 |
5617 +-----+-------------------+
5618 ");
5619 }
5620
5621 let metrics = aggregate_exec.metrics().unwrap();
5622 let skipped_rows = metrics
5623 .sum_by_name("skipped_aggregation_rows")
5624 .map(|m| m.as_usize())
5625 .unwrap_or(0);
5626 assert_eq!(skipped_rows, 3);
5627
5628 Ok(())
5629 }
5630
5631 #[tokio::test]
5635 async fn test_skip_aggregation_disabled_at_threshold_one() -> Result<()> {
5636 let schema = Arc::new(Schema::new(vec![
5637 Field::new("key", DataType::Int32, true),
5638 Field::new("val", DataType::Int32, true),
5639 ]));
5640
5641 let group_by =
5642 PhysicalGroupBy::new_single(vec![(col("key", &schema)?, "key".to_string())]);
5643
5644 let aggr_expr = vec![
5645 AggregateExprBuilder::new(count_udaf(), vec![col("val", &schema)?])
5646 .schema(Arc::clone(&schema))
5647 .alias(String::from("COUNT(val)"))
5648 .build()
5649 .map(Arc::new)?,
5650 ];
5651
5652 let input_data = vec![
5657 RecordBatch::try_new(
5659 Arc::clone(&schema),
5660 vec![
5661 Arc::new(Int32Array::from(vec![1, 2, 3, 4, 5])),
5662 Arc::new(Int32Array::from(vec![0, 0, 0, 0, 0])),
5663 ],
5664 )
5665 .unwrap(),
5666 RecordBatch::try_new(
5668 Arc::clone(&schema),
5669 vec![
5670 Arc::new(Int32Array::from(vec![6, 7, 8, 9, 10])),
5671 Arc::new(Int32Array::from(vec![0, 0, 0, 0, 0])),
5672 ],
5673 )
5674 .unwrap(),
5675 ];
5676
5677 let input =
5678 TestMemoryExec::try_new_exec(&[input_data], Arc::clone(&schema), None)?;
5679 let aggregate_exec = Arc::new(AggregateExec::try_new(
5680 AggregateMode::Partial,
5681 group_by,
5682 aggr_expr,
5683 vec![None],
5684 Arc::clone(&input) as Arc<dyn ExecutionPlan>,
5685 schema,
5686 )?);
5687
5688 let session_config = SessionConfig::default()
5689 .set(
5690 "datafusion.execution.skip_partial_aggregation_probe_rows_threshold",
5691 &ScalarValue::Int64(Some(1)),
5692 )
5693 .set(
5694 "datafusion.execution.skip_partial_aggregation_probe_ratio_threshold",
5695 &ScalarValue::Float64(Some(1.0)),
5696 );
5697
5698 let ctx = TaskContext::default().with_session_config(session_config);
5699 collect(aggregate_exec.execute(0, Arc::new(ctx))?).await?;
5700
5701 let metrics = aggregate_exec.metrics().unwrap();
5702 let skipped_rows = metrics
5703 .sum_by_name("skipped_aggregation_rows")
5704 .map(|m| m.as_usize())
5705 .unwrap_or(0);
5706
5707 assert_eq!(
5708 skipped_rows, 0,
5709 "threshold=1.0 should disable skip aggregation, but {skipped_rows} rows were skipped"
5710 );
5711
5712 Ok(())
5713 }
5714
5715 #[test]
5716 fn group_exprs_nullable() -> Result<()> {
5717 let input_schema = Arc::new(Schema::new(vec![
5718 Field::new("a", DataType::Float32, false),
5719 Field::new("b", DataType::Float32, false),
5720 ]));
5721
5722 let aggr_expr = vec![
5723 AggregateExprBuilder::new(count_udaf(), vec![col("a", &input_schema)?])
5724 .schema(Arc::clone(&input_schema))
5725 .alias("COUNT(a)")
5726 .build()
5727 .map(Arc::new)?,
5728 ];
5729
5730 let grouping_set = PhysicalGroupBy::new(
5731 vec![
5732 (col("a", &input_schema)?, "a".to_string()),
5733 (col("b", &input_schema)?, "b".to_string()),
5734 ],
5735 vec![
5736 (lit(ScalarValue::Float32(None)), "a".to_string()),
5737 (lit(ScalarValue::Float32(None)), "b".to_string()),
5738 ],
5739 vec![
5740 vec![false, true], vec![false, false], ],
5743 true,
5744 );
5745 let aggr_schema = create_schema(
5746 &input_schema,
5747 &grouping_set,
5748 &aggr_expr,
5749 AggregateMode::Final,
5750 )?;
5751 let expected_schema = Schema::new(vec![
5752 Field::new("a", DataType::Float32, false),
5753 Field::new("b", DataType::Float32, true),
5754 Field::new("__grouping_id", DataType::UInt8, false),
5755 Field::new("COUNT(a)", DataType::Int64, false),
5756 ]);
5757 assert_eq!(aggr_schema, expected_schema);
5758 Ok(())
5759 }
5760
5761 async fn run_test_with_spill_pool_if_necessary(
5763 pool_size: usize,
5764 expect_spill: bool,
5765 ) -> Result<()> {
5766 fn create_record_batch(
5767 schema: &Arc<Schema>,
5768 data: (Vec<u32>, Vec<f64>),
5769 ) -> Result<RecordBatch> {
5770 Ok(RecordBatch::try_new(
5771 Arc::clone(schema),
5772 vec![
5773 Arc::new(UInt32Array::from(data.0)),
5774 Arc::new(Float64Array::from(data.1)),
5775 ],
5776 )?)
5777 }
5778
5779 let schema = Arc::new(Schema::new(vec![
5780 Field::new("a", DataType::UInt32, false),
5781 Field::new("b", DataType::Float64, false),
5782 ]));
5783
5784 let group_keys = [2, 3, 4, 4].repeat(1_000);
5785 let values = [1.0, 2.0, 3.0, 4.0].repeat(1_000);
5786 let batches = vec![
5787 create_record_batch(&schema, (group_keys.clone(), values.clone()))?,
5788 create_record_batch(&schema, (group_keys, values))?,
5789 ];
5790 let plan: Arc<dyn ExecutionPlan> =
5791 TestMemoryExec::try_new_exec(&[batches], Arc::clone(&schema), None)?;
5792
5793 let grouping_set = PhysicalGroupBy::new(
5794 vec![(col("a", &schema)?, "a".to_string())],
5795 vec![],
5796 vec![vec![false]],
5797 false,
5798 );
5799
5800 let aggregates: Vec<Arc<AggregateFunctionExpr>> = vec![
5802 Arc::new(
5803 AggregateExprBuilder::new(min_udaf(), vec![col("b", &schema)?])
5804 .schema(Arc::clone(&schema))
5805 .alias("MIN(b)")
5806 .build()?,
5807 ),
5808 Arc::new(
5809 AggregateExprBuilder::new(avg_udaf(), vec![col("b", &schema)?])
5810 .schema(Arc::clone(&schema))
5811 .alias("AVG(b)")
5812 .build()?,
5813 ),
5814 ];
5815
5816 let single_aggregate = Arc::new(AggregateExec::try_new(
5817 AggregateMode::Single,
5818 grouping_set,
5819 aggregates,
5820 vec![None, None],
5821 plan,
5822 Arc::clone(&schema),
5823 )?);
5824
5825 let batch_size = 2;
5826 let memory_pool = Arc::new(FairSpillPool::new(pool_size));
5827 let task_ctx = Arc::new(
5828 TaskContext::default()
5829 .with_session_config(SessionConfig::new().with_batch_size(batch_size))
5830 .with_runtime(Arc::new(
5831 RuntimeEnvBuilder::new()
5832 .with_memory_pool(memory_pool)
5833 .build()?,
5834 )),
5835 );
5836
5837 let result = collect(single_aggregate.execute(0, Arc::clone(&task_ctx))?).await?;
5838
5839 assert_spill_count_metric(expect_spill, single_aggregate);
5840
5841 allow_duplicates! {
5842 assert_snapshot!(batches_to_string(&result), @r"
5843 +---+--------+--------+
5844 | a | MIN(b) | AVG(b) |
5845 +---+--------+--------+
5846 | 2 | 1.0 | 1.0 |
5847 | 3 | 2.0 | 2.0 |
5848 | 4 | 3.0 | 3.5 |
5849 +---+--------+--------+
5850 ");
5851 }
5852
5853 Ok(())
5854 }
5855
5856 fn assert_spill_count_metric(
5857 expect_spill: bool,
5858 single_aggregate: Arc<AggregateExec>,
5859 ) {
5860 if let Some(metrics_set) = single_aggregate.metrics() {
5861 let mut spill_count = 0;
5862
5863 for metric in metrics_set.iter() {
5865 if let MetricValue::SpillCount(count) = metric.value() {
5866 spill_count = count.value();
5867 break;
5868 }
5869 }
5870
5871 if expect_spill && spill_count == 0 {
5872 panic!(
5873 "Expected spill but SpillCount metric not found or SpillCount was 0."
5874 );
5875 } else if !expect_spill && spill_count > 0 {
5876 panic!(
5877 "Expected no spill but found SpillCount metric with value greater than 0."
5878 );
5879 }
5880 } else {
5881 panic!("No metrics returned from the operator; cannot verify spilling.");
5882 }
5883 }
5884
5885 #[tokio::test]
5886 async fn test_aggregate_with_spill_if_necessary() -> Result<()> {
5887 run_test_with_spill_pool_if_necessary(20_000, true).await?;
5889 run_test_with_spill_pool_if_necessary(200_000, false).await?;
5891 Ok(())
5892 }
5893
5894 #[tokio::test]
5895 async fn test_grouped_aggregation_respects_memory_limit() -> Result<()> {
5896 fn create_record_batch(
5898 schema: &Arc<Schema>,
5899 data: (Vec<u32>, Vec<f64>),
5900 ) -> Result<RecordBatch> {
5901 Ok(RecordBatch::try_new(
5902 Arc::clone(schema),
5903 vec![
5904 Arc::new(UInt32Array::from(data.0)),
5905 Arc::new(Float64Array::from(data.1)),
5906 ],
5907 )?)
5908 }
5909
5910 let schema = Arc::new(Schema::new(vec![
5911 Field::new("a", DataType::UInt32, false),
5912 Field::new("b", DataType::Float64, false),
5913 ]));
5914
5915 let batches = vec![
5916 create_record_batch(&schema, (vec![2, 3, 4, 4], vec![1.0, 2.0, 3.0, 4.0]))?,
5917 create_record_batch(&schema, (vec![2, 3, 4, 4], vec![1.0, 2.0, 3.0, 4.0]))?,
5918 ];
5919 let plan: Arc<dyn ExecutionPlan> =
5920 TestMemoryExec::try_new_exec(&[batches], Arc::clone(&schema), None)?;
5921 let proj = ProjectionExec::try_new(
5922 vec![
5923 ProjectionExpr::new(lit("0"), "l".to_string()),
5924 ProjectionExpr::new_from_expression(col("a", &schema)?, &schema)?,
5925 ProjectionExpr::new_from_expression(col("b", &schema)?, &schema)?,
5926 ],
5927 plan,
5928 )?;
5929 let plan: Arc<dyn ExecutionPlan> = Arc::new(proj);
5930 let schema = plan.schema();
5931
5932 let grouping_set = PhysicalGroupBy::new(
5933 vec![
5934 (col("l", &schema)?, "l".to_string()),
5935 (col("a", &schema)?, "a".to_string()),
5936 ],
5937 vec![],
5938 vec![vec![false, false]],
5939 false,
5940 );
5941
5942 let aggregates: Vec<Arc<AggregateFunctionExpr>> = vec![
5944 Arc::new(
5945 AggregateExprBuilder::new(min_udaf(), vec![col("b", &schema)?])
5946 .schema(Arc::clone(&schema))
5947 .alias("MIN(b)")
5948 .build()?,
5949 ),
5950 Arc::new(
5951 AggregateExprBuilder::new(avg_udaf(), vec![col("b", &schema)?])
5952 .schema(Arc::clone(&schema))
5953 .alias("AVG(b)")
5954 .build()?,
5955 ),
5956 ];
5957
5958 let single_aggregate = Arc::new(AggregateExec::try_new(
5959 AggregateMode::Single,
5960 grouping_set,
5961 aggregates,
5962 vec![None, None],
5963 plan,
5964 Arc::clone(&schema),
5965 )?);
5966
5967 let batch_size = 2;
5968 let memory_pool = Arc::new(FairSpillPool::new(2000));
5969 let task_ctx = Arc::new(
5970 TaskContext::default()
5971 .with_session_config(SessionConfig::new().with_batch_size(batch_size))
5972 .with_runtime(Arc::new(
5973 RuntimeEnvBuilder::new()
5974 .with_memory_pool(memory_pool)
5975 .build()?,
5976 )),
5977 );
5978
5979 let result = collect(single_aggregate.execute(0, Arc::clone(&task_ctx))?).await;
5980 match result {
5981 Ok(result) => {
5982 assert_spill_count_metric(true, single_aggregate);
5983
5984 allow_duplicates! {
5985 assert_snapshot!(batches_to_string(&result), @r"
5986 +---+---+--------+--------+
5987 | l | a | MIN(b) | AVG(b) |
5988 +---+---+--------+--------+
5989 | 0 | 2 | 1.0 | 1.0 |
5990 | 0 | 3 | 2.0 | 2.0 |
5991 | 0 | 4 | 3.0 | 3.5 |
5992 +---+---+--------+--------+
5993 ");
5994 }
5995 }
5996 Err(e) => assert!(matches!(e, DataFusionError::ResourcesExhausted(_))),
5997 }
5998
5999 Ok(())
6000 }
6001
6002 #[tokio::test]
6003 async fn test_aggregate_statistics_edge_cases() -> Result<()> {
6004 use datafusion_common::ColumnStatistics;
6005
6006 let schema = Arc::new(Schema::new(vec![
6007 Field::new("a", DataType::Int32, false),
6008 Field::new("b", DataType::Float64, false),
6009 ]));
6010
6011 let absent_byte_stats = Statistics {
6012 num_rows: Precision::Exact(100),
6013 total_byte_size: Precision::Absent,
6014 column_statistics: vec![
6015 ColumnStatistics::new_unknown(),
6016 ColumnStatistics::new_unknown(),
6017 ],
6018 };
6019 let agg = build_test_aggregate(
6020 &schema,
6021 absent_byte_stats,
6022 PhysicalGroupBy::default(),
6023 None,
6024 )?;
6025 let stats = StatisticsContext::new().compute(&agg, &StatisticsArgs::new())?;
6026 assert_eq!(stats.total_byte_size, Precision::Absent);
6027
6028 let zero_row_stats = Statistics {
6029 num_rows: Precision::Exact(0),
6030 total_byte_size: Precision::Exact(0),
6031 column_statistics: vec![
6032 ColumnStatistics::new_unknown(),
6033 ColumnStatistics::new_unknown(),
6034 ],
6035 };
6036 let agg_zero = build_test_aggregate(
6037 &schema,
6038 zero_row_stats,
6039 PhysicalGroupBy::default(),
6040 None,
6041 )?;
6042 let stats_zero =
6043 StatisticsContext::new().compute(&agg_zero, &StatisticsArgs::new())?;
6044 assert_eq!(stats_zero.total_byte_size, Precision::Absent);
6045
6046 let single_input =
6047 Arc::new(EmptyExec::new(Arc::clone(&schema))) as Arc<dyn ExecutionPlan>;
6048 let single_agg_zero = AggregateExec::try_new(
6049 AggregateMode::Single,
6050 PhysicalGroupBy::default(),
6051 vec![count_a_aggregate(&schema)?],
6052 vec![None],
6053 single_input,
6054 Arc::clone(&schema),
6055 )?;
6056 assert_eq!(
6057 single_agg_zero
6058 .properties()
6059 .output_partitioning()
6060 .partition_count(),
6061 1
6062 );
6063 let single_stats_zero =
6064 StatisticsContext::new().compute(&single_agg_zero, &StatisticsArgs::new())?;
6065 assert_eq!(single_stats_zero.num_rows, Precision::Exact(1));
6066
6067 Ok(())
6068 }
6069
6070 #[tokio::test]
6071 async fn test_aggregate_statistics_empty_input_with_grouping_sets() -> Result<()> {
6072 let schema = empty_grouping_sets_test_schema();
6073
6074 let grouped = build_test_aggregate(
6076 &schema,
6077 empty_input_statistics(),
6078 simple_group_by(&schema, &["a"]),
6079 None,
6080 )?;
6081 let stats = StatisticsContext::new().compute(&grouped, &StatisticsArgs::new())?;
6082 assert_eq!(stats.num_rows, Precision::Exact(0));
6083
6084 let with_empty_set = build_test_aggregate(
6087 &schema,
6088 empty_input_statistics(),
6089 grouping_sets_with_empty(&schema, 1)?,
6090 None,
6091 )?;
6092 let stats =
6093 StatisticsContext::new().compute(&with_empty_set, &StatisticsArgs::new())?;
6094 assert_eq!(stats.num_rows, Precision::Exact(1));
6095
6096 let with_duplicate_empty_sets = build_test_aggregate(
6099 &schema,
6100 empty_input_statistics(),
6101 grouping_sets_with_empty(&schema, 2)?,
6102 None,
6103 )?;
6104 let stats = StatisticsContext::new()
6105 .compute(&with_duplicate_empty_sets, &StatisticsArgs::new())?;
6106 assert_eq!(stats.num_rows, Precision::Exact(2));
6107
6108 Ok(())
6109 }
6110
6111 #[tokio::test]
6115 async fn test_aggregate_statistics_empty_input_partial_mode_scaling() -> Result<()> {
6116 let schema = empty_grouping_sets_test_schema();
6117 let input = Arc::new(RepartitionExec::try_new(
6118 Arc::new(StatisticsExec::new(
6119 empty_input_statistics(),
6120 (*schema).clone(),
6121 )),
6122 Partitioning::RoundRobinBatch(4),
6123 )?) as Arc<dyn ExecutionPlan>;
6124
6125 let agg = AggregateExec::try_new(
6126 AggregateMode::Partial,
6127 grouping_sets_with_empty(&schema, 1)?,
6128 vec![count_a_aggregate(&schema)?],
6129 vec![None],
6130 input,
6131 Arc::clone(&schema),
6132 )?;
6133 assert_eq!(agg.properties().output_partitioning().partition_count(), 4);
6134
6135 let context = StatisticsContext::new();
6136 assert_eq!(
6137 context.compute(&agg, &StatisticsArgs::new())?.num_rows,
6138 Precision::Exact(4)
6139 );
6140 let partition_statistics =
6143 context.compute(&agg, &StatisticsArgs::new().with_partition(Some(0)))?;
6144 assert_eq!(partition_statistics.num_rows, Precision::Inexact(1));
6145 let group_column = &partition_statistics.column_statistics[0];
6146 let typed_null = Precision::Inexact(ScalarValue::Int32(None));
6147 assert_eq!(group_column.min_value, typed_null);
6148 assert_eq!(group_column.max_value, typed_null);
6149 assert_eq!(group_column.distinct_count, Precision::Inexact(0));
6150 assert_eq!(group_column.null_count, Precision::Inexact(1));
6151
6152 Ok(())
6153 }
6154
6155 #[tokio::test]
6158 async fn test_aggregate_statistics_empty_input_nullifies_group_columns() -> Result<()>
6159 {
6160 let schema = empty_grouping_sets_test_schema();
6161 let mut input_statistics = empty_input_statistics();
6162 input_statistics.column_statistics[0] = ColumnStatistics {
6163 null_count: Precision::Exact(0),
6164 max_value: Precision::Exact(ScalarValue::Int32(Some(5))),
6165 min_value: Precision::Exact(ScalarValue::Int32(Some(5))),
6166 sum_value: Precision::Absent,
6167 distinct_count: Precision::Exact(1),
6168 byte_size: Precision::Absent,
6169 };
6170
6171 let agg = build_test_aggregate(
6172 &schema,
6173 input_statistics,
6174 grouping_sets_with_empty(&schema, 1)?,
6175 None,
6176 )?;
6177
6178 let stats = StatisticsContext::new().compute(&agg, &StatisticsArgs::new())?;
6179 assert_eq!(stats.num_rows, Precision::Exact(1));
6180 let group_column = &stats.column_statistics[0];
6181 let typed_null = Precision::Exact(ScalarValue::Int32(None));
6182 assert_eq!(group_column.min_value, typed_null);
6183 assert_eq!(group_column.max_value, typed_null);
6184 assert_eq!(group_column.distinct_count, Precision::Exact(0));
6185 assert_eq!(group_column.null_count, Precision::Exact(1));
6186
6187 Ok(())
6188 }
6189
6190 fn empty_grouping_sets_test_schema() -> SchemaRef {
6191 Arc::new(Schema::new(vec![
6192 Field::new("a", DataType::Int32, false),
6193 Field::new("b", DataType::Float64, false),
6194 ]))
6195 }
6196
6197 fn empty_input_statistics() -> Statistics {
6198 Statistics {
6199 num_rows: Precision::Exact(0),
6200 total_byte_size: Precision::Exact(0),
6201 column_statistics: vec![
6202 ColumnStatistics::new_unknown(),
6203 ColumnStatistics::new_unknown(),
6204 ],
6205 }
6206 }
6207
6208 fn grouping_sets_with_empty(
6211 schema: &SchemaRef,
6212 empty_sets: usize,
6213 ) -> Result<PhysicalGroupBy> {
6214 let mut groups = vec![vec![false]];
6215 groups.resize(1 + empty_sets, vec![true]);
6216 Ok(PhysicalGroupBy::new(
6217 vec![(col("a", schema)?, "a".to_string())],
6218 vec![(lit(ScalarValue::Int32(None)), "a".to_string())],
6219 groups,
6220 true,
6221 ))
6222 }
6223
6224 fn build_test_aggregate(
6225 schema: &SchemaRef,
6226 stats: Statistics,
6227 group_by: PhysicalGroupBy,
6228 limit: Option<LimitOptions>,
6229 ) -> Result<AggregateExec> {
6230 build_test_aggregate_with_mode(
6231 schema,
6232 stats,
6233 group_by,
6234 limit,
6235 AggregateMode::Final,
6236 )
6237 }
6238
6239 fn count_a_aggregate(schema: &SchemaRef) -> Result<Arc<AggregateFunctionExpr>> {
6240 Ok(Arc::new(
6241 AggregateExprBuilder::new(count_udaf(), vec![col("a", schema)?])
6242 .schema(Arc::clone(schema))
6243 .alias("COUNT(a)")
6244 .build()?,
6245 ))
6246 }
6247
6248 fn build_test_aggregate_with_mode(
6249 schema: &SchemaRef,
6250 stats: Statistics,
6251 group_by: PhysicalGroupBy,
6252 limit: Option<LimitOptions>,
6253 mode: AggregateMode,
6254 ) -> Result<AggregateExec> {
6255 let input = Arc::new(StatisticsExec::new(stats, (**schema).clone()))
6256 as Arc<dyn ExecutionPlan>;
6257
6258 let mut agg = AggregateExec::try_new(
6259 mode,
6260 group_by,
6261 vec![count_a_aggregate(schema)?],
6262 vec![None],
6263 input,
6264 Arc::clone(schema),
6265 )?;
6266
6267 if let Some(limit) = limit {
6268 agg = agg.with_limit_options(Some(limit));
6269 }
6270
6271 Ok(agg)
6272 }
6273
6274 fn simple_group_by(schema: &SchemaRef, cols: &[&str]) -> PhysicalGroupBy {
6275 if cols.is_empty() {
6276 PhysicalGroupBy::default()
6277 } else {
6278 PhysicalGroupBy::new_single(
6279 cols.iter()
6280 .map(|name| {
6281 (
6282 col(name, schema).unwrap() as Arc<dyn PhysicalExpr>,
6283 name.to_string(),
6284 )
6285 })
6286 .collect(),
6287 )
6288 }
6289 }
6290
6291 #[test]
6292 fn test_aggregate_cardinality_estimation() -> Result<()> {
6293 use datafusion_common::ColumnStatistics;
6294
6295 let schema = Arc::new(Schema::new(vec![
6296 Field::new("a", DataType::Int32, true),
6297 Field::new("b", DataType::Int32, true),
6298 ]));
6299
6300 struct TestCase {
6301 name: &'static str,
6302 input_rows: Precision<usize>,
6303 col_a_stats: ColumnStatistics,
6304 col_b_stats: ColumnStatistics,
6305 group_by_cols: Vec<&'static str>,
6306 limit_options: Option<LimitOptions>,
6307 expected_num_rows: Precision<usize>,
6308 }
6309
6310 let cases = vec![
6311 TestCase {
6313 name: "single group-by col with NDV tightens estimate",
6314 input_rows: Precision::Exact(1_000_000),
6315 col_a_stats: ColumnStatistics {
6316 distinct_count: Precision::Exact(500),
6317 ..ColumnStatistics::new_unknown()
6318 },
6319 col_b_stats: ColumnStatistics::new_unknown(),
6320 group_by_cols: vec!["a"],
6321 limit_options: None,
6322 expected_num_rows: Precision::Inexact(500),
6323 },
6324 TestCase {
6325 name: "multi-col group-by multiplies NDVs",
6326 input_rows: Precision::Exact(1_000_000),
6327 col_a_stats: ColumnStatistics {
6328 distinct_count: Precision::Exact(100),
6329 ..ColumnStatistics::new_unknown()
6330 },
6331 col_b_stats: ColumnStatistics {
6332 distinct_count: Precision::Exact(50),
6333 ..ColumnStatistics::new_unknown()
6334 },
6335 group_by_cols: vec!["a", "b"],
6336 limit_options: None,
6337 expected_num_rows: Precision::Inexact(5_000),
6338 },
6339 TestCase {
6340 name: "NDV product capped by input rows",
6341 input_rows: Precision::Exact(200),
6342 col_a_stats: ColumnStatistics {
6343 distinct_count: Precision::Exact(100),
6344 ..ColumnStatistics::new_unknown()
6345 },
6346 col_b_stats: ColumnStatistics {
6347 distinct_count: Precision::Exact(50),
6348 ..ColumnStatistics::new_unknown()
6349 },
6350 group_by_cols: vec!["a", "b"],
6351 limit_options: None,
6352 expected_num_rows: Precision::Inexact(200),
6353 },
6354 TestCase {
6355 name: "null adjustment adds +1 per column",
6356 input_rows: Precision::Exact(1_000_000),
6357 col_a_stats: ColumnStatistics {
6358 distinct_count: Precision::Exact(99),
6359 null_count: Precision::Exact(10),
6360 ..ColumnStatistics::new_unknown()
6361 },
6362 col_b_stats: ColumnStatistics::new_unknown(),
6363 group_by_cols: vec!["a"],
6364 limit_options: None,
6365 expected_num_rows: Precision::Inexact(100),
6367 },
6368 TestCase {
6369 name: "null adjustment on multiple columns",
6370 input_rows: Precision::Exact(1_000_000),
6371 col_a_stats: ColumnStatistics {
6372 distinct_count: Precision::Exact(99),
6373 null_count: Precision::Exact(5),
6374 ..ColumnStatistics::new_unknown()
6375 },
6376 col_b_stats: ColumnStatistics {
6377 distinct_count: Precision::Exact(49),
6378 null_count: Precision::Exact(3),
6379 ..ColumnStatistics::new_unknown()
6380 },
6381 group_by_cols: vec!["a", "b"],
6382 limit_options: None,
6383 expected_num_rows: Precision::Inexact(5_000),
6385 },
6386 TestCase {
6387 name: "zero null_count means no adjustment",
6388 input_rows: Precision::Exact(1_000_000),
6389 col_a_stats: ColumnStatistics {
6390 distinct_count: Precision::Exact(100),
6391 null_count: Precision::Exact(0),
6392 ..ColumnStatistics::new_unknown()
6393 },
6394 col_b_stats: ColumnStatistics::new_unknown(),
6395 group_by_cols: vec!["a"],
6396 limit_options: None,
6397 expected_num_rows: Precision::Inexact(100),
6398 },
6399 TestCase {
6401 name: "bail out when one group-by col lacks NDV",
6402 input_rows: Precision::Exact(1_000_000),
6403 col_a_stats: ColumnStatistics {
6404 distinct_count: Precision::Exact(100),
6405 ..ColumnStatistics::new_unknown()
6406 },
6407 col_b_stats: ColumnStatistics::new_unknown(),
6408 group_by_cols: vec!["a", "b"],
6409 limit_options: None,
6410 expected_num_rows: Precision::Inexact(1_000_000),
6411 },
6412 TestCase {
6413 name: "bail out when all group-by cols lack NDV",
6414 input_rows: Precision::Exact(1_000_000),
6415 col_a_stats: ColumnStatistics::new_unknown(),
6416 col_b_stats: ColumnStatistics::new_unknown(),
6417 group_by_cols: vec!["a"],
6418 limit_options: None,
6419 expected_num_rows: Precision::Inexact(1_000_000),
6420 },
6421 TestCase {
6423 name: "TopK limit caps output rows",
6424 input_rows: Precision::Exact(1_000_000),
6425 col_a_stats: ColumnStatistics::new_unknown(),
6426 col_b_stats: ColumnStatistics::new_unknown(),
6427 group_by_cols: vec!["a"],
6428 limit_options: Some(LimitOptions::new(10)),
6429 expected_num_rows: Precision::Inexact(10),
6430 },
6431 TestCase {
6432 name: "NDV + TopK limit: min(NDV, limit) when NDV < limit",
6433 input_rows: Precision::Exact(1_000_000),
6434 col_a_stats: ColumnStatistics {
6435 distinct_count: Precision::Exact(5),
6436 ..ColumnStatistics::new_unknown()
6437 },
6438 col_b_stats: ColumnStatistics::new_unknown(),
6439 group_by_cols: vec!["a"],
6440 limit_options: Some(LimitOptions::new(10)),
6441 expected_num_rows: Precision::Inexact(5),
6442 },
6443 TestCase {
6444 name: "NDV + TopK limit: min(NDV, limit) when limit < NDV",
6445 input_rows: Precision::Exact(1_000_000),
6446 col_a_stats: ColumnStatistics {
6447 distinct_count: Precision::Exact(500),
6448 ..ColumnStatistics::new_unknown()
6449 },
6450 col_b_stats: ColumnStatistics::new_unknown(),
6451 group_by_cols: vec!["a"],
6452 limit_options: Some(LimitOptions::new(10)),
6453 expected_num_rows: Precision::Inexact(10),
6454 },
6455 TestCase {
6457 name: "absent input rows without limit stays absent",
6458 input_rows: Precision::Absent,
6459 col_a_stats: ColumnStatistics::new_unknown(),
6460 col_b_stats: ColumnStatistics::new_unknown(),
6461 group_by_cols: vec!["a"],
6462 limit_options: None,
6463 expected_num_rows: Precision::Absent,
6464 },
6465 TestCase {
6466 name: "absent input rows with TopK limit gives inexact(limit)",
6467 input_rows: Precision::Absent,
6468 col_a_stats: ColumnStatistics::new_unknown(),
6469 col_b_stats: ColumnStatistics::new_unknown(),
6470 group_by_cols: vec!["a"],
6471 limit_options: Some(LimitOptions::new(10)),
6472 expected_num_rows: Precision::Inexact(10),
6473 },
6474 TestCase {
6476 name: "no group-by cols (Final mode) returns Exact(1)",
6477 input_rows: Precision::Exact(1_000_000),
6478 col_a_stats: ColumnStatistics::new_unknown(),
6479 col_b_stats: ColumnStatistics::new_unknown(),
6480 group_by_cols: vec![],
6481 limit_options: None,
6482 expected_num_rows: Precision::Exact(1),
6483 },
6484 TestCase {
6486 name: "one input row returns Exact(1)",
6487 input_rows: Precision::Exact(1),
6488 col_a_stats: ColumnStatistics {
6489 distinct_count: Precision::Exact(1),
6490 ..ColumnStatistics::new_unknown()
6491 },
6492 col_b_stats: ColumnStatistics::new_unknown(),
6493 group_by_cols: vec!["a"],
6494 limit_options: None,
6495 expected_num_rows: Precision::Exact(1),
6496 },
6497 TestCase {
6499 name: "zero input rows returns Exact(0)",
6500 input_rows: Precision::Exact(0),
6501 col_a_stats: ColumnStatistics::new_unknown(),
6502 col_b_stats: ColumnStatistics::new_unknown(),
6503 group_by_cols: vec!["a"],
6504 limit_options: None,
6505 expected_num_rows: Precision::Exact(0),
6506 },
6507 TestCase {
6509 name: "inexact NDV still used for estimation",
6510 input_rows: Precision::Exact(1_000_000),
6511 col_a_stats: ColumnStatistics {
6512 distinct_count: Precision::Inexact(200),
6513 ..ColumnStatistics::new_unknown()
6514 },
6515 col_b_stats: ColumnStatistics::new_unknown(),
6516 group_by_cols: vec!["a"],
6517 limit_options: None,
6518 expected_num_rows: Precision::Inexact(200),
6519 },
6520 TestCase {
6521 name: "inexact NDV combined with limit",
6522 input_rows: Precision::Exact(1_000_000),
6523 col_a_stats: ColumnStatistics {
6524 distinct_count: Precision::Inexact(200),
6525 ..ColumnStatistics::new_unknown()
6526 },
6527 col_b_stats: ColumnStatistics::new_unknown(),
6528 group_by_cols: vec!["a"],
6529 limit_options: Some(LimitOptions::new(10)),
6530 expected_num_rows: Precision::Inexact(10),
6531 },
6532 TestCase {
6534 name: "all-null column contributes 1 to the product, not 0",
6535 input_rows: Precision::Exact(1_000),
6536 col_a_stats: ColumnStatistics {
6537 distinct_count: Precision::Exact(0),
6538 null_count: Precision::Exact(1_000),
6539 ..ColumnStatistics::new_unknown()
6540 },
6541 col_b_stats: ColumnStatistics {
6542 distinct_count: Precision::Exact(50),
6543 ..ColumnStatistics::new_unknown()
6544 },
6545 group_by_cols: vec!["a", "b"],
6546 limit_options: None,
6547 expected_num_rows: Precision::Inexact(50),
6549 },
6550 TestCase {
6552 name: "absent num_rows falls back to NDV estimate",
6553 input_rows: Precision::Absent,
6554 col_a_stats: ColumnStatistics {
6555 distinct_count: Precision::Exact(100),
6556 ..ColumnStatistics::new_unknown()
6557 },
6558 col_b_stats: ColumnStatistics::new_unknown(),
6559 group_by_cols: vec!["a"],
6560 limit_options: None,
6561 expected_num_rows: Precision::Inexact(100),
6562 },
6563 TestCase {
6564 name: "absent num_rows with NDV and limit returns min(ndv, limit)",
6565 input_rows: Precision::Absent,
6566 col_a_stats: ColumnStatistics {
6567 distinct_count: Precision::Exact(100),
6568 ..ColumnStatistics::new_unknown()
6569 },
6570 col_b_stats: ColumnStatistics::new_unknown(),
6571 group_by_cols: vec!["a"],
6572 limit_options: Some(LimitOptions::new(10)),
6573 expected_num_rows: Precision::Inexact(10),
6574 },
6575 ];
6576
6577 for case in cases {
6578 let input_stats = Statistics {
6579 num_rows: case.input_rows,
6580 total_byte_size: Precision::Inexact(1_000_000),
6581 column_statistics: vec![
6582 case.col_a_stats.clone(),
6583 case.col_b_stats.clone(),
6584 ],
6585 };
6586
6587 let group_by = simple_group_by(&schema, &case.group_by_cols);
6588 let agg =
6589 build_test_aggregate(&schema, input_stats, group_by, case.limit_options)?;
6590
6591 let stats = StatisticsContext::new().compute(&agg, &StatisticsArgs::new())?;
6592 assert_eq!(
6593 stats.num_rows, case.expected_num_rows,
6594 "FAILED: '{}' — expected {:?}, got {:?}",
6595 case.name, case.expected_num_rows, stats.num_rows
6596 );
6597 }
6598
6599 Ok(())
6600 }
6601
6602 #[test]
6603 fn test_aggregate_stats_distinct_count_propagation() -> Result<()> {
6604 use datafusion_common::ColumnStatistics;
6605
6606 let schema = Arc::new(Schema::new(vec![
6607 Field::new("a", DataType::Int32, true),
6608 Field::new("b", DataType::Int32, true),
6609 ]));
6610
6611 let input_stats = Statistics {
6612 num_rows: Precision::Exact(1000),
6613 total_byte_size: Precision::Inexact(10000),
6614 column_statistics: vec![
6615 ColumnStatistics {
6616 distinct_count: Precision::Exact(100),
6617 null_count: Precision::Exact(5),
6618 ..ColumnStatistics::new_unknown()
6619 },
6620 ColumnStatistics::new_unknown(),
6621 ],
6622 };
6623 let agg = build_test_aggregate(
6624 &schema,
6625 input_stats,
6626 simple_group_by(&schema, &["a"]),
6627 None,
6628 )?;
6629
6630 let stats = StatisticsContext::new().compute(&agg, &StatisticsArgs::new())?;
6631 assert_eq!(
6632 stats.column_statistics[0].distinct_count,
6633 Precision::Exact(100),
6634 "distinct_count should be propagated from child for group-by columns"
6635 );
6636
6637 Ok(())
6638 }
6639
6640 #[test]
6641 fn test_aggregate_stats_grouping_sets() -> Result<()> {
6642 use datafusion_common::ColumnStatistics;
6643
6644 let schema = Arc::new(Schema::new(vec![
6645 Field::new("a", DataType::Int32, true),
6646 Field::new("b", DataType::Int32, true),
6647 ]));
6648
6649 let input_stats = Statistics {
6650 num_rows: Precision::Exact(1_000_000),
6651 total_byte_size: Precision::Inexact(1_000_000),
6652 column_statistics: vec![
6653 ColumnStatistics {
6654 distinct_count: Precision::Exact(100),
6655 ..ColumnStatistics::new_unknown()
6656 },
6657 ColumnStatistics {
6658 distinct_count: Precision::Exact(50),
6659 ..ColumnStatistics::new_unknown()
6660 },
6661 ],
6662 };
6663
6664 let grouping_set = PhysicalGroupBy::new(
6666 vec![
6667 (col("a", &schema)? as Arc<dyn PhysicalExpr>, "a".to_string()),
6668 (col("b", &schema)? as Arc<dyn PhysicalExpr>, "b".to_string()),
6669 ],
6670 vec![
6671 (lit(ScalarValue::Int32(None)), "a".to_string()),
6672 (lit(ScalarValue::Int32(None)), "b".to_string()),
6673 ],
6674 vec![
6675 vec![false, true], vec![true, false], vec![false, false], ],
6679 true,
6680 );
6681
6682 let agg = build_test_aggregate(&schema, input_stats, grouping_set, None)?;
6683
6684 let stats = StatisticsContext::new().compute(&agg, &StatisticsArgs::new())?;
6685 assert_eq!(
6688 stats.num_rows,
6689 Precision::Inexact(5_150),
6690 "grouping sets should sum per-set NDV products"
6691 );
6692
6693 Ok(())
6694 }
6695
6696 #[tokio::test]
6697 async fn test_aggregate_stats_duplicate_empty_grouping_sets() -> Result<()> {
6698 let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, true)]));
6699
6700 let duplicate_empty_grouping_sets =
6701 PhysicalGroupBy::new(vec![], vec![], vec![vec![], vec![]], true);
6702
6703 let single_input =
6704 Arc::new(EmptyExec::new(Arc::clone(&schema))) as Arc<dyn ExecutionPlan>;
6705 let single_agg = AggregateExec::try_new(
6706 AggregateMode::Single,
6707 duplicate_empty_grouping_sets.clone(),
6708 vec![count_a_aggregate(&schema)?],
6709 vec![None],
6710 single_input,
6711 Arc::clone(&schema),
6712 )?;
6713 assert_eq!(
6714 StatisticsContext::new()
6715 .compute(&single_agg, &StatisticsArgs::new())?
6716 .num_rows,
6717 Precision::Exact(2)
6718 );
6719
6720 let partial_input =
6721 Arc::new(EmptyExec::new(Arc::clone(&schema)).with_partitions(2))
6722 as Arc<dyn ExecutionPlan>;
6723 let partial_agg = Arc::new(AggregateExec::try_new(
6724 AggregateMode::Partial,
6725 duplicate_empty_grouping_sets,
6726 vec![count_a_aggregate(&schema)?],
6727 vec![None],
6728 partial_input,
6729 Arc::clone(&schema),
6730 )?);
6731
6732 assert_eq!(
6733 partial_agg
6734 .properties()
6735 .output_partitioning()
6736 .partition_count(),
6737 2
6738 );
6739 let task_ctx = Arc::new(TaskContext::default());
6740 for partition in 0..2 {
6741 assert_eq!(
6742 StatisticsContext::new()
6743 .compute(
6744 partial_agg.as_ref(),
6745 &StatisticsArgs::new().with_partition(Some(partition)),
6746 )?
6747 .num_rows,
6748 Precision::Exact(2)
6749 );
6750 let result =
6751 collect(partial_agg.execute(partition, Arc::clone(&task_ctx))?).await?;
6752 assert_eq!(result.iter().map(RecordBatch::num_rows).sum::<usize>(), 2);
6753 }
6754
6755 assert_eq!(
6756 StatisticsContext::new()
6757 .compute(partial_agg.as_ref(), &StatisticsArgs::new())?
6758 .num_rows,
6759 Precision::Exact(4)
6760 );
6761
6762 Ok(())
6763 }
6764
6765 #[test]
6766 fn test_aggregate_stats_non_column_expr_bails_out() -> Result<()> {
6767 use datafusion_common::ColumnStatistics;
6768 use datafusion_expr::Operator;
6769 use datafusion_physical_expr::expressions::BinaryExpr;
6770
6771 let schema = Arc::new(Schema::new(vec![
6772 Field::new("a", DataType::Int32, true),
6773 Field::new("b", DataType::Int32, true),
6774 ]));
6775
6776 let input_stats = Statistics {
6777 num_rows: Precision::Exact(1_000_000),
6778 total_byte_size: Precision::Inexact(1_000_000),
6779 column_statistics: vec![
6780 ColumnStatistics {
6781 distinct_count: Precision::Exact(100),
6782 ..ColumnStatistics::new_unknown()
6783 },
6784 ColumnStatistics {
6785 distinct_count: Precision::Exact(50),
6786 ..ColumnStatistics::new_unknown()
6787 },
6788 ],
6789 };
6790
6791 let expr_a_plus_b: Arc<dyn PhysicalExpr> = Arc::new(BinaryExpr::new(
6793 col("a", &schema)?,
6794 Operator::Plus,
6795 col("b", &schema)?,
6796 ));
6797
6798 let group_by =
6799 PhysicalGroupBy::new_single(vec![(expr_a_plus_b, "a+b".to_string())]);
6800 let agg = build_test_aggregate(&schema, input_stats, group_by, None)?;
6801
6802 let stats = StatisticsContext::new().compute(&agg, &StatisticsArgs::new())?;
6803 assert_eq!(
6804 stats.num_rows,
6805 Precision::Inexact(1_000_000),
6806 "non-column group-by expression should bail out to input_rows"
6807 );
6808
6809 Ok(())
6810 }
6811
6812 #[tokio::test]
6813 async fn test_order_is_retained_when_spilling() -> Result<()> {
6814 let schema = Arc::new(Schema::new(vec![
6815 Field::new("a", DataType::Int64, false),
6816 Field::new("b", DataType::Int64, false),
6817 Field::new("c", DataType::Int64, false),
6818 ]));
6819
6820 let batches = vec![vec![
6821 RecordBatch::try_new(
6822 Arc::clone(&schema),
6823 vec![
6824 Arc::new(Int64Array::from(vec![2])),
6825 Arc::new(Int64Array::from(vec![2])),
6826 Arc::new(Int64Array::from(vec![1])),
6827 ],
6828 )?,
6829 RecordBatch::try_new(
6830 Arc::clone(&schema),
6831 vec![
6832 Arc::new(Int64Array::from(vec![1])),
6833 Arc::new(Int64Array::from(vec![1])),
6834 Arc::new(Int64Array::from(vec![1])),
6835 ],
6836 )?,
6837 RecordBatch::try_new(
6838 Arc::clone(&schema),
6839 vec![
6840 Arc::new(Int64Array::from(vec![0])),
6841 Arc::new(Int64Array::from(vec![0])),
6842 Arc::new(Int64Array::from(vec![1])),
6843 ],
6844 )?,
6845 ]];
6846 let scan = TestMemoryExec::try_new(&batches, Arc::clone(&schema), None)?;
6847 let scan = scan.try_with_sort_information(vec![
6848 LexOrdering::new([PhysicalSortExpr::new(
6849 col("b", schema.as_ref())?,
6850 SortOptions::default().desc(),
6851 )])
6852 .unwrap(),
6853 ])?;
6854
6855 let aggr = Arc::new(AggregateExec::try_new(
6856 AggregateMode::Single,
6857 PhysicalGroupBy::new(
6858 vec![
6859 (col("b", schema.as_ref())?, "b".to_string()),
6860 (col("c", schema.as_ref())?, "c".to_string()),
6861 ],
6862 vec![],
6863 vec![vec![false, false]],
6864 false,
6865 ),
6866 vec![Arc::new(
6867 AggregateExprBuilder::new(sum_udaf(), vec![col("c", schema.as_ref())?])
6868 .schema(Arc::clone(&schema))
6869 .alias("SUM(c)")
6870 .build()?,
6871 )],
6872 vec![None],
6873 Arc::new(scan) as Arc<dyn ExecutionPlan>,
6874 Arc::clone(&schema),
6875 )?);
6876
6877 let task_ctx = new_spill_ctx(1, 600);
6878 let result = collect(aggr.execute(0, Arc::clone(&task_ctx))?).await?;
6879 assert_spill_count_metric(true, aggr);
6880
6881 allow_duplicates! {
6882 assert_snapshot!(batches_to_string(&result), @r"
6883 +---+---+--------+
6884 | b | c | SUM(c) |
6885 +---+---+--------+
6886 | 2 | 1 | 1 |
6887 | 1 | 1 | 1 |
6888 | 0 | 1 | 1 |
6889 +---+---+--------+
6890 ");
6891 }
6892 Ok(())
6893 }
6894
6895 #[tokio::test]
6899 async fn test_sort_reservation_fails_during_spill() -> Result<()> {
6900 let schema = Arc::new(Schema::new(vec![
6901 Field::new("g", DataType::Int64, false),
6902 Field::new("a", DataType::Float64, false),
6903 Field::new("b", DataType::Float64, false),
6904 Field::new("c", DataType::Float64, false),
6905 Field::new("d", DataType::Float64, false),
6906 Field::new("e", DataType::Float64, false),
6907 ]));
6908
6909 let batches = vec![vec![
6910 RecordBatch::try_new(
6911 Arc::clone(&schema),
6912 vec![
6913 Arc::new(Int64Array::from(vec![1])),
6914 Arc::new(Float64Array::from(vec![10.0])),
6915 Arc::new(Float64Array::from(vec![20.0])),
6916 Arc::new(Float64Array::from(vec![30.0])),
6917 Arc::new(Float64Array::from(vec![40.0])),
6918 Arc::new(Float64Array::from(vec![50.0])),
6919 ],
6920 )?,
6921 RecordBatch::try_new(
6922 Arc::clone(&schema),
6923 vec![
6924 Arc::new(Int64Array::from(vec![2])),
6925 Arc::new(Float64Array::from(vec![11.0])),
6926 Arc::new(Float64Array::from(vec![21.0])),
6927 Arc::new(Float64Array::from(vec![31.0])),
6928 Arc::new(Float64Array::from(vec![41.0])),
6929 Arc::new(Float64Array::from(vec![51.0])),
6930 ],
6931 )?,
6932 RecordBatch::try_new(
6933 Arc::clone(&schema),
6934 vec![
6935 Arc::new(Int64Array::from(vec![3])),
6936 Arc::new(Float64Array::from(vec![12.0])),
6937 Arc::new(Float64Array::from(vec![22.0])),
6938 Arc::new(Float64Array::from(vec![32.0])),
6939 Arc::new(Float64Array::from(vec![42.0])),
6940 Arc::new(Float64Array::from(vec![52.0])),
6941 ],
6942 )?,
6943 ]];
6944
6945 let scan = TestMemoryExec::try_new(&batches, Arc::clone(&schema), None)?;
6946
6947 let aggr = Arc::new(AggregateExec::try_new(
6948 AggregateMode::Single,
6949 PhysicalGroupBy::new(
6950 vec![(col("g", schema.as_ref())?, "g".to_string())],
6951 vec![],
6952 vec![vec![false]],
6953 false,
6954 ),
6955 vec![
6956 Arc::new(
6957 AggregateExprBuilder::new(
6958 avg_udaf(),
6959 vec![col("a", schema.as_ref())?],
6960 )
6961 .schema(Arc::clone(&schema))
6962 .alias("AVG(a)")
6963 .build()?,
6964 ),
6965 Arc::new(
6966 AggregateExprBuilder::new(
6967 avg_udaf(),
6968 vec![col("b", schema.as_ref())?],
6969 )
6970 .schema(Arc::clone(&schema))
6971 .alias("AVG(b)")
6972 .build()?,
6973 ),
6974 Arc::new(
6975 AggregateExprBuilder::new(
6976 avg_udaf(),
6977 vec![col("c", schema.as_ref())?],
6978 )
6979 .schema(Arc::clone(&schema))
6980 .alias("AVG(c)")
6981 .build()?,
6982 ),
6983 Arc::new(
6984 AggregateExprBuilder::new(
6985 avg_udaf(),
6986 vec![col("d", schema.as_ref())?],
6987 )
6988 .schema(Arc::clone(&schema))
6989 .alias("AVG(d)")
6990 .build()?,
6991 ),
6992 Arc::new(
6993 AggregateExprBuilder::new(
6994 avg_udaf(),
6995 vec![col("e", schema.as_ref())?],
6996 )
6997 .schema(Arc::clone(&schema))
6998 .alias("AVG(e)")
6999 .build()?,
7000 ),
7001 ],
7002 vec![None, None, None, None, None],
7003 Arc::new(scan) as Arc<dyn ExecutionPlan>,
7004 Arc::clone(&schema),
7005 )?);
7006
7007 let task_ctx = new_spill_ctx(1, 500);
7010 let result = collect(aggr.execute(0, Arc::clone(&task_ctx))?).await;
7011
7012 match &result {
7013 Ok(_) => panic!("Expected ResourcesExhausted error but query succeeded"),
7014 Err(e) => {
7015 let root = e.find_root();
7016 assert!(
7017 matches!(root, DataFusionError::ResourcesExhausted(_)),
7018 "Expected ResourcesExhausted, got: {root}",
7019 );
7020 }
7021 }
7022
7023 Ok(())
7024 }
7025
7026 async fn evaluate_partial_reduce(
7034 groups: PhysicalGroupBy,
7035 aggregates: Vec<Arc<AggregateFunctionExpr>>,
7036 partition_1_and_2_batches: [Vec<RecordBatch>; 2],
7037 ) -> Result<Vec<RecordBatch>> {
7038 let schema = partition_1_and_2_batches
7039 .iter()
7040 .flatten()
7041 .next()
7042 .expect("Must have at least 1 batch")
7043 .schema();
7044
7045 let [partition_1, partition_2] = partition_1_and_2_batches;
7046
7047 let input1 =
7049 TestMemoryExec::try_new_exec(&[partition_1], Arc::clone(&schema), None)?;
7050 let partial1 = Arc::new(AggregateExec::try_new(
7051 AggregateMode::Partial,
7052 groups.clone(),
7053 aggregates.clone(),
7054 vec![None; aggregates.len()],
7055 input1,
7056 Arc::clone(&schema),
7057 )?);
7058
7059 let input2 =
7061 TestMemoryExec::try_new_exec(&[partition_2], Arc::clone(&schema), None)?;
7062 let partial2 = Arc::new(AggregateExec::try_new(
7063 AggregateMode::Partial,
7064 groups.clone(),
7065 aggregates.clone(),
7066 vec![None; aggregates.len()],
7067 input2,
7068 Arc::clone(&schema),
7069 )?);
7070
7071 let task_ctx = Arc::new(TaskContext::default());
7073 let partial_result1 =
7074 crate::collect(Arc::clone(&partial1) as _, Arc::clone(&task_ctx)).await?;
7075 let partial_result2 =
7076 crate::collect(Arc::clone(&partial2) as _, Arc::clone(&task_ctx)).await?;
7077
7078 let partial_schema = partial1.schema();
7080
7081 let combined_input = TestMemoryExec::try_new_exec(
7083 &[partial_result1, partial_result2],
7084 Arc::clone(&partial_schema),
7085 None,
7086 )?;
7087 let coalesced = Arc::new(CoalescePartitionsExec::new(combined_input));
7089
7090 let partial_reduce = Arc::new(AggregateExec::try_new(
7091 AggregateMode::PartialReduce,
7092 groups.clone(),
7093 aggregates.clone(),
7094 vec![None; aggregates.len()],
7095 coalesced,
7096 Arc::clone(&partial_schema),
7097 )?);
7098
7099 assert_eq!(partial_reduce.schema(), partial_schema);
7102
7103 let reduce_result =
7105 crate::collect(Arc::clone(&partial_reduce) as _, Arc::clone(&task_ctx))
7106 .await?;
7107
7108 let final_input = TestMemoryExec::try_new_exec(
7110 &[reduce_result],
7111 Arc::clone(&partial_schema),
7112 None,
7113 )?;
7114 let final_agg = Arc::new(AggregateExec::try_new(
7115 AggregateMode::Final,
7116 groups.clone(),
7117 aggregates.clone(),
7118 vec![None; aggregates.len()],
7119 final_input,
7120 Arc::clone(&partial_schema),
7121 )?);
7122
7123 let result = crate::collect(final_agg, Arc::clone(&task_ctx)).await?;
7124
7125 Ok(result)
7126 }
7127
7128 async fn run_partial_reduce_pipeline<F>(
7135 build_aggregates: F,
7136 ) -> Result<Vec<RecordBatch>>
7137 where
7138 F: FnOnce(&Arc<Schema>) -> Result<Vec<Arc<AggregateFunctionExpr>>>,
7139 {
7140 let schema = Arc::new(Schema::new(vec![
7141 Field::new("a", DataType::UInt32, false),
7142 Field::new("b", DataType::Float64, false),
7143 ]));
7144
7145 let batch1 = RecordBatch::try_new(
7148 Arc::clone(&schema),
7149 vec![
7150 Arc::new(UInt32Array::from(vec![1, 2, 3])),
7151 Arc::new(Float64Array::from(vec![10.0, 20.0, 30.0])),
7152 ],
7153 )?;
7154 let batch2 = RecordBatch::try_new(
7155 Arc::clone(&schema),
7156 vec![
7157 Arc::new(UInt32Array::from(vec![1, 2, 3])),
7158 Arc::new(Float64Array::from(vec![40.0, 50.0, 60.0])),
7159 ],
7160 )?;
7161
7162 let groups =
7163 PhysicalGroupBy::new_single(vec![(col("a", &schema)?, "a".to_string())]);
7164 let aggregates = build_aggregates(&schema)?;
7165
7166 evaluate_partial_reduce(groups, aggregates, [vec![batch1], vec![batch2]]).await
7167 }
7168
7169 #[tokio::test]
7189 async fn test_partial_reduce_with_single_state_field_and_single_input_arg()
7190 -> Result<()> {
7191 let result = run_partial_reduce_pipeline(|schema| {
7192 Ok(vec![Arc::new(
7193 AggregateExprBuilder::new(sum_udaf(), vec![col("b", schema)?])
7194 .schema(Arc::clone(schema))
7195 .alias("SUM(b)")
7196 .build()?,
7197 )])
7198 })
7199 .await?;
7200
7201 assert_snapshot!(batches_to_sort_string(&result), @r"
7203 +---+--------+
7204 | a | SUM(b) |
7205 +---+--------+
7206 | 1 | 50.0 |
7207 | 2 | 70.0 |
7208 | 3 | 90.0 |
7209 +---+--------+
7210 ");
7211
7212 Ok(())
7213 }
7214
7215 #[tokio::test]
7219 async fn test_partial_reduce_with_multiple_state_fields_and_single_input_arg()
7220 -> Result<()> {
7221 let result = run_partial_reduce_pipeline(|schema| {
7222 Ok(vec![Arc::new(
7223 AggregateExprBuilder::new(avg_udaf(), vec![col("b", schema)?])
7224 .schema(Arc::clone(schema))
7225 .alias("AVG(b)")
7226 .build()?,
7227 )])
7228 })
7229 .await?;
7230
7231 assert_snapshot!(batches_to_sort_string(&result), @r"
7232 +---+--------+
7233 | a | AVG(b) |
7234 +---+--------+
7235 | 1 | 25.0 |
7236 | 2 | 35.0 |
7237 | 3 | 45.0 |
7238 +---+--------+
7239 ");
7240
7241 Ok(())
7242 }
7243
7244 #[tokio::test]
7247 async fn test_partial_reduce_with_more_state_fields_than_input_args() -> Result<()> {
7248 let result = run_partial_reduce_pipeline(|schema| {
7249 Ok(vec![Arc::new(
7250 AggregateExprBuilder::new(
7251 approx_percentile_cont_udaf(),
7252 vec![col("b", schema)?, lit(0.75f32)],
7253 )
7254 .schema(Arc::clone(schema))
7255 .alias("approx_percentile_cont(b, 0.75)")
7256 .build()?,
7257 )])
7258 })
7259 .await?;
7260
7261 assert_snapshot!(batches_to_sort_string(&result), @r"
7262 +---+---------------------------------+
7263 | a | approx_percentile_cont(b, 0.75) |
7264 +---+---------------------------------+
7265 | 1 | 40.0 |
7266 | 2 | 50.0 |
7267 | 3 | 60.0 |
7268 +---+---------------------------------+
7269 ");
7270
7271 Ok(())
7272 }
7273
7274 #[tokio::test]
7287 async fn test_partial_reduce_with_single_state_field_and_single_input_arg_using_unique_types()
7288 -> Result<()> {
7289 let result = run_partial_reduce_pipeline(|schema| {
7290 let udaf = Arc::new(AggregateUDF::from(InputTypeAssertingUdaf::new(
7291 vec![DataType::Float64],
7292 vec![DataType::Int32],
7293 DataType::Int64,
7294 )));
7295 Ok(vec![Arc::new(
7296 AggregateExprBuilder::new(udaf, vec![col("b", schema)?])
7297 .schema(Arc::clone(schema))
7298 .alias("input_type_asserting(b)")
7299 .build()?,
7300 )])
7301 })
7302 .await?;
7303
7304 assert_snapshot!(batches_to_sort_string(&result), @r"
7307 +---+-------------------------+
7308 | a | input_type_asserting(b) |
7309 +---+-------------------------+
7310 | 1 | 0 |
7311 | 2 | 0 |
7312 | 3 | 0 |
7313 +---+-------------------------+
7314 ");
7315
7316 Ok(())
7317 }
7318
7319 #[tokio::test]
7327 async fn test_partial_reduce_with_multiple_state_fields_and_single_input_arg_using_unique_types()
7328 -> Result<()> {
7329 let result = run_partial_reduce_pipeline(|schema| {
7330 let udaf = Arc::new(AggregateUDF::from(InputTypeAssertingUdaf::new(
7331 vec![DataType::Float64],
7332 vec![DataType::Int32, DataType::Utf8],
7333 DataType::Int64,
7334 )));
7335 Ok(vec![Arc::new(
7336 AggregateExprBuilder::new(udaf, vec![col("b", schema)?])
7337 .schema(Arc::clone(schema))
7338 .alias("input_type_asserting(b)")
7339 .build()?,
7340 )])
7341 })
7342 .await?;
7343
7344 assert_snapshot!(batches_to_sort_string(&result), @r"
7345 +---+-------------------------+
7346 | a | input_type_asserting(b) |
7347 +---+-------------------------+
7348 | 1 | 0 |
7349 | 2 | 0 |
7350 | 3 | 0 |
7351 +---+-------------------------+
7352 ");
7353
7354 Ok(())
7355 }
7356
7357 #[tokio::test]
7366 async fn test_partial_reduce_with_more_state_fields_than_input_args_using_unique_types()
7367 -> Result<()> {
7368 let result = run_partial_reduce_pipeline(|schema| {
7369 let udaf = Arc::new(AggregateUDF::from(InputTypeAssertingUdaf::new(
7370 vec![DataType::Float64, DataType::Date32],
7371 vec![DataType::Int32, DataType::Utf8, DataType::Boolean],
7372 DataType::Int64,
7373 )));
7374 Ok(vec![Arc::new(
7375 AggregateExprBuilder::new(
7376 udaf,
7377 vec![col("b", schema)?, lit(ScalarValue::Date32(Some(1)))],
7378 )
7379 .schema(Arc::clone(schema))
7380 .alias("input_type_asserting(b, lit)")
7381 .build()?,
7382 )])
7383 })
7384 .await?;
7385
7386 assert_snapshot!(batches_to_sort_string(&result), @r"
7387 +---+------------------------------+
7388 | a | input_type_asserting(b, lit) |
7389 +---+------------------------------+
7390 | 1 | 0 |
7391 | 2 | 0 |
7392 | 3 | 0 |
7393 +---+------------------------------+
7394 ");
7395
7396 Ok(())
7397 }
7398
7399 #[tokio::test]
7406 async fn test_partial_reduce_with_5_input_args_and_2_state_fields_using_unique_types()
7407 -> Result<()> {
7408 let result = run_partial_reduce_pipeline(|schema| {
7409 let udaf = Arc::new(AggregateUDF::from(InputTypeAssertingUdaf::new(
7410 vec![
7411 DataType::Float64,
7412 DataType::Date32,
7413 DataType::UInt16,
7414 DataType::Boolean,
7415 DataType::Int32,
7416 ],
7417 vec![DataType::Utf8, DataType::Int64],
7418 DataType::Float32,
7419 )));
7420 Ok(vec![Arc::new(
7421 AggregateExprBuilder::new(
7422 udaf,
7423 vec![
7424 col("b", schema)?,
7425 lit(ScalarValue::Date32(Some(1))),
7426 lit(ScalarValue::UInt16(Some(1))),
7427 lit(ScalarValue::Boolean(Some(false))),
7428 lit(ScalarValue::Int32(Some(1))),
7429 ],
7430 )
7431 .schema(Arc::clone(schema))
7432 .alias("input_type_asserting(b, l1, l2, l3, l4)")
7433 .build()?,
7434 )])
7435 })
7436 .await?;
7437
7438 assert_snapshot!(batches_to_sort_string(&result), @r"
7439 +---+-----------------------------------------+
7440 | a | input_type_asserting(b, l1, l2, l3, l4) |
7441 +---+-----------------------------------------+
7442 | 1 | 0.0 |
7443 | 2 | 0.0 |
7444 | 3 | 0.0 |
7445 +---+-----------------------------------------+
7446 ");
7447
7448 Ok(())
7449 }
7450
7451 #[tokio::test]
7458 async fn test_partial_reduce_with_2_input_args_and_5_state_fields_using_unique_types()
7459 -> Result<()> {
7460 let result = run_partial_reduce_pipeline(|schema| {
7461 let udaf = Arc::new(AggregateUDF::from(InputTypeAssertingUdaf::new(
7462 vec![DataType::Float64, DataType::Date32],
7463 vec![
7464 DataType::Boolean,
7465 DataType::Int32,
7466 DataType::Utf8,
7467 DataType::Int64,
7468 DataType::UInt16,
7469 ],
7470 DataType::Float32,
7471 )));
7472 Ok(vec![Arc::new(
7473 AggregateExprBuilder::new(
7474 udaf,
7475 vec![col("b", schema)?, lit(ScalarValue::Date32(Some(1)))],
7476 )
7477 .schema(Arc::clone(schema))
7478 .alias("input_type_asserting(b, lit)")
7479 .build()?,
7480 )])
7481 })
7482 .await?;
7483
7484 assert_snapshot!(batches_to_sort_string(&result), @r"
7485 +---+------------------------------+
7486 | a | input_type_asserting(b, lit) |
7487 +---+------------------------------+
7488 | 1 | 0.0 |
7489 | 2 | 0.0 |
7490 | 3 | 0.0 |
7491 +---+------------------------------+
7492 ");
7493
7494 Ok(())
7495 }
7496
7497 #[derive(Debug, PartialEq, Eq, Hash)]
7506 struct InputTypeAssertingUdaf {
7507 signature: Signature,
7508 input_types: Vec<DataType>,
7509 state_types: Vec<DataType>,
7510 output_type: DataType,
7511 }
7512
7513 fn assert_data_types(
7514 what: &str,
7515 expected: &[DataType],
7516 actual: &[DataType],
7517 ) -> Result<()> {
7518 if actual != expected {
7519 return internal_err!(
7520 "InputTypeAssertingUdaf: {} expected types {:?} but got {:?} — a regression is leaking the wrong types into the accumulator contract",
7521 what,
7522 expected,
7523 actual
7524 );
7525 }
7526 Ok(())
7527 }
7528
7529 fn zero_scalar_for(dt: &DataType) -> Result<ScalarValue> {
7533 match dt {
7534 DataType::Boolean => Ok(ScalarValue::Boolean(Some(false))),
7535 DataType::Int32 => Ok(ScalarValue::Int32(Some(0))),
7536 DataType::Int64 => Ok(ScalarValue::Int64(Some(0))),
7537 DataType::UInt16 => Ok(ScalarValue::UInt16(Some(0))),
7538 DataType::Float32 => Ok(ScalarValue::Float32(Some(0.0))),
7539 DataType::Utf8 => Ok(ScalarValue::Utf8(Some(String::new()))),
7540 other => internal_err!(
7541 "InputTypeAssertingUdaf: no zero ScalarValue registered for {other:?} \
7542 — extend `zero_scalar_for` when adding a new state/output type"
7543 ),
7544 }
7545 }
7546
7547 impl InputTypeAssertingUdaf {
7548 fn new(
7549 input_types: Vec<DataType>,
7550 state_types: Vec<DataType>,
7551 output_type: DataType,
7552 ) -> Self {
7553 assert!(
7556 all_pairwise_distinct(&input_types, &state_types, &output_type),
7557 "InputTypeAssertingUdaf::new: input ({input_types:?}), state \
7558 ({state_types:?}), and output ({output_type:?}) types must be \
7559 pairwise-disjoint to avoid accidental passes",
7560 );
7561 Self {
7562 signature: Signature::exact(input_types.clone(), Volatility::Immutable),
7563 input_types,
7564 state_types,
7565 output_type,
7566 }
7567 }
7568 }
7569
7570 fn all_pairwise_distinct(
7572 inputs: &[DataType],
7573 states: &[DataType],
7574 output: &DataType,
7575 ) -> bool {
7576 let mut seen = HashSet::new();
7577 for dt in inputs
7578 .iter()
7579 .chain(states.iter())
7580 .chain(std::iter::once(output))
7581 {
7582 if !seen.insert(dt) {
7583 return false;
7584 }
7585 }
7586 true
7587 }
7588
7589 impl AggregateUDFImpl for InputTypeAssertingUdaf {
7590 fn name(&self) -> &str {
7591 "input_type_asserting"
7592 }
7593
7594 fn signature(&self) -> &Signature {
7595 &self.signature
7596 }
7597
7598 fn return_type(&self, arg_types: &[DataType]) -> Result<DataType> {
7599 assert_data_types("return_type(arg_types)", &self.input_types, arg_types)?;
7600 Ok(self.output_type.clone())
7601 }
7602
7603 fn state_fields(&self, args: StateFieldsArgs) -> Result<Vec<FieldRef>> {
7604 let actual: Vec<DataType> = args
7605 .input_fields
7606 .iter()
7607 .map(|f| f.data_type().clone())
7608 .collect();
7609 assert_data_types(
7610 "state_fields(args.input_fields)",
7611 &self.input_types,
7612 &actual,
7613 )?;
7614 Ok(self
7615 .state_types
7616 .iter()
7617 .enumerate()
7618 .map(|(i, dt)| {
7619 Field::new(format!("{}[s{i}]", args.name), dt.clone(), true).into()
7620 })
7621 .collect())
7622 }
7623
7624 fn accumulator(&self, acc_args: AccumulatorArgs) -> Result<Box<dyn Accumulator>> {
7625 let actual: Vec<DataType> = acc_args
7626 .expr_fields
7627 .iter()
7628 .map(|f| f.data_type().clone())
7629 .collect();
7630 assert_data_types(
7631 "accumulator(acc_args.expr_fields)",
7632 &self.input_types,
7633 &actual,
7634 )?;
7635 Ok(Box::new(InputTypeAssertingAccumulator {
7636 input_types: self.input_types.clone(),
7637 state_types: self.state_types.clone(),
7638 output_type: self.output_type.clone(),
7639 }))
7640 }
7641 }
7642
7643 #[derive(Debug)]
7652 struct InputTypeAssertingAccumulator {
7653 input_types: Vec<DataType>,
7654 state_types: Vec<DataType>,
7655 output_type: DataType,
7656 }
7657
7658 impl Accumulator for InputTypeAssertingAccumulator {
7659 fn update_batch(&mut self, values: &[ArrayRef]) -> Result<()> {
7660 let actual: Vec<DataType> =
7661 values.iter().map(|a| a.data_type().clone()).collect();
7662 assert_data_types("update_batch(values)", &self.input_types, &actual)
7663 }
7664
7665 fn evaluate(&mut self) -> Result<ScalarValue> {
7666 zero_scalar_for(&self.output_type)
7667 }
7668
7669 fn size(&self) -> usize {
7670 size_of_val(self)
7671 }
7672
7673 fn state(&mut self) -> Result<Vec<ScalarValue>> {
7674 self.state_types.iter().map(zero_scalar_for).collect()
7675 }
7676
7677 fn merge_batch(&mut self, states: &[ArrayRef]) -> Result<()> {
7678 let actual: Vec<DataType> =
7679 states.iter().map(|a| a.data_type().clone()).collect();
7680 assert_data_types("merge_batch(states)", &self.state_types, &actual)
7681 }
7682 }
7683
7684 #[derive(Debug, PartialEq, Eq, Hash)]
7685 struct NoFirstEmitUdaf {
7686 signature: Signature,
7687 }
7688
7689 impl NoFirstEmitUdaf {
7690 fn new() -> Self {
7691 Self {
7692 signature: Signature::exact(vec![DataType::Int32], Volatility::Immutable),
7693 }
7694 }
7695 }
7696
7697 impl AggregateUDFImpl for NoFirstEmitUdaf {
7698 fn name(&self) -> &str {
7699 "no_first_emit"
7700 }
7701
7702 fn signature(&self) -> &Signature {
7703 &self.signature
7704 }
7705
7706 fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
7707 Ok(DataType::Int64)
7708 }
7709
7710 fn state_fields(&self, args: StateFieldsArgs) -> Result<Vec<FieldRef>> {
7711 Ok(vec![Arc::new(Field::new(
7712 format!("{}[count]", args.name),
7713 DataType::Int64,
7714 false,
7715 ))])
7716 }
7717
7718 fn accumulator(
7719 &self,
7720 _acc_args: AccumulatorArgs,
7721 ) -> Result<Box<dyn Accumulator>> {
7722 Ok(Box::new(NoFirstEmitAccumulator))
7723 }
7724
7725 fn groups_accumulator_supported(&self, _args: AccumulatorArgs) -> bool {
7726 true
7727 }
7728
7729 fn create_groups_accumulator(
7730 &self,
7731 _args: AccumulatorArgs,
7732 ) -> Result<Box<dyn GroupsAccumulator>> {
7733 Ok(Box::new(NoFirstEmitGroupsAccumulator { counts: vec![] }))
7734 }
7735 }
7736
7737 #[derive(Debug)]
7738 struct NoFirstEmitAccumulator;
7739
7740 impl Accumulator for NoFirstEmitAccumulator {
7741 fn update_batch(&mut self, _values: &[ArrayRef]) -> Result<()> {
7742 Ok(())
7743 }
7744
7745 fn evaluate(&mut self) -> Result<ScalarValue> {
7746 Ok(ScalarValue::Int64(Some(0)))
7747 }
7748
7749 fn size(&self) -> usize {
7750 size_of_val(self)
7751 }
7752
7753 fn state(&mut self) -> Result<Vec<ScalarValue>> {
7754 Ok(vec![ScalarValue::Int64(Some(0))])
7755 }
7756
7757 fn merge_batch(&mut self, _states: &[ArrayRef]) -> Result<()> {
7758 Ok(())
7759 }
7760 }
7761
7762 #[derive(Debug)]
7763 struct NoFirstEmitGroupsAccumulator {
7764 counts: Vec<i64>,
7765 }
7766
7767 impl NoFirstEmitGroupsAccumulator {
7768 fn emit_counts(&mut self, emit_to: EmitTo) -> Result<ArrayRef> {
7769 match emit_to {
7770 EmitTo::All => {
7771 let counts = std::mem::take(&mut self.counts);
7772 Ok(Arc::new(Int64Array::from(counts)))
7773 }
7774 EmitTo::First(_) => internal_err!(
7775 "partial grouped aggregate output must materialize with EmitTo::All before slicing"
7776 ),
7777 }
7778 }
7779 }
7780
7781 impl GroupsAccumulator for NoFirstEmitGroupsAccumulator {
7782 fn update_batch(
7783 &mut self,
7784 _values: &[ArrayRef],
7785 group_indices: &[usize],
7786 _opt_filter: Option<&BooleanArray>,
7787 total_num_groups: usize,
7788 ) -> Result<()> {
7789 self.counts.resize(total_num_groups, 0);
7790 for group_index in group_indices {
7791 self.counts[*group_index] += 1;
7792 }
7793 Ok(())
7794 }
7795
7796 fn evaluate(&mut self, emit_to: EmitTo) -> Result<ArrayRef> {
7797 self.emit_counts(emit_to)
7798 }
7799
7800 fn state(&mut self, emit_to: EmitTo) -> Result<Vec<ArrayRef>> {
7801 Ok(vec![self.emit_counts(emit_to)?])
7802 }
7803
7804 fn convert_to_state(
7805 &self,
7806 values: &[ArrayRef],
7807 opt_filter: Option<&BooleanArray>,
7808 ) -> Result<Vec<ArrayRef>> {
7809 assert_eq!(values.len(), 1, "one argument to convert_to_state");
7810 let counts = match opt_filter {
7811 Some(filter) => filter
7812 .iter()
7813 .map(|value| i64::from(value.unwrap_or(false)))
7814 .collect::<Vec<_>>(),
7815 None => vec![1; values[0].len()],
7816 };
7817 Ok(vec![Arc::new(Int64Array::from(counts))])
7818 }
7819
7820 fn merge_batch(
7821 &mut self,
7822 _values: &[ArrayRef],
7823 _group_indices: &[usize],
7824 _total_num_groups: usize,
7825 ) -> Result<()> {
7826 Ok(())
7827 }
7828
7829 fn size(&self) -> usize {
7830 size_of_val(self) + self.counts.capacity() * size_of::<i64>()
7831 }
7832 }
7833
7834 #[test]
7836 fn test_with_dynamic_filter() -> Result<()> {
7837 let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int64, false)]));
7838 let child = Arc::new(EmptyExec::new(Arc::clone(&schema)));
7839
7840 let agg = AggregateExec::try_new(
7842 AggregateMode::Partial,
7843 PhysicalGroupBy::new_single(vec![]),
7844 vec![Arc::new(
7845 AggregateExprBuilder::new(min_udaf(), vec![col("a", &schema)?])
7846 .schema(Arc::clone(&schema))
7847 .alias("min_a")
7848 .build()?,
7849 )],
7850 vec![None],
7851 child,
7852 Arc::clone(&schema),
7853 )?;
7854
7855 let new_df = Arc::new(DynamicFilterPhysicalExpr::new(
7858 vec![col("a", &schema)?],
7859 lit(false),
7860 ));
7861 let agg = agg.with_dynamic_filter_expr(Arc::clone(&new_df))?;
7862 let produced = agg.dynamic_expressions_produced();
7863 assert_eq!(produced.len(), 1);
7864 assert_eq!(produced[0].expression_id(), new_df.expression_id());
7865
7866 let swapped = produced[0]
7868 .downcast_ref::<DynamicFilterPhysicalExpr>()
7869 .expect("produced expression should be a DynamicFilterPhysicalExpr")
7870 .current()?;
7871 assert_eq!(format!("{swapped}"), format!("{}", lit(false)));
7872
7873 let new_df_as_pexpr: Arc<dyn PhysicalExpr> =
7876 Arc::<DynamicFilterPhysicalExpr>::clone(&new_df);
7877 let remapped_pexpr =
7878 new_df_as_pexpr.with_new_children(vec![col("a", &schema)?])?;
7879 let Ok(remapped_df) = (remapped_pexpr as Arc<dyn std::any::Any + Send + Sync>)
7880 .downcast::<DynamicFilterPhysicalExpr>()
7881 else {
7882 panic!("should be DynamicFilterPhysicalExpr after with_new_children");
7883 };
7884 let _agg = agg.with_dynamic_filter_expr(remapped_df)?;
7887 Ok(())
7888 }
7889
7890 #[test]
7891 fn test_plan_contains_expression_id_recurses_plans_and_expressions() -> Result<()> {
7892 let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int64, false)]));
7893 let empty: Arc<dyn ExecutionPlan> = Arc::new(EmptyExec::new(Arc::clone(&schema)));
7894 let dynamic_filter = Arc::new(DynamicFilterPhysicalExpr::new(
7895 vec![col("a", &schema)?],
7896 lit(true),
7897 ));
7898 let expression_id = dynamic_filter
7899 .expression_id()
7900 .expect("dynamic filters always have an expression ID");
7901
7902 assert!(!plan_contains_expression_id(&empty, expression_id)?);
7903
7904 let dynamic_filter_expr: Arc<dyn PhysicalExpr> =
7905 Arc::<DynamicFilterPhysicalExpr>::clone(&dynamic_filter);
7906 let predicate: Arc<dyn PhysicalExpr> =
7907 Arc::new(NotExpr::new(dynamic_filter_expr));
7908 let filter: Arc<dyn ExecutionPlan> =
7909 Arc::new(FilterExecBuilder::new(predicate, empty).build()?);
7910 let projection: Arc<dyn ExecutionPlan> = Arc::new(ProjectionExec::try_new(
7911 [ProjectionExpr::new_from_expression(
7912 col("a", &schema)?,
7913 &schema,
7914 )?],
7915 filter,
7916 )?);
7917
7918 assert!(plan_contains_expression_id(&projection, expression_id)?);
7919 Ok(())
7920 }
7921
7922 #[test]
7924 fn test_with_dynamic_filter_error_unsupported() -> Result<()> {
7925 let schema = Arc::new(Schema::new(vec![
7926 Field::new("a", DataType::Int64, false),
7927 Field::new("b", DataType::Int64, false),
7928 ]));
7929 let child = Arc::new(EmptyExec::new(Arc::clone(&schema)));
7930
7931 let agg = AggregateExec::try_new(
7933 AggregateMode::Final,
7934 PhysicalGroupBy::new_single(vec![(col("a", &schema)?, "a".to_string())]),
7935 vec![Arc::new(
7936 AggregateExprBuilder::new(sum_udaf(), vec![col("b", &schema)?])
7937 .schema(Arc::clone(&schema))
7938 .alias("sum_b")
7939 .build()?,
7940 )],
7941 vec![None],
7942 child,
7943 Arc::clone(&schema),
7944 )?;
7945 assert!(agg.dynamic_expressions_produced().is_empty());
7946
7947 let df = Arc::new(DynamicFilterPhysicalExpr::new(
7948 vec![col("a", &schema)?],
7949 lit(true),
7950 ));
7951 assert!(agg.with_dynamic_filter_expr(df).is_err());
7952 Ok(())
7953 }
7954
7955 #[test]
7957 fn test_with_dynamic_filter_error_column_mismatch() -> Result<()> {
7958 let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int64, false)]));
7959 let child = Arc::new(EmptyExec::new(Arc::clone(&schema)));
7960
7961 let agg = AggregateExec::try_new(
7962 AggregateMode::Partial,
7963 PhysicalGroupBy::new_single(vec![]),
7964 vec![Arc::new(
7965 AggregateExprBuilder::new(min_udaf(), vec![col("a", &schema)?])
7966 .schema(Arc::clone(&schema))
7967 .alias("min_a")
7968 .build()?,
7969 )],
7970 vec![None],
7971 child,
7972 Arc::clone(&schema),
7973 )?;
7974
7975 let df = Arc::new(DynamicFilterPhysicalExpr::new(
7976 vec![Arc::new(Column::new("bad", 99)) as _],
7977 lit(true),
7978 ));
7979 assert!(agg.with_dynamic_filter_expr(df).is_err());
7980 Ok(())
7981 }
7982}