Skip to content

Add lateral and grouped aggregate strategies - #3

Draft
wtsnz wants to merge 6 commits into
mainfrom
agent/aggregate-strategies-clear-history
Draft

Add lateral and grouped aggregate strategies#3
wtsnz wants to merge 6 commits into
mainfrom
agent/aggregate-strategies-clear-history

Conversation

@wtsnz

@wtsnz wtsnz commented Jul 22, 2026

Copy link
Copy Markdown
Owner

Summary

Add adapter-selectable aggregate strategies to AshSQL.

The existing lateral implementation is extracted behind stable facades, and a grouped/windowed implementation is added for adapters such as SQLite that cannot use lateral joins. Existing adapters remain on the default :lateral strategy.

Why

AshSQLite needs grouped aggregate planning, but the planner, expression handling, relationship semantics, and strategy dispatch belong in AshSQL. Dialect-specific list SQL remains owned by the selected adapter.

Commit story

  1. Extract existing loaded aggregates into the lateral strategy without an intended behavior change.
  2. Extract existing root aggregate queries into the lateral strategy.
  3. Define the adapter strategy and grouped-list contracts.
  4. Add the complete grouped loaded-aggregate implementation.
  5. Add the complete grouped root-query implementation.
  6. Document the final adapter contract.

Each non-trivial commit explains its problem, change, reason, behavior, and validation.

Correctness included

  • per-parent relationship limits and offsets;
  • attributes, calculations, and aggregate fields;
  • constrained aggregate types;
  • first nil/default/sort semantics;
  • limited and offset root inputs;
  • fieldless distinct counts based on primary-key metadata;
  • adapter-owned grouped list expressions.

Validation

  • mix format --check-formatted
  • AshSQL: 1 passed
  • AshSQLite against this branch: 210 passed
  • AshPostgres against this branch: 851 passed, 21 excluded
  • AshSQL and AshSQLite documentation generation
  • final tree verified identical to the validated backup implementation

Tests used --no-deps-check because the local checkouts have existing dependency-lock mismatches under the current toolchain. No dependency or lockfile was changed.

Review context

This is the clean-history replacement for draft fork PR #1. The old PR and branch remain unchanged as a backup.

The companion adapter and acceptance coverage is in wtsnz/ash_sqlite#6.

wtsnz added 6 commits July 21, 2026 23:02
Move the loaded-aggregate implementation into `AshSql.Aggregate.Lateral` and leave `AshSql.Aggregate` as the compatibility facade.

Implementation notes:

- preserve the existing public function signatures;
- delegate directly to the extracted lateral module;
- retain the upstream aggregate read-action correction;
- avoid the temporary context wrapper from the earlier history.

This is a structural extraction with no intended behavior change.
Move root aggregate-query execution into `AshSql.Aggregate.Lateral.Query` and keep `AshSql.AggregateQuery` as the public entry point.

Implementation notes:

- preserve the existing aggregate-query API;
- normalize bindings once in the facade;
- pass the prepared query to the lateral implementation;
- remove the duplicate binding initialization from the extracted module.

This is a structural extraction with no intended query behavior change.
Introduce the adapter callbacks that select an aggregate planner and provide dialect-specific grouped list expressions.

Implementation notes:

- `aggregate_strategy/1` defaults to `:lateral`;
- adapters can opt into the new `:grouped` strategy;
- `grouped_list_aggregate/2` keeps list SQL and result representation in the adapter;
- both callbacks remain overridable through `AshSql.Implementation`.

Existing adapters continue to use lateral aggregates without any required changes.
Add the grouped and windowed planner used by adapters that cannot load related aggregates through lateral joins.

Implementation notes:

- apply relationship limits and offsets per parent before aggregation;
- resolve attributes, calculations, and aggregate fields through AshSQL expressions;
- reuse the existing relationship and sort planners;
- preserve aggregate type constraints;
- delegate grouped list expressions to the selected adapter.

The companion AshSQLite suite covers the supported aggregate kinds, relationship shapes, rich fields, defaults, and bounds.
Route root aggregate queries through the selected strategy and add the grouped implementation for SQLite-style adapters.

Implementation notes:

- support count, sum, average, minimum, maximum, first, and exists;
- apply filters, limits, offsets, and required ordering before aggregation;
- preserve `first` nil handling, defaults, and Ash sort semantics;
- resolve calculation and aggregate fields through shared expression handling;
- derive fieldless distinct counts from single primary-key metadata and reject unsupported key shapes clearly.

The companion AshSQLite regressions cover each of these query semantics end to end.
Explain how SQL adapters select between lateral and grouped aggregate planning.

Documentation includes:

- the default `:lateral` behavior;
- the adapter opt-in for `:grouped`;
- the adapter-owned grouped list callback;
- the new strategy support in the changelog.

The guide describes the final contract after both loaded and root aggregate paths are in place.
@wtsnz
wtsnz force-pushed the agent/aggregate-strategies-clear-history branch from 56ae4b0 to c8283dd Compare July 22, 2026 06:04
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