Vivek1106-04 opened a new pull request, #58154:
URL: https://github.com/apache/spark/pull/58154

   ### What changes were proposed in this pull request?
   
   This PR makes `OptimizeExpand` reject the rewrite when the pre-aggregate it 
would insert has an empty grouping.
   
   `OptimizeExpand` inserts a grouping-only `Aggregate` below the `Expand` 
produced by `RewriteDistinctAggregates`, so that rows are de-duplicated before 
the `Expand` amplifies them. The pre-aggregate's grouping is collected from the 
attributes the `Expand` references:
   
   ```scala
   expand.child.output.filter(expand.references.contains)
   ```
   
   When the query has no `GROUP BY` and every distinct argument is foldable, 
the `Expand` references no attribute of its child, so the inserted node is 
`Aggregate(Nil, Nil, child)` -- a *global* aggregate. A global aggregate emits 
one row even for an empty input instead of de-duplicating rows, so the `Expand` 
above it sees one row where it previously saw none.
   
   The new check rejects that case; a non-empty grouping is what makes the 
inserted node a de-duplication rather than a global aggregate.
   
   ### Why are the changes needed?
   
   `COUNT(DISTINCT <constant>)` over an empty input returns 1 instead of 0:
   
   ```sql
   -- an empty source, e.g. a daily partition with no rows yet
   CREATE OR REPLACE TEMP VIEW events AS
     SELECT * FROM VALUES (1, 'x', 'y') AS t(id, c1, c2) WHERE 1 = 0;
   
   SET spark.sql.optimizer.optimizeExpandRatio=2;
   
   SELECT COUNT(DISTINCT region), COUNT(DISTINCT channel)
   FROM (SELECT 'US' AS region, 'web' AS channel, id FROM events);
   -- returns [1,1], expected [0,0]
   ```
   
   No constant literal has to appear in the query text: the distinct arguments 
only need to be *foldable*, which the optimizer proves for the common pattern 
of tagging constant dimensions in a subquery, view, or `UNION ALL` branch. The 
minimal form is `SELECT COUNT(DISTINCT 1), COUNT(DISTINCT 2) FROM events`.
   
   The optimized plan shows the inserted global aggregate:
   
   ```
   Aggregate [count(1) FILTER (gid=2) AS count(DISTINCT region), count(1) 
FILTER (gid=1) AS count(DISTINCT channel)]
   +- Aggregate ['web', 'US', gid], [gid]
      +- Expand [[web, null, 1], [null, US, 2]], ['web', 'US', gid]
         +- Aggregate                     <- inserted by OptimizeExpand: no 
grouping, no aggregates
            +- LocalRelation <empty>
   ```
   
   Excluding the rule by name 
(`spark.sql.optimizer.excludedRules=org.apache.spark.sql.catalyst.optimizer.OptimizeExpand`)
 gives the correct answer, confirming the rule is the cause and not the 
surrounding rewrite.
   
   This is the same empty-table failure that 
`RewriteDistinctAggregates.mustRewrite` exists to prevent -- its comment names 
`SELECT COUNT(DISTINCT 1)` and "wrong results when working with empty tables" 
as the reason the rewrite is forced for this shape. `OptimizeExpand` then 
undoes that protection.
   
   The bug was introduced by SPARK-56315 (4.2.0). It is reachable only when the 
internal conf `spark.sql.optimizer.optimizeExpandRatio` is explicitly set, 
since it defaults to `-1` (disabled).
   
   This is independent of SPARK-58387: there the inner `Aggregate` computes a 
duplicate-sensitive aggregate, here it computes no aggregate at all, so that 
fix's duplicate-sensitivity guard does not reject this plan.
   
   ### Does this PR introduce _any_ user-facing change?
   
   Yes, it fixes a correctness bug. With 
`spark.sql.optimizer.optimizeExpandRatio` set, a query with two or more 
foldable-argument distinct aggregates and no `GROUP BY` returned 1 per distinct 
aggregate over an empty input; it now returns 0, matching the result with the 
rule disabled. No behavior change under the default conf value (`-1`).
   
   ### How was this patch tested?
   
   New tests, verified to fail before the fix and pass after:
   
   - `OptimizeExpandSuite`
     - "SPARK-58888: skips when every distinct argument is foldable" -- asserts 
no pre-aggregate is inserted.
     - "SPARK-58888: applies when one distinct argument is a real column" -- 
guards against over-rejecting: the column enters the pre-aggregate's `GROUP 
BY`, so the rewrite still applies.
   - `OptimizeExpandQuerySuite`
     - "SPARK-58888: count distinct of constants over an empty input"
     - "SPARK-58888: count distinct of constant-projected columns over an empty 
input" (the reported repro)
     - "SPARK-58888: count distinct of constants over a non-empty input" -- 
unchanged result of 1.
   
   Both suites pass in full (`OptimizeExpandSuite` 10/10, 
`OptimizeExpandQuerySuite` 11/11).
   
   ### Was this patch authored or co-authored using generative AI tooling?
   
   Generated-by: Claude Code (Claude Opus 5)
   


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