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

   ## Which issue does this PR close?
   
   Closes #23602.
   Part of the epic #23600.
   
   > **Depends on #23628** (Closes #23601). This rule produces a `Struct`-typed 
`first_value`
   > argument, which only takes the fast `GroupsAccumulator` path once #23628 
merges. On current
   > `main` that fast path doesn't exist for `Struct`, so enabling the flag is 
a regression — the
   > config therefore defaults to `false`, and the speedup is not observable 
until #23628 lands.
   
   ## Rationale for this change
   
   `SELECT ..., first_value(a ORDER BY o), first_value(b ORDER BY o), ... GROUP 
BY p` plans one
   independent `first_value` / `last_value` aggregate per selected column. When 
those peers all
   share the same `ORDER BY`, they pick the *same* winning row for every 
column, so the work is
   duplicated across N expressions even though a single pass already determines 
the winner for all.
   
   Two costs on wide "one row per group" payloads:
   
   - **Runtime.** Each `first_value` runs its own argmax pass over the input, 
comparing the
     `ORDER BY` key row by row. Selecting N winner columns is N independent 
scans of the same rows,
     so cost scales linearly with the number of peer expressions.
   - **State.** Each aggregate holds its own per-group accumulator slot, so the 
plan keeps N slots
     per group — on wide payloads, N copies of the winner's columns retained 
for every group key.
   
   ## What changes are included in this PR?
   
   Adds a logical optimizer rule `CoalesceFirstLast` 
(`datafusion/optimizer/src/coalesce_first_last.rs`)
   that runs bottom-up over `Aggregate` nodes. Within a single `Aggregate`, it 
groups peer
   `first_value` / `last_value` expressions by `(function, ORDER BY, null 
treatment)` and, for any
   bucket with two or more members, rewrites them into one struct-valued 
aggregate whose argument is
   a `named_struct` of the original values. A `Projection` on top unpacks the 
struct back into the
   original columns with `get_field`, so the rewrite is transparent to 
everything above the `Aggregate`:
   
   ```text
   Aggregate: groupBy=[[p]], aggr=[[first_value(a ORDER BY o), first_value(b 
ORDER BY o)]]
   ```
   
   becomes
   
   ```text
   Projection: p, get_field(s, 'c0') AS first_value(a ...), get_field(s, 'c1') 
AS first_value(b ...)
     Aggregate: groupBy=[[p]], aggr=[[first_value(named_struct('c0', a, 'c1', 
b) ORDER BY o) AS s]]
   ```
   
   - Coalesces only within a bucket keyed by `(function, ORDER BY, null 
treatment)`; buckets with
     fewer than two members are left unchanged.
   - Bails on `DISTINCT` / `FILTER` / `IGNORE NULLS`, grouping-set aggregates
     (`GROUPING SETS` / `ROLLUP` / `CUBE`), and expressions without an `ORDER 
BY`.
   - No-ops when `named_struct` / `get_field` are not in the function registry.
   - Gated behind a new config `optimizer.enable_coalesce_first_last`, default 
`false`.
   - Registers the new config/rule in the generated artifacts: `configs.md`,
     `information_schema.slt`, `optimizer_rule_reference.md`, and the `EXPLAIN 
VERBOSE` list in
     `explain.slt`.
   
   ## Are these changes tested?
   
   Yes.
   
   - Unit tests in `coalesce_first_last.rs` asserting the rewritten plan shape 
(peer `first_value`
     collapsed into one `named_struct` aggregate + `get_field` projection; same 
for `last_value`; a
     non-coalesced aggregate such as `count` preserved alongside), plus no-op 
cases: a single
     `first_value`, mixed `first_value` + `last_value`, `DISTINCT`, a 
grouping-set aggregate, flag
     disabled, missing registry.
   - An end-to-end sqllogictest (`coalesce_first_last.slt`) that runs the query 
with the flag off and
     on over multi-group data containing NULLs and asserts identical results, 
plus an `EXPLAIN`
     pinning the rewritten logical and physical plans.
   - `cargo fmt`, `cargo clippy --all-targets --all-features -- -D warnings`, 
the full sqllogictest
     suite, and the config/rule doc-generation checks all pass.
   
   ## Are there any user-facing changes?
   
   Adds one new configuration option, 
`datafusion.optimizer.enable_coalesce_first_last`, defaulting
   to `false`. No SQL or API surface changes: with the flag off the plan is 
unchanged, and with it on
   the rewrite is result-preserving, so existing queries and previously-passing 
tests behave identically.
   
   ## Follow-ups
   
   - Once #23628 (Closes #23601) merges, consider flipping the default after 
benchmarking across
     payload widths and group cardinalities.
   - #23603 (logical rewrite `Filter(row_number() = 1) → 
Aggregate(FIRST_VALUE(... ORDER BY))`)
     composes with this rule: the peer `FIRST_VALUE` aggregates it emits would 
be coalesced here.
   
   


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