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

   ## Which issue does this PR close?
   
   - Closes apache/datafusion#26237.
   
   Stacked on apache/datafusion#25580 and on #26269 (apache/datafusion#26238). 
Only the last commit is new.
   
   ## Rationale for this change
   
   A CTE that has no directive is inlined at each reference, so its body runs 
one time for each reference:
   
   ```sql
   set datafusion.optimizer.enable_automatic_cte_materialization = true;
   
   EXPLAIN WITH c AS (SELECT k, sum(v) AS s FROM t GROUP BY k)
   SELECT a.k FROM c a JOIN c b ON a.s = b.s;
   ```
   
   With the option off, which is the default, `t` is aggregated two times. With 
the option on, the body is computed one time:
   
   ```
   MaterializedCte: name=c, automatic=true
     SubqueryAlias: c
       Projection: t.k, sum(t.v) AS s
         Aggregate: groupBy=[[t.k]], aggr=[[sum(CAST(t.v AS Int64))]]
           TableScan: t projection=[k, v]
     Projection: a.k
       Inner Join: a.s = b.s
         SubqueryAlias: a
           MaterializedCteScan: name=c
         SubqueryAlias: b
           Projection: c.s
             MaterializedCteScan: name=c
   ```
   
   This works in each SQL dialect. A CTE with one reference, or with a cheap 
body, plans exactly as before.
   
   ## What changes are included in this PR?
   
   - New option `datafusion.optimizer.enable_automatic_cte_materialization`, 
default `false`. With the default, each query plans as before.
   - `MaterializedCte` has the new field `automatic`. When the option is on, 
the SQL planner plans each non-recursive CTE that has no directive as an 
automatic `MaterializedCte`.
   - New optimizer rule `InlineAutomaticCtes`, the first rule of the optimizer. 
It replaces an automatic node with a copy of the body in place of each scan 
when one of these conditions is true:
     1. The CTE has zero or one reference.
     2. The body has a volatile expression. Materialization would change the 
result (apache/datafusion#10337).
     3. The body has an outer reference.
     4. The body is cheap: only projections, filters and aliases on one table 
scan, materialized CTE scan, `VALUES` list or empty relation, without a 
subquery expression.
   - In all other cases the CTE stays materialized. The rule does not use 
statistics or a cost model.
   - `AS MATERIALIZED` and `AS NOT MATERIALIZED` override the rule.
   - A CTE in a `WITH RECURSIVE` block and a CTE with a correlated body are 
inlined by the SQL planner, as before. `SELECT INTO` with an automatic CTE 
inlines the CTE.
   - An automatic CTE with an unbounded body cannot be buffered. The physical 
planner inlines it and plans the query again, so a query that worked before 
still works. This uses a private marker error that `create_initial_plan` 
catches; a different mechanism is possible.
   
   Known limits:
   
   - For an unbounded body, `EXPLAIN` shows the `MaterializedCte` node in the 
logical plan and the inlined query in the physical plan, and the inlined copies 
do not get logical filter pushdown.
   - With the option on, a plan that is not optimized has `MaterializedCte` 
nodes for each CTE. The unparser and the Substrait producer return a "not 
implemented" error for these nodes.
   - "Cheap" does not look at the cost of projection expressions. No benchmarks 
were run.
   
   ## Are these changes tested?
   
   Yes. New file `cte_auto_materialize.slt`: two references to an aggregate 
body, one reference, no reference, cheap body, volatile body, scan in a 
subquery expression, CTE read by another CTE, correlated body, recursive CTE, 
`SELECT INTO`, the two directives, the MySQL dialect, and an unbounded body. 
Unit tests for `InlineAutomaticCtes` and for `MaterializedCte::inline`. The 
full sqllogictest suite also passes with the default of the option set to 
`true`, except for the tests that show the default value.
   
   ## Are there any user-facing changes?
   
   Yes. There is a new configuration option, which is off by default, and a new 
optimizer rule. `MaterializedCte` has the new public field `automatic` and the 
new methods `with_automatic`, `is_automatic` and `inline`. The upgrade guide 
for 56.0.0 and the configuration documentation are updated.
   
   🤖 Generated with [Claude Code](https://claude.com/claude-code)
   


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