Skip to content

Add SQLite grouped aggregate support - #6

Draft
wtsnz wants to merge 11 commits into
mainfrom
agent/sqlite-grouped-aggregates-clear-history
Draft

Add SQLite grouped aggregate support#6
wtsnz wants to merge 11 commits into
mainfrom
agent/sqlite-grouped-aggregates-clear-history

Conversation

@wtsnz

@wtsnz wtsnz commented Jul 22, 2026

Copy link
Copy Markdown
Owner

Summary

Add SQLite aggregate support through AshSQL's grouped aggregate strategy.

AshSQLite selects the grouped planner, declares its supported capability and relationship shapes, provides SQLite's JSON-backed list expression, and exposes a custom aggregate behaviour. The PR includes end-to-end acceptance coverage and a complete aggregate guide.

Dependency

Review and merge wtsnz/ash_sql#3 first. This PR is validated against that clean-history AshSQL branch.

Commit story

  1. Enable the final grouped strategy routing and capabilities.
  2. Implement SQLite-owned grouped list SQL.
  3. Add the SQLite custom aggregate behaviour.
  4. Add the main grouped aggregate acceptance matrix.
  5. Cover grouped first semantics.
  6. Cover fieldless distinct-count key shapes.
  7. Verify constrained aggregate types.
  8. Cover offset-only root query inputs.
  9. Cover rich fields and limited root queries.
  10. Cover rich fields and bounded relationship loads.
  11. Document the final supported behavior and limitations.

The sequence contains no implementation-and-immediate-removal commits. Each focused regression commit explains the defect and its provenance.

Supported behavior

  • root count, sum, average, minimum, maximum, first, and exists queries;
  • loaded scalar, first, list, exists, and custom aggregates;
  • filtering, sorting, pagination, calculations, and join filters;
  • one-hop, unrelated, many-to-many, and supported multi-hop relationships;
  • per-parent relationship limits and offsets;
  • attribute, calculation, and aggregate fields;
  • nil handling, defaults, distinct values, constrained types, and read actions;
  • stable errors for unsupported relationship and key shapes.

Validation

  • mix format --check-formatted
  • AshSQL: 1 passed
  • AshSQLite: 210 passed
  • AshPostgres against the companion AshSQL 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 #5. The old PR and branch remain unchanged as a backup.

wtsnz added 11 commits July 21, 2026 23:03
Enable AshSQLite to route supported aggregate operations through AshSQL's grouped planner.

Implementation notes:

- select `:grouped` in the SQL implementation;
- advertise the supported aggregate kinds and relationship shapes;
- delegate query return, filtering, calculations, and root aggregates to the shared AshSQL paths;
- keep manual, parent-dependent, and no-attribute relationship shapes unsupported.

The following commits add end-to-end coverage for each enabled path.
Provide the SQLite expression used by AshSQL's grouped list planner.

Implementation notes:

- build lists with windowed `json_group_array`;
- filter nil values inside the SQL aggregate when `include_nil?` is false;
- leave window planning and result typing in AshSQL;
- keep SQLite syntax and JSON representation inside the adapter.

The acceptance suite covers ordering, nil inclusion, defaults, constrained values, relationship bounds, and calculation fields.
Add a small adapter API for custom Ecto aggregate expressions.

Implementation notes:

- define callbacks for the aggregate expression and result type;
- let custom modules use SQLite-specific functions;
- leave relationships, filters, windows, and result loading in the shared planner.

The acceptance suite exercises custom aggregates in root queries and related aggregate loads.
Add the main SQLite acceptance matrix for the grouped strategy.

Coverage includes:

- root and loaded scalar aggregates;
- first, list, exists, and custom aggregates;
- filtering, sorting, pagination, calculations, and join filters;
- one-hop, unrelated, many-to-many, and multi-hop paths;
- nil handling, defaults, distinct values, read actions, and stable unsupported errors.

These tests verify the generated grouped and windowed SQL through the concrete SQLite adapter.
Add focused coverage for the parts of `first` that differ from a simple ordered `LIMIT 1` query.

Coverage includes:

- excluding or including nil values;
- applying a fallback to an empty filtered result;
- ascending and descending ordering;
- all four Ash null-ordering variants.

These tests protect behavior that was missing from the original grouped query implementation.
Exercise fieldless distinct counts against the primary-key shapes supported by Ash resources.

Coverage includes:

- a valid single primary key whose Ash name is not `:id`;
- a composite primary key;
- a resource without a primary key;
- clear errors for the unsupported key shapes.

These tests protect the grouped query path from assuming that every resource has an `:id` attribute.
Verify that grouped list and first expressions retain the aggregate item's type constraints.

Implementation notes:

- build the aggregate data-layer query directly;
- inspect the generated Ecto type for the list value;
- assert that `trim?` and `allow_empty?` survive query construction.

This protects a constraint loss inherited from the earlier SQLite implementation rather than a defect introduced by the extraction.
Verify that an ordered root query with only an offset is bounded before aggregation.

Implementation notes:

- create three ordered records;
- offset the first record without specifying a limit;
- assert that the aggregate sees the remaining two records;
- exercise SQLite's required `LIMIT -1` representation for a standalone offset.

The missing offset condition came from existing AshSQL wrapping logic and was copied into the grouped path.
Add root-query regressions for bounded inputs and non-attribute aggregate fields.

Coverage includes:

- `first` and `exists` over a zero-limit query;
- a sorted and limited query whose outer aggregate has its own sort;
- an offset query with no remaining rows;
- calculation fields and aggregate-on-aggregate fields.

These tests protect grouped query behavior introduced by the aggregate extraction.
Add loaded-aggregate regressions for relationship bounds and rich related fields.

Coverage includes:

- per-parent relationship limits and offsets;
- count and list results over each bounded relationship;
- calculation fields on related records;
- aggregate fields on related records;
- sum, maximum, first, and list results.

These tests protect grouped loading behavior introduced by the aggregate extraction.
Add a complete guide to the grouped aggregate behavior exposed by AshSQLite.

Documentation includes:

- supported root and loaded aggregate kinds;
- calculations, filters, sorting, pagination, and relationship paths;
- custom aggregates, limits, offsets, and unsupported shapes;
- SQLite requirements for windows, `FILTER`, JSON, and null ordering;
- navigation, tutorial, API grouping, and changelog updates.

The guide reflects the behavior exercised by the preceding acceptance commits.
@wtsnz
wtsnz force-pushed the agent/sqlite-grouped-aggregates-clear-history branch from 727d1c6 to d6ee81a 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