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 (implementsDatabaseAdaptertrait)
§Ownership and Lifetimes
The executor holds owned references to schema and runtime data, with no borrowed pointers:
schema: OwnedCompiledSchema(immutable after construction)adapter: Shared viaArc<A>to allow multiple executors/tasks to use the same connection poolintrospection: Owned cached GraphQL schema responsesconfig: 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>
impl<A: DatabaseAdapter> Executor<A>
Sourcepub async fn execute_parallel(
&self,
parsed: &ParsedQuery,
variables: Option<&Value>,
) -> Result<PipelineResult>
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>
impl<A: DatabaseAdapter> Executor<A>
Sourcepub fn new(schema: CompiledSchema, adapter: Arc<A>) -> Self
pub fn new(schema: CompiledSchema, adapter: Arc<A>) -> Self
Create new executor.
§Arguments
schema- Compiled schemaadapter- 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));Sourcepub fn with_config(
schema: CompiledSchema,
adapter: Arc<A>,
config: RuntimeConfig,
) -> Self
pub fn with_config( schema: CompiledSchema, adapter: Arc<A>, config: RuntimeConfig, ) -> Self
Create new executor with custom configuration.
§Arguments
schema- Compiled schemaadapter- Database adapterconfig- Runtime configuration
Sourcepub fn pool_metrics(&self) -> PoolMetrics
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.
Sourcepub const fn schema(&self) -> &CompiledSchema
pub const fn schema(&self) -> &CompiledSchema
Get the compiled schema.
Sourcepub const fn config(&self) -> &RuntimeConfig
pub const fn config(&self) -> &RuntimeConfig
Get runtime configuration.
Source§impl<A: DatabaseAdapter + RelayDatabaseAdapter + 'static> Executor<A>
impl<A: DatabaseAdapter + RelayDatabaseAdapter + 'static> Executor<A>
Sourcepub fn new_with_relay(schema: CompiledSchema, adapter: Arc<A>) -> Self
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));Sourcepub fn with_config_and_relay(
schema: CompiledSchema,
adapter: Arc<A>,
config: RuntimeConfig,
) -> Self
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>
impl<A: DatabaseAdapter> Executor<A>
Sourcepub async fn execute_aggregate_query(
&self,
query_json: &Value,
query_name: &str,
metadata: &FactTableMetadata,
) -> Result<String>
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 queryquery_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?;Sourcepub async fn execute_window_query(
&self,
query_json: &Value,
query_name: &str,
metadata: &FactTableMetadata,
) -> Result<String>
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 queryquery_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>
impl<A: DatabaseAdapter> Executor<A>
Sourcepub async fn execute(
&self,
query: &str,
variables: Option<&Value>,
) -> Result<String>
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 whenRuntimeConfig::query_validationisSome).FraiseQLError::Timeout— query exceededRuntimeConfig::query_timeout_ms.- Any error returned by
execute_internal.
Sourcepub async fn execute_with_scopes(
&self,
query: &str,
variables: Option<&Value>,
user_scopes: &[String],
) -> Result<String>
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 stringvariables- 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 thefield_filterpolicy.- 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>
impl<A: DatabaseAdapter> Executor<A>
Sourcepub async fn explain(
&self,
query_name: &str,
variables: Option<&Value>,
limit: Option<u32>,
offset: Option<u32>,
) -> Result<ExplainResult>
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 conditionslimit- Optional LIMIT to pass to the queryoffset- Optional OFFSET to pass to the query
§Errors
FraiseQLError::Validation— unknown query name or mutation givenFraiseQLError::Unsupported— database adapter does not support EXPLAIN ANALYZEFraiseQLError::Database— EXPLAIN execution failed
Source§impl<A: DatabaseAdapter + SupportsMutations> Executor<A>
Compile-time enforcement: SqliteAdapter must NOT implement SupportsMutations.
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();
}Sourcepub async fn execute_mutation(
&self,
mutation_name: &str,
variables: Option<&Value>,
) -> Result<String>
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>
impl<A: DatabaseAdapter> Executor<A>
Sourcepub fn plan_query(
&self,
query: &str,
variables: Option<&Value>,
) -> Result<ExplainPlan>
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>
impl<A: DatabaseAdapter> Executor<A>
Sourcepub async fn execute_query_direct(
&self,
query_match: &QueryMatch,
_variables: Option<&Value>,
security_context: Option<&SecurityContext>,
) -> Result<String>
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.
Sourcepub async fn execute_mutation_with_security(
&self,
mutation_name: &str,
arguments: &Value,
security_context: Option<&SecurityContext>,
) -> Result<String>
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.
Sourcepub async fn execute_mutation_batch(
&self,
mutation_name: &str,
items: &[Value],
security_context: Option<&SecurityContext>,
) -> Result<BulkResult>
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.
Sourcepub 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>
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.
Sourcepub async fn count_rows(
&self,
query_match: &QueryMatch,
_variables: Option<&Value>,
security_context: Option<&SecurityContext>,
) -> Result<u64>
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 filtersvariables- 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>
impl<A: DatabaseAdapter> Executor<A>
Sourcepub async fn execute_with_context(
&self,
query: &str,
variables: Option<&Value>,
ctx: &ExecutionContext,
) -> Result<String>
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 stringvariables- Query variables (optional)ctx-ExecutionContextwith cancellation token
§Returns
GraphQL response as JSON string, or error if cancelled or execution fails
§Errors
FraiseQLError::Cancelled— the cancellation token was triggered before or during execution.- Propagates any error from the underlying
executecall.
§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;Sourcepub async fn execute_with_security(
&self,
query: &str,
variables: Option<&Value>,
security_context: &SecurityContext,
) -> Result<String>
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
WHEREclauses are applied so each user only sees their own rows, as determined by the RLS policy inRuntimeConfig. - Mutations: The security context is forwarded to
execute_mutation_query_with_securityso server-sideinjectparameters (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 valuessecurity_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
FraiseQLError::Parse— the query string is not valid GraphQLFraiseQLError::Validation— unknown mutation name, missingsql_source, or a mutation requiresinjectparams but the security context is absentFraiseQLError::Database— the underlying adapter returns an errorFraiseQLError::Timeout— execution exceededquery_timeout_ms
§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?;Sourcepub fn check_field_access(
&self,
type_name: &str,
field_name: &str,
user_scopes: &[String],
) -> Result<(), FieldAccessError>
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 namefield_name- The field nameuser_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.
Sourcepub async fn execute_json(
&self,
query: &str,
variables: Option<&Value>,
) -> Result<Value>
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> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
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 moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
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