@@ -1297,13 +1297,15 @@ impl GroupedHashAggregateStream {
12971297
12981298 // Recreate `group_values` for streaming merge so group ids are assigned
12991299 // in first-seen order, as required by `GroupOrderingFull`.
1300- // The pre-spill multi-column collector may use `vectorized_intern`, which
1301- // can assign new group ids out of input order under hash collisions.
1300+ // The pre-spill collector may not track group ids for DISTINCT-only
1301+ // aggregation. The pre-spill multi-column collector may use
1302+ // `vectorized_intern`, which can assign new group ids out of input
1303+ // order under hash collisions.
13021304 let group_schema = self
13031305 . spill_state
13041306 . merging_group_by
13051307 . group_schema ( & self . spill_state . spill_schema ) ?;
1306- if group_schema. fields ( ) . len ( ) > 1 {
1308+ if self . accumulators . is_empty ( ) || group_schema. fields ( ) . len ( ) > 1 {
13071309 self . group_values = new_group_values ( group_schema, & self . group_ordering ) ?;
13081310 }
13091311
@@ -1393,9 +1395,12 @@ mod tests {
13931395 use super :: * ;
13941396 use crate :: InputOrderMode ;
13951397 use crate :: execution_plan:: ExecutionPlan ;
1398+ use crate :: metrics:: MetricValue ;
13961399 use crate :: test:: TestMemoryExec ;
13971400 use arrow:: array:: { Int32Array , Int64Array } ;
13981401 use arrow:: datatypes:: { DataType , Field , Schema } ;
1402+ use datafusion_execution:: config:: SessionConfig ;
1403+ use datafusion_execution:: memory_pool:: FairSpillPool ;
13991404 use datafusion_execution:: runtime_env:: RuntimeEnvBuilder ;
14001405 use datafusion_functions_aggregate:: count:: count_udaf;
14011406 use datafusion_physical_expr:: aggregate:: AggregateExprBuilder ;
@@ -1507,6 +1512,80 @@ mod tests {
15071512 Ok ( ( ) )
15081513 }
15091514
1515+ #[ tokio:: test]
1516+ async fn test_spill_distinct_single_primitive_group_by ( ) -> Result < ( ) > {
1517+ let schema = Arc :: new ( Schema :: new ( vec ! [ Field :: new(
1518+ "group_col" ,
1519+ DataType :: Int32 ,
1520+ false ,
1521+ ) ] ) ) ;
1522+
1523+ let num_distinct = 512 ;
1524+ let num_spills = 24 ;
1525+ let input_partitions = vec ! [
1526+ ( 0 ..num_spills)
1527+ . map( |_| {
1528+ Ok ( RecordBatch :: try_new(
1529+ Arc :: clone( & schema) ,
1530+ vec![ Arc :: new( Int32Array :: from_iter_values( 0 ..num_distinct) ) ] ,
1531+ ) ?)
1532+ } )
1533+ . collect:: <Result <Vec <_>>>( ) ?,
1534+ ] ;
1535+
1536+ let session_config = SessionConfig :: new ( ) . with_batch_size ( 4 ) ;
1537+ let runtime = RuntimeEnvBuilder :: new ( )
1538+ . with_memory_pool ( Arc :: new ( FairSpillPool :: new ( 6000 ) ) )
1539+ . build_arc ( ) ?;
1540+ let task_ctx = Arc :: new (
1541+ TaskContext :: default ( )
1542+ . with_session_config ( session_config)
1543+ . with_runtime ( runtime) ,
1544+ ) ;
1545+
1546+ let group_expr = vec ! [ ( col( "group_col" , & schema) ?, "group_col" . to_string( ) ) ] ;
1547+ let exec = TestMemoryExec :: try_new ( & input_partitions, Arc :: clone ( & schema) , None ) ?;
1548+ let exec = Arc :: new ( TestMemoryExec :: update_cache ( & Arc :: new ( exec) ) ) ;
1549+
1550+ let aggregate_exec = AggregateExec :: try_new (
1551+ AggregateMode :: Single ,
1552+ PhysicalGroupBy :: new_single ( group_expr) ,
1553+ Vec :: < Arc < AggregateFunctionExpr > > :: new ( ) ,
1554+ vec ! [ ] ,
1555+ exec,
1556+ Arc :: clone ( & schema) ,
1557+ ) ?;
1558+
1559+ let mut stream =
1560+ GroupedHashAggregateStream :: new ( & aggregate_exec, & Arc :: clone ( & task_ctx) , 0 ) ?;
1561+ let mut values = Vec :: new ( ) ;
1562+
1563+ while let Some ( result) = stream. next ( ) . await {
1564+ let batch = result?;
1565+ let group_col = batch
1566+ . column ( 0 )
1567+ . as_primitive :: < arrow:: datatypes:: Int32Type > ( ) ;
1568+ values. extend ( group_col. values ( ) . iter ( ) . copied ( ) ) ;
1569+ }
1570+
1571+ let spill_count = aggregate_exec
1572+ . metrics ( )
1573+ . unwrap ( )
1574+ . iter ( )
1575+ . find_map ( |metric| match metric. value ( ) {
1576+ MetricValue :: SpillCount ( count) => Some ( count. value ( ) ) ,
1577+ _ => None ,
1578+ } )
1579+ . unwrap_or_default ( ) ;
1580+ assert ! ( spill_count > 0 , "expected test to exercise spilling" ) ;
1581+
1582+ values. sort_unstable ( ) ;
1583+ let expected = ( 0 ..num_distinct) . collect :: < Vec < _ > > ( ) ;
1584+ assert_eq ! ( values, expected) ;
1585+
1586+ Ok ( ( ) )
1587+ }
1588+
15101589 #[ tokio:: test]
15111590 async fn test_skip_aggregation_probe_not_locked_until_skip ( ) -> Result < ( ) > {
15121591 // Test that the probe is not locked until we actually decide to skip.
0 commit comments