Skip to content

Move aggregates out of vortex-array into vortex-aggregates - #10769

Draft
robert3005 wants to merge 6 commits into
rk/split-array-06-scalar-fnsfrom
rk/split-array-04-aggregates
Draft

robert3005 wants to merge 6 commits into
rk/split-array-06-scalar-fnsfrom
rk/split-array-04-aggregates

Conversation

@robert3005

Copy link
Copy Markdown
Contributor

No description provided.

@robert3005
robert3005 added this pull request to stack #10770 October 24, 2026 12:37
@robert3005
robert3005 force-pushed the rk/split-array-04-aggregates branch from 350aebd to 4c3f491 Compare October 24, 2026 13:40
vortex-array keeps the aggregate framework (vtables, accumulators, session
registry, kernels) and the aggregates that back array statistics: min/max,
sum, null/NaN counts, sortedness, constancy, uncompressed size, the all_*
predicates and all_non_distinct. These are wired into `Stat`,
`StatsSetRef::compute_stat` and array validation, so they cannot move up
without first making statistics computation session-dispatched.

Everything else moves to a new `vortex-aggregates` crate: `count`, `first`,
`last`, `mean`, `bounded_min`, `bounded_max` and the `combined` adapter,
together with the tests and benches that exercise them.

`vortex_aggregates::initialize` registers BoundedMin/BoundedMax/First/Last
and the Count grouped kernel, which `AggregateFnSession::default` used to do.
It is called from `vortex_file::register_default_encodings` and from
`VortexSessionDefault`, so sessions built through either keep writing the
bounded string min/max zone stats. Sessions built only from
`vortex_array::array_session()` no longer know these aggregates.

`Min`/`Max::can_satisfy` now recognise the bounded variants by id instead of
by type. A few helpers the moved code needs become public:
`columnar_min_max`, `sum_decimal_dtype`, `DecimalValue::{rescale_i256,
try_from_i256}`, `Accumulator::{dtypes, empty_partial, fold_partial}`, and
the `partial_ord` module (doc-hidden).

Signed-off-by: Claude <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ps4mp7KF5c4p66aoSoXx5j
…allback

Offset validation (List, ListView, VarBin), `PrimitiveArray::narrow`, the
same-width integer cast range check, ListView's referenced element bounds,
and the debug sortedness checks in `Patches` called the `min_max` and
`is_sorted` aggregates directly, often through `legacy_session()`. That
ties core array construction to aggregates being available in core, so
they could not be resolved by session.

They now go through `arrays::primitive::{integer_bounds, integer_is_sorted,
integer_is_strict_sorted}`, which read the `Min`, `Max`, `IsSorted` and
`IsStrictSorted` statistics: cached when present, otherwise computed by the
session's aggregates and any kernel the encoding provides. Only when a
statistic has no answer, because the session registers no aggregate for
it, is the array executed to a `PrimitiveArray` and scanned once, caching
`Min`, `Max` and `NullCount`. Validation therefore works in every session,
and still benefits from statistics where they exist. Valid counts come from
the validity bitmap and ListView sizes are summed directly.

Signed-off-by: Claude <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ps4mp7KF5c4p66aoSoXx5j
`StatsSetRef::compute_stat` called the `min_max`, `sum`, `is_sorted`,
`is_constant`, `nan_count` and `uncompressed_size_in_bytes` functions
directly. It now resolves the aggregate backing each statistic from
`ctx.session()`'s `AggregateFnSession` and runs its accumulator, so the
aggregate implementations can be registered from any crate. A session with
no aggregate registered for a statistic cannot compute it.

- New `aggregate_fn::stat_aggregates` holds the IDs of the aggregates
  backing statistics, the options types those statistics bind
  (`UncompressedSizeOpts`, `IsSortedOptions`), and the dtype rules
  (`sum_return_dtype`, `supports_uncompressed_size_in_bytes`) that both the
  aggregates and `Stat::dtype` use. The old paths re-export them.
- `Stat` gains `aggregate_fn_id`, `aggregate_fn_options` and
  `resolve_aggregate_fn`. `Stat::from_aggregate_fn` matches by ID and
  serialized options instead of by vtable type, and `StatFn` compares IDs.
- `IsSorted` and `IsConstant` are now serializable, so they can be resolved
  by ID.
- The whole-array shortcuts of `is_sorted` and `is_constant` (cached
  statistics, validity-only answers, cached `min == max`) move into each
  aggregate's `try_accumulate`, so every caller of the aggregate gets them.
- NaN-skipping `Min` and `Max` accumulate through `min_max`, which caches both
  extrema on the batch, as `compute_stat` did before.

`compute_stat` caches sortedness and constancy, which have no stat slot of
their own, the same way `is_sorted`/`is_constant` did.

Signed-off-by: Claude <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ps4mp7KF5c4p66aoSoXx5j
@robert3005
robert3005 force-pushed the rk/split-array-04-aggregates branch from 00ea291 to d066dd6 Compare October 25, 2026 06:10
vortex-array now keeps only the aggregate function framework and
`all_non_distinct`, which `assert_arrays_eq!` uses to compare arrays. The
other concrete aggregates (min/max, sum, counts, sortedness, constancy,
uncompressed size and the rest), their Dict kernels, the grouped encoding
kernels and their benches move to vortex-aggregates, whose `initialize`
registers them on a session. `Stat::aggregate_fn` is replaced by
`vortex_aggregates::stat_aggregate_fn`.

`ExtensionStoragePushdown` stays in vortex-array. Pushing `is_sorted` and
`min_max` down into extension storage becomes
`vortex_aggregates::register_storage_ordered`, which `initialize` calls for the
temporal types and `vortex_uuid::initialize` calls for UUIDs.

Validation and encoding paths that must work with any session no longer go
through the registry: integer arrays use `integer_bounds` (which now also
reports strict sortedness and caches Min, Max and NullCount), and boolean
arrays use the new `bool_bounds`. This covers validity, RunEnd ends,
bit-packing, FoR, datetime-parts comparison and the compressor's integer
statistics.

Tests of aggregate behaviour move alongside the aggregates. Core tests use a
test-only row-count aggregate and run the aggregate consistency checks only
when aggregates are registered. Test
sessions that compute statistics register the aggregates.

Signed-off-by: Claude <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ps4mp7KF5c4p66aoSoXx5j
Signed-off-by: Claude <noreply@anthropic.com>
`integer_bounds` read the `Min` and `Max` statistics separately, which is
two passes over a cold array. It now resolves the `min_max` aggregate
from the session and computes both extrema in one pass, caching `Min`,
`Max` and `NullCount` as the separate statistics would. Exact cached
statistics are still read first, and the primitive scan remains the
fallback when the session registers no `min_max` aggregate.

`stat_aggregates::MIN_MAX_ID` names the aggregate in core, and `MinMax`
now serializes its `NumericalAggregateOpts` like `Min` and `Max`, so the
session can resolve it by ID.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ps4mp7KF5c4p66aoSoXx5j
Signed-off-by: Claude <noreply@anthropic.com>
`valid_count` uses an exact cached `NullCount` first (`len - null count`).
Otherwise, for array-backed validity, it now reads the validity array's
`Sum` statistic, which is the number of valid rows and can be cached or
computed by an encoding kernel, as develop did with the `sum` aggregate.
It counts the bitmap only when the session registers no `Sum` aggregate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ps4mp7KF5c4p66aoSoXx5j
Signed-off-by: Claude <noreply@anthropic.com>
@robert3005
robert3005 force-pushed the rk/split-array-04-aggregates branch from d066dd6 to 87719b7 Compare October 25, 2026 06:42

This branch has not been deployed

No deployments
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.

2 participants