use crate::telemetry::logging::targets;
use ahash::HashSet;
use indexmap::IndexMap;
use std::fmt::{Display, Formatter as FmtFormatter, Result as FmtResult};
use std::sync::Arc;
use tracing::{instrument, warn};
use crate::query_planner::{
ast::{
operation::OperationDefinition,
selection_item::SelectionItem,
selection_set::{FieldSelection, InlineFragmentSelection, SelectionSet},
},
state::supergraph_state::OperationKind,
utils::pretty_display::{get_indent, PrettyDisplay},
};
use crate::executor::projection::error::ProjectionError;
use crate::executor::{
introspection::schema::{FieldNullability, SchemaMetadata},
utils::consts::TYPENAME_FIELD_NAME,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TypeCondition {
Exact(String),
OneOf(HashSet<String>),
}
impl TypeCondition {
pub fn matches(&self, type_name: &str) -> bool {
match self {
TypeCondition::Exact(expected) => type_name == expected,
TypeCondition::OneOf(possible) => possible.contains(type_name),
}
}
pub fn union(self, other: TypeCondition) -> TypeCondition {
use TypeCondition::*;
match (self, other) {
(Exact(left), Exact(right)) => {
if left == right {
Exact(left)
} else {
OneOf(HashSet::from_iter(vec![left, right]))
}
}
(OneOf(mut types), Exact(exact)) | (Exact(exact), OneOf(mut types)) => {
types.insert(exact);
OneOf(types)
}
(OneOf(mut left), OneOf(right)) => {
left.extend(right);
OneOf(left)
}
}
}
pub fn intersect(self, other: TypeCondition) -> TypeCondition {
use TypeCondition::*;
match (self, other) {
(Exact(left), Exact(right)) => {
if left == right {
Exact(left)
} else {
OneOf(HashSet::default())
}
}
(OneOf(types), Exact(exact)) | (Exact(exact), OneOf(types)) => {
if types.contains(&exact) {
Exact(exact)
} else {
OneOf(HashSet::default())
}
}
(OneOf(mut left), OneOf(right)) => {
left.retain(|t| right.contains(t));
if left.len() == 1 {
Exact(left.into_iter().next().expect("Set has one element"))
} else {
OneOf(left)
}
}
}
}
}
#[derive(Debug, Clone)]
pub enum ProjectionValueSource {
ResponseData {
selections: Option<Arc<Vec<FieldProjectionPlan>>>,
},
Null,
}
type SelectionVariants = IndexMap<String, Vec<FieldProjectionPlan>>;
#[derive(Debug, Clone)]
pub struct FieldProjectionPlan {
pub field_name: String,
pub response_key: String,
pub is_typename: bool,
pub nullability: FieldNullability,
pub parent_type_guard: Option<TypeCondition>,
pub conditions: Option<FieldProjectionCondition>,
pub value: ProjectionValueSource,
}
#[cfg(debug_assertions)]
fn debug_plans_vec(plans: &[FieldProjectionPlan]) {
for (i, plan) in plans.iter().enumerate() {
use crate::telemetry::logging::targets;
tracing::trace!(target: targets::GRAPHQL_EXECUTION, "plan {}:\n{}", i, plan);
}
}
#[cfg(debug_assertions)]
fn debug_plans_map(plans: &SelectionVariants) {
for (i, (key, plans)) in plans.iter().enumerate() {
use crate::telemetry::logging::targets;
tracing::trace!(target: targets::GRAPHQL_EXECUTION, "key {}: {}", i, key);
debug_plans_vec(plans);
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FieldProjectionCondition {
IncludeIfVariable(String),
SkipIfVariable(String),
ParentTypeCondition(TypeCondition),
FieldTypeCondition(TypeCondition),
EnumValuesCondition(HashSet<String>),
Or(Box<FieldProjectionCondition>, Box<FieldProjectionCondition>),
And(Box<FieldProjectionCondition>, Box<FieldProjectionCondition>),
}
pub enum FieldProjectionConditionError {
InvalidParentType,
InvalidFieldType,
Skip,
InvalidEnumValue,
Fatal(ProjectionError),
}
impl From<ProjectionError> for FieldProjectionConditionError {
fn from(err: ProjectionError) -> Self {
FieldProjectionConditionError::Fatal(err)
}
}
impl FieldProjectionCondition {
pub fn and(&self, right: FieldProjectionCondition) -> FieldProjectionCondition {
use FieldProjectionCondition::*;
match (self, right) {
(ParentTypeCondition(left), ParentTypeCondition(right)) => {
ParentTypeCondition(left.clone().intersect(right))
}
(FieldTypeCondition(left), FieldTypeCondition(right)) => {
FieldTypeCondition(left.clone().intersect(right))
}
(EnumValuesCondition(left), EnumValuesCondition(right)) => {
let mut left = left.clone();
left.retain(|v| right.contains(v));
EnumValuesCondition(left)
}
(left, right) => And(Box::new(left.clone()), Box::new(right)),
}
}
pub fn or(&self, right: FieldProjectionCondition) -> FieldProjectionCondition {
use FieldProjectionCondition::*;
if self == &right {
return self.clone();
}
match (self, right) {
(ParentTypeCondition(left), ParentTypeCondition(right)) => {
ParentTypeCondition(left.clone().union(right))
}
(FieldTypeCondition(left), FieldTypeCondition(right)) => {
FieldTypeCondition(left.clone().union(right))
}
(EnumValuesCondition(left), EnumValuesCondition(right)) => {
let mut result = left.clone();
result.extend(right);
EnumValuesCondition(result)
}
(left, right) => Or(Box::new(left.clone()), Box::new(right)),
}
}
}
impl FieldProjectionPlan {
#[instrument(level = "trace", skip_all)]
pub fn from_operation<'a>(
operation: &'a OperationDefinition,
schema_metadata: &'a SchemaMetadata,
) -> (&'a str, Vec<FieldProjectionPlan>) {
let root_type_name = match operation.operation_kind {
None | Some(OperationKind::Query) => schema_metadata.query_type_name.as_ref(),
Some(OperationKind::Mutation) => schema_metadata.mutation_type_name.as_ref(),
Some(OperationKind::Subscription) => schema_metadata.subscription_type_name.as_ref(),
}
.unwrap_or_else(|| {
panic!(
"No root type found for operation kind: {:?}",
operation.operation_kind
);
});
let mut plans = Self::from_selection_set(
&operation.selection_set,
schema_metadata,
root_type_name,
&None,
)
.unwrap_or_default();
for plan in &mut plans {
Self::remove_redundant_child_guards(plan, schema_metadata);
}
(root_type_name, plans)
}
#[instrument(level = "trace", skip_all, fields(
selection_set = %selection_set,
parent_type_name,
parent_condition = ?parent_condition,
))]
fn from_selection_set(
selection_set: &SelectionSet,
schema_metadata: &SchemaMetadata,
parent_type_name: &str,
parent_condition: &Option<FieldProjectionCondition>,
) -> Option<Vec<FieldProjectionPlan>> {
let mut field_selections = SelectionVariants::new();
for selection_item in &selection_set.items {
match selection_item {
SelectionItem::Field(field) => {
Self::process_field(
field,
&mut field_selections,
schema_metadata,
parent_type_name,
parent_condition,
);
}
SelectionItem::InlineFragment(inline_fragment) => {
Self::process_inline_fragment(
inline_fragment,
&mut field_selections,
schema_metadata,
parent_type_name,
parent_condition,
);
}
SelectionItem::FragmentSpread(_) => {
unreachable!(
"Fragment spreads should not exist in the final response projection."
);
}
}
}
if field_selections.is_empty() {
return None;
}
let resolved = Self::resolve_variants(field_selections, parent_type_name, schema_metadata);
if resolved.is_empty() {
None
} else {
Some(resolved)
}
}
#[instrument(level = "trace", skip_all, fields(parent_type_name,))]
fn resolve_variants(
field_selections: SelectionVariants,
parent_type_name: &str,
schema_metadata: &SchemaMetadata,
) -> Vec<FieldProjectionPlan> {
#[cfg(debug_assertions)]
{
use crate::telemetry::logging::targets;
tracing::trace!(target: targets::GRAPHQL_EXECUTION, "input:\n");
debug_plans_map(&field_selections);
}
let mut concrete_types: Option<Vec<String>> = None;
let mut resolved = Vec::new();
for (_, variants) in field_selections {
let already_exclusive = variants.len() == 1
|| variants
.iter()
.all(|v| matches!(v.parent_type_guard, Some(TypeCondition::Exact(_))));
if already_exclusive {
resolved.extend(variants);
continue;
}
let concrete_types = concrete_types.get_or_insert_with(|| {
let mut types: Vec<String> = schema_metadata
.possible_types
.get_possible_types(parent_type_name)
.into_iter()
.filter(|t| schema_metadata.is_object_type(t))
.collect();
types.sort();
types
});
for concrete_type in concrete_types.iter() {
let merged = Self::merge_variants_for_type(
&variants,
concrete_type,
parent_type_name,
schema_metadata,
);
resolved.extend(merged);
}
}
#[cfg(debug_assertions)]
{
use crate::telemetry::logging::targets;
tracing::trace!(target: targets::GRAPHQL_EXECUTION, "output:\n");
debug_plans_vec(&resolved);
}
resolved
}
fn merge_variants_for_type(
variants: &[FieldProjectionPlan],
concrete_type: &str,
parent_type_name: &str,
schema_metadata: &SchemaMetadata,
) -> Option<FieldProjectionPlan> {
let mut merged: Option<FieldProjectionPlan> = None;
for variant in variants {
let matches = variant
.parent_type_guard
.as_ref()
.is_none_or(|guard| guard.matches(concrete_type));
if !matches {
continue;
}
let mut scoped = variant.clone();
scoped.parent_type_guard = Some(TypeCondition::Exact(concrete_type.to_string()));
match &mut merged {
None => merged = Some(scoped),
Some(existing) => {
Self::merge_matching(existing, scoped, parent_type_name, schema_metadata)
}
}
}
merged
}
fn field_output_type<'a>(
schema_metadata: &'a SchemaMetadata,
parent_type_name: &'a str,
guard: &'a Option<TypeCondition>,
field_name: &str,
) -> Option<&'a str> {
if field_name == TYPENAME_FIELD_NAME {
return None;
}
let lookup = |candidate: &str| {
schema_metadata
.type_fields
.get(candidate)
.and_then(|fields| fields.get(field_name))
.map(|info| info.output_type_name.as_str())
};
match guard {
Some(guard) => Self::type_names_from(guard).into_iter().find_map(lookup),
None => lookup(parent_type_name),
}
}
fn type_names_from(condition: &TypeCondition) -> Vec<&str> {
match condition {
TypeCondition::Exact(ty) => vec![ty.as_str()],
TypeCondition::OneOf(types) => types.iter().map(|s| s.as_str()).collect(),
}
}
fn remove_redundant_child_guards(
parent_field: &mut FieldProjectionPlan,
schema_metadata: &SchemaMetadata,
) {
let ProjectionValueSource::ResponseData { selections } = &mut parent_field.value else {
return;
};
let Some(selections_arc) = selections else {
return;
};
let selections_mut = Arc::make_mut(selections_arc);
let possible_child_types = parent_field.parent_type_guard.as_ref().map(|parent_guard| {
let parent_types = Self::type_names_from(parent_guard);
HashSet::from_iter(parent_types.iter().filter_map(|parent_type| {
schema_metadata
.type_fields
.get(*parent_type)
.and_then(|fields| fields.get(&parent_field.field_name))
.map(|field_info| field_info.output_type_name.as_str())
}))
});
for child in selections_mut {
if let (Some(child_guard), Some(possible_types)) =
(&child.parent_type_guard, &possible_child_types)
{
let is_redundant = match child_guard {
TypeCondition::Exact(ty) => {
possible_types.len() == 1 && possible_types.contains(ty.as_str())
}
TypeCondition::OneOf(guard_types) => {
guard_types.len() == possible_types.len()
&& guard_types
.iter()
.all(|t| possible_types.contains(t.as_str()))
}
};
if is_redundant {
child.parent_type_guard = None;
}
}
Self::remove_redundant_child_guards(child, schema_metadata);
}
}
fn apply_directive_conditions(
condition: Option<FieldProjectionCondition>,
include_if: &Option<String>,
skip_if: &Option<String>,
) -> Option<FieldProjectionCondition> {
let mut condition = condition;
if let Some(include_if_var) = include_if {
condition = Self::and_optional(
condition,
Some(FieldProjectionCondition::IncludeIfVariable(
include_if_var.clone(),
)),
);
}
if let Some(skip_if_var) = skip_if {
condition = Self::and_optional(
condition,
Some(FieldProjectionCondition::SkipIfVariable(
skip_if_var.clone(),
)),
);
}
condition
}
fn combine_optional<T, F>(left: Option<T>, right: Option<T>, combiner: F) -> Option<T>
where
F: FnOnce(T, T) -> T,
{
match (left, right) {
(None, None) => None,
(Some(c), None) | (None, Some(c)) => Some(c),
(Some(l), Some(r)) => Some(combiner(l, r)),
}
}
fn or_optional(
left: Option<FieldProjectionCondition>,
right: Option<FieldProjectionCondition>,
) -> Option<FieldProjectionCondition> {
match (left, right) {
(None, _) | (_, None) => None,
(Some(l), Some(r)) => Some(l.or(r)),
}
}
fn and_optional(
left: Option<FieldProjectionCondition>,
right: Option<FieldProjectionCondition>,
) -> Option<FieldProjectionCondition> {
Self::combine_optional(left, right, |l, r| l.and(r))
}
fn conditions_for_child_selections(
condition: &FieldProjectionCondition,
) -> Option<FieldProjectionCondition> {
use FieldProjectionCondition::*;
match condition {
IncludeIfVariable(variable_name) => Some(IncludeIfVariable(variable_name.clone())),
SkipIfVariable(variable_name) => Some(SkipIfVariable(variable_name.clone())),
And(left, right) => Self::and_optional(
Self::conditions_for_child_selections(left),
Self::conditions_for_child_selections(right),
),
Or(left, right) => Self::or_optional(
Self::conditions_for_child_selections(left),
Self::conditions_for_child_selections(right),
),
ParentTypeCondition(_) | FieldTypeCondition(_) | EnumValuesCondition(_) => None,
}
}
fn condition_with_optional_guard(
guard: &Option<TypeCondition>,
condition: Option<FieldProjectionCondition>,
) -> Option<FieldProjectionCondition> {
match guard {
None => condition,
Some(guard) => Some(Self::condition_with_guard(guard, condition)),
}
}
fn condition_with_guard(
guard: &TypeCondition,
condition: Option<FieldProjectionCondition>,
) -> FieldProjectionCondition {
let parent_check = FieldProjectionCondition::ParentTypeCondition(guard.clone());
match condition {
Some(cond) => parent_check.and(cond),
None => parent_check,
}
}
fn merge_conditions(
left_guard: &Option<TypeCondition>,
left_condition: Option<FieldProjectionCondition>,
right_guard: &Option<TypeCondition>,
right_condition: Option<FieldProjectionCondition>,
) -> Option<FieldProjectionCondition> {
if left_guard == right_guard {
return Self::or_optional(left_condition, right_condition);
}
let left = Self::condition_with_optional_guard(left_guard, left_condition);
let right = Self::condition_with_optional_guard(right_guard, right_condition);
Self::or_optional(left, right)
}
#[instrument(level = "trace", skip_all, fields(parent_type_name))]
fn merge_plan(
field_selections: &mut SelectionVariants,
plan_to_merge: FieldProjectionPlan,
parent_type_name: &str,
schema_metadata: &SchemaMetadata,
) {
let Some(variants) = field_selections.get_mut(plan_to_merge.response_key.as_str()) else {
field_selections.insert(plan_to_merge.response_key.clone(), vec![plan_to_merge]);
return;
};
match variants
.iter_mut()
.find(|v| v.parent_type_guard == plan_to_merge.parent_type_guard)
{
Some(existing) => {
Self::merge_matching(existing, plan_to_merge, parent_type_name, schema_metadata)
}
None => variants.push(plan_to_merge),
}
}
#[instrument(level = "trace", skip_all, fields(parent_type_name))]
fn merge_matching(
existing_plan: &mut FieldProjectionPlan,
plan_to_merge: FieldProjectionPlan,
parent_type_name: &str,
schema_metadata: &SchemaMetadata,
) {
existing_plan.conditions = Self::merge_conditions(
&existing_plan.parent_type_guard,
existing_plan.conditions.take(),
&existing_plan.parent_type_guard,
plan_to_merge.conditions,
);
let child_parent_type = Self::field_output_type(
schema_metadata,
parent_type_name,
&existing_plan.parent_type_guard,
&existing_plan.field_name,
)
.unwrap_or(parent_type_name);
match (&mut existing_plan.value, plan_to_merge.value) {
(
ProjectionValueSource::ResponseData {
selections: existing_selections,
},
ProjectionValueSource::ResponseData {
selections: new_selections,
},
) => {
if let Some(new_selections) = new_selections {
match existing_selections {
Some(selections) => {
let selections_mut = Arc::make_mut(selections);
let new_selections_vec = Arc::try_unwrap(new_selections)
.unwrap_or_else(|arc| (*arc).clone());
let mut child_selections = SelectionVariants::new();
for child in selections_mut.drain(..) {
child_selections
.entry(child.response_key.clone())
.or_default()
.push(child);
}
for new_plan in new_selections_vec {
Self::merge_plan(
&mut child_selections,
new_plan,
child_parent_type,
schema_metadata,
);
}
selections_mut.extend(Self::resolve_variants(
child_selections,
child_parent_type,
schema_metadata,
));
}
None => *existing_selections = Some(new_selections),
}
}
}
(ProjectionValueSource::Null, ProjectionValueSource::Null) => {
}
_ => {
warn!(target: targets::GRAPHQL_EXECUTION, "Merging plans with `Null` value source is not supported during initial plan construction.");
existing_plan.value = ProjectionValueSource::Null;
}
}
}
fn simplify_condition(
condition: FieldProjectionCondition,
parent_type_guard: &Option<TypeCondition>,
) -> Option<FieldProjectionCondition> {
let Some(TypeCondition::Exact(guard_type)) = parent_type_guard else {
return Some(condition);
};
match condition {
FieldProjectionCondition::ParentTypeCondition(TypeCondition::Exact(cond_type))
if &cond_type == guard_type =>
{
None
}
FieldProjectionCondition::ParentTypeCondition(TypeCondition::OneOf(types))
if types.len() == 1
&& types.iter().next().map(|t| t.as_str()) == Some(guard_type) =>
{
None
}
FieldProjectionCondition::And(left, right) => {
let left_simplified = Self::simplify_condition(*left, parent_type_guard);
let right_simplified = Self::simplify_condition(*right, parent_type_guard);
match (left_simplified, right_simplified) {
(None, None) => None,
(Some(cond), None) | (None, Some(cond)) => Some(cond),
(Some(l), Some(r)) => Some(l.and(r)),
}
}
FieldProjectionCondition::Or(left, right) => {
let left_simplified = Self::simplify_condition(*left, parent_type_guard);
let right_simplified = Self::simplify_condition(*right, parent_type_guard);
Self::or_optional(left_simplified, right_simplified)
}
other => Some(other),
}
}
#[instrument(level = "trace", skip_all, fields(
field_name = field.name,
field_alias = field.alias.as_deref(),
parent_type_name,
parent_condition = ?parent_condition,
))]
fn process_field(
field: &FieldSelection,
field_selections: &mut SelectionVariants,
schema_metadata: &SchemaMetadata,
parent_type_name: &str,
parent_condition: &Option<FieldProjectionCondition>,
) {
let field_name = &field.name;
let response_key = field.alias.as_ref().unwrap_or(field_name).clone();
let (field_type, nullability) = if field_name == TYPENAME_FIELD_NAME {
("String".to_string(), FieldNullability::type_name())
} else {
let field_map = match schema_metadata.type_fields.get(parent_type_name) {
Some(fields) => fields,
None => {
warn!(
target: targets::GRAPHQL_EXECUTION,
parent_type_name,
"No fields found for type in schema metadata",
);
return;
}
};
match field_map.get(field_name) {
Some(f) => (f.output_type_name.clone(), f.nullability.clone()),
None => {
warn!(
target: targets::GRAPHQL_EXECUTION,
field_name, parent_type_name,
"Field not found in type in schema metadata",
);
return;
}
}
};
let type_condition = if schema_metadata.is_object_type(&field_type)
|| schema_metadata.is_scalar_type(&field_type)
{
TypeCondition::Exact(field_type.to_string())
} else {
TypeCondition::OneOf(
schema_metadata
.possible_types
.get_possible_types(&field_type),
)
};
let parent_type_guard = parent_condition.as_ref().and_then(Self::get_type_guard);
let inherited_selection_conditions = parent_condition
.as_ref()
.and_then(Self::conditions_for_child_selections);
let conditions_for_selections = Self::apply_directive_conditions(
Self::and_optional(
inherited_selection_conditions,
parent_type_guard
.as_ref()
.map(|_| FieldProjectionCondition::ParentTypeCondition(type_condition.clone())),
),
&field.include_if,
&field.skip_if,
);
let mut condition_for_field = if schema_metadata.is_union_type(&field_type)
|| schema_metadata.is_interface_type(&field_type)
{
Self::and_optional(
parent_condition.clone(),
Some(FieldProjectionCondition::FieldTypeCondition(type_condition)),
)
} else {
parent_condition.clone()
};
condition_for_field = Self::apply_directive_conditions(
condition_for_field,
&field.include_if,
&field.skip_if,
);
if let Some(enum_values) = schema_metadata.enum_values.get(&field_type) {
condition_for_field = Self::and_optional(
condition_for_field,
Some(FieldProjectionCondition::EnumValuesCondition(
enum_values.clone(),
)),
);
}
let final_conditions =
condition_for_field.and_then(|cond| Self::simplify_condition(cond, &parent_type_guard));
let new_plan = if matches!(
field.selections.items.as_slice(),
[SelectionItem::Field(FieldSelection {
omit_from_response: true,
..
})]
) {
FieldProjectionPlan {
field_name: field.name.to_string(),
response_key,
parent_type_guard,
is_typename: field_name == TYPENAME_FIELD_NAME,
nullability: nullability.clone(),
conditions: final_conditions,
value: ProjectionValueSource::ResponseData {
selections: Some(Arc::new(Vec::new())),
},
}
} else {
FieldProjectionPlan {
field_name: field_name.to_string(),
response_key,
parent_type_guard,
is_typename: field_name == TYPENAME_FIELD_NAME,
nullability: nullability.clone(),
conditions: final_conditions,
value: ProjectionValueSource::ResponseData {
selections: Self::from_selection_set(
&field.selections,
schema_metadata,
&field_type,
&conditions_for_selections,
)
.map(Arc::new),
},
}
};
Self::merge_plan(
field_selections,
new_plan,
parent_type_name,
schema_metadata,
);
}
#[instrument(level = "trace", skip_all, fields(
inline_fragment_type = inline_fragment.type_condition,
fragment_str = %inline_fragment.selections,
parent_type_name,
parent_condition = ?parent_condition,
))]
fn process_inline_fragment(
inline_fragment: &InlineFragmentSelection,
field_selections: &mut SelectionVariants,
schema_metadata: &SchemaMetadata,
parent_type_name: &str,
parent_condition: &Option<FieldProjectionCondition>,
) {
let inline_fragment_type = &inline_fragment.type_condition;
let type_condition = if schema_metadata.is_object_type(inline_fragment_type) {
TypeCondition::Exact(inline_fragment_type.to_string())
} else {
TypeCondition::OneOf(
schema_metadata
.possible_types
.get_possible_types(inline_fragment_type),
)
};
let mut condition_for_fragment = Self::and_optional(
parent_condition.clone(),
Some(FieldProjectionCondition::ParentTypeCondition(
type_condition.clone(),
)),
);
condition_for_fragment = Self::apply_directive_conditions(
condition_for_fragment,
&inline_fragment.include_if,
&inline_fragment.skip_if,
);
if let Some(mut inline_fragment_selections) = Self::from_selection_set(
&inline_fragment.selections,
schema_metadata,
inline_fragment_type,
&condition_for_fragment,
) {
for selection in &mut inline_fragment_selections {
selection.parent_type_guard = Some(type_condition.clone());
}
for selection in inline_fragment_selections {
Self::merge_plan(
field_selections,
selection,
parent_type_name,
schema_metadata,
);
}
}
}
pub fn with_new_value(&self, new_value: ProjectionValueSource) -> FieldProjectionPlan {
FieldProjectionPlan {
field_name: self.field_name.clone(),
response_key: self.response_key.clone(),
parent_type_guard: self.parent_type_guard.clone(),
conditions: self.conditions.clone(),
is_typename: self.is_typename,
nullability: self.nullability.clone(),
value: new_value,
}
}
fn get_type_guard(condition: &FieldProjectionCondition) -> Option<TypeCondition> {
match condition {
FieldProjectionCondition::ParentTypeCondition(tc) => Some(tc.clone()),
FieldProjectionCondition::And(a, b) => Self::combine_optional(
Self::get_type_guard(a),
Self::get_type_guard(b),
|ga, gb| ga.intersect(gb),
),
FieldProjectionCondition::Or(a, b) => Self::combine_optional(
Self::get_type_guard(a),
Self::get_type_guard(b),
|ga, gb| ga.union(gb),
),
_ => None,
}
}
}
impl Display for TypeCondition {
fn fmt(&self, f: &mut FmtFormatter<'_>) -> FmtResult {
match self {
TypeCondition::Exact(type_name) => write!(f, "Exact({})", type_name),
TypeCondition::OneOf(types) => {
write!(f, "OneOf(")?;
let mut types_vec: Vec<_> = types.iter().collect();
types_vec.sort();
for (i, type_name) in types_vec.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", type_name)?;
}
write!(f, ")")
}
}
}
}
impl Display for FieldProjectionCondition {
fn fmt(&self, f: &mut FmtFormatter<'_>) -> FmtResult {
match self {
FieldProjectionCondition::IncludeIfVariable(var) => {
write!(f, "Include(if: ${})", var)
}
FieldProjectionCondition::SkipIfVariable(var) => {
write!(f, "Skip(if: ${})", var)
}
FieldProjectionCondition::ParentTypeCondition(tc) => {
write!(f, "ParentType({})", tc)
}
FieldProjectionCondition::FieldTypeCondition(tc) => {
write!(f, "FieldType({})", tc)
}
FieldProjectionCondition::EnumValuesCondition(values) => {
write!(f, "EnumValues(")?;
let values_vec: Vec<_> = values.iter().collect();
for (i, value) in values_vec.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", value)?;
}
write!(f, ")")
}
FieldProjectionCondition::Or(left, right) => {
write!(f, "({} OR {})", left, right)
}
FieldProjectionCondition::And(left, right) => {
write!(f, "({} AND {})", left, right)
}
}
}
}
impl Display for FieldProjectionPlan {
fn fmt(&self, f: &mut FmtFormatter<'_>) -> FmtResult {
self.pretty_fmt(f, 0)
}
}
impl PrettyDisplay for FieldProjectionPlan {
fn pretty_fmt(&self, f: &mut FmtFormatter<'_>, depth: usize) -> FmtResult {
let indent = get_indent(depth);
if self.response_key == self.field_name {
writeln!(f, "{}{}: {{", indent, self.response_key)?;
} else {
writeln!(
f,
"{}{} (alias for {}) {{",
indent, self.response_key, self.field_name
)?;
}
if let Some(parent_type_guard) = self.parent_type_guard.as_ref() {
writeln!(f, "{} type guard: {}", indent, parent_type_guard)?;
}
if let Some(conditions) = self.conditions.as_ref() {
writeln!(f, "{} conditions: {}", indent, conditions)?;
}
match &self.value {
ProjectionValueSource::ResponseData { selections } => {
if let Some(selections) = selections {
writeln!(f, "{} selections:", indent)?;
for selection in selections.iter() {
selection.pretty_fmt(f, depth + 2)?;
}
}
}
ProjectionValueSource::Null => {
writeln!(f, "{} value: Null", indent)?;
}
}
writeln!(f, "{}}}", indent)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::FieldProjectionPlan;
use crate::executor::introspection::schema::SchemaWithMetadata;
use crate::query_planner::{
ast::normalization::normalize_operation,
consumer_schema::ConsumerSchema,
state::supergraph_state::SupergraphState,
utils::parsing::{parse_operation, parse_schema},
};
const SCHEMA_1: &str = r#"
schema
@link(url: "https://specs.apollo.dev/link/v1.0")
@link(url: "https://specs.apollo.dev/join/v0.3", for: EXECUTION) {
query: Query
}
directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE
directive @join__graph(name: String!, url: String!) on ENUM_VALUE
directive @join__field(
graph: join__Graph
requires: join__FieldSet
provides: join__FieldSet
type: String
external: Boolean
override: String
usedOverridden: Boolean
) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION
directive @join__implements(
graph: join__Graph!
interface: String!
) repeatable on OBJECT | INTERFACE
directive @join__type(
graph: join__Graph!
key: join__FieldSet
extension: Boolean! = false
resolvable: Boolean! = true
isInterfaceObject: Boolean! = false
) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR
directive @join__unionMember(
graph: join__Graph!
member: String!
) repeatable on UNION
scalar join__FieldSet
directive @link(
url: String
as: String
for: link__Purpose
import: [link__Import]
) repeatable on SCHEMA
scalar link__Import
enum link__Purpose {
"""
`SECURITY` features provide metadata necessary to securely resolve fields.
"""
SECURITY
"""
`EXECUTION` features provide metadata necessary for operation execution.
"""
EXECUTION
}
enum join__Graph {
LOCAL @join__graph(name: "local", url: "")
}
type Query @join__type(graph: LOCAL) {
search: [SearchResult!]!
feed: [Content!]!
concrete: ConcreteType
}
type ConcreteType @join__type(graph: LOCAL) {
id: ID!
test: String!
someEnum: SomeEnum
inner: InnerType
}
type InnerType @join__type(graph: LOCAL) {
inner: Boolean
}
type Article implements Content
@join__type(graph: LOCAL)
@join__implements(graph: LOCAL, interface: "Content") {
id: ID!
meta: Meta!
headline: String!
}
type Video implements Content
@join__type(graph: LOCAL)
@join__implements(graph: LOCAL, interface: "Content") {
id: ID!
meta: Meta!
duration: Int!
}
type Photo implements Content
@join__type(graph: LOCAL)
@join__implements(graph: LOCAL, interface: "Content") {
id: ID!
meta: Meta!
}
type Meta @join__type(graph: LOCAL) {
title: String
wordCount: Int!
arch: String!
}
interface Content @join__type(graph: LOCAL) {
id: ID!
meta: Meta!
}
union SearchResult
@join__type(graph: LOCAL)
@join__unionMember(graph: LOCAL, member: "Article")
@join__unionMember(graph: LOCAL, member: "Video") =
| Article
| Video
enum SomeEnum @join__type(graph: LOCAL) {
TEST @join__enumValue(graph: LOCAL)
}
"#;
fn plan(schema: &str, query: &str) -> String {
let supergraph = parse_schema(schema);
let consumer_schema = ConsumerSchema::new_from_supergraph(&supergraph);
let schema_metadata = consumer_schema.schema_metadata();
let operation = parse_operation(query);
let supergraph_state = SupergraphState::new(&supergraph);
let normalized =
normalize_operation(&supergraph_state, &operation, None).expect("failed to normalize");
println!("{}", normalized.operation);
let (_, selections) =
FieldProjectionPlan::from_operation(&normalized.operation, &schema_metadata);
selections
.iter()
.map(|p| p.to_string())
.collect::<Vec<_>>()
.join("\n")
}
#[test]
fn simple_concrete_mixed_fields_defs() {
insta::assert_snapshot!(plan(SCHEMA_1,
r#"{
concrete {
id
test
someEnum
inner {
inner
}
}
}"#
), @r###"
concrete: {
selections:
id: {
}
test: {
}
someEnum: {
conditions: EnumValues(TEST)
}
inner: {
selections:
inner: {
}
}
}
"###);
}
#[test]
fn inline_fragment_on_concrete_type_with_dups() {
insta::assert_snapshot!(plan(SCHEMA_1,
r#"{
concrete {
... on ConcreteType {
id
inner {
inner
}
}
test
someEnum
inner {
inner
}
}
}"#
), @r###"
concrete: {
selections:
id: {
}
inner: {
selections:
inner: {
}
}
test: {
}
someEnum: {
conditions: EnumValues(TEST)
}
}
"###);
}
#[test]
fn union_disjoint_fragments_kept_per_type() {
insta::assert_snapshot!(plan(SCHEMA_1,
r#"{
search {
__typename
... on Article { meta { title wordCount } }
... on Video { meta { title arch } }
}
}"#
), @r###"
search: {
conditions: FieldType(OneOf(Article, SearchResult, Video))
selections:
__typename: {
}
meta: {
type guard: Exact(Article)
selections:
title: {
}
wordCount: {
}
}
meta: {
type guard: Exact(Video)
selections:
title: {
}
arch: {
}
}
}
"###);
}
#[test]
fn interface_overlapping_guard_splits_per_type() {
insta::assert_snapshot!(plan(SCHEMA_1,
r#"{
feed {
meta { title }
... on Article { meta { wordCount } }
}
}"#
), @r###"
feed: {
conditions: FieldType(OneOf(Article, Content, Photo, Video))
selections:
meta: {
type guard: Exact(Article)
selections:
title: {
}
wordCount: {
}
}
meta: {
type guard: Exact(Photo)
selections:
title: {
}
}
meta: {
type guard: Exact(Video)
selections:
title: {
}
}
}
"###);
}
#[test]
fn same_guard_fragments_merge_into_one() {
insta::assert_snapshot!(plan(SCHEMA_1,
r#"{
search {
... on Article { meta { title } }
... on Article { meta { title } }
}
}"#
), @r###"
search: {
conditions: FieldType(OneOf(Article, SearchResult, Video))
selections:
meta: {
type guard: Exact(Article)
selections:
title: {
}
}
}
"###);
}
const SCHEMA_2: &str = r#"
schema
@link(url: "https://specs.apollo.dev/link/v1.0")
@link(url: "https://specs.apollo.dev/join/v0.3", for: EXECUTION) {
query: Query
}
directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE
directive @join__graph(name: String!, url: String!) on ENUM_VALUE
directive @join__field(
graph: join__Graph
requires: join__FieldSet
provides: join__FieldSet
type: String
external: Boolean
override: String
usedOverridden: Boolean
) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION
directive @join__implements(
graph: join__Graph!
interface: String!
) repeatable on OBJECT | INTERFACE
directive @join__type(
graph: join__Graph!
key: join__FieldSet
extension: Boolean! = false
resolvable: Boolean! = true
isInterfaceObject: Boolean! = false
) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR
directive @join__unionMember(
graph: join__Graph!
member: String!
) repeatable on UNION
scalar join__FieldSet
directive @link(
url: String
as: String
for: link__Purpose
import: [link__Import]
) repeatable on SCHEMA
scalar link__Import
enum link__Purpose {
"""
`SECURITY` features provide metadata necessary to securely resolve fields.
"""
SECURITY
"""
`EXECUTION` features provide metadata necessary for operation execution.
"""
EXECUTION
}
enum join__Graph {
LOCAL @join__graph(name: "local", url: "")
}
type Dog implements Pet & Animal & Node
@join__type(graph: LOCAL)
@join__implements(graph: LOCAL, interface: "Pet")
@join__implements(graph: LOCAL, interface: "Animal")
@join__implements(graph: LOCAL, interface: "Node") {
id: ID!
name: String!
bestFriend: Animal
weight(unit: WeightUnit = KG): Float
nickname: String
tags: [String!]
}
type Cat implements Pet & Animal & Node
@join__type(graph: LOCAL)
@join__implements(graph: LOCAL, interface: "Pet")
@join__implements(graph: LOCAL, interface: "Animal")
@join__implements(graph: LOCAL, interface: "Node") {
id: ID!
name: String!
bestFriend: Cat
weight(unit: WeightUnit = KG): Float
age: Int
tags: [String!]
}
type Robot implements Node
@join__type(graph: LOCAL)
@join__implements(graph: LOCAL, interface: "Node") {
id: ID!
model: String!
weight(unit: WeightUnit = KG): Float
}
type Owner implements Node
@join__type(graph: LOCAL)
@join__implements(graph: LOCAL, interface: "Node") {
id: ID!
name: String!
pets: [Pet!]!
primaryPet: Pet
}
type Query @join__type(graph: LOCAL) {
pet: Pet
animal: Animal
node: Node
search: SearchResult
searchMany: [SearchResult!]!
pets: [Pet!]!
owner: Owner
}
interface Node @join__type(graph: LOCAL) {
id: ID!
}
interface Animal implements Node
@join__type(graph: LOCAL)
@join__implements(graph: LOCAL, interface: "Node") {
id: ID!
name: String!
}
interface Pet implements Animal & Node
@join__type(graph: LOCAL)
@join__implements(graph: LOCAL, interface: "Animal")
@join__implements(graph: LOCAL, interface: "Node") {
id: ID!
name: String!
bestFriend: Animal
weight(unit: WeightUnit = KG): Float
}
union SearchResult
@join__type(graph: LOCAL)
@join__unionMember(graph: LOCAL, member: "Dog")
@join__unionMember(graph: LOCAL, member: "Cat")
@join__unionMember(graph: LOCAL, member: "Robot") =
| Dog
| Cat
| Robot
enum WeightUnit @join__type(graph: LOCAL) {
KG @join__enumValue(graph: LOCAL)
LB @join__enumValue(graph: LOCAL)
G @join__enumValue(graph: LOCAL)
}
"#;
#[test]
fn field_order_when_grouping_with_fragments() {
let plan = plan(
SCHEMA_2,
r#"
query FieldOrder {
animal {
name
id
... on Dog {
id
nickname
}
}
}"#,
);
insta::assert_snapshot!(plan, @r###"
animal: {
conditions: FieldType(OneOf(Animal, Cat, Dog, Pet))
selections:
name: {
}
id: {
type guard: Exact(Cat)
}
id: {
type guard: Exact(Dog)
}
nickname: {
type guard: Exact(Dog)
}
}
"###);
}
#[test]
fn multiple_aliases() {
let plan = plan(
SCHEMA_2,
r#"
query AliasFanout {
pet {
... on Dog {
kg: weight(unit: KG)
lb: weight(unit: LB)
g: weight(unit: G)
}
... on Cat {
kg: weight(unit: KG)
}
}
}"#,
);
insta::assert_snapshot!(plan, @r###"
pet: {
conditions: FieldType(OneOf(Cat, Dog, Pet))
selections:
kg (alias for weight) {
type guard: Exact(Dog)
}
kg (alias for weight) {
type guard: Exact(Cat)
}
lb (alias for weight) {
type guard: Exact(Dog)
}
g (alias for weight) {
type guard: Exact(Dog)
}
}
"###);
}
#[test]
fn nested_redundant_fragments() {
let plan = plan(
SCHEMA_2,
r#"
query M_IdentityConditions {
pet {
... on Pet {
... on Pet {
name
... on Animal { id }
}
}
}
}"#,
);
insta::assert_snapshot!(plan, @"
pet: {
conditions: FieldType(OneOf(Cat, Dog, Pet))
selections:
name: {
}
id: {
type guard: Exact(Cat)
}
id: {
type guard: Exact(Dog)
}
}
");
}
#[test]
fn directive_on_named_fragment_spread_with_field_merged_across_two_fragments() {
let plan = plan(
SCHEMA_2,
r#"
query SpreadDirective($withName: Boolean!, $deep: Boolean!) {
node {
id
...AnimalBits @include(if: $withName)
... on Dog @skip(if: $deep) { nickname }
}
}
fragment AnimalBits on Animal {
name
... on Dog { nickname }
}"#,
);
insta::assert_snapshot!(plan, @"
node: {
conditions: FieldType(OneOf(Animal, Cat, Dog, Node, Owner, Pet, Robot))
selections:
id: {
}
name: {
type guard: Exact(Cat)
conditions: Include(if: $withName)
}
name: {
type guard: Exact(Dog)
conditions: Include(if: $withName)
}
nickname: {
type guard: Exact(Dog)
conditions: (Include(if: $withName) OR Skip(if: $deep))
}
}
");
}
}