Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,25 @@ jobs:
done
cargo clippy --manifest-path crates/graphql-orm/tests/fixtures/repository-aggregate-consumer/Cargo.toml --locked --no-default-features --features sqlite --all-targets -- -D warnings

orm-policy-projections:
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- uses: Swatinem/rust-cache@v2
- run: cargo test -p graphql-orm --locked --no-default-features --features sqlite --test projection_visibility --test read_projections
- name: Execute projection authorization on a test-owned PostgreSQL container
run: cargo test -p graphql-orm --locked --no-default-features --features postgres --test projection_visibility
- run: cargo run --manifest-path crates/graphql-orm/tests/fixtures/repository-aggregate-consumer/Cargo.toml --locked --no-default-features --features sqlite --example policy_projections
- name: Check explicit backend lanes
run: |
for backend in sqlite postgres mssql; do
cargo clippy -p graphql-orm -p graphql-orm-macros --locked --no-default-features --features "$backend" --lib --test projection_visibility -- -D warnings
done

companions:
runs-on: ubuntu-latest
steps:
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,10 @@ jobs:
cargo test -p graphql-orm --no-default-features --features sqlite,field-case-pascal,argument-case-snake --test federation_entity_keys_case_features --locked
cargo check -p graphql-orm --no-default-features --features postgres --locked
cargo test -p graphql-orm --no-default-features --features postgres --test federation_entity_keys_postgres --locked
cargo test -p graphql-orm --no-default-features --features postgres --test projection_visibility --test cross_crate_relations_fixture --locked
cargo test -p graphql-orm --no-default-features --features postgres --test grouped_aggregates_postgres --locked -- --ignored --test-threads=1
cargo run --manifest-path crates/graphql-orm/tests/fixtures/repository-aggregate-consumer/Cargo.toml --locked --no-default-features --features sqlite --example policy_projections
cargo run --manifest-path crates/graphql-orm/tests/fixtures/repository-aggregate-consumer/Cargo.toml --locked --no-default-features --features sqlite --example complete_events
cargo check -p graphql-orm --no-default-features --features mssql --locked
cargo test -p graphql-orm --no-default-features --features mssql --test federation_entity_keys_mssql --locked
cargo clippy -p graphql-orm --all-targets --locked -- -D warnings
Expand Down
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,20 @@ This file is the authoritative user-facing release chronology. The former
[release-notes ledger](docs/archive/2026/graphql-orm-release-notes.md) is retained
for historical context.

## 0.35.1 - 2026-10-01

Companion macros crate: `graphql-orm-macros` **0.35.1**.

- Allow typed repository projections under an installed global row-policy provider
when current visibility is explicitly unrestricted or complete SQL authorization.
Apply entity-bound predicates before limits and primary/unique-key lookups,
including pinned transactions, without selecting excluded private fields.
- Retain fail-closed callback-only/prefilter policies and normal entity/selected-field
checks. Add a provided generated-identity hook without changing existing trait
implementation requirements, static GraphQL SDL, storage or cursor formats.
- Add executable private repository projection/group examples and isolated
SQLite/PostgreSQL regression evidence; observe actual projection query counts.

## 0.35.0 - 2026-10-01

Companion macros crate: `graphql-orm-macros` **0.35.0** (alignment only).
Expand Down
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ cynic-parser = { version = "=0.11.2", features = ["pretty"] }
futures = "0.3"
getrandom = "0.3"
graphql-composition = "=0.12.2"
graphql-orm = { path = "crates/graphql-orm", version = "0.35.0", default-features = false }
graphql-orm = { path = "crates/graphql-orm", version = "0.35.1", default-features = false }
graphql-orm-ai-tool-profiles = { path = "crates/graphql-orm-ai-tool-profiles", version = "0.15.0" }
graphql-orm-backup = { path = "crates/graphql-orm-backup", version = "0.7.2", default-features = false }
graphql-orm-operation-catalog = { path = "crates/graphql-orm-operation-catalog", version = "0.4.0" }
Expand Down
21 changes: 21 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,27 @@ supersedes: []
`graphql-orm` is distributed from GitHub only. Use a reviewed full 40-character commit in `rev`;
neither the runtime nor macros crate is published to crates.io.

## Policy-aware typed projections (0.35.1)

Adopt aligned ORM/macros 0.35.1. No schema or GraphQL SDL migration is required.
A global `RowPolicy` no longer automatically rejects every projection: return
`ReadVisibility::Unrestricted` deliberately for an unrestricted model or
`Complete(ReadPredicate::from_filter(...))` for visibility fully expressed in SQL.
This is the same contract supported by complete SQLite group pages. The default
`CallbackOnly` and `Prefilter` continue to reject these reads before query I/O;
there is no full-entity fallback. Entity and selected-field policies remain active.

Re-evaluate visibility for every call using verified identity/current ownership.
A complete predicate replaces the residual callback on these supported paths;
do not label a partial predicate complete. Generated projections carry their
associated entity identity. Existing handwritten `ReadProjection` implementations
remain source compatible and support unrestricted visibility; complete predicates
require the provided `entity_type_id` hook to return that associated entity's
`TypeId`. No public GraphQL roots or private model fields are added.

See [typed projections](docs/reference/graphql-orm/read-projections.md) for a
compiled SQL-free private repository example and exact backend/authorization rules.

## Complete text-group pages (0.35.0)

Adopt aligned ORM/macros 0.35.0 after owner review/publication. This is an additive
Expand Down
2 changes: 1 addition & 1 deletion crates/graphql-orm-macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "graphql-orm-macros"
version = "0.35.0"
version = "0.35.1"
edition = "2024"
authors = ["Toby Martin"]
description = "Procedural macros for async-graphql and ORM-backed entities, relations, and CRUD operations."
Expand Down
7 changes: 4 additions & 3 deletions crates/graphql-orm-macros/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ macro/runtime versions aligned:

```toml
[dependencies]
graphql-orm = { git = "https://github.com/Dastari/graphql-orm.git", rev = "<reviewed-full-40-character-commit-sha>", version = "0.35.0", default-features = false, features = ["sqlite"] }
graphql-orm = { git = "https://github.com/Dastari/graphql-orm.git", rev = "<reviewed-full-40-character-commit-sha>", version = "0.35.1", default-features = false, features = ["sqlite"] }
```

Direct use is supported for tooling that needs the macro package:

```toml
graphql-orm-macros = { git = "https://github.com/Dastari/graphql-orm.git", rev = "<reviewed-full-40-character-commit-sha>", version = "0.35.0", default-features = false, features = ["sqlite"] }
graphql-orm-macros = { git = "https://github.com/Dastari/graphql-orm.git", rev = "<reviewed-full-40-character-commit-sha>", version = "0.35.1", default-features = false, features = ["sqlite"] }
```

The direct dependency still requires a compatible `graphql-orm` runtime in the
Expand Down Expand Up @@ -170,6 +170,7 @@ Version 0.33.3 gives the repository aggregate compatibility fix a distinct packa
identity after the independent 0.33.2 fixture maintenance. Generated behavior is
unchanged from the reviewed aggregate fix.

Version 0.35.0 aligns with the additive core text-group paging API. Existing generated
Version 0.35.1 generates projection identity for complete SQL row visibility and
aligns with the core policy-aware projection fix. Existing generated
aggregate enums/builders and ordinary GraphQL aggregate SDL remain unchanged;
repository-only consumers still need no direct async-graphql dependency.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions crates/graphql-orm-macros/src/entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5564,6 +5564,9 @@ fn generate_projection_definitions(
type Entity = #entity;
type Filter = #where_input;
type Order = #order_input;
fn entity_type_id() -> Option<::std::any::TypeId> {
Some(::std::any::TypeId::of::<#entity>())
}
const COLUMNS: &'static [&'static str] = &[#(#selected_columns),*];
const FIELD_NAMES: &'static [&'static str] = &[#(#selected_field_names),*];
const UNIQUE_COLUMNS: &'static [&'static str] = &[#(#unique_columns),*];
Expand Down
4 changes: 2 additions & 2 deletions crates/graphql-orm/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "graphql-orm"
version = "0.35.0"
version = "0.35.1"
edition = "2024"
description = "Runtime support crate for graphql-orm-macros"
license = "MIT"
Expand Down Expand Up @@ -68,7 +68,7 @@ futures = "0.3"
geo = { version = "0.33", optional = true, default-features = false }
geo-types = { version = "0.7", optional = true }
geojson = { version = "1", optional = true, default-features = true }
graphql-orm-macros = { path = "../graphql-orm-macros", version = "0.35.0", default-features = false }
graphql-orm-macros = { path = "../graphql-orm-macros", version = "0.35.1", default-features = false }
graphql-orm-operation-catalog = { workspace = true }
rust_decimal = { workspace = true }
serde = { version = "1", features = ["derive"] }
Expand Down
2 changes: 1 addition & 1 deletion crates/graphql-orm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ backend:

```toml
[dependencies]
graphql-orm = { git = "https://github.com/Dastari/graphql-orm.git", rev = "<reviewed-full-40-character-commit-sha>", version = "0.35.0", default-features = false, features = ["sqlite"] }
graphql-orm = { git = "https://github.com/Dastari/graphql-orm.git", rev = "<reviewed-full-40-character-commit-sha>", version = "0.35.1", default-features = false, features = ["sqlite"] }
```

This unpublished package has no docs.rs release. Use this Git README and the
Expand Down
32 changes: 29 additions & 3 deletions crates/graphql-orm/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -975,7 +975,7 @@ impl<B: OrmBackend> Database<B> {
}

/// Observe SQL text (without bind values) and database rows fetched by
/// EntityQuery and aggregate-page reads. Intended for diagnostics and query-behavior tests.
/// EntityQuery, projection and aggregate-page reads. Intended for diagnostics and query-behavior tests.
pub fn with_read_query_observer(
mut self,
observer: impl crate::graphql::orm::ReadQueryObserver + 'static,
Expand All @@ -996,9 +996,36 @@ impl<B: OrmBackend> Database<B> {
&self,
ctx: Option<&async_graphql::Context<'_>>,
surface: crate::graphql::orm::EntityAccessSurface,
) -> async_graphql::Result<crate::graphql::orm::ReadVisibility> {
let visibility = self
.read_visibility_for(T::metadata(), ctx, surface)
.await?;
visibility.validate::<T, B>()?;
Ok(visibility)
}

pub(crate) async fn projection_read_visibility<P: crate::graphql::orm::ReadProjection<B>>(
&self,
) -> async_graphql::Result<crate::graphql::orm::ReadVisibility> {
use crate::graphql::orm::Entity;
let visibility = self
.read_visibility_for(
P::Entity::metadata(),
None,
crate::graphql::orm::EntityAccessSurface::Repository,
)
.await?;
visibility.validate_identity(P::entity_type_id(), B::DIALECT)?;
Ok(visibility)
}

async fn read_visibility_for(
&self,
metadata: &'static crate::graphql::orm::EntityMetadata,
ctx: Option<&async_graphql::Context<'_>>,
surface: crate::graphql::orm::EntityAccessSurface,
) -> async_graphql::Result<crate::graphql::orm::ReadVisibility> {
use crate::graphql::orm::ReadVisibility;
let metadata = T::metadata();
let visibility = if let Some(policy) = &self.row_policy {
policy
.read_visibility(
Expand All @@ -1013,7 +1040,6 @@ impl<B: OrmBackend> Database<B> {
// No RowPolicy is installed: retain the existing entity-policy-only contract.
ReadVisibility::Unrestricted
};
visibility.validate::<T, B>()?;
Ok(visibility)
}

Expand Down
9 changes: 8 additions & 1 deletion crates/graphql-orm/src/graphql/orm/authorized_pagination.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,15 @@ impl ReadVisibility {
matches!(self, Self::CallbackOnly | Self::Prefilter(_))
}
pub(crate) fn validate<T: Entity + 'static, B: OrmBackend>(&self) -> async_graphql::Result<()> {
self.validate_identity(Some(TypeId::of::<T>()), B::DIALECT)
}
pub(crate) fn validate_identity(
&self,
entity: Option<TypeId>,
backend: DatabaseBackend,
) -> async_graphql::Result<()> {
if let Self::Prefilter(p) | Self::Complete(p) = self
&& (p.entity != TypeId::of::<T>() || p.backend != B::DIALECT)
&& (Some(p.entity) != entity || p.backend != backend)
{
return Err(invalid());
}
Expand Down
4 changes: 2 additions & 2 deletions crates/graphql-orm/src/graphql/orm/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2511,8 +2511,8 @@ pub trait FieldPolicy<B: OrmBackend = DefaultBackend>: Send + Sync {
pub trait RowPolicy<B: OrmBackend = DefaultBackend>: Send + Sync {
/// Describe read visibility for this request, entity, and access surface.
/// The default preserves callback-only authorization. A complete predicate
/// (or `Unrestricted`) replaces row callbacks only on pagination paths that
/// explicitly support this contract. Errors fail the request closed.
/// (or `Unrestricted`) replaces row callbacks only on read paths that
/// explicitly support this contract (including typed projections and group pages). Errors fail the request closed.
fn read_visibility<'a>(
&'a self,
_ctx: Option<&'a async_graphql::Context<'_>>,
Expand Down
Loading
Loading