@@ -5438,6 +5438,83 @@ mod tests {
54385438 Ok ( ( ) )
54395439 }
54405440
5441+ #[ tokio:: test]
5442+ async fn unsorted_contiguous_groups_use_final_emission ( ) -> Result < ( ) > {
5443+ let schema = Arc :: new ( Schema :: new ( vec ! [
5444+ Field :: new( "key" , DataType :: Int32 , false ) ,
5445+ Field :: new( "time_bin" , DataType :: Int64 , false ) ,
5446+ Field :: new( "value" , DataType :: Int64 , false ) ,
5447+ ] ) ) ;
5448+ // Two sorted logical runs are emitted as batches in one DataFusion
5449+ // partition. Every distinct grouping tuple occupies one contiguous range,
5450+ // but tuple order resets at the batch boundary, so (key, time_bin) is not
5451+ // globally sorted.
5452+ let input_batches = vec ! [
5453+ RecordBatch :: try_new(
5454+ Arc :: clone( & schema) ,
5455+ vec![
5456+ Arc :: new( Int32Array :: from( vec![ 1 , 1 , 2 , 2 ] ) ) ,
5457+ Arc :: new( Int64Array :: from( vec![ 20 , 20 , 20 , 20 ] ) ) ,
5458+ Arc :: new( Int64Array :: from( vec![ 10 , 20 , 30 , 40 ] ) ) ,
5459+ ] ,
5460+ ) ?,
5461+ RecordBatch :: try_new(
5462+ Arc :: clone( & schema) ,
5463+ vec![
5464+ Arc :: new( Int32Array :: from( vec![ 1 , 1 , 2 , 2 ] ) ) ,
5465+ Arc :: new( Int64Array :: from( vec![ 0 , 0 , 0 , 0 ] ) ) ,
5466+ Arc :: new( Int64Array :: from( vec![ 50 , 60 , 70 , 80 ] ) ) ,
5467+ ] ,
5468+ ) ?,
5469+ ] ;
5470+ let group_by = PhysicalGroupBy :: new_single ( vec ! [
5471+ ( col( "key" , & schema) ?, "key" . to_string( ) ) ,
5472+ ( col( "time_bin" , & schema) ?, "time_bin" . to_string( ) ) ,
5473+ ] ) ;
5474+ let aggr_expr = Arc :: new (
5475+ AggregateExprBuilder :: new ( sum_udaf ( ) , vec ! [ col( "value" , & schema) ?] )
5476+ . schema ( Arc :: clone ( & schema) )
5477+ . alias ( "SUM(value)" )
5478+ . build ( ) ?,
5479+ ) ;
5480+ let input: Arc < dyn ExecutionPlan > =
5481+ TestMemoryExec :: try_new_exec ( & [ input_batches] , Arc :: clone ( & schema) , None ) ?;
5482+ assert_eq ! ( input. output_partitioning( ) . partition_count( ) , 1 ) ;
5483+
5484+ let aggregate = AggregateExec :: try_new (
5485+ AggregateMode :: Single ,
5486+ group_by,
5487+ vec ! [ aggr_expr] ,
5488+ vec ! [ None ] ,
5489+ input,
5490+ schema,
5491+ ) ?;
5492+
5493+ assert_eq ! ( aggregate. input_order_mode( ) , & InputOrderMode :: Linear ) ;
5494+ // This captures the behavior before #24438. When the source can declare
5495+ // `(key, time_bin)` group-contiguous, the corresponding case can use
5496+ // `EmissionType::Incremental`.
5497+ assert_eq ! ( aggregate. cache( ) . emission_type, EmissionType :: Final ) ;
5498+
5499+ let task_ctx = new_migrated_hash_ctx ( 1024 ) ;
5500+ let stream = aggregate. execute_typed ( 0 , & task_ctx) ?;
5501+ assert ! ( matches!( stream, StreamType :: SingleHash ( _) ) ) ;
5502+ let stream: SendableRecordBatchStream = stream. into ( ) ;
5503+ let output = collect ( stream) . await ?;
5504+ assert_snapshot ! ( batches_to_sort_string( & output) , @r"
5505+ +-----+----------+------------+
5506+ | key | time_bin | SUM(value) |
5507+ +-----+----------+------------+
5508+ | 1 | 0 | 110 |
5509+ | 1 | 20 | 30 |
5510+ | 2 | 0 | 150 |
5511+ | 2 | 20 | 70 |
5512+ +-----+----------+------------+
5513+ " ) ;
5514+
5515+ Ok ( ( ) )
5516+ }
5517+
54415518 /// Ensures for ordered input, `OrderedPartialAggregateStream` is used.
54425519 #[ tokio:: test]
54435520 async fn ordered_partial_aggregate_planning ( ) -> Result < ( ) > {
0 commit comments