Skip to main content

Executor

Struct Executor 

Source
pub struct Executor<A: DatabaseAdapter> { /* 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.

§Type Parameters

  • A - The database adapter type (implements DatabaseAdapter trait)

§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<A> to allow multiple executors/tasks to 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<A> is Send + Sync when A is Send + Sync. 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<A: DatabaseAdapter> Executor<A>

Source

pub async fn execute_parallel( &self, parsed: &ParsedQuery, variables: Option<&Value>, ) -> 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<A: DatabaseAdapter> Executor<A>

Source

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

Create new executor.

§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)?;
let adapter = PostgresAdapter::new(connection_string).await?;
let executor = Executor::new(schema, Arc::new(adapter));
Source

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

Create new 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 const fn schema(&self) -> &CompiledSchema

Get the compiled schema.

Source

pub const fn config(&self) -> &RuntimeConfig

Get runtime configuration.

Source

pub const fn adapter(&self) -> &Arc<A>

Get database adapter reference.

Source§

impl<A: DatabaseAdapter + RelayDatabaseAdapter + 'static> Executor<A>

Source

pub fn new_with_relay(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( schema: CompiledSchema, adapter: Arc<A>, config: RuntimeConfig, ) -> Self

Create a new executor with relay support and custom configuration.

Source§

impl<A: DatabaseAdapter> Executor<A>

Source

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

Execute an aggregate query.

§Arguments
  • query_json - JSON representation of the aggregate query
  • query_name - GraphQL field name (e.g., “sales_aggregate”)
  • metadata - Fact table metadata
§Returns

GraphQL response as JSON string

§Errors

Returns error if:

  • Query parsing fails
  • Execution plan generation fails
  • SQL generation fails
  • Database execution fails
  • Result projection fails
§Example
// Requires: a live database adapter and compiled fact table metadata.
// See: tests/integration/ for runnable examples.
let query_json = json!({
    "table": "tf_sales",
    "groupBy": { "category": true },
    "aggregates": [{"count": {}}]
});
// let result = executor.execute_aggregate_query(&query_json, "sales_aggregate", &metadata).await?;
Source

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

Execute a window query.

§Arguments
  • query_json - JSON representation of the window query
  • query_name - GraphQL field name (e.g., “sales_window”)
  • metadata - Fact table metadata
§Returns

GraphQL response as JSON string

§Errors

Returns error if:

  • Query parsing fails
  • Execution plan generation fails
  • SQL generation fails
  • Database execution fails
  • Result projection fails
§Example
// Requires: a live database adapter and compiled fact table metadata.
// See: tests/integration/ for runnable examples.
let query_json = json!({
    "table": "tf_sales",
    "select": [{"type": "measure", "name": "revenue", "alias": "revenue"}],
    "windows": [{
        "function": {"type": "row_number"},
        "alias": "rank",
        "partitionBy": [{"type": "dimension", "path": "category"}],
        "orderBy": [{"field": "revenue", "direction": "DESC"}]
    }]
});
// let result = executor.execute_window_query(&query_json, "sales_window", &metadata).await?;
Source§

impl<A: DatabaseAdapter> Executor<A>

Source

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

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_internal.
Source

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

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<A: DatabaseAdapter> Executor<A>

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<A: DatabaseAdapter + SupportsMutations> Executor<A>

Compile-time enforcement: SqliteAdapter must NOT implement SupportsMutations.

Calling execute_mutation on an Executor<SqliteAdapter> must not compile because SqliteAdapter does not implement the SupportsMutations marker trait.

use fraiseql_core::runtime::Executor;
use fraiseql_core::db::sqlite::SqliteAdapter;
use fraiseql_core::schema::CompiledSchema;
use std::sync::Arc;
async fn _wont_compile() {
    let adapter = Arc::new(SqliteAdapter::new_in_memory().await.unwrap());
    let executor = Executor::new(CompiledSchema::new(), adapter);
    executor.execute_mutation("createUser", None).await.unwrap();
}
Source

pub async fn execute_mutation( &self, mutation_name: &str, variables: Option<&Value>, ) -> Result<String>

Execute a GraphQL mutation directly, with compile-time capability enforcement.

Unlike execute() (which accepts raw GraphQL strings and performs a runtime supports_mutations() check), this method is only available on adapters that implement SupportsMutations. The capability is enforced at compile time: attempting to call this method with SqliteAdapter results in a compiler error.

§Arguments
  • mutation_name - The GraphQL mutation field name (e.g. "createUser")
  • variables - Optional JSON object of GraphQL variable values
§Returns

A JSON-encoded GraphQL response string on success.

§Errors

Same as execute_mutation_query, minus the adapter capability check.

Source§

impl<A: DatabaseAdapter> Executor<A>

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.

Source§

impl<A: DatabaseAdapter> Executor<A>

Source

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

Execute a pre-built QueryMatch directly, bypassing GraphQL string parsing.

Used by the REST transport for embedded sub-queries and NDJSON streaming where the query parameters are already resolved from HTTP request parameters.

§Errors

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

Source

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

Execute a mutation with security context for REST transport.

Delegates to the standard mutation execution path with RLS enforcement.

§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_filter( &self, query_match: &QueryMatch, mutation_name: &str, body: Option<&Value>, _id_field: &str, _max_affected: u64, security_context: Option<&SecurityContext>, ) -> Result<BulkResult>

Execute a bulk operation (collection-level PATCH/DELETE) by filter.

§Errors

Returns FraiseQLError::Database if the adapter returns an error.

Source

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

Count the total number of rows matching the query’s WHERE and RLS conditions.

Issues a SELECT COUNT(*) FROM {view} WHERE {conditions} query, ignoring pagination (ORDER BY, LIMIT, OFFSET). Useful for REST X-Total-Count headers and count=exact query parameter support.

§Arguments
  • query_match - Pre-built query match identifying the SQL source and filters
  • variables - Optional variables (unused for count, reserved for future use)
  • security_context - Optional authenticated user context for RLS and inject
§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§

impl<A: DatabaseAdapter> Executor<A>

Source

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

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<String>

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 to execute_mutation_query_with_security so server-side inject parameters (e.g. jwt:sub) are resolved from the caller’s JWT claims.
  • Aggregations, window queries, federation, introspection: Delegated to their respective handlers (security context is not yet applied to these).

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 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>

Execute a query and return parsed JSON.

Same as execute() but returns parsed serde_json::Value instead of string.

§Errors

Propagates all errors from Self::execute and additionally returns FraiseQLError::Database if the response string is not valid JSON.

Auto Trait Implementations§

§

impl<A> Freeze for Executor<A>

§

impl<A> !RefUnwindSafe for Executor<A>

§

impl<A> Send for Executor<A>

§

impl<A> Sync for Executor<A>

§

impl<A> Unpin for Executor<A>

§

impl<A> UnsafeUnpin for Executor<A>

§

impl<A> !UnwindSafe for Executor<A>

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> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. 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: 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: 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 = Infallible

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

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

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