Skip to content

fix(calcite): aggregate push-down wrong results (DISTINCT, FILTER) and Latin-1 failure - #554

Merged
dfa1 merged 2 commits into
mainfrom
fix/calcite-agg-distinct-filter
Oct 18, 2026
Merged

dfa1 merged 2 commits into
mainfrom
fix/calcite-agg-distinct-filter

Conversation

@dfa1

@dfa1 dfa1 commented Oct 18, 2026 •

Copy link
Copy Markdown
Owner

Two bugs in VortexAggregatePushDownRule, one commit each.

1. DISTINCT and FILTER were ignored

The rule answered COUNT/MIN/MAX/SUM from zone statistics without checking AggregateCall.isDistinct() or hasFilter(). Statistics cover every row of a zone, so both returned wrong results:

Query Before Correct
count(distinct g) (7 values, 3000 rows) 3000 7
count(*) filter (where id < 10) 3000 10
min(id) filter (where id > 2000) 0 2001
Raincloud sf-salaries count(distinct "Organization Group") 1,180,053 7

The rule now leaves the plan to the scan when any call is DISTINCT or has a FILTER clause.

2. String MIN/MAX failed outside Latin-1

Calcite types VARCHAR as ISO-8859-1, and the rule built a literal from a string stat without checking it fits: select min(s), max(s) over a column holding Ωmega failed with Failed to encode 'Ωmega' in character set 'ISO-8859-1'. The rule now abandons when the charset cannot encode the value; the scan answers.

Tests

  • vortex-jni cross-checks: 7 DISTINCT/FILTER queries over a Rust-written file (5 failed before), and MIN/MAX over Rust-written non-Latin-1 strings.
  • AggregateRuleBranchTest: DISTINCT/FILTER aggregates stay in the plan (3 failed before); AggregatePushDownTest: MIN/MAX over Ωmega answers (failed before).

🤖 Generated with Claude Code

https://claude.ai/code/session_01WtuVGQ5h5Lk6Zn4JnkxF1K

dfa1 and others added 2 commits October 18, 2026 09:08
VortexAggregatePushDownRule answered every COUNT/MIN/MAX/SUM call from
zone statistics without looking at the call's modifiers. Statistics
describe every row of a zone, so they cannot answer an aggregate over a
subset of its rows:

  count(distinct g)                       3000 rows, 7 values -> 3000
  count(*) filter (where id < 10)         -> 3000, not 10
  min(id) filter (where id > 2000)        -> 0, not 2001

On Raincloud sf-salaries, count(distinct `Organization Group`) returned
1,180,053 instead of 7. The rule now leaves the plan to the scan when any
call is DISTINCT or carries a FILTER clause.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WtuVGQ5h5Lk6Zn4JnkxF1K
Calcite types VARCHAR as ISO-8859-1, and the statistics push-down built
a literal from a string MIN/MAX stat without checking it fits. A column
whose max holds a character Latin-1 cannot encode failed the whole query:

  select min(s), max(s) from t        -- s holds "Ωmega"
  CalciteException: Failed to encode 'Ωmega' in character set 'ISO-8859-1'

The scan returns the same strings without building a literal, so the rule
now abandons when the type's charset cannot encode the value. Rust records
string statistics as bounds the rule does not use, so this hit files with
exact string stats, as the Java writer emits.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WtuVGQ5h5Lk6Zn4JnkxF1K
@dfa1 dfa1 changed the title fix(calcite): aggregate push-down ignored DISTINCT and FILTER fix(calcite): aggregate push-down wrong results (DISTINCT, FILTER) and Latin-1 failure Oct 18, 2026
@dfa1
dfa1 merged commit 8e4ee45 into main Oct 18, 2026
9 checks passed
@dfa1
dfa1 deleted the fix/calcite-agg-distinct-filter branch October 18, 2026 07:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant