Skip to main content

Executor

Struct Executor 

Source
pub struct Executor { /* private fields */ }
Expand description

Query executor - executes compiled GraphQL queries.

This is the main entry point for runtime query execution. It coordinates matching, planning, execution, and projection.

§The adapter has no name here

Executor is not generic over its adapter. The concrete type is consumed by the constructor and stored erased, as Arc<dyn DatabaseAdapter>, so a holder of an Executor cannot name the adapter, cannot recover it, and cannot call anything on it that the engine does not offer. That is the whole point: a transport handed an executor is handed the engine’s surface, not a database handle it can go around the engine with.

The capability that the type parameter used to carry is carried by a value instead. A constructor bounded on Writer holds the adapter as its write handle; everything else holds None and refuses every write. See Executor::new and Executor::read_only.

§Ownership and Lifetimes

The executor holds owned references to schema and runtime data, with no borrowed pointers:

  • schema: Owned CompiledSchema (immutable after construction)
  • adapter: Shared via Arc<dyn DatabaseAdapter> so multiple executors and tasks use the same connection pool
  • introspection: Owned cached GraphQL schema responses
  • config: Owned runtime configuration

No explicit lifetimes required - all data is either owned or wrapped in Arc, so the executor can be stored in long-lived structures without lifetime annotations or borrow-checker issues.

§Concurrency

Executor is Send + Sync — DatabaseAdapter requires both of every implementor, so erasing the adapter cannot lose them. It can be safely shared across threads and tasks without cloning:

// Requires: a live database adapter.
// See: tests/integration/ for runnable examples.
// let executor = Arc::new(Executor::new(schema, adapter));
// Can be cloned into multiple tasks
// let exec_clone = executor.clone();
// tokio::spawn(async move {
//     let result = exec_clone.execute(query, vars).await;
// });

§Query Timeout

Queries are protected by the query_timeout_ms configuration in RuntimeConfig (default: 30s). When a query exceeds this timeout, it returns FraiseQLError::Timeout without panicking. Set query_timeout_ms to 0 to disable timeout enforcement.

Implementations§

Source§

impl Executor

Compile-time enforcement lives here, on the constructor.

The bound on this block is what keeps a read-only adapter from ever being handed a write handle. Its witness is FraiseWireAdapter, which implements DatabaseAdapter and deliberately not Writer.

⚠ The two blocks below are a pair, and only the pair is the assertion. A compile_fail block is satisfied by any compile error, including one with nothing to do with the rule — the previous version of this pair named SqliteAdapter, and when #374 deleted that adapter the block passed for a full release while proving nothing. The two differ by exactly the constructor called. If the witness ever stops resolving, the first block goes red rather than the second going quietly green.

FraiseWireAdapter needs --all-features; every --doc invocation in this repository passes it (Makefile, .dagger/main.go).

The witness resolves, and the read-only constructor accepts it:

use fraiseql_core::{db::FraiseWireAdapter, runtime::Executor, schema::CompiledSchema};
use std::sync::Arc;
fn _read_only_is_available(schema: CompiledSchema, adapter: Arc<FraiseWireAdapter>) {
    let _ = Executor::read_only(schema, adapter);
}

…and the write-capable constructor does not compile for it:

ⓘ
use fraiseql_core::{db::FraiseWireAdapter, runtime::Executor, schema::CompiledSchema};
use std::sync::Arc;
fn _wont_compile(schema: CompiledSchema, adapter: Arc<FraiseWireAdapter>) {
    let _ = Executor::new(schema, adapter);
}
Source

pub fn new<A: Writer>(schema: CompiledSchema, adapter: Arc<A>) -> Self

Create a new write-capable executor.

Available only for adapters that implement Writer — implementing its one method is the write capability (ruling AA 6). For one that does not — or one whose write capability you do not want to grant — use read_only, which is bounded only on DatabaseAdapter.

§Arguments
  • schema - Compiled schema
  • adapter - Database adapter
§Example
// Requires: a live PostgreSQL database.
// See: tests/integration/ for runnable examples.
let schema = CompiledSchema::from_json(schema_json, false)?;
let adapter = PostgresAdapter::new(connection_string).await?;
let executor = Executor::new(schema, Arc::new(adapter));
Source

pub fn with_config<A: Writer>( schema: CompiledSchema, adapter: Arc<A>, config: RuntimeConfig, ) -> Self

Create a new write-capable executor with custom configuration.

§Arguments
  • schema - Compiled schema
  • adapter - Database adapter
  • config - Runtime configuration
Source

pub fn read_only<A: DatabaseAdapter>( schema: CompiledSchema, adapter: Arc<A>, ) -> Self

Create a new executor that cannot write.

The entry for an adapter that is not a Writer — a read-replica handle, FraiseWireAdapter, a read-only test double — and for a write-capable adapter you want to expose read-only. Mutations are refused on every transport, because the refusal is the absence of a handle rather than a check somebody has to remember.

§Arguments
  • schema - Compiled schema
  • adapter - Database adapter
Source

pub fn read_only_with_config<A: DatabaseAdapter>( schema: CompiledSchema, adapter: Arc<A>, config: RuntimeConfig, ) -> Self

Create a new read-only executor with custom configuration.

§Arguments
  • schema - Compiled schema
  • adapter - Database adapter
  • config - Runtime configuration
Source

pub fn pool_metrics(&self) -> PoolMetrics

Return current connection pool metrics from the underlying database adapter.

Values are sampled live on each call — not cached — so callers (e.g., the /metrics endpoint) always observe up-to-date pool health.

Source

pub fn max_query_depth(&self) -> u32

How deep a selection may nest on this executor: its depth gate’s max_query_depth — declared, installed, or DEFAULT_MAX_QUERY_DEPTH. Every resolution of a document’s selection set refuses past it, including the transport’s own (the SSE planner), so no path answers depth differently.

Source

pub fn schema(&self) -> &CompiledSchema

Get the compiled schema.

Source

pub fn config(&self) -> &RuntimeConfig

Get runtime configuration.

Source

pub fn relay_enabled(&self) -> bool

Whether this executor can dispatch relay (cursor) pagination.

true only for executors built by Executor::new_with_relay or Executor::with_config_and_relay; a relay query issued against any other executor returns a Validation error. Exposed so a rebuild of the executor — a hot-reload, a per-tenant provision — can assert it preserved the capability rather than silently downgrading it (#750).

Source

pub fn database_type(&self) -> DatabaseType

Which backend this executor is bound to.

Source

pub async fn health_check(&self) -> Result<()>

Whether the backend is reachable.

§Errors

Returns the backend’s own error when the probe fails.

Source

pub async fn query_stats(&self, limit: u32) -> Result<Vec<QueryStatEntry>>

The slowest limit statements the backend is willing to report.

§Errors

Returns FraiseQLError::Unsupported on a backend with no statement-stats facility, or the backend’s own error when the read fails.

Source

pub async fn query_stats_by_id( &self, id: &str, ) -> Result<Option<QueryStatEntry>>

One statement’s stats by backend-assigned id.

§Errors

As Executor::query_stats.

Source

pub async fn reset_query_stats(&self) -> Result<()>

Discard the backend’s accumulated statement statistics.

§Errors

As Executor::query_stats.

Source

pub fn result_cache_stats(&self) -> Option<ResultCacheStats>

Adapter-level result-cache counters, or None when no cache is active.

Source

pub async fn clear_result_cache(&self) -> Result<Option<usize>>

Evict every entry from the adapter-level result cache.

Ok(None) means the backend has no such cache to clear.

§Errors

Returns the backend’s own error when eviction fails.

Source

pub async fn invalidate_views(&self, views: &[ViewName]) -> Result<u64>

Evict adapter-level result-cache entries derived from the given views.

Returns the number of entries removed; 0 on a backend with no such cache.

§Errors

Returns the backend’s own error when eviction fails.

Source

pub async fn explain_query(&self, sql: &str, params: &[Value]) -> Result<Value>

The backend’s plan for a statement, as its own JSON shape.

§Errors

Returns FraiseQLError::Unsupported on a backend with no EXPLAIN, or the backend’s own error when planning fails.

Source

pub async fn execute_admin_sql( &self, request: &AdminSqlRequest, ) -> Result<AdminSqlOutcome>

Run the admin-SQL route’s bounded statement.

§Errors

Returns FraiseQLError::Unsupported on a backend that declines admin SQL, or the backend’s own error when the statement fails.

Source

pub fn on_schema_reload(&self)

Tell the backend the compiled schema changed, so it can drop anything it derived from the old one — a result cache keyed by the old views, say.

Call this before Executor::rebuild_with: the backend is shared between the old executor and the new one, so anything stale it holds would otherwise outlive the swap.

Source

pub async fn refuse_standby_unreadable_sources( &self, schema: &CompiledSchema, ) -> Result<()>

Refuse schema when this executor’s reads go to hot standbys and a source of it depends on an UNLOGGED or temporary table (#1390).

The boot-time check, re-run by a hot reload before it swaps: a query added by a reload reads through the same replicas the server was started with.

§Errors

See crate::schema::refuse_standby_unreadable_sources.

Source

pub async fn drop_tenant_schema(&self, schema_name: &str) -> Result<()>

Drop a tenant’s PostgreSQL schema and everything in it.

Takes the schema name, not a statement: the engine composes the DDL, so no caller-supplied SQL reaches the backend through this door. Deleting Executor::adapter removed the transports’ general raw-SQL reach, and this is deliberately not a replacement for it — it does one thing.

The name is re-validated here even though fraiseql-server validates the tenant key before deriving it. This is the interpolation site, so it is the site that has to be safe on its own; a guard that holds only while every caller remembers to validate is not a guard.

§Errors

Returns FraiseQLError::Validation if schema_name is not a bare identifier (ASCII alphanumeric and underscore, non-empty, at most 63 bytes), and the backend’s own error if the DDL fails.

Source

pub fn rebuild_with( &self, schema: CompiledSchema, config: RuntimeConfig, ) -> Self

Build a new executor over this executor’s backend, for a hot-reload or a re-provision.

Supersedes the recorded-rebuilder closure of #750. That closure existed because relay dispatch needs a RelayDatabaseAdapter bound that is only in scope at the relay constructor, so a rebuild had to re-run the constructor that had it — and the recording could be wrong, which is what #750 guarded against by hand. Nothing needs re-running: RelayDispatchImpl holds the adapter and nothing schema-derived, so the same dispatch object is still correct for the new schema and is carried over as-is. A rebuild cannot downgrade a relay executor to a non-relay one, because there is no longer a step that could omit it.

The write grant is carried over for the same reason and in the same way. It is resolved from the adapter, and the adapter is unchanged, so recomputing it could only ever agree — but a rebuild that recomputed a capability is precisely the step #750 showed can omit one. Carrying it makes a downgrade unrepresentable rather than merely unlikely. The refusal beside it is recomputed from the same adapter, so it agrees with the boot (ruling Z 1).

Source

pub async fn execute_aggregate_query( &self, query_json: &Value, query_name: &str, metadata: &FactTableMetadata, ) -> Result<Value>

Execute an aggregate query directly.

§Errors

Returns error if query parsing, plan generation, SQL generation, database execution, or result projection fails.

Source

pub async fn execute_window_query( &self, query_json: &Value, query_name: &str, metadata: &FactTableMetadata, ) -> Result<Value>

Execute a window query directly.

§Errors

Returns error if query parsing, plan generation, SQL generation, database execution, or result projection fails.

Source

pub async fn count_rows( &self, query_match: &QueryMatch, variables: Option<&Value>, security_context: Option<&SecurityContext>, ) -> Result<u64>

Count rows matching a query’s filters.

Delegates to QueryRunner::count_rows.

§Errors

Returns FraiseQLError::Validation if the query has no SQL source, or if inject params are required but no security context is provided. Returns FraiseQLError::Database if the adapter returns an error.

Source

pub async fn execute_query_direct( &self, query_match: &QueryMatch, variables: Option<&Value>, security_context: Option<&SecurityContext>, request_budget: Option<&RequestBudget>, ) -> Result<Value>

Execute a pre-resolved query match directly, bypassing GraphQL parsing.

Used by the REST transport after route resolution: the QueryMatch is already computed from HTTP path/query parameters, so there is no need to re-parse a GraphQL query string.

§Errors

Returns FraiseQLError::Validation if the query has no SQL source. Returns FraiseQLError::Database if the adapter returns an error.

Source

pub fn supports_composed_reads(&self) -> bool

Whether this executor’s database adapter can compose related resources into a read (execute_query_composed).

The REST mount reads it to warn at boot; execute_query_composed refuses from it with 501. One answer for both, so the boot and the request cannot disagree.

Source

pub async fn execute_query_composed( &self, query_match: &QueryMatch, embeds: &[EmbedSelection], counts: &[CountSelection], variables: Option<&Value>, security_context: Option<&SecurityContext>, request_budget: Option<&RequestBudget>, ) -> Result<Value>

Execute a pre-resolved query match with related resources composed into it — the REST ?select= embed.

One statement, not one read per parent row per level: each embedded level is a correlated subquery joined into the parent’s, and every level is gated as the read of its own target it replaces — the target query’s authorization and role gates, its RLS predicate, and field-level RBAC against the target type, all decided before anything is sent. See runners::query_composed.

request_budget is charged exactly as by execute_query_direct: the cost of the whole tree once, before the statement, and the bytes of what it returns.

§Errors

Everything execute_query_direct returns, for the root and for every embedded level; FraiseQLError::Validation for a relationship the parent type does not declare; FraiseQLError::Unsupported from a database adapter that cannot compose a read.

Source

pub fn request_budget(&self) -> RequestBudget

The allowances one request holds, to be shared by every read that request issues.

A transport that answers one request with several reads — the REST ?select= embed, which resolves one sub-read per parent row per level — builds one of these and passes it by reference to each execute_query_direct. Without it each sub-read gets allowances of its own, and then [validation] max_response_bytes bounds each sub-read rather than the response they add up to, and [security.cost_budget] per_request_max scores each sub-read rather than the request they are all part of — two ceilings named for a request and enforced on something smaller.

Built here rather than in the transport so the ceilings are read from the compiled configuration in the one place that owns them, and a transport cannot supply a budget with ceilings of its own choosing.

Always a budget, never an Option: the two ceilings are declared independently and either may be absent, which is a distinction RequestBudget keeps internally rather than collapsing into “no budget”.

Source

pub async fn stream_query_direct( &self, query_match: QueryMatch, variables: Option<Value>, security_context: Option<SecurityContext>, ) -> Result<JsonRowStream>

The same read as execute_query_direct, delivered as a stream of projected rows (#958).

The export representations — NDJSON, CSV, XLSX — consume rows, not a response envelope, and used to obtain them by re-executing execute_query_direct with a walking OFFSET. That is O(offset) per page and gives each page its own snapshot, so a concurrent write can move a row across a page boundary and the export duplicates or drops it. One statement has neither problem.

The read resolves through the same authorization, RLS, inject_params and field-RBAC path as the buffered call — see QueryRunner::resolve_direct_read.

⚠ On PostgreSQL the returned stream holds a pooled connection until it is dropped. Consume it promptly and drop it when the response ends.

§Errors

Returns FraiseQLError::Authorization if the operation or a selected field is refused, FraiseQLError::Validation if the query has no SQL source or a required principal is absent, and FraiseQLError::Database if the read fails before its first row.

Source

pub async fn execute_row_read( &self, query_match: &QueryMatch, variables: Option<&Value>, security_context: Option<&SecurityContext>, columns: &[ColumnSpec], ) -> Result<RowRead>

Execute a row-shaped read — the same read as execute_query_direct, answered as typed column values rather than GraphQL-shaped JSON (#1351).

This is the gRPC transport’s read entry. gRPC answers row-shaped results projected through protobuf ColumnSpecs built from the message descriptor, and there is no selection set on the wire — so it cannot use the JSON entry without a round-trip through a shape it chose this transport to avoid. It used to resolve its own predicate and call DatabaseAdapter::execute_row_query directly instead, which made it a second read implementation: the operation Authorizer (#422), the requires_role gate (#1122), the actor allow-list (#966), the field gate (#423) and the compiled page-size ceiling (#421) applied to every transport but that one.

The caller must project with RowRead::columns, not with the columns it passed in — see that field for why.

§Errors

Returns FraiseQLError::Authorization when the operation, the role gate, the actor gate or a selected gated field is refused; FraiseQLError::Validation when the query has no SQL source or a configured policy has no principal to evaluate for (fail closed, #784); FraiseQLError::Unsupported for a resolved shape the row path cannot carry; and FraiseQLError::Database if the read fails.

Source

pub async fn stream_row_read( &self, query_match: &QueryMatch, variables: Option<&Value>, security_context: Option<&SecurityContext>, columns: &[ColumnSpec], ) -> Result<StreamedRowRead>

The same read as execute_row_read, delivered one row at a time (#1351).

The gRPC server-streaming arm’s source. Both arms resolve through one function, because the two have already drifted apart once: #1348 found the unary arm fail-closed and the streaming arm fail-open on the same RLS evaluation failure.

⚠ On PostgreSQL the returned stream holds a pooled connection until it is dropped. Consume it promptly and drop it when the response ends.

§Errors

The same as execute_row_read.

Source§

impl Executor

Source

pub fn new_with_relay<A: Writer + RelayDatabaseAdapter + 'static>( schema: CompiledSchema, adapter: Arc<A>, ) -> Self

Create a new executor with relay cursor pagination enabled.

Only callable when A: RelayDatabaseAdapter. The relay capability is encoded once at construction time as a type-erased Arc<dyn RelayDispatch>, so there is no per-query overhead beyond an Option::is_some() check.

§Example
// Requires: a live PostgreSQL database with relay support.
// See: tests/integration/ for runnable examples.
let adapter = PostgresAdapter::new(connection_string).await?;
let executor = Executor::new_with_relay(schema, Arc::new(adapter));
Source

pub fn with_config_and_relay<A: Writer + RelayDatabaseAdapter + 'static>( schema: CompiledSchema, adapter: Arc<A>, config: RuntimeConfig, ) -> Self

Create a new executor with relay support and custom configuration.

Source§

impl Executor

Source

pub async fn execute( &self, query: &str, variables: Option<&Value>, ) -> Result<Value>

Execute a GraphQL query string and return a serialized JSON response.

Applies the configured query timeout if one is set. Handles queries, mutations, introspection, federation, and node lookups.

If RuntimeConfig::query_validation is set, QueryValidator::validate() runs first (before parsing or SQL dispatch) to enforce size, depth, and complexity limits. This protects direct fraiseql-core embedders that do not route through fraiseql-server.

§Errors
  • FraiseQLError::Validation — query violates configured depth/complexity/alias limits (only when RuntimeConfig::query_validation is Some).
  • FraiseQLError::Timeout — query exceeded RuntimeConfig::query_timeout_ms.
  • Any error returned by execute_dispatch.
Source

pub async fn execute_operation( &self, query: &str, variables: Option<&Value>, operation_name: Option<&str>, ) -> Result<Value>

Execute a document, selecting the operation named by operation_name (GraphQL § 6.1 GetOperation).

execute is this with None, which requires the document to define exactly one operation.

§Errors
  • FraiseQLError::Parse — the document does not parse, names an operation that does not exist, or defines several operations while operation_name is None.
  • Any error returned by execute.
Source

pub async fn execute_with_scopes( &self, query: &str, variables: Option<&Value>, user_scopes: &[String], ) -> Result<Value>

Execute a GraphQL query with user context for field-level access control.

This method validates that the user has permission to access all requested fields before executing the query. If field filtering is enabled in the RuntimeConfig and the user lacks required scopes, this returns an error.

§Arguments
  • query - GraphQL query string
  • variables - Query variables (optional)
  • user_scopes - User’s scopes from JWT token (pass empty slice if unauthenticated)
§Returns

GraphQL response as JSON string, or error if access denied

§Errors
  • FraiseQLError::Validation — query validation fails, or the user’s scopes do not include a field required by the field_filter policy.
  • Propagates errors from query classification and execution.
§Example
// Requires: a live database adapter and authenticated user context.
// See: tests/integration/ for runnable examples.
let query = r#"query { users { id name salary } }"#;
// let user_scopes = user.scopes.clone();
// let result = executor.execute_with_scopes(query, None, &user_scopes).await?;
Source§

impl Executor

The typed write entries.

These used to sit on an impl<A: DatabaseAdapter + SupportsMutations> block, and the bound was described as the thing keeping a read-only adapter out. It kept out an adapter without the marker; it could not speak for supports_mutations(), the runtime gate execute_function_call is keyed on, and these entries skipped that check on the strength of the bound. An adapter carrying one and not the other was dispatched.

The question is now asked once, at step 0 of the chokepoint, against the write handle the executor holds. The compile-time refusal did not disappear: it lives on Executor::new, which is bounded on Writer — implementing its one method is the capability (ruling AA 6) — and is where the compile_fail pair lives. An adapter that does not write cannot be built into a write-capable executor.

Source

pub async fn execute_mutation( &self, mutation_name: &str, variables: Option<&Value>, selections: WriteSelections<'_>, ) -> Result<Value>

Execute a GraphQL mutation directly, by name.

Unlike execute(), which accepts a raw GraphQL string, this takes the mutation name and variables directly. Capability is settled by the executor’s write slot: an executor built by Executor::read_only holds no write handle and refuses here before any other gate runs.

§Arguments
  • mutation_name - The GraphQL mutation field name (e.g. "createUser")
  • variables - Optional JSON object of GraphQL variable values
  • selections - The result selection set (inline fragments intact) used to project the response; pass &[] for no field filtering.
§Returns

A JSON-encoded GraphQL response value on success.

§Errors

Same as execute_mutation_query, minus the adapter capability check.

Source

pub async fn execute_mutation_as( &self, mutation_name: &str, variables: Option<&Value>, security_context: Option<&SecurityContext>, selections: WriteSelections<'_>, ) -> Result<MutationExecution>

Execute a mutation with a principal, binding arguments by name from variables (#1330).

This is execute_mutation plus the caller’s identity, and it is the entry a non-GraphQL transport should use when it already holds structured arguments. It converges at execute_mutation_impl like every other write, so requires_role, requires_actor, the Authorizer, argument validation, before:mutation and the change-log write all run.

execute_mutation_with_security is the same call with the selection set derived from the mutation’s return type rather than supplied by the caller — the entry a transport uses when it has no selection set of its own. It used to reach the engine by formatting a GraphQL document out of the arguments, a round-trip through text that could not represent every JSON value faithfully; that is fixed (#1331).

§Errors

Same as execute_mutation, plus the refusals the gates raise for this principal.

Returns the projection and the parsed mutation_response envelope: a transport whose own wire format is that envelope cannot reconstruct entity_id or a failure message from the projection alone.

Source

pub async fn execute_mutation_with_security( &self, mutation_name: &str, arguments: &Value, security_context: Option<&SecurityContext>, ) -> Result<Value>

Execute a mutation for a transport that holds structured arguments, with an optional principal.

Both arms of the REST write come here — authenticated and anonymous — so both are projected through the same selection set and face the same gates (#1352).

This used to reach the engine by formatting a GraphQL document out of its arguments: format!("{k}: {v}") renders a serde_json::Value through Display, which emits JSON, and JSON quotes object keys where GraphQL does not. Any argument that was — or contained — an object produced a document the parser refused, so an authenticated REST write carrying a nested body failed outright, and under the JSONB data-column model a nested object is the ordinary body shape (#1331). Arguments are bound as values now; nothing round-trips through text.

§Errors

Returns FraiseQLError::Database if the adapter returns an error. Returns FraiseQLError::Validation if inject params require a missing security context.

Source

pub async fn execute_mutation_batch( &self, mutation_name: &str, items: &[Value], security_context: Option<&SecurityContext>, ) -> Result<BulkResult>

Execute a batch of mutations (for REST bulk insert).

Executes each mutation individually and collects results into a BulkResult.

§Errors

Returns the first error encountered during batch execution.

Source

pub async fn execute_bulk_by_ids( &self, mutation_name: &str, id_field: &str, ids: &[Value], body: Option<&Value>, security_context: Option<&SecurityContext>, ) -> Result<BulkResult>

Execute a mutation once per identified row — the engine behind a collection-level PATCH/DELETE.

ids are the primary-key values the caller’s filter selected; each is merged into the request body under id_field so the mutation function receives the row it is meant to act on. affected_rows is the number of mutations that actually ran.

This replaces execute_bulk_by_filter, which ran the filter query, discarded the matched rows, invoked the mutation exactly once with the body and no row identity, and then reported affected_rows as the filter’s row count — a fabricated success on a write path (#913). Its _id_field and _max_affected parameters were both unused.

Row selection and the max_affected cap now live in the caller (the REST bulk handler), where the filter guard and the HTTP status for “too many rows” belong. Keeping them there is deliberate: this function can no longer run without a caller having decided which rows it applies to.

§Errors

Returns whatever the underlying mutation returns; the first failure aborts and propagates, so a partially-applied bulk reports the error rather than a count.

Source§

impl Executor

Source

pub fn plan_subscription( &self, document: &ParsedQuery, variables: Option<&Value>, principal: Option<&SecurityContext>, ) -> Result<SubscriptionPlan>

Plan a subscription for one subscriber: document is the subscription operation as the client sent it, variables its variables, principal the subscriber.

§Errors

Validation for a document that selects no subscription or more than one, an unknown subscription, a field the type does not declare or the subscription does not deliver; Authorization for the type’s role, a filter on a field the subscriber may not read, and whatever the read plan refuses.

Source

pub fn plan_type_stream( &self, type_name: &str, principal: Option<&SecurityContext>, ) -> Result<SubscriptionPlan>

Plan a stream of type_name’s change events for one reader — the REST /{resource}/stream (ruling AC 7): a read of the type through the selection a GET of the resource with no ?select= reads, every field the type declares, object fields expanded as their type.

§Errors

Validation for a type nesting an object deeper than a whole-object read expands; Authorization for the type’s role and whatever the read plan refuses — a Reject field the reader may not read refuses the stream, as it refuses the GET.

Source§

impl Executor

Source

pub async fn execute_with_context( &self, query: &str, variables: Option<&Value>, ctx: &ExecutionContext, ) -> Result<Value>

Execute a GraphQL query with cancellation support via ExecutionContext.

This method allows graceful cancellation of long-running queries through a cancellation token. If the token is cancelled during execution, the query returns a FraiseQLError::Cancelled error.

§Arguments
  • query - GraphQL query string
  • variables - Query variables (optional)
  • ctx - ExecutionContext with cancellation token
§Returns

GraphQL response as JSON string, or error if cancelled or execution fails

§Errors
§Example
// Requires: a live database adapter and running tokio runtime.
// See: tests/integration/ for runnable examples.
use fraiseql_core::runtime::ExecutionContext;
use fraiseql_core::error::FraiseQLError;
use std::time::Duration;

let ctx = ExecutionContext::new("user-query-123".to_string());
let cancel_token = ctx.cancellation_token().clone();

// Spawn a task to cancel after 5 seconds
tokio::spawn(async move {
    tokio::time::sleep(Duration::from_secs(5)).await;
    cancel_token.cancel();
});

// let result = executor.execute_with_context(query, None, &ctx).await;
Source

pub async fn execute_with_security( &self, query: &str, variables: Option<&Value>, security_context: &SecurityContext, ) -> Result<Value>

Execute a GraphQL query or mutation with a JWT SecurityContext.

This is the main authenticated entry point for the executor. It routes the incoming request to the appropriate handler based on the query type:

  • Regular queries: RLS WHERE clauses are applied so each user only sees their own rows, as determined by the RLS policy in RuntimeConfig.
  • Mutations: the security context is forwarded so server-side inject parameters (e.g. jwt:sub) are resolved from the caller’s JWT claims.
  • Multi-root queries (e.g. { users { id } posts { id } }): each root is dispatched in parallel with the security context applied to every root (H19).
  • Aggregations, window queries, federation, node lookups: the security context is forwarded to each handler (RLS / requires_role / inject gates apply).
  • Introspection: served from the pre-built response (no per-user data).

If query_timeout_ms is non-zero in the RuntimeConfig, the entire execution is raced against a Tokio deadline and returns FraiseQLError::Timeout when the deadline is exceeded.

§Arguments
  • query - GraphQL query string (e.g. "query { posts { id title } }")
  • variables - Optional JSON object of GraphQL variable values
  • security_context - Authenticated user context extracted from a validated JWT
§Returns

A JSON-encoded GraphQL response string on success, conforming to the GraphQL over HTTP specification.

§Errors
§Example
// Requires: a live database adapter and a SecurityContext from authentication.
// See: tests/integration/ for runnable examples.
use fraiseql_core::security::SecurityContext;

// let query = r#"query { posts { id title } }"#;
// Returns a JSON string: {"data":{"posts":[...]}}
// let result = executor.execute_with_security(query, None, &context).await?;
Source

pub async fn execute_operation_with_security( &self, query: &str, variables: Option<&Value>, security_context: &SecurityContext, operation_name: Option<&str>, ) -> Result<Value>

Authenticated execution that selects the operation named by operation_name (GraphQL § 6.1 GetOperation).

execute_with_security is this with None, which requires the document to define exactly one operation. Every HTTP request carries an operationName field, so the server path uses this entry point.

§Errors
Source

pub fn check_field_access( &self, type_name: &str, field_name: &str, user_scopes: &[String], ) -> Result<(), FieldAccessError>

Check if a specific field can be accessed with given scopes.

This is a convenience method for checking field access without executing a query.

§Arguments
  • type_name - The GraphQL type name
  • field_name - The field name
  • user_scopes - User’s scopes from JWT token
§Returns

Ok(()) if access is allowed, Err(FieldAccessError) if denied

§Errors

Returns FieldAccessError::AccessDenied if the user’s scopes do not include the required scope for the field.

Source

pub async fn execute_json( &self, query: &str, variables: Option<&Value>, ) -> Result<Value>

👎Deprecated since 2.2.0:

use execute() directly — it now returns Value

Execute a query and return parsed JSON.

This method is now equivalent to execute() since execute() already returns serde_json::Value.

§Errors

Returns any error from execute().

Source§

impl Executor

Source

pub async fn explain( &self, query_name: &str, variables: Option<&Value>, limit: Option<u32>, offset: Option<u32>, ) -> Result<ExplainResult>

Run EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) for a named query.

Looks up query_name in the compiled schema, builds a parameterized WHERE clause from variables, and delegates to DatabaseAdapter::explain_where_query. The result includes the generated SQL and the raw PostgreSQL EXPLAIN output.

§Arguments
  • query_name - Name of a regular query in the schema (e.g., "users")
  • variables - JSON object whose keys map to equality WHERE conditions
  • limit - Optional LIMIT to pass to the query
  • offset - Optional OFFSET to pass to the query
§Errors
  • FraiseQLError::Validation — unknown query name or mutation given
  • FraiseQLError::Unsupported — database adapter does not support EXPLAIN ANALYZE
  • FraiseQLError::Database — EXPLAIN execution failed
Source§

impl Executor

Source

pub async fn execute_parallel( &self, parsed: &ParsedQuery, variables: Option<&Value>, security_context: Option<&SecurityContext>, ) -> Result<PipelineResult>

Execute all root fields of a multi-root query concurrently.

Each root field is dispatched as an independent single-root query. Results are awaited with futures::future::try_join_all and merged into a PipelineResult.

§Errors

Returns the first error encountered across all concurrent sub-queries.

Source§

impl Executor

Source

pub fn plan_query( &self, query: &str, variables: Option<&Value>, ) -> Result<ExplainPlan>

Generate an explain plan for a query without executing it.

Returns the SQL that would be generated, parameters, cost estimate, and views that would be accessed.

§Errors

Returns error if the query cannot be parsed or matched against the schema.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more