Skip to content

fix: keep constant filters above grand totals - #27117

Merged
jayzhan211 merged 1 commit into
apache:mainfrom
Phoenix500526:issue/26545
Nov 14, 2026
Merged

jayzhan211 merged 1 commit into
apache:mainfrom
Phoenix500526:issue/26545

Conversation

@Phoenix500526

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

Closes #26545.

Rationale for this change

Filtering a constant column above a global aggregate can return an extra row. For example, this query should return no rows, but currently returns one row containing 0.

SELECT c
FROM (
  SELECT 'all' AS k, COUNT(*) AS c
  FROM (VALUES (1)) AS t(a)
) AS s
WHERE k = 'none';

Pushing the false predicate below COUNT(*) empties its input, but the aggregate still produces a row. Aggregates with an empty grouping set, such as ROLLUP(a), have the same problem.

What changes are included in this PR?

  • Keep predicates without column references above aggregates that produce a row on empty input.
  • Share the empty-input check with PropagateEmptyRelation.
  • Add regression queries for global aggregation and ROLLUP, and update a Substrait snapshot where a redundant TRUE scan filter is now eliminated.

Constant folding can still remove a true filter or replace a false-filtered aggregate with an empty relation. Ordinary grouping-key predicates can still be pushed below aggregation.

What is the testing strategy for this PR?

Added two result-based regressions in push_down_filter_regression.slt, both verified to fail before the fix and pass afterward.

Passed locally at this commit

  • cargo fmt --all
  • cargo clippy --all-targets --all-features -- -D warnings
  • The full extended workspace test suite with avro,json,backtrace,extended_tests,recursive_protection,parquet_encryption, including all 547 SQL logic test files and 228 Substrait integration tests.

Also checked optimized plans for false and true constant predicates and ordinary grouping-key filters, and checked query results for bound parameters, CUBE, and explicit empty grouping sets.

The additional ./dev/rust_lint.sh suite stopped at the circular-dependency check because the local Cargo configuration replaces crates.io with the tuna registry. The complete lint suite therefore has not passed locally.

Are there any user-facing changes?

Queries with constant filters above global aggregates or empty grouping sets now return the correct number of rows.

@github-actions github-actions Bot added optimizer Optimizer rules sqllogictest SQL Logic Tests (.slt) substrait Changes to the substrait crate labels Nov 5, 2026
@Phoenix500526
Phoenix500526 force-pushed the issue/26545 branch 2 times, most recently from 1312f6d to f4f4bac Compare November 8, 2026 01:45
@Phoenix500526
Phoenix500526 marked this pull request as ready for review November 8, 2026 03:18
@Phoenix500526
Phoenix500526 marked this pull request as draft November 10, 2026 08:35
@Phoenix500526
Phoenix500526 marked this pull request as ready for review November 11, 2026 15:00

@jayzhan211 jayzhan211 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Phoenix500526 thanks for the fix! decorrelate.rs around line 891 still has its own copy of this rule (has_empty_set), and this is the fourth fix of this fact (#22039, #25781, #22926). Could it call the new helper too, so every logical check uses one rule? At that point grouping_sets is non-empty, so group_expr is always [GroupingSet] and the result is the same. Locally, clippy and the optimizer and subquery tests pass with this change.

-use crate::utils::replace_qualified_name;
+use crate::utils::{aggregate_emits_on_empty_input, replace_qualified_name};
@@
-        let has_empty_set = grouping_sets.iter().any(|grouping_set| match grouping_set {
-            GroupingSet::Rollup(_) | GroupingSet::Cube(_) => true,
-            GroupingSet::GroupingSets(sets) => sets.iter().any(|set| set.is_empty()),
-        });
+        let has_empty_set = aggregate_emits_on_empty_input(group_expr);

@Phoenix500526
Phoenix500526 force-pushed the issue/26545 branch 2 times, most recently from 02aad2f to 9e52c06 Compare November 12, 2026 14:04
@Phoenix500526

Phoenix500526 commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor Author

@Phoenix500526 thanks for the fix! decorrelate.rs around line 891 still has its own copy of this rule (has_empty_set), and this is the fourth fix of this fact (#22039, #25781, #22926). Could it call the new helper too, so every logical check uses one rule?

Thanks @jayzhan211 Good catch. I've used aggregate_emits_on_empty_input instead

A global aggregate or empty grouping set produces a row even on empty
input. Pushing a false predicate below it can return that extra row.

CLOSES apache#26545

@jayzhan211 jayzhan211 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @Phoenix500526 !

@jayzhan211
jayzhan211 added this pull request to the merge queue Nov 14, 2026
Merged via the queue into apache:main with commit 3d6a5f1 Nov 14, 2026
44 checks passed
@Phoenix500526
Phoenix500526 deleted the issue/26545 branch November 14, 2026 07:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

optimizer Optimizer rules sqllogictest SQL Logic Tests (.slt) substrait Changes to the substrait crate v57.0.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Wrong results: a filter on a constant column keeps the row of an aggregate without GROUP BY

2 participants