Skip to content

Add support for dictionary for approx_distinct - #24646

Open
mkleen wants to merge 3 commits into
apache:mainfrom
mkleen:dictionary_approx_distinct
Open

Add support for dictionary for approx_distinct#24646
mkleen wants to merge 3 commits into
apache:mainfrom
mkleen:dictionary_approx_distinct

Conversation

@mkleen

@mkleen mkleen commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

Rationale for this change

  • Support the Arrow type Dictionary for approx_distinct

What changes are included in this PR?

  • Enable HLLAccumulator and HllGroupsAccumulator to support Dictionary
  • Tests

Are these changes tested?

Yes

Are there any user-facing changes?

Yes, approx_distinct supports now Dictionary but no breaking changes.

@github-actions github-actions Bot added sqllogictest SQL Logic Tests (.slt) functions Changes to functions implementation labels Aug 25, 2026
@mkleen
mkleen marked this pull request as ready for review August 25, 2026 07:58
@codecov-commenter

codecov-commenter commented Aug 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 81.58%. Comparing base (5980374) to head (9f9aa29).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #24646      +/-   ##
==========================================
- Coverage   81.63%   81.58%   -0.06%     
==========================================
  Files        1123     1123              
  Lines      410298   410674     +376     
  Branches   410298   410674     +376     
==========================================
+ Hits       334965   335040      +75     
- Misses      55642    55916     +274     
- Partials    19691    19718      +27     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@mkleen

mkleen commented Aug 25, 2026

Copy link
Copy Markdown
Contributor Author

@Jefffrey

@kumarUjjawal kumarUjjawal 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.

Thank you @mkleen this looks good overall. I had two comments please check.

| DataType::Map(_, _)
| DataType::Struct(_)
| DataType::Union(_, _)
| DataType::Dictionary(_, _)

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.

Would it work to match on the value type instead, so a dictionary is supported exactly when its values would be? Something like DataType::Dictionary(_, value_type)

@mkleen mkleen Aug 25, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good point. Floats will be unsupported see #23084, So we should exclude them in all container types.

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.

i still feel we can support floats here if we already support it in regular distinct count 🤔

also for reference seems duckdb supports:

memory D select approx_count_distinct(a) from values (1.5::double), (1.5), ('nan'::double), (null), (0) t(a);
┌──────────────────────────┐
│ approx_count_distinct(a) │
│          int64           │
├──────────────────────────┤
│                        3 │
└──────────────────────────┘

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ok makes sense. I will look into this.

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.

The unconditional arm accepts dictionaries whose value type remains intentionally unsupported.

For example, Dictionary(Int32, Float64) reaches HLLAccumulator, whose hashing path supports floats, although plain Float64 returns NotImplemented. Grouped aggregation also bypasses the restriction through GroupsAccumulatorAdapter.

We can validate the value type recursively with a shared supported-type predicate and add a negative float-dictionary regression.

| DataType::Map(_, _)
| DataType::Struct(_)
| DataType::Union(_, _)
| DataType::Dictionary(_, _)

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.

would it make sense to have both dictionary arms on the value type, so Dictionary(_, value_type) is accepted only when is_hll_groups_type(value_type) is true?

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.

this is a good point

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That's a good idea. Thank you!

@Rich-T-kid Rich-T-kid 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.

you beat me to it #24731 😆.

@mkleen
mkleen force-pushed the dictionary_approx_distinct branch 2 times, most recently from f2593d7 to a3520c7 Compare September 4, 2026 01:45
@mkleen
mkleen force-pushed the dictionary_approx_distinct branch from a3520c7 to 5f60729 Compare September 4, 2026 01:46
@mkleen
mkleen force-pushed the dictionary_approx_distinct branch from c898a95 to 9f9aa29 Compare September 4, 2026 02:39
/// [`HllGroupsAccumulator`]. The fixed-domain types (booleans / small ints) and
/// `Null` fall back to the per-group [`Accumulator`] path.
fn is_hll_groups_type(data_type: &DataType) -> bool {
if let DataType::Dictionary(_, value_type) = data_type {

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.

Recursing here returns false for dictionaries containing Boolean, small integers, or Null, because those plain types use specialized scalar accumulators. The grouped executor then creates one HLLAccumulator per group through GroupsAccumulatorAdapter; each accumulator embeds a 16 KiB sketch. A dictionary-encoded Boolean with 100,000 groups can therefore consume about 1.5 GiB for sketches alone. Please route every supported dictionary through HllGroupsAccumulator, or provide a compact dictionary-aware fallback, and add a path-sensitive regression for a fixed-domain dictionary.

| DataType::Map(_, _)
| DataType::Struct(_)
| DataType::Union(_, _)
| DataType::Dictionary(_, _)

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.

The unconditional arm accepts dictionaries whose value type remains intentionally unsupported.

For example, Dictionary(Int32, Float64) reaches HLLAccumulator, whose hashing path supports floats, although plain Float64 returns NotImplemented. Grouped aggregation also bypasses the restriction through GroupsAccumulatorAdapter.

We can validate the value type recursively with a shared supported-type predicate and add a negative float-dictionary regression.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

functions Changes to functions implementation sqllogictest SQL Logic Tests (.slt)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants