@@ -4945,6 +4945,83 @@ mod tests {
49454945 Ok ( ( ) )
49464946 }
49474947
4948+ #[ tokio:: test]
4949+ async fn unsorted_contiguous_groups_use_final_emission ( ) -> Result < ( ) > {
4950+ let schema = Arc :: new ( Schema :: new ( vec ! [
4951+ Field :: new( "key" , DataType :: Int32 , false ) ,
4952+ Field :: new( "time_bin" , DataType :: Int64 , false ) ,
4953+ Field :: new( "value" , DataType :: Int64 , false ) ,
4954+ ] ) ) ;
4955+ // Two sorted logical runs are emitted as batches in one DataFusion
4956+ // partition. Every distinct grouping tuple occupies one contiguous range,
4957+ // but tuple order resets at the batch boundary, so (key, time_bin) is not
4958+ // globally sorted.
4959+ let input_batches = vec ! [
4960+ RecordBatch :: try_new(
4961+ Arc :: clone( & schema) ,
4962+ vec![
4963+ Arc :: new( Int32Array :: from( vec![ 1 , 1 , 2 , 2 ] ) ) ,
4964+ Arc :: new( Int64Array :: from( vec![ 20 , 20 , 20 , 20 ] ) ) ,
4965+ Arc :: new( Int64Array :: from( vec![ 10 , 20 , 30 , 40 ] ) ) ,
4966+ ] ,
4967+ ) ?,
4968+ RecordBatch :: try_new(
4969+ Arc :: clone( & schema) ,
4970+ vec![
4971+ Arc :: new( Int32Array :: from( vec![ 1 , 1 , 2 , 2 ] ) ) ,
4972+ Arc :: new( Int64Array :: from( vec![ 0 , 0 , 0 , 0 ] ) ) ,
4973+ Arc :: new( Int64Array :: from( vec![ 50 , 60 , 70 , 80 ] ) ) ,
4974+ ] ,
4975+ ) ?,
4976+ ] ;
4977+ let group_by = PhysicalGroupBy :: new_single ( vec ! [
4978+ ( col( "key" , & schema) ?, "key" . to_string( ) ) ,
4979+ ( col( "time_bin" , & schema) ?, "time_bin" . to_string( ) ) ,
4980+ ] ) ;
4981+ let aggr_expr = Arc :: new (
4982+ AggregateExprBuilder :: new ( sum_udaf ( ) , vec ! [ col( "value" , & schema) ?] )
4983+ . schema ( Arc :: clone ( & schema) )
4984+ . alias ( "SUM(value)" )
4985+ . build ( ) ?,
4986+ ) ;
4987+ let input: Arc < dyn ExecutionPlan > =
4988+ TestMemoryExec :: try_new_exec ( & [ input_batches] , Arc :: clone ( & schema) , None ) ?;
4989+ assert_eq ! ( input. output_partitioning( ) . partition_count( ) , 1 ) ;
4990+
4991+ let aggregate = AggregateExec :: try_new (
4992+ AggregateMode :: Single ,
4993+ group_by,
4994+ vec ! [ aggr_expr] ,
4995+ vec ! [ None ] ,
4996+ input,
4997+ schema,
4998+ ) ?;
4999+
5000+ assert_eq ! ( aggregate. input_order_mode( ) , & InputOrderMode :: Linear ) ;
5001+ // This captures the behavior before #24438. When the source can declare
5002+ // `(key, time_bin)` group-contiguous, the corresponding case can use
5003+ // `EmissionType::Incremental`.
5004+ assert_eq ! ( aggregate. cache( ) . emission_type, EmissionType :: Final ) ;
5005+
5006+ let task_ctx = new_migrated_hash_ctx ( 1024 ) ;
5007+ let stream = aggregate. execute_typed ( 0 , & task_ctx) ?;
5008+ assert ! ( matches!( stream, StreamType :: SingleHash ( _) ) ) ;
5009+ let stream: SendableRecordBatchStream = stream. into ( ) ;
5010+ let output = collect ( stream) . await ?;
5011+ assert_snapshot ! ( batches_to_sort_string( & output) , @r"
5012+ +-----+----------+------------+
5013+ | key | time_bin | SUM(value) |
5014+ +-----+----------+------------+
5015+ | 1 | 0 | 110 |
5016+ | 1 | 20 | 30 |
5017+ | 2 | 0 | 150 |
5018+ | 2 | 20 | 70 |
5019+ +-----+----------+------------+
5020+ " ) ;
5021+
5022+ Ok ( ( ) )
5023+ }
5024+
49485025 /// Ensures for ordered input, `OrderedPartialAggregateStream` is used.
49495026 #[ tokio:: test]
49505027 async fn ordered_partial_aggregate_planning ( ) -> Result < ( ) > {
0 commit comments