Skip to main content

datafusion_physical_plan/aggregates/
mod.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! Aggregate functionality
19//!
20//! # Aggregate planning
21//!
22//! DataFusion selects different aggregate implementations (streams) based on the
23//! query shape and configuration. This section provides an overview of the
24//! available stream variants.
25//!
26//! See each stream's documentation for details.
27//!
28//! ## 1. Two-stage hash aggregation
29//!
30//! Two-stage hash aggregation is used for regular parallel execution.
31//!
32//! The input passes through three execution operators to produce the final
33//! aggregation result:
34//!
35//! 1. Partial aggregation reads the input and produces partial states. It
36//!    aggregates independently within each partition, which usually reduces
37//!    cardinality before the later shuffle.
38//! 2. Hash repartitioning on the group keys sends all partial states for each
39//!    group to the same output partition for final aggregation.
40//! 3. Final aggregation reads the partial states, combines them, and emits the
41//!    final results.
42//!
43//! ```text
44//! AggregateExec (final)
45//!   RepartitionExec (hash by group keys)
46//!     AggregateExec (partial)
47//! ```
48//!
49//! See [`PartialHashAggregateStream`] and [`FinalHashAggregateStream`] for details.
50//!
51//! ### Ordering optimization
52//!
53//! When the input is ordered by the group key, an ordered fast path is used. It
54//! uses a similar two-stage hash aggregation with an early-emission optimization.
55//!
56//! ```text
57//! AggregateExec (final, ordered)
58//!   RepartitionExec (hash by group keys, order-preserving)
59//!     AggregateExec (partial, ordered)
60//! ```
61//!
62//! See [`OrderedPartialAggregateStream`] and [`OrderedFinalAggregateStream`] for
63//! details.
64//!
65//! Related configuration:
66//!
67//! - [`datafusion.execution.target_partitions`](datafusion_common::config::ExecutionOptions::target_partitions)
68//! - [`datafusion.optimizer.repartition_aggregations`](datafusion_common::config::OptimizerOptions::repartition_aggregations)
69//! - [`datafusion.optimizer.prefer_existing_sort`](datafusion_common::config::OptimizerOptions::prefer_existing_sort)
70//!
71//! ## 2. Single-stage hash aggregation
72//!
73//! When there is a single partition, or the aggregation input is already
74//! key-partitioned (e.g., a data source has existing range partitioning),
75//! `Single` mode aggregation is used.
76//!
77//! It takes raw input and directly produces the final result.
78//!
79//! ```text
80//! AggregateExec (mode=Single or SinglePartitioned)
81//!   input
82//! ```
83//!
84//! See [`SingleHashAggregateStream`] for details.
85//!
86//! Related configuration:
87//!
88//! - [`datafusion.execution.target_partitions`](datafusion_common::config::ExecutionOptions::target_partitions)
89//! - [`datafusion.optimizer.repartition_aggregations`](datafusion_common::config::OptimizerOptions::repartition_aggregations)
90//!
91//! ## 3. Aggregation without grouping expressions
92//!
93//! A global aggregate maintains one accumulator set per input partition rather
94//! than a hash table of groups. Partial stages compute local states and a final
95//! stage combines them into one output row:
96//!
97//! ```text
98//! AggregateExec (final, no-grouping)
99//!   CoalescePartitionsExec
100//!     AggregateExec (partial, no-grouping)
101//! ```
102//!
103//! Every stage without grouping expressions uses [`AggregateStream`]. This path
104//! is selected before the grouped-stream migration setting is considered.
105//!
106//! ## 4. Grouped TopK aggregation
107//!
108//! When a query only needs the best `N` groups, retaining every group in a hash
109//! table and sorting them afterward does unnecessary work. The optimizer pushes
110//! the sort limit and direction into the aggregate:
111//!
112//! ```text
113//! SortExec (fetch=N)
114//!   AggregateExec (limit=N, order=...)
115//!     input
116//! ```
117//!
118//! [`GroupedTopKAggregateStream`] keeps a bounded priority map for a single group
119//! key. It supports group-by-only queries and compatible `MIN` or `MAX`
120//! aggregates. An unordered group-by-only soft limit instead stays on the normal
121//! hash aggregation path.
122//!
123//! Related configuration:
124//!
125//! - [`datafusion.optimizer.enable_topk_aggregation`](datafusion_common::config::OptimizerOptions::enable_topk_aggregation)
126//! - [`datafusion.optimizer.enable_distinct_aggregation_soft_limit`](datafusion_common::config::OptimizerOptions::enable_distinct_aggregation_soft_limit)
127//!
128//! ## 5. Partial-reduce hash aggregation
129//!
130//! This implementation will not be planned by DataFusion SQL interface, it must be
131//! manually constructed at [`ExecutionPlan`] level.
132//!
133//! This mode is useful in a distributed setting.
134//!
135//! See [`PartialReduceHashAggregateStream`] for details.
136//!
137//! ## 6. Fallback grouped hash aggregation
138//!
139//! [`GroupedHashAggregateStream`] is the legacy implementation for several of the
140//! stream types above. It is being incrementally migrated to separate streams.
141//!
142//! See the issue for details: <https://github.com/apache/datafusion/issues/22710>
143#![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
221/// Returns true if TopK aggregation data structures support the provided key and value types.
222///
223/// This function checks whether both the key type (used for grouping) and value type
224/// (used in min/max aggregation) can be handled by the TopK aggregation heap and hash table.
225/// Supported types include Arrow primitives (integers, floats, decimals, intervals) and
226/// UTF-8 strings (`Utf8`, `LargeUtf8`, `Utf8View`).
227/// ```text
228pub 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
232/// Hard-coded seed for aggregations to ensure hash values differ from `RepartitionExec`, avoiding collisions.
233const AGGREGATION_HASH_SEED: datafusion_common::hash_utils::RandomState =
234    // This seed is chosen to be a large 64-bit number
235    datafusion_common::hash_utils::RandomState::with_seed(15395726432021054657);
236
237/// Whether an aggregate stage consumes raw input data or intermediate
238/// accumulator state from a previous aggregation stage.
239///
240/// See the [table on `AggregateMode`](AggregateMode#variants-and-their-inputoutput-modes)
241/// for how this relates to aggregate modes.
242#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
243pub enum AggregateInputMode {
244    /// The stage consumes raw, unaggregated input data and calls
245    /// [`Accumulator::update_batch`].
246    Raw,
247    /// The stage consumes intermediate accumulator state from a previous
248    /// aggregation stage and calls [`Accumulator::merge_batch`].
249    Partial,
250}
251
252/// Whether an aggregate stage produces intermediate accumulator state
253/// or final output values.
254///
255/// See the [table on `AggregateMode`](AggregateMode#variants-and-their-inputoutput-modes)
256/// for how this relates to aggregate modes.
257#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
258pub enum AggregateOutputMode {
259    /// The stage produces intermediate accumulator state, serialized via
260    /// [`Accumulator::state`].
261    Partial,
262    /// The stage produces final output values via
263    /// [`Accumulator::evaluate`].
264    Final,
265}
266
267/// Aggregation modes
268///
269/// See [`Accumulator::state`] for background information on multi-phase
270/// aggregation and how these modes are used.
271///
272/// # Variants and their input/output modes
273///
274/// Each variant can be characterized by its [`AggregateInputMode`] and
275/// [`AggregateOutputMode`]:
276///
277/// ```text
278///                       | Input: Raw data           | Input: Partial state
279/// Output: Final values  | Single, SinglePartitioned | Final, FinalPartitioned
280/// Output: Partial state | Partial                   | PartialReduce
281/// ```
282///
283/// Use [`AggregateMode::input_mode`] and [`AggregateMode::output_mode`]
284/// to query these properties.
285#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
286pub enum AggregateMode {
287    /// One of multiple layers of aggregation, any input partitioning
288    ///
289    /// Partial aggregate that can be applied in parallel across input
290    /// partitions.
291    ///
292    /// This is the first phase of a multi-phase aggregation.
293    Partial,
294    /// *Final* of multiple layers of aggregation, in exactly one partition
295    ///
296    /// Final aggregate that produces a single partition of output by combining
297    /// the output of multiple partial aggregates.
298    ///
299    /// This is the second phase of a multi-phase aggregation.
300    ///
301    /// This mode requires that the input is a single partition
302    ///
303    /// Note: Adjacent `Partial` and `Final` mode aggregation is equivalent to a `Single`
304    /// mode aggregation node. The `Final` mode is required since this is used in an
305    /// intermediate step. The [`CombinePartialFinalAggregate`] physical optimizer rule
306    /// will replace this combination with `Single` mode for more efficient execution.
307    ///
308    /// [`CombinePartialFinalAggregate`]: https://docs.rs/datafusion/latest/datafusion/physical_optimizer/combine_partial_final_agg/struct.CombinePartialFinalAggregate.html
309    Final,
310    /// *Final* of multiple layers of aggregation, input is *Partitioned*
311    ///
312    /// Final aggregate that works on pre-partitioned data.
313    ///
314    /// This mode requires that all rows with a particular grouping key are in
315    /// the same partitions, such as is the case with Hash repartitioning on the
316    /// group keys. If a group key is duplicated, duplicate groups would be
317    /// produced
318    FinalPartitioned,
319    /// *Single* layer of Aggregation, input is exactly one partition
320    ///
321    /// Applies the entire logical aggregation operation in a single operator,
322    /// as opposed to Partial / Final modes which apply the logical aggregation using
323    /// two operators.
324    ///
325    /// This mode requires that the input is a single partition (like Final)
326    Single,
327    /// *Single* layer of Aggregation, input is *Partitioned*
328    ///
329    /// Applies the entire logical aggregation operation in a single operator,
330    /// as opposed to Partial / Final modes which apply the logical aggregation
331    /// using two operators.
332    ///
333    /// This mode requires that the input has more than one partition, and is
334    /// partitioned by group key (like FinalPartitioned).
335    SinglePartitioned,
336    /// Combine multiple partial aggregations to produce a new partial
337    /// aggregation.
338    ///
339    /// Input is intermediate accumulator state (like Final), but output is
340    /// also intermediate accumulator state (like Partial). This enables
341    /// tree-reduce aggregation strategies where partial results from
342    /// multiple workers are combined in multiple stages before a final
343    /// evaluation.
344    ///
345    /// ```text
346    ///               Final
347    ///            /        \
348    ///     PartialReduce   PartialReduce
349    ///     /         \      /         \
350    ///  Partial   Partial  Partial   Partial
351    /// ```
352    ///
353    /// # Motivation
354    ///
355    /// This reduces shuffling traffic in a distributed setting. See
356    /// <https://github.com/datafusion-contrib/datafusion-distributed/issues/360>
357    /// for details.
358    PartialReduce,
359}
360
361impl AggregateMode {
362    /// Returns the [`AggregateInputMode`] for this mode: whether this
363    /// stage consumes raw input data or intermediate accumulator state.
364    ///
365    /// See the [table above](AggregateMode#variants-and-their-inputoutput-modes)
366    /// for details.
367    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    /// Returns the [`AggregateOutputMode`] for this mode: whether this
379    /// stage produces intermediate accumulator state or final output values.
380    ///
381    /// See the [table above](AggregateMode#variants-and-their-inputoutput-modes)
382    /// for details.
383    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/// Represents `GROUP BY` clause in the plan (including the more general GROUPING SET)
397/// In the case of a simple `GROUP BY a, b` clause, this will contain the expression [a, b]
398/// and a single group [false, false].
399/// In the case of `GROUP BY GROUPING SETS/CUBE/ROLLUP` the planner will expand the expression
400/// into multiple groups, using null expressions to align each group.
401/// For example, with a group by clause `GROUP BY GROUPING SETS ((a,b),(a),(b))` the planner should
402/// create a `PhysicalGroupBy` like
403/// ```text
404/// PhysicalGroupBy {
405///     expr: [(col(a), a), (col(b), b)],
406///     null_expr: [(NULL, a), (NULL, b)],
407///     groups: [
408///         [false, false], // (a,b)
409///         [false, true],  // (a) <=> (a, NULL)
410///         [true, false]   // (b) <=> (NULL, b)
411///     ]
412/// }
413/// ```
414#[derive(Clone, Debug, Default)]
415pub struct PhysicalGroupBy {
416    /// Distinct (Physical Expr, Alias) in the grouping set
417    expr: Vec<(Arc<dyn PhysicalExpr>, String)>,
418    /// Corresponding NULL expressions for expr
419    null_expr: Vec<(Arc<dyn PhysicalExpr>, String)>,
420    /// Null mask for each group in this grouping set. Each group is
421    /// composed of either one of the group expressions in expr or a null
422    /// expression in null_expr. If `groups[i][j]` is true, then the
423    /// j-th expression in the i-th group is NULL, otherwise it is `expr[j]`.
424    groups: Vec<Vec<bool>>,
425    /// True when GROUPING SETS/CUBE/ROLLUP are used so `__grouping_id` should
426    /// be included in the output schema.
427    has_grouping_set: bool,
428}
429
430impl PhysicalGroupBy {
431    /// Create a new `PhysicalGroupBy`
432    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    /// Create a GROUPING SET with only a single group. This is the "standard"
447    /// case when building a plan from an expression such as `GROUP BY a,b,c`
448    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    /// Calculate GROUP BY expressions nullable
459    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    /// Returns true if this has no grouping at all (including no GROUPING SETS)
472    pub fn is_true_no_grouping(&self) -> bool {
473        self.is_empty() && !self.has_grouping_set
474    }
475
476    /// Returns the group expressions
477    pub fn expr(&self) -> &[(Arc<dyn PhysicalExpr>, String)] {
478        &self.expr
479    }
480
481    /// Returns the null expressions
482    pub fn null_expr(&self) -> &[(Arc<dyn PhysicalExpr>, String)] {
483        &self.null_expr
484    }
485
486    /// Returns the group null masks
487    pub fn groups(&self) -> &[Vec<bool>] {
488        &self.groups
489    }
490
491    /// Returns true if this grouping uses GROUPING SETS, CUBE or ROLLUP.
492    pub fn has_grouping_set(&self) -> bool {
493        self.has_grouping_set
494    }
495
496    /// Returns true if this `PhysicalGroupBy` has no group expressions
497    pub fn is_empty(&self) -> bool {
498        self.expr.is_empty()
499    }
500
501    /// Returns true if this is a "simple" GROUP BY (not using GROUPING SETS/CUBE/ROLLUP).
502    /// This determines whether the `__grouping_id` column is included in the output schema.
503    pub fn is_single(&self) -> bool {
504        !self.has_grouping_set
505    }
506
507    /// Calculate GROUP BY expressions according to input schema.
508    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    /// The number of expressions in the output schema.
516    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    /// Return grouping expressions as they occur in the output schema.
525    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    /// Returns the number expression as grouping keys.
545    pub fn num_group_exprs(&self) -> usize {
546        self.expr.len() + usize::from(self.has_grouping_set)
547    }
548
549    /// Returns the Arrow data type of the `__grouping_id` column.
550    ///
551    /// The type is chosen to be wide enough to hold both the semantic bitmask
552    /// (in the low `n` bits, where `n` is the number of grouping expressions)
553    /// and the duplicate ordinal (in the high bits).
554    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    /// Returns the fields that are used as the grouping keys.
563    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    /// Returns the output fields of the group by.
592    ///
593    /// This might be different from the `group_fields` that might contain internal expressions that
594    /// should not be part of the output schema.
595    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    /// Returns the `PhysicalGroupBy` for a final aggregation if `self` is used for a partial
602    /// aggregation.
603    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            // No GROUP BY expressions - should have no groups
616            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/// Streams used by [`AggregateExec`].
649///
650/// # Stream Variant Schema Notation
651/// For example, `SELECT g, AVG(x) FROM t GROUP BY g` uses these schemas:
652///
653/// ```text
654/// initial input:              [g, x]
655/// partial state:              [g, AVG(x) state columns, e.g. sum/count]
656/// final result:               [g, AVG(x)]
657/// ```
658#[expect(clippy::large_enum_variant)]
659enum StreamType {
660    /// Single group (no group by) aggregate stream.
661    /// Input output scheme: initial input -> final result
662    AggregateStream(AggregateStream),
663    /// Partial stage of the hash aggregation
664    /// Input output scheme: initial input -> partial state
665    PartialHash(PartialHashAggregateStream),
666    /// Partial-reduce stage of the hash aggregation
667    /// Input output scheme: partial state -> partial state
668    PartialReduceHash(PartialReduceHashAggregateStream),
669    /// Final stage of the hash aggregation
670    /// Input output scheme: partial state -> final result
671    FinalHash(FinalHashAggregateStream),
672    /// Single stage of the hash aggregation
673    /// Input output scheme: initial input -> final result
674    SingleHash(SingleHashAggregateStream),
675    /// Partial stage of aggregation for ordered input.
676    OrderedPartialAggregate(OrderedPartialAggregateStream),
677    /// Final stage of aggregation for ordered input.
678    OrderedFinalAggregate(OrderedFinalAggregateStream),
679    /// Hash aggregation reused for multiple stages
680    ///
681    /// Note this is being incrementally migrated to dedicated streams like
682    /// [`StreamType::PartialHash`], [`StreamType::FinalHash`],
683    /// [`StreamType::OrderedPartialAggregate`], and
684    /// [`StreamType::OrderedFinalAggregate`]
685    ///
686    /// See issue for details: <https://github.com/apache/datafusion/issues/22710>
687    GroupedHash(GroupedHashAggregateStream),
688    /// Grouped TopK aggregate stream.
689    /// Input output scheme: initial input -> final result
690    ///
691    /// Used for grouped aggregation with LIMIT / ordering, where the stream keeps
692    /// only the top groups required by the query.
693    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/// # Aggregate Dynamic Filter Pushdown Overview
713///
714/// For queries like
715///   -- `example_table(type TEXT, val INT)`
716///   SELECT min(val)
717///   FROM example_table
718///   WHERE type='A';
719///
720/// And `example_table`'s physical representation is a partitioned parquet file with
721/// column statistics
722/// - part-0.parquet: val {min=0, max=100}
723/// - part-1.parquet: val {min=100, max=200}
724/// - ...
725/// - part-100.parquet: val {min=10000, max=10100}
726///
727/// After scanning the 1st file, we know we only have to read files if their minimal
728/// value on `val` column is less than 0, the minimal `val` value in the 1st file.
729///
730/// We can skip scanning the remaining file by implementing dynamic filter, the
731/// intuition is we keep a shared data structure for current min in both `AggregateExec`
732/// and `DataSourceExec`, and let it update during execution, so the scanner can
733/// know during execution if it's possible to skip scanning certain files. See
734/// physical optimizer rule `FilterPushdown` for details.
735///
736/// # Implementation
737///
738/// ## Enable Condition
739/// - No grouping (no `GROUP BY` clause in the sql, only a single global group to aggregate)
740/// - The aggregate expression must be `min`/`max`, and evaluate directly on columns.
741///   Note multiple aggregate expressions that satisfy this requirement are allowed,
742///   and a dynamic filter will be constructed combining all applicable expr's
743///   states. See more in the following example with dynamic filter on multiple columns.
744///
745/// ## Filter Construction
746/// The filter is kept in the `DataSourceExec`, and it will gets update during execution,
747/// the reader will interpret it as "the upstream only needs rows that such filter
748/// predicate is evaluated to true", and certain scanner implementation like `parquet`
749/// can evaluate column statistics on those dynamic filters, to decide if they can
750/// prune a whole range.
751///
752/// ### Examples
753/// - Expr: `min(a)`, Dynamic Filter: `a < a_cur_min`
754/// - Expr: `min(a), max(a), min(b)`, Dynamic Filter: `(a < a_cur_min) OR (a > a_cur_max) OR (b < b_cur_min)`
755#[derive(Debug, Clone)]
756struct AggrDynFilter {
757    /// The physical expr for the dynamic filter shared between the `AggregateExec`
758    /// and the parquet scanner.
759    filter: Arc<DynamicFilterPhysicalExpr>,
760    /// The current bounds for the dynamic filter, updates during the execution to
761    /// tighten the bound for more effective pruning.
762    ///
763    /// Each vector element is for the accumulators that support dynamic filter.
764    /// e.g. This `AggregateExec` has accumulator:
765    /// min(a), avg(a), max(b)
766    /// And this field stores [PerAccumulatorDynFilter(min(a)), PerAccumulatorDynFilter(min(b))]
767    supported_accumulators_info: Vec<PerAccumulatorDynFilter>,
768}
769
770// ---- Aggregate Dynamic Filter Utility Structs ----
771
772/// Aggregate expressions that support the dynamic filter pushdown in aggregation.
773/// See comments in [`AggrDynFilter`] for conditions.
774#[derive(Debug, Clone)]
775struct PerAccumulatorDynFilter {
776    aggr_type: DynamicFilterAggregateType,
777    /// During planning and optimization, the parent structure is kept in `AggregateExec`,
778    /// this index is into `aggr_expr` vec inside `AggregateExec`.
779    /// During execution, the parent struct is moved into `AggregateStream` (stream
780    /// for no grouping aggregate execution), and this index is into    `aggregate_expressions`
781    /// vec inside `AggregateStreamInner`
782    aggr_index: usize,
783    // The current bound. Shared among all streams.
784    shared_bound: Arc<Mutex<ScalarValue>>,
785}
786
787/// Aggregate types that are supported for dynamic filter in `AggregateExec`
788#[derive(Debug, Clone)]
789enum DynamicFilterAggregateType {
790    Min,
791    Max,
792}
793
794/// Configuration for limit-based optimizations in aggregation
795#[derive(Debug, Clone, Copy, PartialEq, Eq)]
796pub struct LimitOptions {
797    /// The maximum number of rows to return
798    pub limit: usize,
799    /// Optional ordering direction (true = descending, false = ascending)
800    /// This is used for TopK aggregation to maintain a priority queue with the correct ordering
801    pub descending: Option<bool>,
802}
803
804impl LimitOptions {
805    /// Create a new LimitOptions with a limit and no specific ordering
806    pub fn new(limit: usize) -> Self {
807        Self {
808            limit,
809            descending: None,
810        }
811    }
812
813    /// Create a new LimitOptions with a limit and ordering direction
814    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/// Hash aggregate execution plan
831#[derive(Debug, Clone)]
832pub struct AggregateExec {
833    /// Aggregation mode (full, partial)
834    mode: AggregateMode,
835    /// Group by expressions
836    /// [`Arc`] used for a cheap clone, which improves physical plan optimization performance.
837    group_by: Arc<PhysicalGroupBy>,
838    /// Aggregate expressions
839    /// The same reason to [`Arc`] it as for [`Self::group_by`].
840    aggr_expr: Arc<[Arc<AggregateFunctionExpr>]>,
841    /// FILTER (WHERE clause) expression for each aggregate expression
842    /// The same reason to [`Arc`] it as for [`Self::group_by`].
843    filter_expr: Arc<[Option<Arc<dyn PhysicalExpr>>]>,
844    /// Configuration for limit-based optimizations
845    limit_options: Option<LimitOptions>,
846    /// Input plan, could be a partial aggregate or the input to the aggregate
847    pub input: Arc<dyn ExecutionPlan>,
848    /// Schema after the aggregate is applied. Contains the group by columns followed by the
849    /// aggregate outputs.
850    schema: SchemaRef,
851    /// Input schema before any aggregation is applied. For partial aggregate this will be the
852    /// same as input.schema() but for the final aggregate it will be the same as the input
853    /// to the partial aggregate, i.e., partial and final aggregates have same `input_schema`.
854    /// We need the input schema of partial aggregate to be able to deserialize aggregate
855    /// expressions from protobuf for final aggregate.
856    pub input_schema: SchemaRef,
857    /// Execution metrics
858    metrics: ExecutionPlanMetricsSet,
859    required_input_ordering: Option<OrderingRequirements>,
860    /// Describes how the input is ordered relative to the group by columns
861    input_order_mode: InputOrderMode,
862    cache: Arc<PlanProperties>,
863    /// During initialization, if the plan supports dynamic filtering (see [`AggrDynFilter`]),
864    /// it is set to `Some(..)` regardless of whether it can be pushed down to a child node.
865    ///
866    /// During filter pushdown optimization, if a child node can accept this filter,
867    /// it remains `Some(..)` to enable dynamic filtering during aggregate execution;
868    /// otherwise, it is cleared to `None`.
869    dynamic_filter: Option<Arc<AggrDynFilter>>,
870}
871
872impl AggregateExec {
873    /// Function used in `OptimizeAggregateOrder` optimizer rule,
874    /// where we need parts of the new value, others cloned from the old one
875    /// Rewrites aggregate exec with new aggregate expressions.
876    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            // clone the rest of the fields
883            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    /// Clone this exec, overriding only the limit hint.
899    pub fn with_new_limit_options(&self, limit_options: Option<LimitOptions>) -> Self {
900        Self {
901            limit_options,
902            // clone the rest of the fields
903            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    /// Create a new hash aggregate execution plan
923    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    /// Create a new hash aggregate execution plan with the given schema.
947    /// This constructor isn't part of the public API, it is used internally
948    /// by DataFusion to enforce schema consistency during when re-creating
949    /// `AggregateExec`s inside optimization rules. Schema field names of an
950    /// `AggregateExec` depends on the names of aggregate expressions. Since
951    /// a rule may re-write aggregate expressions (e.g. reverse them) during
952    /// initialization, field names may change inadvertently if one re-creates
953    /// the schema in such cases.
954    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        // Make sure arguments are consistent in size
967        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        // Get GROUP BY expressions:
977        let groupby_exprs = group_by.input_exprs();
978        // If existing ordering satisfies a prefix of the GROUP BY expressions,
979        // prefix requirements with this section. In this case, aggregation will
980        // work more efficiently.
981        // Copy the `PhysicalSortExpr`s to retain the sort options.
982        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        // If our aggregation has grouping sets then our base grouping exprs will
1002        // be expanded based on the flags in `group_by.groups` where for each
1003        // group we swap the grouping expr for `null` if the flag is `true`
1004        // That means that each index in `indices` is valid if and only if
1005        // it is not null in every group
1006        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        // construct a map from the input expression to the output expression of the Aggregation group by
1023        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    /// Aggregation mode (full, partial)
1058    pub fn mode(&self) -> &AggregateMode {
1059        &self.mode
1060    }
1061
1062    /// Set the limit options for this AggExec
1063    pub fn with_limit_options(mut self, limit_options: Option<LimitOptions>) -> Self {
1064        self.limit_options = limit_options;
1065        self
1066    }
1067
1068    /// Get the limit options (if set)
1069    pub fn limit_options(&self) -> Option<LimitOptions> {
1070        self.limit_options
1071    }
1072
1073    /// Grouping expressions
1074    pub fn group_expr(&self) -> &PhysicalGroupBy {
1075        &self.group_by
1076    }
1077
1078    /// Grouping expressions as they occur in the output schema
1079    pub fn output_group_expr(&self) -> Vec<Arc<dyn PhysicalExpr>> {
1080        self.group_by.output_exprs()
1081    }
1082
1083    /// Aggregate expressions
1084    pub fn aggr_expr(&self) -> &[Arc<AggregateFunctionExpr>] {
1085        &self.aggr_expr
1086    }
1087
1088    /// FILTER (WHERE clause) expression for each aggregate expression
1089    pub fn filter_expr(&self) -> &[Option<Arc<dyn PhysicalExpr>>] {
1090        &self.filter_expr
1091    }
1092
1093    /// Returns the dynamic filter expression for this aggregate, if set.
1094    #[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    /// Replace the dynamic filter expression. This method errors if the aggregate does not
1103    /// support dynamic filtering or if the filter expression is incompatible with this
1104    /// [`AggregateExec`].
1105    pub fn with_dynamic_filter_expr(
1106        mut self,
1107        filter: Arc<DynamicFilterPhysicalExpr>,
1108    ) -> Result<Self> {
1109        // If there is no dynamic filter state initialized via `try_new`, then
1110        // we can safely assume that the aggregate does not support dynamic filtering.
1111        let Some(dyn_filter) = self.dynamic_filter.as_ref() else {
1112            return internal_err!("Aggregate does not support dynamic filtering");
1113        };
1114
1115        // Validate that the filter is compatible with the aggregation columns.
1116        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        // Overwrite our filter
1131        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    /// Input plan
1139    pub fn input(&self) -> &Arc<dyn ExecutionPlan> {
1140        &self.input
1141    }
1142
1143    /// Get the input schema before any aggregates are applied
1144    pub fn input_schema(&self) -> SchemaRef {
1145        Arc::clone(&self.input_schema)
1146    }
1147
1148    /// Aggregation has multiple specialized implementations optimized for
1149    /// different workloads. This function picks the best available path.
1150    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        // grouping by an expression that has a sort/limit upstream
1162        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        // Select the stream type based on the query shape and configuration.
1171        // For an overview, see the `Aggregate planning` section in this file's
1172        // documentation.
1173        //
1174        // # Implementation Note
1175        //
1176        // `GroupedHashAggregateStream` is being incrementally refactored. See the
1177        // tracking issue for details.
1178        //
1179        // New features and improvements should go directly into the new implementation.
1180        // Please coordinate through the tracking issue.
1181        //
1182        // Issue: <https://github.com/apache/datafusion/issues/22710>
1183        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        // Execution paths that have not been migrated use the fallback implementation
1227        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        // TODO: implement memory-limited path and remove this limitation
1263        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    /// See comments in `PartialHashAggregateStream` limit optimization section
1295    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    /// Finds the DataType and SortDirection for this Aggregate, if there is one
1300    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    /// true, if this Aggregate has a group-by with no required or explicit ordering,
1306    /// no filtering and no aggregate expressions
1307    /// This method qualifies the use of the LimitedDistinctAggregation rewrite rule
1308    /// on an AggregateExec.
1309    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        // ensure there is a group by
1318        if self.group_expr().is_empty() && !self.group_expr().has_grouping_set() {
1319            return false;
1320        }
1321        // ensure there are no aggregate expressions
1322        if !self.aggr_expr().is_empty() {
1323            return false;
1324        }
1325        // ensure there are no filters on aggregate expressions; the above check
1326        // may preclude this case
1327        if self.filter_expr().iter().any(|e| e.is_some()) {
1328            return false;
1329        }
1330        // ensure there are no order by expressions
1331        if !self.aggr_expr().iter().all(|e| e.order_bys().is_empty()) {
1332            return false;
1333        }
1334        // ensure there is no output ordering; can this rule be relaxed?
1335        if self.properties().output_ordering().is_some() {
1336            return false;
1337        }
1338        // ensure no ordering is required on the input
1339        if let Some(requirement) = self.required_input_ordering().swap_remove(0) {
1340            return matches!(requirement, OrderingRequirements::Hard(_));
1341        }
1342        true
1343    }
1344
1345    /// This function creates the cache object that stores the plan properties such as schema, equivalence properties, ordering, partitioning, etc.
1346    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        // Construct equivalence properties:
1356        let mut eq_properties = input
1357            .equivalence_properties()
1358            .project(group_expr_mapping, schema);
1359
1360        // True no-group aggregates produce only one row in each output
1361        // partition, so aggregate outputs are constants within the partition.
1362        // Grouping sets with empty grouping expressions are not covered here:
1363        // their output schema can include grouping-set columns before the
1364        // aggregate columns, so this aggregate-column mapping does not apply.
1365        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        // Group by expression will be a distinct value after the aggregation.
1374        // Add it into the constraint set.
1375        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        // Get output partitioning:
1391        let input_partitioning = input.output_partitioning().clone();
1392        let output_partitioning = match mode.input_mode() {
1393            AggregateInputMode::Raw => {
1394                // First stage aggregation will not change the output partitioning,
1395                // but needs to respect aliases (e.g. mapping in the GROUP BY
1396                // expression).
1397                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        // TODO: Emission type and boundedness information can be enhanced here
1404        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    /// Estimates output statistics for this aggregate node.
1423    ///
1424    /// For aggregations without group-by expressions, row count follows the
1425    /// number of logical aggregate rows and the aggregate output mode. True
1426    /// no-group aggregates have one logical row; empty grouping sets have one
1427    /// logical row per grouping-set occurrence.
1428    ///
1429    /// For grouped aggregations with known input row count > 1, the output row
1430    /// count is estimated as:
1431    ///
1432    /// ```text
1433    /// ndv        = sum over each grouping set of product(max(NDV_i + nulls_i, 1))
1434    /// output_rows = input_rows                       // baseline
1435    /// output_rows = min(output_rows, ndv)             // if NDV available
1436    /// output_rows = min(output_rows, limit)           // if TopK active
1437    /// ```
1438    ///
1439    /// **Example 1 — single group key:**
1440    /// `GROUP BY city` where input_rows = 10,000, NDV(city) = 200
1441    /// → output_rows = min(10_000, 200) = 200
1442    ///
1443    /// **Example 2 — two group keys with TopK:**
1444    /// `GROUP BY city, category` where input_rows = 10,000, NDV(city) = 200,
1445    /// NDV(category) = 5, limit = 100
1446    /// → ndv = 200 × 5 = 1,000
1447    /// → output_rows = min(10_000, 1_000) = 1,000
1448    /// → output_rows = min(1_000, 100) = 100
1449    ///
1450    /// When `input_rows` is absent but NDV is available, falls back to:
1451    ///
1452    /// ```text
1453    /// output_rows = min(ndv, limit)   // if both available
1454    /// output_rows = ndv               // if only NDV available
1455    /// output_rows = limit             // if only limit available
1456    /// ```
1457    ///
1458    /// NDV estimation details (see [`Self::compute_group_ndv`]):
1459    /// - For each grouping set, only active (non-NULL) columns contribute
1460    /// - Per-column contribution is `max(NDV + null_adj, 1)` where `null_adj`
1461    ///   is 1 when nulls are present, 0 otherwise (a null group is a distinct
1462    ///   output row; `.max(1)` prevents a zero NDV from zeroing the product)
1463    /// - Per-set products are summed across all grouping sets
1464    /// - Requires NDV stats for ALL active group-by columns; if any lacks stats,
1465    ///   falls back to `input_rows` (or `Absent` if that is also unknown)
1466    fn statistics_inner(
1467        &self,
1468        child_statistics: &Statistics,
1469        partition: Option<usize>,
1470    ) -> Result<Statistics> {
1471        // TODO stats: group expressions:
1472        // - once expressions will be able to compute their own stats, use it here
1473        // - case where we group by on a column for which with have the `distinct` stat
1474        // TODO stats: aggr expression:
1475        // - aggregations sometimes also preserve invariants such as min, max...
1476
1477        let column_statistics = {
1478            // self.schema: [<group by exprs>, <aggregate exprs>]
1479            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    /// Exact physical output row count for aggregates without group-by
1532    /// expressions.
1533    ///
1534    /// `partition` follows [`ExecutionPlan::partition_statistics`]: `Some(_)`
1535    /// requests one output partition, while `None` requests the entire plan.
1536    /// Partial-state output contains the logical rows in each output partition;
1537    /// final-value output contains the global logical rows once.
1538    /// This mirrors execution, where partial aggregation without group-by
1539    /// expressions emits its logical rows from every output partition, including
1540    /// empty input partitions.
1541    ///
1542    /// Returns `None` when grouping expressions are present and grouped
1543    /// cardinality estimation should be used instead.
1544    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    /// Scales a logical aggregate row count to the rows this operator emits,
1554    /// which for partial aggregation is once per output partition.
1555    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    /// Number of rows a grouped aggregate emits for an empty input.
1566    ///
1567    /// Grouping expressions yield no groups, so the only rows are the
1568    /// grand-total rows of the empty grouping sets that `GROUPING SETS(())`,
1569    /// `ROLLUP` and `CUBE` introduce alongside the non-empty ones.
1570    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    /// Reports the grouping columns of an empty input as all NULL.
1582    ///
1583    /// The only rows such an input produces are grand-total rows, which hold
1584    /// NULL in every grouping column, so the values copied from the child do not
1585    /// describe the output. Rules that answer `MIN`/`MAX` from statistics read
1586    /// these values, so an input value here becomes a wrong query result.
1587    ///
1588    /// The bounds are typed nulls rather than [`Precision::Absent`], both
1589    /// because NULL is the `MIN`/`MAX` of such a column and because the data
1590    /// type lets downstream interval analysis keep intersecting intervals of
1591    /// that type, as `FilterExec` does for a column with no rows.
1592    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    /// Exact number of logical aggregate rows for aggregates without group-by
1626    /// expressions.
1627    ///
1628    /// A true no-group aggregate has one logical aggregate row. Empty grouping
1629    /// sets have one logical aggregate row per grouping-set occurrence, even
1630    /// when there are duplicate empty grouping sets. Returns `None` when there
1631    /// are grouping expressions.
1632    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    /// Estimates the output row count for grouped aggregations, combining NDV,
1643    /// input row count, and TopK limit into a single [`Precision<usize>`].
1644    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                // The limit bounds groups built from input rows, not the rows
1668                // the empty grouping sets contribute.
1669                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    /// Computes the estimated number of distinct groups across all grouping sets.
1692    /// For each grouping set, computes `product(NDV_i + null_adj_i)` for active columns,
1693    /// then sums across all sets. Returns `None` if any active column is not a direct
1694    /// column reference or lacks `distinct_count` stats. Non-column expressions
1695    /// (e.g. `abs(a)`) are not yet supported because expression-level statistics
1696    /// propagation is still in progress (see <https://github.com/apache/datafusion/pull/21122>).
1697    /// When `null_count` is absent or unknown, null_adjustment defaults to 0.
1698    ///
1699    /// **Single key:** `GROUP BY a` where NDV(a) = 100, null_count(a) = 5
1700    /// → product = max(100 + 1, 1) = 101, total = 101
1701    ///
1702    /// **Two keys:** `GROUP BY a, b` where NDV(a) = 100, NDV(b) = 50, no nulls
1703    /// → product = 100 × 50 = 5,000, total = 5,000
1704    ///
1705    /// **Grouping sets:** `GROUPING SETS ((a), (b), (a, b))` with NDV(a) = 100, NDV(b) = 50
1706    /// → set(a) = 100, set(b) = 50, set(a, b) = 100 × 50 = 5,000
1707    /// → total = 100 + 50 + 5,000 = 5,150
1708    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    /// Check if dynamic filter is possible for the current plan node.
1732    /// - If yes, init one inside `AggregateExec`'s `dynamic_filter` field.
1733    /// - If not supported, `self.dynamic_filter` should be kept `None`
1734    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        // Already initialized.
1744        if self.dynamic_filter.is_some() {
1745            return;
1746        }
1747
1748        // Collect supported accumulators
1749        // It is assumed the order of aggregate expressions are not changed from `AggregateExec`
1750        // to `AggregateStream`
1751        let mut aggr_dyn_filters = Vec::new();
1752        // All column references in the dynamic filter, used when initializing the dynamic
1753        // filter, and it's used to decide if this dynamic filter is able to get push
1754        // through certain node during optimization.
1755        let mut all_cols: Vec<Arc<dyn PhysicalExpr>> = Vec::new();
1756        for (i, aggr_expr) in self.aggr_expr.iter().enumerate() {
1757            // 1. Only `min` or `max` aggregate function
1758            let fun_name = aggr_expr.fun().name();
1759            // HACK: Should check the function type more precisely
1760            // Issue: <https://github.com/apache/datafusion/issues/18643>
1761            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            // 2. arg should be only 1 column reference
1770            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    // Collect column references for the dynamic filter expression from the supported accumulators.
1791    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                // This should always be true due to how the supported accumulators
1799                // are constructed. See `init_dynamic_filter` for more details.
1800                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    /// Calculate scaled byte size based on row count ratio.
1813    /// Returns `Precision::Absent` if input statistics are insufficient.
1814    /// Returns `Precision::Inexact` with the scaled value otherwise.
1815    ///
1816    /// This is a simple heuristic that assumes uniform row sizes.
1817    #[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    /// Return a reference to Any that can be used for down-casting
1992    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    /// The output ordering of [`AggregateExec`] is determined by its `group_by`
2019    /// columns. Although this method is not explicitly used by any optimizer
2020    /// rules yet, overriding the default implementation ensures that it
2021    /// accurately reflects the actual behavior.
2022    ///
2023    /// If the [`InputOrderMode`] is `Linear`, the `group_by` columns don't have
2024    /// an ordering, which means the results do not either. However, in the
2025    /// `Ordered` and `PartiallyOrdered` cases, the `group_by` columns do have
2026    /// an ordering, which is preserved in the output.
2027    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    /// Push down parent filters when possible (see implementation comment for details),
2154    /// and also pushdown self dynamic filters (see `AggrDynFilter` for details)
2155    fn gather_filters_for_pushdown(
2156        &self,
2157        phase: FilterPushdownPhase,
2158        parent_filters: Vec<Arc<dyn PhysicalExpr>>,
2159        config: &ConfigOptions,
2160    ) -> Result<FilterDescription> {
2161        // It's safe to push down filters through aggregates when filters only reference
2162        // grouping columns, because such filters determine which groups to compute, not
2163        // *how* to compute them. Each group's aggregate values (SUM, COUNT, etc.) are
2164        // calculated from the same input rows regardless of whether we filter before or
2165        // after grouping - filtering before just eliminates entire groups early.
2166        // This optimization is NOT safe for filters on aggregated columns (like filtering on
2167        // the result of SUM or COUNT), as those require computing all groups first.
2168
2169        // Grouping columns are output before aggregate columns, in the same order
2170        // as the grouping expressions. A grouping-set null mask marks grouping
2171        // columns that are not available in that set.
2172        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        // Global aggregates and grouping sets containing an empty grouping set
2180        // emit a row even when their input is empty. Parent filters therefore
2181        // cannot be pushed below them, including filters without column
2182        // references.
2183        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        // Include self dynamic filter when it's possible
2200        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    /// If child accepts self's dynamic filter, keep `self.dynamic_filter` with Some,
2212    /// otherwise clear it to None.
2213    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 this node tried to pushdown some dynamic filter before, now we check
2222        // if the child accept the filter
2223        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                // Child can't consume the self dynamic filter, so disable it by setting
2235                // to `None`
2236                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        // Exhaustive destructure: adding a field to `AggregateExec` without
2255        // deciding how it is serialized is a compile error, not a silent
2256        // round-trip gap.
2257        let Self {
2258            mode,
2259            group_by,
2260            aggr_expr,
2261            filter_expr,
2262            limit_options,
2263            input,
2264            // Derived at construction by `create_schema` from `input_schema`,
2265            // `group_by`, `aggr_expr` and `mode`.
2266            schema: _,
2267            input_schema,
2268            // Runtime execution state, rebuilt empty on decode.
2269            metrics: _,
2270            // Derived at construction from the input ordering and `group_by`.
2271            required_input_ordering: _,
2272            // Derived at construction from the input ordering and `group_by`.
2273            input_order_mode: _,
2274            // Derived at construction by `Self::compute_properties`.
2275            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        // Match by name because the protobuf and execution enums use different
2310        // discriminants, so a numeric cast would corrupt the wire format.
2311        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        // Only the shared `filter` expr is on the wire; the accumulator bounds
2326        // in `AggrDynFilter` are runtime state repopulated during execution.
2327        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/// Keep this marker byte-identical to the copy used by the deprecated
2362/// aggregate serializer in `datafusion-proto` until that path is removed.
2363#[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    // The context already applies `(!buf.is_empty()).then_some(buf)`.
2409    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    /// Reconstruct an [`AggregateExec`] from its protobuf representation.
2439    ///
2440    /// Grouping expressions are decoded against the child schema. Aggregate
2441    /// arguments, ordering, filters, and the dynamic filter are decoded against
2442    /// the aggregate input schema carried in the protobuf node.
2443    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        // Exhaustive destructure: a new field on `AggregateExecNode` is a
2458        // compile error here rather than a silently ignored wire field.
2459        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        // Match by name because the protobuf and execution enums use different
2479        // discriminants, so a numeric cast would corrupt the wire format.
2480        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        // Grouping expressions refer to the child plan's output schema.
2497        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        // Aggregate arguments, ordering, filters, and dynamic filters refer to
2527        // the aggregate input schema carried in the protobuf node.
2528        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                // The context owns the payload-to-codec and
2576                // registry-to-codec fallback order.
2577                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
2653/// Creates the output schema for an [`AggregateExec`] containing the group by columns followed
2654/// by the aggregate columns.
2655fn 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            // in final mode, the field with the final result of the accumulator
2667            for expr in aggr_expr {
2668                fields.push(expr.field())
2669            }
2670        }
2671        AggregateOutputMode::Partial => {
2672            // in partial mode, the fields of the accumulator's state
2673            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
2685/// Determines the lexical ordering requirement for an aggregate expression.
2686///
2687/// # Parameters
2688///
2689/// - `aggr_expr`: A reference to an `AggregateFunctionExpr` representing the
2690///   aggregate expression.
2691/// - `group_by`: A reference to a `PhysicalGroupBy` instance representing the
2692///   physical GROUP BY expression.
2693/// - `agg_mode`: A reference to an `AggregateMode` instance representing the
2694///   mode of aggregation.
2695/// - `include_soft_requirement`: When `false`, only hard requirements are
2696///   considered, as indicated by [`AggregateFunctionExpr::order_sensitivity`]
2697///   returning [`AggregateOrderSensitivity::HardRequirement`].
2698///   Otherwise, also soft requirements ([`AggregateOrderSensitivity::SoftRequirement`])
2699///   are considered.
2700///
2701/// # Returns
2702///
2703/// A `LexOrdering` instance indicating the lexical ordering requirement for
2704/// the aggregate expression.
2705fn 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 the aggregation is performing a "second stage" calculation,
2712    // then ignore the ordering requirement. Ordering requirement applies
2713    // only to the aggregation input data.
2714    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    // In non-first stage modes, we accumulate data (using `merge_batch`) from
2731    // different partitions (i.e. merge partial results). During this merge, we
2732    // consider the ordering of each partial result. Hence, we do not need to
2733    // use the ordering requirement in such modes as long as partial results are
2734    // generated with the correct ordering.
2735    if group_by.is_single() {
2736        // Remove all orderings that occur in the group by. These requirements
2737        // will definitely be satisfied -- Each group by expression will have
2738        // distinct values per group, hence all requirements are satisfied.
2739        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
2747/// Concatenates the given slices.
2748pub fn concat_slices<T: Clone>(lhs: &[T], rhs: &[T]) -> Vec<T> {
2749    [lhs, rhs].concat()
2750}
2751
2752// Determines if the candidate ordering is finer than the current ordering.
2753// Returns `None` if they are incomparable, `Some(true)` if there is no current
2754// ordering or candidate ordering is finer, and `Some(false)` otherwise.
2755fn 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
2766/// Gets the common requirement that satisfies all the aggregate expressions.
2767/// When possible, chooses the requirement that is already satisfied by the
2768/// equivalence properties.
2769///
2770/// # Parameters
2771///
2772/// - `aggr_exprs`: A slice of `AggregateFunctionExpr` containing all the
2773///   aggregate expressions.
2774/// - `group_by`: A reference to a `PhysicalGroupBy` instance representing the
2775///   physical GROUP BY expression.
2776/// - `eq_properties`: A reference to an `EquivalenceProperties` instance
2777///   representing equivalence properties for ordering.
2778/// - `agg_mode`: A reference to an `AggregateMode` instance representing the
2779///   mode of aggregation.
2780///
2781/// # Returns
2782///
2783/// A `Result<Vec<PhysicalSortRequirement>>` instance, which is the requirement
2784/// that satisfies all the aggregate requirements. Returns an error in case of
2785/// conflicting requirements.
2786pub 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    // First try and find a match for all hard and soft requirements.
2795    // If a match can't be found, try a second time just matching hard
2796    // requirements.
2797    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                // There is no aggregate ordering requirement, or it is trivially
2807                // satisfied -- we can skip this expression.
2808                continue;
2809            };
2810            // If the common requirement is finer than the current expression's,
2811            // we can skip this expression. If the latter is finer than the former,
2812            // adopt it if it is satisfied by the equivalence properties. Otherwise,
2813            // defer the analysis to the reverse expression.
2814            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                    // The reverse requirement is trivially satisfied -- just reverse
2832                    // the expression and continue with the next one:
2833                    *aggr_expr = Arc::new(reverse_aggr_expr);
2834                    continue;
2835                };
2836                // If the common requirement is finer than the reverse expression's,
2837                // just reverse it and continue the loop with the next aggregate
2838                // expression. If the latter is finer than the former, adopt it if
2839                // it is satisfied by the equivalence properties. Otherwise, adopt
2840                // the forward expression.
2841                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                    // Neither the existing requirement nor the current aggregate
2854                    // requirement satisfy the other (forward or reverse), this
2855                    // means they are conflicting. This is a problem only for hard
2856                    // requirements. Unsatisfied soft requirements can be ignored.
2857                    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
2870/// Returns physical expressions for arguments to evaluate against a batch.
2871///
2872/// The expressions are different depending on `mode`:
2873/// * Partial: AggregateFunctionExpr::expressions
2874/// * Final: columns of `AggregateFunctionExpr::state_fields()`
2875pub 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                // Append ordering requirements to expressions' results. This
2886                // way order sensitive aggregators can satisfy requirement
2887                // themselves.
2888                result.extend(agg.order_bys().iter().map(|item| Arc::clone(&item.expr)));
2889                result
2890            })
2891            .collect()),
2892        AggregateInputMode::Partial => {
2893            // In merge mode, we build the merge expressions of the aggregation.
2894            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
2907/// uses `state_fields` to build a vec of physical column expressions required to merge the
2908/// AggregateFunctionExpr' accumulator's state.
2909///
2910/// `index_base` is the starting physical column index for the next expanded state field.
2911fn 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
2935/// returns a vector of ArrayRefs, where each entry corresponds to either the
2936/// final value (mode = Final, FinalPartitioned and Single) or states (mode = Partial)
2937pub fn finalize_aggregation(
2938    accumulators: &mut [AccumulatorItem],
2939    mode: &AggregateMode,
2940) -> Result<Vec<ArrayRef>> {
2941    match mode.output_mode() {
2942        AggregateOutputMode::Final => {
2943            // Merge the state to the final value
2944            accumulators
2945                .iter_mut()
2946                .map(|accumulator| accumulator.evaluate().and_then(|v| v.to_array()))
2947                .collect()
2948        }
2949        AggregateOutputMode::Partial => {
2950            // Build the vector of states
2951            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
2966/// Evaluates groups of expressions against a record batch.
2967pub 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
2992/// Builds the internal `__grouping_id` array for a single grouping set.
2993///
2994/// The returned array packs two values into a single integer:
2995///
2996/// - Low `n` bits (positions 0 .. n-1): the semantic bitmask.  A `1` bit
2997///   at position `i` means that the `i`-th grouping column (counting from the
2998///   least significant bit, i.e. the *last* column in the `group` slice) is
2999///   `NULL` for this grouping set.
3000/// - High bits (positions n and above): the duplicate `ordinal`, which
3001///   distinguishes multiple occurrences of the same grouping-set pattern.  The
3002///   ordinal is `0` for the first occurrence, `1` for the second, and so on.
3003///
3004/// The integer type is chosen to be the smallest `UInt8 / UInt16 / UInt32 /
3005/// UInt64` that can represent both parts.  It matches the type returned by
3006/// [`Aggregate::grouping_id_type`].
3007pub(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
3042/// Returns the highest duplicate ordinal across all grouping sets.
3043///
3044/// At the call-site, the ordinal is the 0-based index assigned to each
3045/// occurrence of a repeated grouping-set pattern: the first occurrence gets
3046/// ordinal 0, the second gets 1, and so on.  If the same `Vec<bool>` appears
3047/// three times the ordinals are 0, 1, 2 and this function returns 2.
3048/// Returns 0 when no grouping set is duplicated.
3049pub(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
3057/// Evaluate a group by expression against a `RecordBatch`
3058///
3059/// Arguments:
3060/// - `group_by`: the expression to evaluate
3061/// - `batch`: the `RecordBatch` to evaluate against
3062///
3063/// Returns: A Vec of Vecs of Array of results
3064/// The outer Vec appears to be for grouping sets
3065/// The inner Vec contains the results per expression
3066/// The inner-inner Array contains the results per row
3067///
3068/// For example, for `GROUP BY GROUPING SETS ((a, b), (a))` with input:
3069///
3070/// ```text
3071/// a  b
3072/// 1  1
3073/// 1  2
3074/// 2  1
3075/// ```
3076///
3077/// The output is:
3078///
3079/// ```text
3080/// [
3081///   [
3082///     a:           [1, 1, 2]
3083///     b:           [1, 2, 1]
3084///     grouping_id: [0, 0, 0]
3085///   ],
3086///   [
3087///     a:           [1, 1, 2]
3088///     b:           [NULL, NULL, NULL]
3089///     grouping_id: [1, 1, 1]
3090///   ]
3091/// ]
3092/// ```
3093pub 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    // Generate a schema which consists of 5 columns (a, b, c, d, e)
3216    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    /// some mock data to aggregates
3228    fn some_data() -> (Arc<Schema>, Vec<RecordBatch>) {
3229        // define a schema.
3230        let schema = Arc::new(Schema::new(vec![
3231            Field::new("a", DataType::UInt32, false),
3232            Field::new("b", DataType::Float64, false),
3233        ]));
3234
3235        // define data.
3236        (
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    /// Generates some mock data for aggregate tests.
3260    fn some_data_v2() -> (Arc<Schema>, Vec<RecordBatch>) {
3261        // Define a schema:
3262        let schema = Arc::new(Schema::new(vec![
3263            Field::new("a", DataType::UInt32, false),
3264            Field::new("b", DataType::Float64, false),
3265        ]));
3266
3267        // Generate data so that first and last value results are at 2nd and
3268        // 3rd partitions.  With this construction, we guarantee we don't receive
3269        // the expected result by accident, but merging actually works properly;
3270        // i.e. it doesn't depend on the data insertion order.
3271        (
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],  // (a, NULL)
3367                vec![true, false],  // (NULL, b)
3368                vec![false, false], // (a,b)
3369            ],
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            // adjust the max memory size to have the partial aggregate result for spill mode.
3382            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            // In spill mode, we test with the limited memory, if the mem usage exceeds,
3401            // we trigger the early emit rule, which turns out the partial aggregate result.
3402            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    /// build the aggregates on the data from some_data() and check the results
3513    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            // set to an appropriate value to trigger spill
3532            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        // Verify statistics are preserved proportionally through aggregation
3592        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            // enlarge memory limit to let the final aggregation finish
3598            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            // For row 2: 3, (2 + 3 + 2) / 3
3618            // For row 3: 4, (3 + 4 + 4) / 3
3619        }
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    /// Define a test source that can yield back to runtime before returning its first item ///
3642
3643    #[derive(Debug)]
3644    struct TestYieldingExec {
3645        /// True if this exec should yield back to runtime the first time it is polled
3646        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        /// This function creates the cache object that stores the plan properties such as schema, equivalence properties, ordering, partitioning, etc.
3661        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                    // TODO: collect info
3683                    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    /// A stream using the demo data. If inited as new, it will first yield to runtime before returning records
3759    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    //--- Tests ---//
3799
3800    #[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    // Median(a)
3857    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        // something that allocates within the aggregator
3884        let aggregates_v0: Vec<Arc<AggregateFunctionExpr>> =
3885            vec![Arc::new(test_median_agg_expr(Arc::clone(&input_schema))?)];
3886
3887        // Use the fast path in `single_stream.rs`.
3888        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            // ensure that we really got the version we wanted
3912            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            // error root cause traversal is a bit complicated, see #4172.
3929            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    /// For single aggregation, ensures `SingleHashAggregateStream` is used when
4231    /// enabled by migration config.
4232    #[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    /// Single hash aggregation supports finite memory.
4256    #[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    /// For partial-reduce aggregation, ensures `PartialReduceHashAggregateStream`
4316    /// is used when enabled by migration config.
4317    #[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    /// Spilling behavior is not implemented for partial-reduce stream yet, so fall
4337    /// back to the existing `GroupedHashAggregateStream`
4338    #[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    /// Ensures for ordered input, `OrderedPartialAggregateStream` is used.
4361    #[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        // Ordered partial aggregation supports finite memory.
4436        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    /// Ensures for ordered input, `OrderedFinalAggregateStream` is used.
4444    #[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        // Ordered final aggregation supports finite memory.
4513        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        // Reproducer for #20445: emitting from PartiallySorted input must not
4523        // drain more groups than the completed sort boundary allows.
4524        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        // All rows share sort_col=1, so there is no completed sort boundary
4531        // inside this batch even though there are many distinct groups.
4532        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    // FIRST_VALUE(b ORDER BY b <SortOptions>)
4685    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    // LAST_VALUE(b ORDER BY b <SortOptions>)
4704    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    // This function constructs the physical plan below,
4863    //
4864    // "AggregateExec: mode=Final, gby=[a@0 as a], aggr=[FIRST_VALUE(b)]",
4865    // "  CoalescePartitionsExec",
4866    // "    AggregateExec: mode=Partial, gby=[a@0 as a], aggr=[FIRST_VALUE(b)], ordering_mode=None",
4867    // "      DataSourceExec: partitions=4, partition_sizes=[1, 1, 1, 1]",
4868    //
4869    // and checks whether the function `merge_batch` works correctly for
4870    // FIRST_VALUE and LAST_VALUE functions.
4871    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        // Columns a and b are equal.
4972        eq_properties.add_equal_conditions(Arc::clone(col_a), Arc::clone(col_b))?;
4973        // Aggregate requirements are
4974        // [None], [a ASC], [a ASC, b ASC, c ASC], [a ASC, b ASC] respectively
4975        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    // Migrated to PartialHashAggregateStream coverage below;
5272    // kept here for the legacy GroupedHashAggregateStream implementation.
5273    #[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    // Migrated to PartialHashAggregateStream coverage below;
5356    // kept here for the legacy GroupedHashAggregateStream implementation.
5357    #[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    /// When `skip_partial_aggregation_probe_ratio_threshold` is set to 1.0,
5632    /// the feature must be effectively disabled: even with 100% cardinality
5633    /// (every row is a unique group), no rows should be skipped.
5634    #[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        // Two batches are required: batch 1 triggers the probe threshold so the
5653        // skip decision is evaluated; batch 2 is what would be skipped on main
5654        // (where >= caused threshold=1.0 to still skip at 100% cardinality).
5655        // All rows have unique keys => ratio = 1.0 (100% cardinality).
5656        let input_data = vec![
5657            // Batch 1: fires the probe check (ratio = 5/5 = 1.0)
5658            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            // Batch 2: would be skipped if threshold=1.0 did not disable the feature
5667            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],  // (a, NULL)
5741                vec![false, false], // (a,b)
5742            ],
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    // test for https://github.com/apache/datafusion/issues/13949
5762    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        // Test with MIN for simple intermediate state (min) and AVG for multiple intermediate states (partial sum, partial count).
5801        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            // Inspect metrics for SpillCount
5864            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        // test with spill
5888        run_test_with_spill_pool_if_necessary(20_000, true).await?;
5889        // test without spill
5890        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        // test with spill
5897        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        // Test with MIN for simple intermediate state (min) and AVG for multiple intermediate states (partial sum, partial count).
5943        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        // `GROUP BY a` produces no groups for an empty input.
6075        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        // `GROUPING SETS((a), ())`, as ROLLUP and CUBE produce, still emits the
6085        // grand-total row of the empty grouping set on an empty input.
6086        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        // `GROUPING SETS((a), (), ())` emits one grand-total row per empty
6097        // grouping set, because execution gives each duplicate its own ordinal.
6098        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    /// Partial aggregation emits the grand-total row from every output
6112    /// partition, so the whole-plan estimate scales with the partition count
6113    /// while a single-partition request does not.
6114    #[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        // Inexact because a repartition only estimates its per-partition row
6141        // count. The grouping column statistics carry that same precision.
6142        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    /// The input's min, max and distinct values must not reach the output
6156    /// column statistics. See `nullify_group_columns_for_empty_input`.
6157    #[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    /// `GROUPING SETS((a), (), ...)` with `empty_sets` empty grouping sets, as
6209    /// `ROLLUP(a)` and `CUBE(a)` produce with one.
6210    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            // --- NDV-based estimation ---
6312            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                // 99 + 1 (null adjustment) = 100
6366                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                // (99+1) * (49+1) = 100 * 50 = 5000
6384                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            // --- Bail-out: partial NDV stats (Spark-style) ---
6400            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            // --- TopK limit capping ---
6422            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            // --- Absent input rows ---
6456            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            // --- No group-by (global aggregation) ---
6475            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            // --- One input row ---
6485            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            // --- Zero input rows ---
6498            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            // --- Inexact NDV stats ---
6508            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            // --- NDV zero column (all-null) ---
6533            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                // NDV(a)=0 with nulls => max(0+1, 1)=1, NDV(b)=50 => 1*50=50
6548                expected_num_rows: Precision::Inexact(50),
6549            },
6550            // --- Absent num_rows with NDV ---
6551            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        // CUBE-like grouping set: (a, NULL), (NULL, b), (a, b) — 3 groups
6665        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],  // (a, NULL)
6676                vec![true, false],  // (NULL, b)
6677                vec![false, false], // (a, b)
6678            ],
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        // Per-set NDV: (a,NULL)=100, (NULL,b)=50, (a,b)=100*50=5000
6686        // Total = 100 + 50 + 5000 = 5150
6687        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        // GROUP BY (a + b) — not a direct column reference
6792        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    /// Tests that when the memory pool is too small to accommodate the sort
6896    /// reservation during spill, the error is properly propagated as
6897    /// ResourcesExhausted rather than silently exceeding memory limits.
6898    #[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        // Pool must be large enough for accumulation to start but too small for
7008        // sort_memory after clearing.
7009        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    /// Tests that PartialReduce mode:
7027    /// 1. Accepts state as input (like Final)
7028    /// 2. Produces state as output (like Partial)
7029    /// 3. Can be followed by a Final stage to get the correct result
7030    ///
7031    /// This simulates a tree-reduce pattern:
7032    ///   Partial -> PartialReduce -> Final
7033    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        // Step 1: Partial aggregation on partition 1
7048        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        // Step 2: Partial aggregation on partition 2
7060        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        // Collect partial results
7072        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        // The partial results have state schema (group cols + accumulator state)
7079        let partial_schema = partial1.schema();
7080
7081        // Step 3: PartialReduce — combine partial results, still producing state
7082        let combined_input = TestMemoryExec::try_new_exec(
7083            &[partial_result1, partial_result2],
7084            Arc::clone(&partial_schema),
7085            None,
7086        )?;
7087        // Coalesce into a single partition for the PartialReduce
7088        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        // Verify PartialReduce output schema matches Partial output schema
7100        // (both produce state, not final values)
7101        assert_eq!(partial_reduce.schema(), partial_schema);
7102
7103        // Collect PartialReduce results
7104        let reduce_result =
7105            crate::collect(Arc::clone(&partial_reduce) as _, Arc::clone(&task_ctx))
7106                .await?;
7107
7108        // Step 4: Final aggregation on the PartialReduce output
7109        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    /// Builds the shared `Partial -> PartialReduce -> Final` fixture used by
7129    /// the `test_partial_reduce_*` tests below and runs the pipeline against
7130    /// the aggregate produced by `build_aggregates`.
7131    ///
7132    /// Each test only needs to supply the UDAF/alias under test, so the test
7133    /// body stays focused on which aggregate shape is being exercised.
7134    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        // Two partitions of input data so the Partial stage produces multiple
7146        // partial states that PartialReduce must combine.
7147        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    // -------------------------------------------------------------------
7170    // PartialReduce regression coverage.
7171    //
7172    // Each shape (single state field / single input arg, multi-state /
7173    // single-input, more-state-than-input) is covered twice:
7174    //   * once against a real UDAF, to round-trip an actual aggregate end
7175    //     to end through `Partial -> PartialReduce -> Final`; and
7176    //   * once against [`InputTypeAssertingUdaf`], whose input / state /
7177    //     output types are deliberately pairwise-disjoint within each test
7178    //     so a regression that swapped state-field types for input-field
7179    //     types (or vice versa) fails the assertion instead of slipping
7180    //     through on a coincidental type match.
7181    //
7182    // The stub variants do the heavy lifting on the contract; the real
7183    // ones make sure no real aggregate is broken by it.
7184    // -------------------------------------------------------------------
7185
7186    /// Real-UDAF round-trip: aggregate with a single state field and a
7187    /// single input argument (`SUM(b)` — state and input are both `Float64`).
7188    #[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        // Expected: group 1 -> 10+40=50, group 2 -> 20+50=70, group 3 -> 30+60=90
7202        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    /// Real-UDAF round-trip: aggregate with multiple state fields and a
7216    /// single input argument (`AVG(b)` — state is `[sum: Float64, count:
7217    /// UInt64]`).
7218    #[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    /// Real-UDAF round-trip: aggregate whose state has more fields than the
7245    /// input has arguments (`approx_percentile_cont` carries a t-digest).
7246    #[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    /// Stub variant of
7275    /// [`test_partial_reduce_with_single_state_field_and_single_input_arg`]
7276    /// with disjoint input / state / output types.
7277    ///
7278    /// - input: `Float64`
7279    /// - state: `Int32`
7280    /// - output: `Int64`
7281    ///
7282    /// Any mode that accidentally forwarded state-field types in place of
7283    /// input-field types would fail the assertion in
7284    /// [`InputTypeAssertingUdaf`] instead of being masked by a coincidental
7285    /// type match.
7286    #[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        // Pipeline completing without error is the real assertion. The
7305        // snapshot guards against silent regressions in the row shape.
7306        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    /// Stub variant of
7320    /// [`test_partial_reduce_with_multiple_state_fields_and_single_input_arg`]
7321    /// with disjoint input / state / output types.
7322    ///
7323    /// - input: `Float64`
7324    /// - state: `[Int32, Utf8]`
7325    /// - output: `Int64`
7326    #[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    /// Stub variant of
7358    /// [`test_partial_reduce_with_more_state_fields_than_input_args`] with
7359    /// disjoint input / state / output types — and with multiple input
7360    /// arguments to exercise the multi-arg path explicitly.
7361    ///
7362    /// - input: `[Float64, Date32]`
7363    /// - state: `[Int32, Utf8, Boolean]`
7364    /// - output: `Int64`
7365    #[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    /// Stub test: many input args, few state fields (5 inputs / 2 state).
7400    ///
7401    /// All eight types involved are pairwise-disjoint:
7402    ///   - input:  `[Float64, Date32, UInt16, Boolean, Int32]`
7403    ///   - state:  `[Utf8, Int64]`
7404    ///   - output: `Float32`
7405    #[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    /// Stub test: few input args, many state fields (2 inputs / 5 state).
7452    ///
7453    /// All eight types involved are pairwise-disjoint:
7454    ///   - input:  `[Float64, Date32]`
7455    ///   - state:  `[Boolean, Int32, Utf8, Int64, UInt16]`
7456    ///   - output: `Float32`
7457    #[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    /// Test-only aggregate whose `return_type`, `state_fields`, and
7498    /// `accumulator` hooks all assert that they receive the originally-
7499    /// declared input types; the companion accumulator further asserts
7500    /// `update_batch` sees inputs and `merge_batch` sees state.
7501    ///
7502    /// Each test instantiates it with input / state / output types that
7503    /// are pairwise-disjoint, so a regression that forwarded the wrong
7504    /// types fails on type mismatch rather than passing by accident.
7505    #[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    /// Produce a zeroed [`ScalarValue`] for `dt`. Only the data types the
7530    /// tests above plug into [`InputTypeAssertingUdaf`] are listed; adding
7531    /// a new type to a test requires extending this match.
7532    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            // Within-test type-disjointness is enforced by construction so
7554            // a future test author can't quietly reintroduce overlap.
7555            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    /// True iff every type in `inputs ∪ states ∪ {output}` is unique.
7571    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    /// Companion accumulator for [`InputTypeAssertingUdaf`].
7644    ///
7645    /// - `update_batch` must always receive arrays of the original input
7646    ///   types.
7647    /// - `merge_batch` must always receive arrays of the declared state
7648    ///   types.
7649    ///
7650    /// Anything else means a non-input mode is calling the wrong path.
7651    #[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 that [`AggregateExec::with_dynamic_filter_expr`] overrides the existing dynamic filter
7835    #[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        // Partial min aggregate supports dynamic filtering
7841        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        // Assertion 1: A filter with the same children can override the existing
7856        // dynamic filter.
7857        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        // The aggregate's filter should now resolve to the new inner expression.
7867        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        // Assertion 2: A filter that has been through `PhysicalExpr::with_new_children`
7874        // should still be accepted when the new children are equivalent to the originals.
7875        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        // Hard to assert this because the filter is identical. No error means
7885        // the filter was accepted. That's a good enough assertion for now.
7886        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 that [`AggregateExec::with_dynamic_filter_expr`] errors when the aggregate does not support dynamic filtering
7923    #[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        // Final mode with a group-by does not support dynamic filters.
7932        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 that [`AggregateExec::with_dynamic_filter_expr`] errors when the column is not in the schema
7956    #[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}