Skip to content

feat(orm): complete bounded SQLite text-group pages - #99

Merged
Dastari merged 2 commits into
mainfrom
feat/complete-group-pages-20261001
Oct 1, 2026
Merged

Dastari merged 2 commits into
mainfrom
feat/complete-group-pages-20261001

Conversation

@Dastari

@Dastari Dastari commented Oct 1, 2026 •

Copy link
Copy Markdown
Owner

Complete event-type enumeration currently ends at the aggregate group cap. This change adds fetch_group_page to the existing generated aggregate builder so private repository consumers can collect the entire native distinct set through bounded database pages, without application query SQL or public GraphQL roots.

The initial profile is one SQLite TEXT-affinity group, optional existing metrics, ascending BINARY or SQLite NOCASE ordering. Native GROUP BY equality preserves distinct identities; NOCASE order adds a deterministic BINARY tie-breaker. Continuation uses HAVING over the same original representative, retaining all authorized source rows in each metric. SQL NULL sorts first; optional native TRIM blank exclusion leaves padded nonblank strings unchanged. Each request returns 1–1,000 groups plus one database lookahead, subject to the existing stricter pagination configuration.

AggregateGroupPageOptions carries order, blank exclusion and trusted host context. AggregateGroupCursor::{to_json,from_json,key} provides a strict typed boundary for the host’s existing opaque envelope: bounded JSON/text, exact NULL/string shape and query/identity/policy-context fingerprint. Current entity, group/metric/filter field authority and complete SQL row visibility are reapplied before grouping. Residual callbacks fail closed. Context/filter/order/policy changes reject continuation before SELECT. This fingerprint is not encryption or authority; hosts validate/protect their complete outer cursor envelope.

Consumer API and runnable evidence

let page = PrivateEvent::aggregate(&database)
    .filter(PrivateEventWhereInput {
        tenant: Some(StringFilter { eq: Some(verified_tenant), ..Default::default() }),
        ..Default::default()
    })
    .group_by(PrivateEventAggregateField::Event)?
    .group_limit(37)?
    .fetch_group_page(AggregateGroupPageOptions {
        order: AggregateGroupOrder::SqliteNoCase,
        exclude_blank: true,
        context: trusted_authorization_partition_and_public_revision,
    }, previous_cursor.as_ref())
    .await?;

The external dependency-minimal example crates/graphql-orm/tests/fixtures/repository-aggregate-consumer/examples/complete_events.rs executes: 225 original event types collected in 37-group pages, isolated from another tenant. It creates only its own disposable SQLite database using ORM migration/typed inserts and has no direct async-graphql dependency or generated public roots.

Executed verification

All commands use disk-backed TMPDIR/target caches, explicit packages/backend features, locked dependencies and bounded build jobs. No workspace all-features or application databases.

  • cargo test -p graphql-orm --locked --no-default-features --features sqlite --lib --test complete_group_pages --test grouped_aggregates: 38 passed, no ignored tests. Six new regressions cover more groups than the default maximum; NULL/blank/padded/native case identities; full metrics after continuation; current tenant/policy and denied entity/field checks; strict cursor/context rejection; 1,000 groups plus one lookahead. Existing aggregate SDL/behavior tests pass.
  • cargo test -p graphql-orm --locked --no-default-features --features postgres --lib --test complete_group_pages_unsupported: 29 passed. The new method rejects before pool I/O.
  • cargo test -p graphql-orm --locked --no-default-features --features mssql --lib --test complete_group_pages_unsupported: 32 passed. The new method rejects before pool I/O; no live MSSQL execution is claimed.
  • cargo test -p graphql-orm --locked --no-default-features --features postgres --test grouped_aggregates_postgres -- --ignored --test-threads=1: 1 passed on a test-owned disposable PostgreSQL container. The explicitly selected normally ignored test actually ran; no skipped backend execution is represented as a pass.
  • cargo run --manifest-path crates/graphql-orm/tests/fixtures/repository-aggregate-consumer/Cargo.toml --locked --no-default-features --features sqlite --example complete_events: passed, 225 events.
  • External repository fixture SQLite test: 1 passed; all targets compile separately on SQLite/PostgreSQL/MSSQL. Warnings-denied consumer Clippy passes on SQLite.
  • Warnings-denied core/macros Clippy (libs and both new test targets) and Rustdoc pass separately on SQLite/PostgreSQL/MSSQL. Formatting, documentation governance (191 governed files), generated inventory, dependency trees/architecture and package release-policy checks pass. Compatibility analysis uses the exact published 0.33.3 baseline: cargo semver-checks -p graphql-orm --manifest-path crates/graphql-orm/Cargo.toml --baseline-root <owned-baseline>/crates/graphql-orm --default-features --release-type patch passed 223 checks (30 inapplicable checks skipped). The repository runner also exits successfully; explicit package selection ensures the Git-only package is actually analyzed.

A focused CI job repeats execution, the private example, unsupported profiles, disposable PostgreSQL behavior and all three Clippy/Rustdoc lanes.

An isolated synthetic source-combination checkout of #97 + #96/#98 + this head passes 107 SQLite core/migration/group tests, 78 PostgreSQL core/migration/profile tests plus 1 actually executed aggregate regression, and 6 generated-relation tests on each backend. The two-crate relation fixture has 2 illustrative generated doctests ignored per lane. Combined MSSQL has 31 library and 2 pre-I/O rejection tests, no live execution. Combined core/macros warnings-denied Clippy passes separately on all three backends. This is compatibility evidence, not a merged/reviewed release; the four committed review heads are unchanged.

Scope, compatibility and dependencies

Existing static aggregate APIs, GraphQL aggregate SDL and existing static/runtime cursor formats remain unchanged. PostgreSQL/MSSQL ordinary aggregate behavior is retained. The new method rejects unsupported backends, other scalar/multiple grouping fields and residual policies; there is no cross-request snapshot guarantee under concurrent source writes. Cursor bounds do not impose a new storage-field size limit.

This implements the SQLite complete-event-set slice of coordination #91 (#3a). Joined/computed source queries, computed SQL Server summaries/whole-set totals, SQL Server computed cursor pages and private generated views remain separate gaps. It does not extend the narrow aggregate fix #88 or depend functionally on runtime A–D.

Base: published 82503290c0d0b96b78c687b4e75e5e279439eb59. ORM/macros: aligned unpublished 0.35.0 (macros alignment only). Preferred identity order remains #96 0.33.4 → #98 0.33.5 → #97 0.34.0 → this 0.35.0 slice; owners may realign unpublished identities for another merge order. The pending review gates remain independent. No release is published or authorized; GEMA adoption still requires a reviewed published immutable tag and metadata.

  • Documentation updated

Canonical typed-aggregate reference, ORM/macros READMEs, changelog, migration notes and generated inventory describe the implemented contract and limits.

@Dastari
Dastari force-pushed the feat/complete-group-pages-20261001 branch from ccb5797 to 0e48a76 Compare October 1, 2026 07:05
@Dastari
Dastari changed the base branch from main to test/generated-relation-authorization-20261001 October 1, 2026 07:08
@Dastari
Dastari changed the base branch from test/generated-relation-authorization-20261001 to main October 1, 2026 07:19
@Dastari
Dastari merged commit 6bfd522 into main Oct 1, 2026
11 checks passed
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