ulysses-you opened a new pull request, #57396:
URL: https://github.com/apache/spark/pull/57396

   ### What changes were proposed in this pull request?
   
   Adds a new physical rule `PushDownLocalSort`, wired into both the AQE 
`queryStagePreparationRules` and the non-AQE `QueryExecution` preparations 
(right after `EnsureRequirements`, before `CombineAdjacentAggregation`), gated 
by a new internal config `spark.sql.execution.pushDownLocalSort` (default 
`true`).
   
   `EnsureRequirements` inserts one local `SortExec(global = false)` above 
every operator whose `requiredChildOrdering` is not satisfied. When two such 
requirements are in a prefix-cover relationship, a stage computes multiple 
local sorts that only differ in width. The rule pushes the wider local sort 
down through order-preserving operators onto the narrower one below, widening 
it so a single sort serves both operators and the redundant upper sort is 
dropped.
   
   For example, a sort aggregate stacked on a window over the same clustering 
keys:
   
   ```
   SortAggregate(key = [a, b, c])
     Sort([a, b, c], global = false)   <- upper, wider
       Window([a], [b])
         Sort([a, b], global = false)  <- lower, narrower
           Exchange(hashpartitioning([a]))
   ```
   
   becomes:
   
   ```
   SortAggregate(key = [a, b, c])
     Window([a], [b])                  requiredChildOrdering [a, b] satisfied 
by [a, b, c]
       Sort([a, b, c], global = false) <- single sort now serves both operators
         Exchange(hashpartitioning([a]))
   ```
   
   Order-preserving operators traversed: `ProjectExec`, `FilterExec`, 
`SortAggregateExec`, `WindowExecBase`, `WindowGroupLimitExec`. When one renames 
an ordering column in its output (`b AS x`), the ordering is rewritten from the 
operator's output space back to its child's space as it is pushed through 
(plain renames only). The rule never crosses a shuffle or a 
non-order-preserving operator.
   
   `CollectMetricsExec` is intentionally excluded: it observes rows in input 
order, so widening a sort under it would silently change order-sensitive 
observed metrics (`first`/`last`/`collect_list`).
   
   This is complementary to `RemoveRedundantSorts`, which only removes a sort 
already satisfied by its child; it never moves a wider sort down to absorb a 
narrower one.
   
   ### Why are the changes needed?
   
   Stacked windows, or a sort aggregate over a window with prefix-compatible 
ordering requirements, compute several local sorts that a single wider sort 
could satisfy. Reusing the widest sort saves the redundant sorting work in the 
stage.
   
   ### Does this PR introduce _any_ user-facing change?
   
   No. It only removes redundant local sorts from physical plans; query results 
are unchanged. Guarded by `spark.sql.execution.pushDownLocalSort`.
   
   ### How was this patch tested?
   
   New `PushDownLocalSortSuite` (AQE on/off variants) with:
   - positive SQL cases: stacked windows, filter above the sorts, window to 
sort aggregate, renaming project;
   - negative SQL cases: disjoint orderings, a shuffle between the sorts, 
sort-direction mismatch, a project that computes the ordering column;
   - plan-level cases: multi-operator chain, renaming-project rewrite, three 
stacked sorts in one pass.
   
   Verified the TPCDS/TPCH `PlanStability` golden plans are unchanged.
   
   ### Was this patch authored or co-authored using generative AI tooling?
   
   Generated-by: Claude Code (Opus 4.8)
   


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