use super::super::{Executor, MutationRoot, QueryType};
use crate::{
error::{FraiseQLError, Result},
graphql::{operation_selection_error, parse_query_with_operation_name, value_json},
runtime::{collect_variable_references, validate_variable_uses, validate_variables_used},
schema::is_fact_table_root,
security::SecurityContext,
};
pub(in crate::runtime::executor) type Classification = (
QueryType,
Option<std::sync::Arc<crate::graphql::ParsedQuery>>,
Vec<(String, serde_json::Value)>,
Option<std::sync::Arc<CompletionTarget>>,
DocumentVariables,
);
#[derive(Debug, Default)]
pub(in crate::runtime::executor) struct DocumentVariables {
operation_name: Option<String>,
definitions: Vec<crate::graphql::types::VariableDefinition>,
defined: Vec<String>,
referenced: Vec<String>,
}
#[derive(Debug)]
pub(in crate::runtime::executor) struct CachedClassification {
source: Box<str>,
operation_name: Option<Box<str>>,
pub(in crate::runtime::executor) classification: Classification,
}
impl CachedClassification {
pub(in crate::runtime::executor) fn new(
source: &str,
operation_name: Option<&str>,
classification: Classification,
) -> Self {
Self {
source: source.into(),
operation_name: operation_name.map(Into::into),
classification,
}
}
pub(in crate::runtime::executor) fn answers(
&self,
source: &str,
operation_name: Option<&str>,
) -> bool {
*self.source == *source && self.operation_name.as_deref() == operation_name
}
}
#[derive(Debug)]
pub(in crate::runtime::executor) struct CompletionTarget {
pub(in crate::runtime::executor) root_type: &'static str,
pub(in crate::runtime::executor) selections: crate::graphql::selection_set::CollectedFields,
}
impl Executor {
pub(in crate::runtime::executor) fn check_variables(
&self,
variables: &DocumentVariables,
caller: Option<&SecurityContext>,
) -> Result<()> {
let operation_name = variables.operation_name.as_deref();
self.validate_variable_types_for(
caller,
operation_name,
&variables.definitions,
self.ctx.config.suggestions,
)?;
validate_variables_used(operation_name, &variables.defined, &variables.referenced)
}
pub(in crate::runtime::executor) fn check_variables_against_the_whole_schema(
&self,
variables: &DocumentVariables,
) -> Result<()> {
let operation_name = variables.operation_name.as_deref();
crate::runtime::validate_variable_types(
&self.ctx.schema,
&self.ctx.schema,
operation_name,
&variables.definitions,
self.ctx.config.suggestions,
)?;
validate_variables_used(operation_name, &variables.defined, &variables.referenced)
}
pub(in crate::runtime::executor) fn classify_query(
&self,
query: &str,
operation_name: Option<&str>,
) -> Result<QueryType> {
self.classify_query_with_parse(query, operation_name).map(|(qt, ..)| qt)
}
pub(in crate::runtime::executor) fn classify_query_with_parse(
&self,
query: &str,
operation_name: Option<&str>,
) -> Result<Classification> {
let parsed = parse_query_with_operation_name(query, operation_name)
.map_err(|e| operation_selection_error(&e))?;
let defaults = value_json::variable_defaults(&parsed.variables)?;
let root_type = match parsed.operation_type.as_str() {
"mutation" => "Mutation",
"subscription" => "Subscription",
_ => "Query",
};
let completion = crate::graphql::selection_set::resolve(
&parsed.selections,
&parsed.fragments,
self.max_query_depth(),
)
.ok()
.map(|selections| {
std::sync::Arc::new(CompletionTarget {
root_type,
selections: crate::graphql::selection_set::CollectedFields::new(selections),
})
});
let variables = Self::document_variables(&parsed, self.ctx.config.suggestions)?;
let (query_type, parsed) = self.classify_parsed(parsed)?;
Ok((query_type, parsed.map(std::sync::Arc::new), defaults, completion, variables))
}
fn document_variables(
parsed: &crate::graphql::ParsedQuery,
suggestions: bool,
) -> Result<DocumentVariables> {
let operation_name = parsed.operation_name.as_deref();
let defined: Vec<String> = parsed.variables.iter().map(|v| v.name.clone()).collect();
let referenced = collect_variable_references(parsed)?;
validate_variable_uses(operation_name, &defined, &referenced, suggestions)?;
Ok(DocumentVariables {
operation_name: parsed.operation_name.clone(),
definitions: parsed.variables.clone(),
defined,
referenced,
})
}
fn classify_parsed(
&self,
parsed: crate::graphql::ParsedQuery,
) -> Result<(QueryType, Option<crate::graphql::ParsedQuery>)> {
let root_field = &parsed.root_field;
if parsed.operation_type != "mutation" && parsed.selections.len() > 1 {
if let Some(fact_root) =
parsed.selections.iter().find(|root| is_fact_table_root(&root.name))
{
let others: Vec<String> = parsed
.selections
.iter()
.filter(|root| !std::ptr::eq(*root, fact_root))
.map(|root| format!("`{}`", root.response_key()))
.collect();
return Err(FraiseQLError::Validation {
message: format!(
"`{}` must be the only root of its document; it was sent with {}: send \
each in its own request",
fact_root.response_key(),
others.join(", ")
),
path: None,
});
}
}
if root_field == "__schema" || root_field == "__type" {
return Ok((QueryType::Introspection, Some(parsed)));
}
if root_field == "__typename" && parsed.selections.len() == 1 {
return Ok((
QueryType::TypeName {
selection: Box::new(parsed.selections[0].clone()),
operation_type: parsed.operation_type.clone(),
},
None,
));
}
if root_field == "_service" || root_field == "_entities" {
return Ok((QueryType::Federation(root_field.clone()), None));
}
if parsed.operation_type == "query" && root_field == "node" {
let raw = parsed.selections.first().map_or(&[][..], |s| s.nested_fields.as_slice());
let resolved = crate::graphql::selection_set::resolve(
raw,
&parsed.fragments,
self.max_query_depth(),
)?;
let mut selections = Vec::with_capacity(resolved.len());
for sel in resolved {
if sel.name.starts_with("...on ") {
for mut child in sel.nested_fields {
child.inherit_directives(&sel.directives);
selections.push(child);
}
} else {
selections.push(sel);
}
}
return Ok((QueryType::NodeQuery { selections }, None));
}
if parsed.operation_type == "mutation" {
let roots = parsed
.selections
.iter()
.map(|root| {
Ok(MutationRoot {
response_key: root.response_key().to_string(),
name: root.name.clone(),
selections: crate::graphql::selection_set::resolve(
&root.nested_fields,
&parsed.fragments,
self.max_query_depth(),
)?,
arguments: root.arguments.clone(),
})
})
.collect::<Result<Vec<_>>>()?;
return Ok((QueryType::Mutation { roots }, None));
}
if is_fact_table_root(root_field) {
let query_type = if root_field.ends_with("_aggregate") {
QueryType::Aggregate(root_field.clone())
} else {
QueryType::Window(root_field.clone())
};
return Ok((query_type, Some(parsed)));
}
Ok((QueryType::Regular, Some(parsed)))
}
}