@@ -1064,7 +1064,14 @@ impl AggregateExec {
10641064 input_order_mode = InputOrderMode :: Linear ;
10651065 }
10661066
1067- let group_completion_mode = GroupCompletionMode :: from ( & input_order_mode) ;
1067+ let group_completion_mode = if !group_by. has_grouping_set ( )
1068+ && !groupby_exprs. is_empty ( )
1069+ && input_eq_properties. grouping_satisfy ( groupby_exprs. iter ( ) . cloned ( ) ) ?
1070+ {
1071+ GroupCompletionMode :: Full
1072+ } else {
1073+ GroupCompletionMode :: from ( & input_order_mode)
1074+ } ;
10681075
10691076 // construct a map from the input expression to the output expression of the Aggregation group by
10701077 let group_expr_mapping =
@@ -1083,6 +1090,16 @@ impl AggregateExec {
10831090 aggr_expr. as_ref ( ) ,
10841091 ) ?
10851092 } ;
1093+ // `compute_properties` derives emission from the public input-order
1094+ // mode. Override it only for the new case where unsorted input still
1095+ // has a group-completion guarantee.
1096+ let cache = if input_order_mode == InputOrderMode :: Linear
1097+ && group_completion_mode != GroupCompletionMode :: None
1098+ {
1099+ cache. with_emission_type ( input. pipeline_behavior ( ) )
1100+ } else {
1101+ cache
1102+ } ;
10861103
10871104 let mut exec = AggregateExec {
10881105 mode,
@@ -1436,6 +1453,10 @@ impl AggregateExec {
14361453 let mut eq_properties = input
14371454 . equivalence_properties ( )
14381455 . project ( group_expr_mapping, schema) ;
1456+ // Grouping information is consumed by this aggregate. The aggregate
1457+ // output may have a different row layout, so do not pass explicit
1458+ // input grouping assertions to another aggregate.
1459+ eq_properties. clear_groupings ( ) ;
14391460
14401461 // True no-group aggregates produce only one row in each output
14411462 // partition, so aggregate outputs are constants within the partition.
@@ -2374,7 +2395,7 @@ impl ExecutionPlan for AggregateExec {
23742395 required_input_ordering : _,
23752396 // Derived at construction from the input ordering and `group_by`.
23762397 input_order_mode : _,
2377- // Derived at construction from `input_order_mode `.
2398+ // Derived at construction from the input properties and `group_by `.
23782399 group_completion_mode : _,
23792400 // Derived at construction by `Self::compute_properties`.
23802401 cache : _,
@@ -3266,7 +3287,7 @@ mod tests {
32663287 Int64Array , NullArray , StructArray , UInt32Array , UInt64Array ,
32673288 } ;
32683289 use arrow:: compute:: { SortOptions , concat_batches} ;
3269- use arrow:: datatypes:: Int32Type ;
3290+ use arrow:: datatypes:: { Int32Type , TimeUnit } ;
32703291 use datafusion_common:: test_util:: { batches_to_sort_string, batches_to_string} ;
32713292 use datafusion_common:: { DataFusionError , internal_err} ;
32723293 use datafusion_execution:: config:: SessionConfig ;
@@ -3277,6 +3298,7 @@ mod tests {
32773298 Accumulator , AggregateUDF , AggregateUDFImpl , EmitTo , GroupsAccumulator ,
32783299 Signature , Volatility ,
32793300 } ;
3301+ use datafusion_functions:: datetime:: date_bin;
32803302 use datafusion_functions_aggregate:: approx_percentile_cont:: approx_percentile_cont_udaf;
32813303 use datafusion_functions_aggregate:: array_agg:: array_agg_udaf;
32823304 use datafusion_functions_aggregate:: average:: avg_udaf;
@@ -3285,10 +3307,9 @@ mod tests {
32853307 use datafusion_functions_aggregate:: median:: median_udaf;
32863308 use datafusion_functions_aggregate:: min_max:: min_udaf;
32873309 use datafusion_functions_aggregate:: sum:: sum_udaf;
3288- use datafusion_physical_expr:: Partitioning ;
3289- use datafusion_physical_expr:: PhysicalSortExpr ;
32903310 use datafusion_physical_expr:: aggregate:: AggregateExprBuilder ;
32913311 use datafusion_physical_expr:: expressions:: { Literal , NotExpr } ;
3312+ use datafusion_physical_expr:: { Partitioning , PhysicalSortExpr , ScalarFunctionExpr } ;
32923313
32933314 use crate :: projection:: ProjectionExec ;
32943315 use crate :: repartition:: RepartitionExec ;
@@ -4852,7 +4873,7 @@ mod tests {
48524873 }
48534874
48544875 #[ tokio:: test]
4855- async fn unsorted_contiguous_groups_use_final_emission ( ) -> Result < ( ) > {
4876+ async fn unsorted_contiguous_groups_use_incremental_emission ( ) -> Result < ( ) > {
48564877 let schema = Arc :: new ( Schema :: new ( vec ! [
48574878 Field :: new( "key" , DataType :: Int32 , false ) ,
48584879 Field :: new( "time_bin" , DataType :: Int64 , false ) ,
@@ -4880,18 +4901,21 @@ mod tests {
48804901 ] ,
48814902 ) ?,
48824903 ] ;
4904+ let key = col ( "key" , & schema) ?;
4905+ let time_bin = col ( "time_bin" , & schema) ?;
48834906 let group_by = PhysicalGroupBy :: new_single ( vec ! [
4884- ( col ( "key" , & schema ) ? , "key" . to_string( ) ) ,
4885- ( col ( "time_bin" , & schema ) ? , "time_bin" . to_string( ) ) ,
4907+ ( Arc :: clone ( & key ) , "key" . to_string( ) ) ,
4908+ ( Arc :: clone ( & time_bin ) , "time_bin" . to_string( ) ) ,
48864909 ] ) ;
48874910 let aggr_expr = Arc :: new (
48884911 AggregateExprBuilder :: new ( sum_udaf ( ) , vec ! [ col( "value" , & schema) ?] )
48894912 . schema ( Arc :: clone ( & schema) )
48904913 . alias ( "SUM(value)" )
48914914 . build ( ) ?,
48924915 ) ;
4893- let input: Arc < dyn ExecutionPlan > =
4894- TestMemoryExec :: try_new_exec ( & [ input_batches] , Arc :: clone ( & schema) , None ) ?;
4916+ let input = TestMemoryExec :: try_new ( & [ input_batches] , Arc :: clone ( & schema) , None ) ?
4917+ . try_with_grouping_information ( vec ! [ vec![ key, time_bin] ] ) ?;
4918+ let input: Arc < dyn ExecutionPlan > = Arc :: new ( input) ;
48954919 assert_eq ! ( input. output_partitioning( ) . partition_count( ) , 1 ) ;
48964920
48974921 let aggregate = AggregateExec :: try_new (
@@ -4904,15 +4928,20 @@ mod tests {
49044928 ) ?;
49054929
49064930 assert_eq ! ( aggregate. input_order_mode( ) , & InputOrderMode :: Linear ) ;
4907- assert_eq ! ( aggregate. group_completion_mode, GroupCompletionMode :: None ) ;
4908- // This captures the behavior before #24438. When the source can declare
4909- // `(key, time_bin)` group-contiguous, the corresponding case can use
4910- // `EmissionType::Incremental`.
4911- assert_eq ! ( aggregate. cache( ) . emission_type, EmissionType :: Final ) ;
4931+ assert_eq ! ( aggregate. group_completion_mode, GroupCompletionMode :: Full ) ;
4932+ assert_eq ! ( aggregate. cache( ) . emission_type, EmissionType :: Incremental ) ;
4933+ assert ! (
4934+ aggregate
4935+ . cache( )
4936+ . equivalence_properties( )
4937+ . geq_class( )
4938+ . is_empty( )
4939+ ) ;
4940+ assert ! ( aggregate. cache( ) . output_ordering( ) . is_none( ) ) ;
49124941
49134942 let task_ctx = new_migrated_hash_ctx ( 1024 ) ;
49144943 let stream = aggregate. execute_typed ( 0 , & task_ctx) ?;
4915- assert ! ( matches!( stream, StreamType :: SingleHash ( _) ) ) ;
4944+ assert ! ( matches!( stream, StreamType :: OrderedSingleAggregate ( _) ) ) ;
49164945 let stream: SendableRecordBatchStream = stream. into ( ) ;
49174946 let output = collect ( stream) . await ?;
49184947 assert_snapshot ! ( batches_to_sort_string( & output) , @r"
@@ -4929,6 +4958,65 @@ mod tests {
49294958 Ok ( ( ) )
49304959 }
49314960
4961+ #[ test]
4962+ fn grouped_date_bin_projects_to_aggregate ( ) -> Result < ( ) > {
4963+ let schema = Arc :: new ( Schema :: new ( vec ! [
4964+ Field :: new( "key" , DataType :: Int32 , false ) ,
4965+ Field :: new( "time" , DataType :: Timestamp ( TimeUnit :: Second , None ) , false ) ,
4966+ ] ) ) ;
4967+ let time_bin_expr = || -> Result < Arc < dyn PhysicalExpr > > {
4968+ Ok ( Arc :: new ( ScalarFunctionExpr :: try_new (
4969+ date_bin ( ) ,
4970+ vec ! [
4971+ lit( ScalarValue :: new_interval_dt( 0 , 10_000 ) ) ,
4972+ col( "time" , & schema) ?,
4973+ ] ,
4974+ & schema,
4975+ Arc :: new ( ConfigOptions :: default ( ) ) ,
4976+ ) ?) )
4977+ } ;
4978+ let input = TestMemoryExec :: try_new ( & [ vec ! [ ] ] , Arc :: clone ( & schema) , None ) ?
4979+ . try_with_grouping_information ( vec ! [ vec![
4980+ col( "key" , & schema) ?,
4981+ time_bin_expr( ) ?,
4982+ ] ] ) ?;
4983+ let projection = ProjectionExec :: try_new (
4984+ [
4985+ ProjectionExpr :: new ( col ( "key" , & schema) ?, "key" ) ,
4986+ // Build this expression independently from the source
4987+ // assertion so the test exercises semantic expression matching.
4988+ ProjectionExpr :: new ( time_bin_expr ( ) ?, "time_bin" ) ,
4989+ ] ,
4990+ Arc :: new ( input) ,
4991+ ) ?;
4992+
4993+ let projected_schema = projection. schema ( ) ;
4994+ let key = col ( "key" , & projected_schema) ?;
4995+ let time_bin = col ( "time_bin" , & projected_schema) ?;
4996+ assert ! (
4997+ projection
4998+ . properties( )
4999+ . equivalence_properties( )
5000+ . grouping_satisfy( [ Arc :: clone( & key) , Arc :: clone( & time_bin) ] ) ?
5001+ ) ;
5002+ let aggregate = AggregateExec :: try_new (
5003+ AggregateMode :: Single ,
5004+ PhysicalGroupBy :: new_single ( vec ! [
5005+ ( key, "key" . to_string( ) ) ,
5006+ ( time_bin, "time_bin" . to_string( ) ) ,
5007+ ] ) ,
5008+ vec ! [ ] ,
5009+ vec ! [ ] ,
5010+ Arc :: new ( projection) ,
5011+ projected_schema,
5012+ ) ?;
5013+
5014+ assert_eq ! ( aggregate. input_order_mode, InputOrderMode :: Linear ) ;
5015+ assert_eq ! ( aggregate. group_completion_mode, GroupCompletionMode :: Full ) ;
5016+ assert_eq ! ( aggregate. cache( ) . emission_type, EmissionType :: Incremental ) ;
5017+ Ok ( ( ) )
5018+ }
5019+
49325020 /// Ensures for ordered input, `OrderedPartialAggregateStream` is used.
49335021 #[ tokio:: test]
49345022 async fn ordered_partial_aggregate_planning ( ) -> Result < ( ) > {
0 commit comments