use std::{collections::HashMap, sync::Arc};
use moka::sync::Cache as MokaCache;
use super::{
context::ExecutorContext,
runners,
support::relay::{RelayDispatch, RelayDispatchImpl},
};
use crate::{
db::{RelayDatabaseAdapter, traits::DatabaseAdapter, types::PoolMetrics},
error::Result,
runtime::{QueryMatcher, QueryPlanner, RuntimeConfig, matcher::QueryMatch},
schema::{CompiledSchema, IntrospectionResponses},
security::SecurityContext,
};
fn build_introspection(schema: &CompiledSchema) -> IntrospectionResponses {
#[cfg_attr(not(feature = "federation"), allow(unused_mut))]
let mut introspection = IntrospectionResponses::build(schema);
#[cfg(feature = "federation")]
if let Some(fed_meta) = schema.federation_metadata() {
let inaccessible: HashMap<String, Vec<String>> = fed_meta
.types
.iter()
.filter(|t| !t.inaccessible_fields.is_empty())
.map(|t| (t.name.clone(), t.inaccessible_fields.clone()))
.collect();
introspection.filter_inaccessible(&inaccessible);
}
introspection
}
const PARSE_CACHE_CAPACITY: u64 = 1_024;
pub struct Executor<A: DatabaseAdapter> {
pub(super) ctx: Arc<ExecutorContext<A>>,
}
impl<A: DatabaseAdapter> Executor<A> {
#[must_use]
pub fn new(schema: CompiledSchema, adapter: Arc<A>) -> Self {
Self::with_config(schema, adapter, RuntimeConfig::default())
}
#[must_use]
pub fn with_config(schema: CompiledSchema, adapter: Arc<A>, config: RuntimeConfig) -> Self {
let matcher = QueryMatcher::new(schema.clone());
let planner = QueryPlanner::new(config.cache_query_plans);
let introspection = build_introspection(&schema);
let mut node_type_index: HashMap<String, Arc<str>> = HashMap::new();
for q in &schema.queries {
if let Some(src) = q.sql_source.as_deref() {
node_type_index.entry(q.return_type.clone()).or_insert_with(|| Arc::from(src));
}
}
let schema_version: Arc<str> = Arc::from(schema.content_hash());
let ctx = Arc::new(ExecutorContext {
schema,
schema_version,
adapter,
relay: None,
matcher,
planner,
config,
introspection,
node_type_index,
parse_cache: MokaCache::new(PARSE_CACHE_CAPACITY),
response_cache: None,
});
Self { ctx }
}
#[must_use]
pub fn pool_metrics(&self) -> PoolMetrics {
self.ctx.pool_metrics()
}
#[must_use]
pub fn schema(&self) -> &CompiledSchema {
&self.ctx.schema
}
#[must_use]
pub fn config(&self) -> &RuntimeConfig {
&self.ctx.config
}
#[must_use]
pub fn adapter(&self) -> &Arc<A> {
&self.ctx.adapter
}
#[cfg(test)]
#[must_use]
pub fn parse_cache_entry_count(&self) -> u64 {
self.ctx.parse_cache.entry_count()
}
#[must_use]
pub fn with_response_cache(mut self, cache: Arc<crate::cache::ResponseCache>) -> Self {
Arc::get_mut(&mut self.ctx)
.expect("with_response_cache called after Arc was shared")
.response_cache = Some(cache);
self
}
#[must_use]
pub fn response_cache(&self) -> Option<&Arc<crate::cache::ResponseCache>> {
self.ctx.response_cache.as_ref()
}
pub(super) fn query_runner(&self) -> runners::query::QueryRunner<A> {
runners::query::QueryRunner::new(Arc::clone(&self.ctx))
}
pub(super) fn aggregate_runner(&self) -> runners::aggregate::AggregateRunner<A> {
runners::aggregate::AggregateRunner::new(Arc::clone(&self.ctx))
}
pub async fn execute_aggregate_query(
&self,
query_json: &serde_json::Value,
query_name: &str,
metadata: &crate::compiler::fact_table::FactTableMetadata,
) -> Result<serde_json::Value> {
if let Some(authorizer) = self.ctx.config.authorizer.as_ref() {
let ops = [(crate::security::OperationKind::Query, query_name.to_string())];
crate::security::authorizer::enforce_authz(
authorizer.as_ref(),
None,
&ops,
Some(query_json),
)?;
}
self.aggregate_runner()
.execute_aggregate_query(query_json, query_name, metadata, None)
.await
}
pub async fn execute_window_query(
&self,
query_json: &serde_json::Value,
query_name: &str,
metadata: &crate::compiler::fact_table::FactTableMetadata,
) -> Result<serde_json::Value> {
if let Some(authorizer) = self.ctx.config.authorizer.as_ref() {
let ops = [(crate::security::OperationKind::Query, query_name.to_string())];
crate::security::authorizer::enforce_authz(
authorizer.as_ref(),
None,
&ops,
Some(query_json),
)?;
}
self.aggregate_runner()
.execute_window_query(query_json, query_name, metadata, None)
.await
}
pub async fn count_rows(
&self,
query_match: &QueryMatch,
variables: Option<&serde_json::Value>,
security_context: Option<&SecurityContext>,
) -> Result<u64> {
self.query_runner().count_rows(query_match, variables, security_context).await
}
pub async fn execute_query_direct(
&self,
query_match: &QueryMatch,
variables: Option<&serde_json::Value>,
security_context: Option<&SecurityContext>,
) -> Result<serde_json::Value> {
self.query_runner()
.execute_query_direct(query_match, variables, security_context)
.await
}
}
impl<A: DatabaseAdapter + RelayDatabaseAdapter + 'static> Executor<A> {
#[must_use]
pub fn new_with_relay(schema: CompiledSchema, adapter: Arc<A>) -> Self {
Self::with_config_and_relay(schema, adapter, RuntimeConfig::default())
}
#[must_use]
pub fn with_config_and_relay(
schema: CompiledSchema,
adapter: Arc<A>,
config: RuntimeConfig,
) -> Self {
let relay_dispatch: Arc<dyn RelayDispatch> =
Arc::new(RelayDispatchImpl(Arc::clone(&adapter)));
let matcher = QueryMatcher::new(schema.clone());
let planner = QueryPlanner::new(config.cache_query_plans);
let introspection = build_introspection(&schema);
let mut node_type_index: HashMap<String, Arc<str>> = HashMap::new();
for q in &schema.queries {
if let Some(src) = q.sql_source.as_deref() {
node_type_index.entry(q.return_type.clone()).or_insert_with(|| Arc::from(src));
}
}
let schema_version: Arc<str> = Arc::from(schema.content_hash());
let ctx = Arc::new(ExecutorContext {
schema,
schema_version,
adapter,
relay: Some(relay_dispatch),
matcher,
planner,
config,
introspection,
node_type_index,
parse_cache: MokaCache::new(PARSE_CACHE_CAPACITY),
response_cache: None,
});
Self { ctx }
}
}