Skip to content

feat: add GroupColumn support for Duration in multi-column GROUP BY - #23783

Open
tohuya6 wants to merge 1 commit into
apache:mainfrom
tohuya6:feat-22715-duration-group-column
Open

feat: add GroupColumn support for Duration in multi-column GROUP BY#23783
tohuya6 wants to merge 1 commit into
apache:mainfrom
tohuya6:feat-22715-duration-group-column

Conversation

@tohuya6

@tohuya6 tohuya6 commented Jul 22, 2026

Copy link
Copy Markdown

Which issue does this PR close?

Rationale for this change

multi_group_by::group_column_supported_type gates which GROUP BY columns may
use the column-wise GroupValuesColumn fast path, and the gate is
all-or-nothing: a single unsupported column forces the entire grouping onto
the byte-encoded GroupValuesRows fallback, even when every other key column
would have qualified. A Duration key triggers exactly that today, so an
otherwise-qualifying multi-column GROUP BY pays the row-encoding tax because of
one column.

Duration shares the i64 native representation already used by Timestamp,
so supporting it is a pure slot-in of the existing PrimitiveGroupValueBuilder
no new builder type and no new comparison/hash logic.

What changes are included in this PR?

  • Accept Duration(_) in group_column_supported_type (all four TimeUnits are
    valid Arrow types, unlike the restricted Time32/Time64 set).
  • Dispatch the four Duration*Type units in make_group_column.
  • Extend the group_column_supported_typemake_group_column consistency fuzz
    with all four Duration units.
  • Add a (Duration, Int32) group-count benchmark to benches/multi_group_by.rs.

Are these changes tested?

Yes.

  • New unit test test_group_values_column_duration: a Duration key stays on the
    GroupValuesColumn path, dedups equal durations (including nulls), and
    round-trips with the Duration output type preserved (not the bare i64).
  • The consistency fuzz now asserts every Duration unit routes through the
    dispatcher.
  • New single- and multi-column Duration GROUP BY coverage in aggregate.slt.

Are there any user-facing changes?

No API changes. GROUP BY queries with a Duration key now use the column-wise
fast path instead of the row-encoded fallback; results are unchanged.

…lumn GROUP BY

`multi_group_by::group_column_supported_type` gates which GROUP BY columns can
use the column-wise `GroupValuesColumn` fast path. Any unsupported column forces
the entire grouping onto the byte-encoded `GroupValuesRows` fallback, so a single
`Duration` key dragged an otherwise-qualifying multi-column GROUP BY onto the
slow path.

`Duration` shares the `i64` native representation of `Timestamp`, so it reuses
the existing `PrimitiveGroupValueBuilder` with no new builder type:

- dispatch the four `Duration*Type` units in `make_group_column`
- accept `Duration(_)` in `group_column_supported_type` (all four units are
  valid Arrow types, unlike Time32/Time64)
- extend the `group_column_supported_type` <-> `make_group_column` consistency
  fuzz with the four Duration units
- add an end-to-end unit test (Duration GROUP BY dedups including nulls and
  preserves the Duration output type) and a Duration GROUP BY block
  (single- and multi-column keys) in aggregate.slt
- add a `(Duration, Int32)` group-count benchmark to `benches/multi_group_by.rs`

Part of apache#22715
@github-actions github-actions Bot added sqllogictest SQL Logic Tests (.slt) physical-plan Changes to the physical-plan crate labels Jul 22, 2026

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

@tohuya6
Thanks for the fix! I didn't find any blocking issues. I have one small suggestion that could make the regression test align even more closely with the scenario being fixed.

fn test_group_values_column_duration() {
use arrow::datatypes::TimeUnit;

let schema = Arc::new(Schema::new(vec![Field::new(

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.

Nice addition to cover Duration group keys. One small thought: this regression is specifically about the multi-column all-or-nothing gate, but this unit test exercises a single-column Duration GroupValuesColumn. The SQL logic test already covers the multi-column behaviour end to end, but it might be worth making this unit test use (Duration(Microsecond), Int64) input, or adding a small second unit test that does. That would exercise the GroupValuesColumn helper at the same boundary as the fix and make the regression a little more direct.

@github-actions

Copy link
Copy Markdown

Thank you for opening this pull request!

Reviewer note: cargo-semver-checks reported the current version number is not SemVer-compatible with the changes in this pull request (compared against the base branch).

Details
     Cloning apache/main
    Building datafusion-physical-plan v54.1.0 (current)
       Built [  46.425s] (current)
     Parsing datafusion-physical-plan v54.1.0 (current)
      Parsed [   0.148s] (current)
    Building datafusion-physical-plan v54.1.0 (baseline)
       Built [  41.637s] (baseline)
     Parsing datafusion-physical-plan v54.1.0 (baseline)
      Parsed [   0.148s] (baseline)
    Checking datafusion-physical-plan v54.1.0 -> v54.1.0 (no change; assume patch)
     Checked [   0.637s] 223 checks: 222 pass, 1 fail, 0 warn, 30 skip

--- failure inherent_method_missing: pub method removed or renamed ---

Description:
A publicly-visible method or associated fn is no longer available under its prior name. It may have been renamed or removed entirely.
        ref: https://doc.rust-lang.org/cargo/reference/semver.html#item-remove
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.49.0/src/lints/inherent_method_missing.ron

Failed in:
  WindowAggExec::try_from_proto, previously in file /home/runner/work/datafusion/datafusion/target/semver-checks/git-apache_main/14a066cea32bc8ad22b51dd42877d798ed1e0a3d/datafusion/physical-plan/src/windows/window_agg_exec.rs:367
  AsyncFuncExec::try_from_proto, previously in file /home/runner/work/datafusion/datafusion/target/semver-checks/git-apache_main/14a066cea32bc8ad22b51dd42877d798ed1e0a3d/datafusion/physical-plan/src/async_func.rs:290
  GroupOrderingPartial::reset, previously in file /home/runner/work/datafusion/datafusion/target/semver-checks/git-apache_main/14a066cea32bc8ad22b51dd42877d798ed1e0a3d/datafusion/physical-plan/src/aggregates/order/partial.rs:191
  EmptyExec::try_from_proto, previously in file /home/runner/work/datafusion/datafusion/target/semver-checks/git-apache_main/14a066cea32bc8ad22b51dd42877d798ed1e0a3d/datafusion/physical-plan/src/empty.rs:215
  ExplainExec::try_from_proto, previously in file /home/runner/work/datafusion/datafusion/target/semver-checks/git-apache_main/14a066cea32bc8ad22b51dd42877d798ed1e0a3d/datafusion/physical-plan/src/explain.rs:217
  CrossJoinExec::try_from_proto, previously in file /home/runner/work/datafusion/datafusion/target/semver-checks/git-apache_main/14a066cea32bc8ad22b51dd42877d798ed1e0a3d/datafusion/physical-plan/src/joins/cross_join.rs:485
  HashJoinExec::try_from_proto, previously in file /home/runner/work/datafusion/datafusion/target/semver-checks/git-apache_main/14a066cea32bc8ad22b51dd42877d798ed1e0a3d/datafusion/physical-plan/src/joins/hash_join/exec.rs:1857
  NestedLoopJoinExec::try_from_proto, previously in file /home/runner/work/datafusion/datafusion/target/semver-checks/git-apache_main/14a066cea32bc8ad22b51dd42877d798ed1e0a3d/datafusion/physical-plan/src/joins/nested_loop_join.rs:810
  AggregateExec::try_from_proto, previously in file /home/runner/work/datafusion/datafusion/target/semver-checks/git-apache_main/14a066cea32bc8ad22b51dd42877d798ed1e0a3d/datafusion/physical-plan/src/aggregates/mod.rs:2349
  SymmetricHashJoinExec::try_from_proto, previously in file /home/runner/work/datafusion/datafusion/target/semver-checks/git-apache_main/14a066cea32bc8ad22b51dd42877d798ed1e0a3d/datafusion/physical-plan/src/joins/symmetric_hash_join.rs:749
  GroupOrderingFull::reset, previously in file /home/runner/work/datafusion/datafusion/target/semver-checks/git-apache_main/14a066cea32bc8ad22b51dd42877d798ed1e0a3d/datafusion/physical-plan/src/aggregates/order/full.rs:119
  GlobalLimitExec::try_from_proto, previously in file /home/runner/work/datafusion/datafusion/target/semver-checks/git-apache_main/14a066cea32bc8ad22b51dd42877d798ed1e0a3d/datafusion/physical-plan/src/limit.rs:274
  LocalLimitExec::try_from_proto, previously in file /home/runner/work/datafusion/datafusion/target/semver-checks/git-apache_main/14a066cea32bc8ad22b51dd42877d798ed1e0a3d/datafusion/physical-plan/src/limit.rs:498
  PlaceholderRowExec::try_from_proto, previously in file /home/runner/work/datafusion/datafusion/target/semver-checks/git-apache_main/14a066cea32bc8ad22b51dd42877d798ed1e0a3d/datafusion/physical-plan/src/placeholder_row.rs:216
  AnalyzeExec::try_from_proto, previously in file /home/runner/work/datafusion/datafusion/target/semver-checks/git-apache_main/14a066cea32bc8ad22b51dd42877d798ed1e0a3d/datafusion/physical-plan/src/analyze.rs:348
  RepartitionExec::try_from_proto, previously in file /home/runner/work/datafusion/datafusion/target/semver-checks/git-apache_main/14a066cea32bc8ad22b51dd42877d798ed1e0a3d/datafusion/physical-plan/src/repartition/mod.rs:1785
  SortExec::try_from_proto, previously in file /home/runner/work/datafusion/datafusion/target/semver-checks/git-apache_main/14a066cea32bc8ad22b51dd42877d798ed1e0a3d/datafusion/physical-plan/src/sorts/sort.rs:1606
  SortPreservingMergeExec::try_from_proto, previously in file /home/runner/work/datafusion/datafusion/target/semver-checks/git-apache_main/14a066cea32bc8ad22b51dd42877d798ed1e0a3d/datafusion/physical-plan/src/sorts/sort_preserving_merge.rs:479
  UnionExec::try_from_proto, previously in file /home/runner/work/datafusion/datafusion/target/semver-checks/git-apache_main/14a066cea32bc8ad22b51dd42877d798ed1e0a3d/datafusion/physical-plan/src/union.rs:495
  InterleaveExec::try_from_proto, previously in file /home/runner/work/datafusion/datafusion/target/semver-checks/git-apache_main/14a066cea32bc8ad22b51dd42877d798ed1e0a3d/datafusion/physical-plan/src/union.rs:741
  UnnestExec::try_from_proto, previously in file /home/runner/work/datafusion/datafusion/target/semver-checks/git-apache_main/14a066cea32bc8ad22b51dd42877d798ed1e0a3d/datafusion/physical-plan/src/unnest.rs:346
  GroupOrdering::reset, previously in file /home/runner/work/datafusion/datafusion/target/semver-checks/git-apache_main/14a066cea32bc8ad22b51dd42877d798ed1e0a3d/datafusion/physical-plan/src/aggregates/order/mod.rs:102

     Summary semver requires new major version: 1 major and 0 minor checks failed
    Finished [  90.828s] datafusion-physical-plan
    Building datafusion-sqllogictest v54.1.0 (current)
       Built [ 216.843s] (current)
     Parsing datafusion-sqllogictest v54.1.0 (current)
      Parsed [   0.023s] (current)
    Building datafusion-sqllogictest v54.1.0 (baseline)
       Built [ 193.861s] (baseline)
     Parsing datafusion-sqllogictest v54.1.0 (baseline)
      Parsed [   0.023s] (baseline)
    Checking datafusion-sqllogictest v54.1.0 -> v54.1.0 (no change; assume patch)
     Checked [   0.083s] 223 checks: 223 pass, 30 skip
     Summary no semver update required
    Finished [ 413.642s] datafusion-sqllogictest

@github-actions github-actions Bot added the auto detected api change Auto detected API change label Jul 28, 2026
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.10526% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 80.71%. Comparing base (5de7f1d) to head (9f0bb3e).
⚠️ Report is 85 commits behind head on main.

Files with missing lines Patch % Lines
.../src/aggregates/group_values/multi_group_by/mod.rs 92.10% 0 Missing and 3 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #23783      +/-   ##
==========================================
- Coverage   80.71%   80.71%   -0.01%     
==========================================
  Files        1089     1089              
  Lines      368760   368798      +38     
  Branches   368760   368798      +38     
==========================================
+ Hits       297647   297667      +20     
- Misses      53372    53382      +10     
- Partials    17741    17749       +8     

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

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

Labels

auto detected api change Auto detected API change physical-plan Changes to the physical-plan crate sqllogictest SQL Logic Tests (.slt)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants