jayzhan211 opened a new pull request, #27416:
URL: https://github.com/apache/datafusion/pull/27416

   ## Which issue does this PR close?
   
   - Closes #27219.
   
   ## Rationale for this change
   
   When a query declares a `MATERIALIZED` CTE whose body groups by the sort 
column, `ORDER BY ... LIMIT n` above that query returns wrong rows. The query 
below returns no rows instead of 3:
   
   ```sql
   SET datafusion.sql_parser.dialect = 'PostgreSQL';  -- needed to parse 
MATERIALIZED
   CREATE TABLE t AS SELECT value AS x FROM generate_series(1, 200);
   
   SELECT * FROM (
     WITH c AS MATERIALIZED (SELECT x FROM t GROUP BY x)
     SELECT x FROM c WHERE x > 100
   ) s ORDER BY x LIMIT 3;
   ```
   
   | Query | main | This PR |
   |---|---|---|
   | Above | no rows | 101, 102, 103 |
   | Same CTE, `SELECT c.x, m.n FROM c CROSS JOIN (SELECT count(*) AS n FROM c) 
m` | `n` = 3 | `n` = 200 |
   
   The physical optimizer rule `TopKAggregation` starts at `SortExec: 
TopK(fetch=3)` and walks down through nodes that keep the row count. 
`MaterializedCteExec` says it keeps the row count, and its first child is the 
CTE body. So the walk goes into the body and sets `lim=[3]` on `GROUP BY x`. 
The body runs once, and every reference to the CTE reads its output, so every 
reference sees only the groups that the limit kept. `MaterializedCteExec` 
outputs only its second child, the continuation; the body is never its output. 
The same bug is on `branch-56` (56.0.0-rc1), and this patch applies there 
cleanly.
   
   This PR stops the walk at `MaterializedCteExec`.
   
   ## What changes are included in this PR?
   
   `datafusion/physical-optimizer/src/topk_aggregation.rs`: one new branch 
stops the walk at `MaterializedCteExec`. `cte_materialized.slt`: the two 
queries above.
   
   The decision I'd most like reviewed: the rule names `MaterializedCteExec` 
instead of stopping at every node with more than one child. The general rule 
would also remove a `lim` that main sets today, on an aggregate that is the 
first input of a `UnionExec`, which is a valid limit:
   
   ```text
   SortExec: TopK(fetch=3), expr=[x@0 ASC NULLS LAST]
     UnionExec
       AggregateExec: mode=FinalPartitioned, gby=[x@0 as x], aggr=[], lim=[3]
       ...
   ```
   
   <details>
   <summary>Why the walk doesn't go into the continuation instead</summary>
   
   Limiting an aggregate in the continuation would be valid, but it is a new 
optimization, and it only matters when `ORDER BY ... LIMIT` is outside the 
`WITH` query. When it is inside, the sort is already in the continuation and 
the rule applies there as before:
   
   ```text
   MaterializedCteExec: name=c
     DataSourceExec: ...
     SortPreservingMergeExec: [y@0 ASC NULLS LAST], fetch=3
       SortExec: TopK(fetch=3), expr=[y@0 ASC NULLS LAST]
         AggregateExec: mode=FinalPartitioned, gby=[y@0 as y], aggr=[], lim=[3]
   ```
   
   </details>
   
   Who is affected: queries that use `AS MATERIALIZED` (new in 56, PostgreSQL 
dialect only) with `ORDER BY` + `LIMIT` above the `WITH` query and a `GROUP BY` 
on the sort column in the CTE body. No other plan changes. No API change.
   
   ## What is the testing strategy for this PR?
   
   - The two queries above are added to `cte_materialized.slt`. Both fail on 
main and pass with this PR.
   - The full sqllogictest suite passes (306 files) with no other plan changes, 
and so do the `datafusion-physical-optimizer` unit tests.
   - I compared the plans of several `UNION ALL`, scalar subquery, and 
`MATERIALIZED` CTE queries before and after this change. Only the plan of the 
query above changed: its body aggregates no longer have `lim=[3]`.
   
   ## Are there any user-facing changes?
   
   Yes: these queries now return correct results. There are no API changes.
   


-- 
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