alexy opened a new issue, #26054:
URL: https://github.com/apache/datafusion/issues/26054

   ### Describe the bug
   
   In a recursive CTE, an uncorrelated `MIN`/`MAX` aggregate over a table scan 
in the recursive term returns the right value on the first iteration and NULL 
(or a wrong value) on every later iteration.
   
   `RecursiveQueryExec` runs the recursive term again for each iteration after 
`reset_plan_states`. `AggregateExec` has no `reset_state` of its own, and its 
`replace_children` copies `dynamic_filter` into the new node 
(`me.dynamic_filter.clone_from(&self.dynamic_filter)`). So the rebuilt plan 
shares the `DynamicFilterPhysicalExpr` that the first iteration tightened. The 
scan in the second iteration starts with `r > 128`, finds nothing, and `MAX` is 
NULL.
   
   `HashJoinExec` and `SortExec` reset their dynamic filters in `reset_state`; 
`AggregateExec` does not. Reproduced on 55.1.0, and the same code is on `main` 
(8248a57969).
   
   ### To Reproduce
   
   With datafusion-cli (or any `SessionContext`):
   
   ```sql
   COPY (SELECT CAST(value AS DOUBLE) AS r FROM generate_series(0, 128)) TO 
'/tmp/df_recursive_max/t.parquet';
   CREATE EXTERNAL TABLE t STORED AS PARQUET LOCATION 
'/tmp/df_recursive_max/t.parquet';
   
   WITH RECURSIVE w(n, m) AS (
     SELECT 0, CAST(NULL AS DOUBLE)
     UNION ALL
     SELECT w.n + 1, x.m
     FROM w CROSS JOIN (SELECT MAX(r) AS m FROM t WHERE r % 2 = 0) x
     WHERE w.n < 3
   )
   SELECT n, m FROM w ORDER BY n;
   ```
   
   ```
   +---+-------+
   | n | m     |
   +---+-------+
   | 0 |       |
   | 1 | 128.0 |
   | 2 |       |
   | 3 |       |
   +---+-------+
   ```
   
   The `WHERE r % 2 = 0` matters only because without a filter, `MAX(r)` is 
answered from the Parquet statistics and nothing is scanned.
   
   ### Expected behavior
   
   ```
   +---+-------+
   | n | m     |
   +---+-------+
   | 0 |       |
   | 1 | 128.0 |
   | 2 | 128.0 |
   | 3 | 128.0 |
   +---+-------+
   ```
   
   which is what `SET 
datafusion.optimizer.enable_aggregate_dynamic_filter_pushdown = false` gives.
   
   ### Additional context
   
   - A possible fix is an `AggregateExec::reset_state` that creates a fresh 
dynamic filter (as `init_dynamic_filter` does) and pushes it to the input 
again, as `HashJoinExec::reset_state` and `SortExec::reset_state` do. #21650 
discusses the general problem of shared state across `reset_state`.
   - Found through a downstream project 
([Sail](https://github.com/lakehq/sail)) running a long recursive query, where 
a later iteration silently lost rows. Work around it by turning off 
`enable_aggregate_dynamic_filter_pushdown` for plans that contain a 
`RecursiveQueryExec`.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to