1use std::fmt::Display;
2use std::fmt::Formatter;
3use std::hash::Hash;
4use std::sync::Arc;
5
6use apollo_compiler::Name;
7use apollo_compiler::Node;
8use apollo_compiler::collections::IndexMap;
9use apollo_compiler::collections::IndexSet;
10use apollo_compiler::schema::NamedType;
11use apollo_compiler::schema::Type;
12use petgraph::Direction;
13use petgraph::graph::DiGraph;
14use petgraph::graph::EdgeIndex;
15use petgraph::graph::EdgeReference;
16use petgraph::graph::NodeIndex;
17use petgraph::visit::EdgeRef;
18
19use crate::error::FederationError;
20use crate::error::SingleFederationError;
21use crate::internal_error;
22use crate::operation::Field;
23use crate::operation::InlineFragment;
24use crate::operation::SelectionSet;
25use crate::schema::ValidFederationSchema;
26use crate::schema::field_set::parse_field_set;
27use crate::schema::position::CompositeTypeDefinitionPosition;
28use crate::schema::position::FieldDefinitionPosition;
29use crate::schema::position::InterfaceFieldDefinitionPosition;
30use crate::schema::position::ObjectFieldArgumentDefinitionPosition;
31use crate::schema::position::ObjectTypeDefinitionPosition;
32use crate::schema::position::OutputTypeDefinitionPosition;
33use crate::schema::position::SchemaRootDefinitionKind;
34use crate::utils::FallibleIterator;
35
36pub mod build_query_graph;
37pub(crate) mod condition_resolver;
38pub(crate) mod graph_path;
39pub mod output;
40pub(crate) mod path_tree;
41
42pub use build_query_graph::build_federated_query_graph;
43
44use crate::query_graph::condition_resolver::ConditionResolution;
45use crate::query_graph::condition_resolver::ConditionResolver;
46use crate::query_graph::graph_path::ExcludedConditions;
47use crate::query_graph::graph_path::ExcludedDestinations;
48use crate::query_graph::graph_path::OpGraphPathContext;
49use crate::query_graph::graph_path::OpGraphPathTrigger;
50use crate::query_graph::graph_path::OpPathElement;
51use crate::query_plan::QueryPlanCost;
52use crate::query_plan::query_planner::EnabledOverrideConditions;
53use crate::query_plan::query_planning_traversal::non_local_selections_estimation;
54
55#[derive(Debug, Clone, PartialEq, Eq, Hash)]
56pub(crate) struct QueryGraphNode {
57 pub(crate) type_: QueryGraphNodeType,
59 pub(crate) source: Arc<str>,
62 pub(crate) has_reachable_cross_subgraph_edges: bool,
64 pub(crate) provide_id: Option<u32>,
72 pub(crate) root_kind: Option<SchemaRootDefinitionKind>,
74}
75
76impl QueryGraphNode {
77 pub(crate) fn is_root_node(&self) -> bool {
78 matches!(self.type_, QueryGraphNodeType::FederatedRootType(_))
79 }
80}
81
82impl Display for QueryGraphNode {
83 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
84 write!(f, "{}({})", self.type_, self.source)?;
85 if let Some(provide_id) = self.provide_id {
86 write!(f, "-{}", provide_id)?;
87 }
88 if self.is_root_node() {
89 write!(f, "*")?;
90 }
91 Ok(())
92 }
93}
94
95#[derive(Debug, Clone, PartialEq, Eq, Hash, derive_more::From, derive_more::IsVariant)]
96pub(crate) enum QueryGraphNodeType {
97 SchemaType(OutputTypeDefinitionPosition),
98 FederatedRootType(SchemaRootDefinitionKind),
99}
100
101impl Display for QueryGraphNodeType {
102 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
103 match self {
104 QueryGraphNodeType::SchemaType(pos) => pos.fmt(f),
105 QueryGraphNodeType::FederatedRootType(root_kind) => {
106 write!(f, "[{root_kind}]")
107 }
108 }
109 }
110}
111
112impl TryFrom<QueryGraphNodeType> for CompositeTypeDefinitionPosition {
113 type Error = FederationError;
114
115 fn try_from(value: QueryGraphNodeType) -> Result<Self, Self::Error> {
116 match value {
117 QueryGraphNodeType::SchemaType(ty) => Ok(ty.try_into()?),
118 QueryGraphNodeType::FederatedRootType(_) => Err(FederationError::internal(format!(
119 r#"Type "{value}" was unexpectedly not a composite type"#
120 ))),
121 }
122 }
123}
124
125impl TryFrom<QueryGraphNodeType> for ObjectTypeDefinitionPosition {
126 type Error = FederationError;
127
128 fn try_from(value: QueryGraphNodeType) -> Result<Self, Self::Error> {
129 match value {
130 QueryGraphNodeType::SchemaType(ty) => Ok(ty.try_into()?),
131 QueryGraphNodeType::FederatedRootType(_) => Err(FederationError::internal(format!(
132 r#"Type "{value}" was unexpectedly not an object type"#
133 ))),
134 }
135 }
136}
137
138#[derive(Debug, PartialEq, Clone)]
141pub struct ContextCondition {
142 context: String,
143 subgraph_name: Arc<str>,
144 selection: String,
147 types_with_context_set: IndexSet<CompositeTypeDefinitionPosition>,
148 argument_name: Name,
151 argument_coordinate: ObjectFieldArgumentDefinitionPosition,
153 argument_type: Node<Type>,
156}
157
158#[derive(Debug, PartialEq, Clone)]
159pub(crate) struct QueryGraphEdge {
160 pub(crate) transition: QueryGraphEdgeTransition,
164 pub(crate) conditions: Option<Arc<SelectionSet>>,
176 pub(crate) override_condition: Option<OverrideCondition>,
182 pub(crate) required_contexts: Vec<ContextCondition>,
185}
186
187impl QueryGraphEdge {
188 pub(crate) fn new(
189 transition: QueryGraphEdgeTransition,
190 conditions: Option<Arc<SelectionSet>>,
191 ) -> Self {
192 Self {
193 transition,
194 conditions,
195 override_condition: None,
196 required_contexts: Vec::new(),
197 }
198 }
199
200 fn satisfies_override_conditions(
201 &self,
202 conditions_to_check: &EnabledOverrideConditions,
203 ) -> bool {
204 if let Some(override_condition) = &self.override_condition {
205 override_condition.check(conditions_to_check)
206 } else {
207 true
208 }
209 }
210}
211
212impl Display for QueryGraphEdge {
213 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
214 if matches!(
215 self.transition,
216 QueryGraphEdgeTransition::SubgraphEnteringTransition
217 ) && self.conditions.is_none()
218 {
219 return Ok(());
220 }
221
222 match (&self.override_condition, &self.conditions) {
223 (Some(override_condition), Some(conditions)) => write!(
224 f,
225 "{}, {} ⊢ {}",
226 conditions, override_condition, self.transition
227 ),
228 (Some(override_condition), None) => {
229 write!(f, "{} ⊢ {}", override_condition, self.transition)
230 }
231 (None, Some(conditions)) => write!(f, "{} ⊢ {}", conditions, self.transition),
232 _ => self.transition.fmt(f),
233 }
234 }
235}
236#[derive(Debug, Clone, PartialEq, Eq, Hash)]
237pub(crate) struct OverrideCondition {
238 pub(crate) label: String,
239 pub(crate) condition: bool,
240}
241
242impl OverrideCondition {
243 pub(crate) fn check(&self, enabled_conditions: &EnabledOverrideConditions) -> bool {
244 self.condition == enabled_conditions.contains(&self.label)
245 }
246}
247
248impl Display for OverrideCondition {
249 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
250 write!(f, "{} = {}", self.label, self.condition)
251 }
252}
253
254#[derive(Debug, Clone, PartialEq, Eq, Hash)]
258pub(crate) enum QueryGraphEdgeTransition {
259 FieldCollection {
261 source: Arc<str>,
263 field_definition_position: FieldDefinitionPosition,
265 is_part_of_provides: bool,
267 },
268 Downcast {
272 source: Arc<str>,
274 from_type_position: CompositeTypeDefinitionPosition,
277 to_type_position: CompositeTypeDefinitionPosition,
279 },
280 KeyResolution,
284 RootTypeResolution {
289 root_kind: SchemaRootDefinitionKind,
291 },
292 SubgraphEnteringTransition,
297 InterfaceObjectFakeDownCast {
304 source: Arc<str>,
306 from_type_position: CompositeTypeDefinitionPosition,
309 to_type_name: Name,
311 },
312}
313
314impl QueryGraphEdgeTransition {
315 pub(crate) fn collect_operation_elements(&self) -> bool {
316 match self {
317 QueryGraphEdgeTransition::FieldCollection { .. } => true,
318 QueryGraphEdgeTransition::Downcast { .. } => true,
319 QueryGraphEdgeTransition::KeyResolution => false,
320 QueryGraphEdgeTransition::RootTypeResolution { .. } => false,
321 QueryGraphEdgeTransition::SubgraphEnteringTransition => false,
322 QueryGraphEdgeTransition::InterfaceObjectFakeDownCast { .. } => true,
323 }
324 }
325}
326
327impl Display for QueryGraphEdgeTransition {
328 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
329 match self {
330 QueryGraphEdgeTransition::FieldCollection {
331 field_definition_position,
332 ..
333 } => {
334 write!(f, "{}", field_definition_position.field_name())
335 }
336 QueryGraphEdgeTransition::Downcast {
337 to_type_position, ..
338 } => {
339 write!(f, "... on {}", to_type_position.type_name())
340 }
341 QueryGraphEdgeTransition::KeyResolution => {
342 write!(f, "key()")
343 }
344 QueryGraphEdgeTransition::RootTypeResolution { root_kind } => {
345 write!(f, "{}()", root_kind)
346 }
347 QueryGraphEdgeTransition::SubgraphEnteringTransition => {
348 write!(f, "∅")
349 }
350 QueryGraphEdgeTransition::InterfaceObjectFakeDownCast { to_type_name, .. } => {
351 write!(f, "... on {}", to_type_name)
352 }
353 }
354 }
355}
356
357#[derive(Debug)]
358pub struct QueryGraph {
359 current_source: Arc<str>,
364 graph: DiGraph<QueryGraphNode, QueryGraphEdge>,
367 sources: IndexMap<Arc<str>, ValidFederationSchema>,
371 subgraphs_by_name: IndexMap<Arc<str>, ValidFederationSchema>,
375 supergraph_schema: Option<ValidFederationSchema>,
377 types_to_nodes_by_source: IndexMap<Arc<str>, IndexMap<NamedType, IndexSet<NodeIndex>>>,
381 root_kinds_to_nodes_by_source:
383 IndexMap<Arc<str>, IndexMap<SchemaRootDefinitionKind, NodeIndex>>,
384 non_trivial_followup_edges: IndexMap<EdgeIndex, Vec<EdgeIndex>>,
407 arguments_to_context_ids_by_source:
414 IndexMap<Arc<str>, IndexMap<ObjectFieldArgumentDefinitionPosition, Name>>,
415 non_local_selection_metadata: non_local_selections_estimation::QueryGraphMetadata,
418}
419
420impl QueryGraph {
421 pub(crate) fn name(&self) -> &Arc<str> {
422 &self.current_source
423 }
424
425 pub(crate) fn graph(&self) -> &DiGraph<QueryGraphNode, QueryGraphEdge> {
426 &self.graph
427 }
428
429 pub(crate) fn supergraph_schema(&self) -> Result<ValidFederationSchema, FederationError> {
430 self.supergraph_schema
431 .clone()
432 .ok_or_else(|| internal_error!("Supergraph schema unexpectedly missing"))
433 }
434
435 pub(crate) fn node_weight(&self, node: NodeIndex) -> Result<&QueryGraphNode, FederationError> {
436 self.graph
437 .node_weight(node)
438 .ok_or_else(|| internal_error!("Node unexpectedly missing"))
439 }
440
441 fn node_weight_mut(&mut self, node: NodeIndex) -> Result<&mut QueryGraphNode, FederationError> {
442 self.graph
443 .node_weight_mut(node)
444 .ok_or_else(|| internal_error!("Node unexpectedly missing"))
445 }
446
447 pub(crate) fn edge_weight(&self, edge: EdgeIndex) -> Result<&QueryGraphEdge, FederationError> {
448 self.graph
449 .edge_weight(edge)
450 .ok_or_else(|| internal_error!("Edge unexpectedly missing"))
451 }
452
453 fn edge_weight_mut(&mut self, edge: EdgeIndex) -> Result<&mut QueryGraphEdge, FederationError> {
454 self.graph
455 .edge_weight_mut(edge)
456 .ok_or_else(|| internal_error!("Edge unexpectedly missing"))
457 }
458
459 pub(crate) fn edge_head_weight(
460 &self,
461 edge: EdgeIndex,
462 ) -> Result<&QueryGraphNode, FederationError> {
463 let (head_id, _) = self.edge_endpoints(edge)?;
464 self.node_weight(head_id)
465 }
466
467 pub(crate) fn edge_endpoints(
468 &self,
469 edge: EdgeIndex,
470 ) -> Result<(NodeIndex, NodeIndex), FederationError> {
471 self.graph
472 .edge_endpoints(edge)
473 .ok_or_else(|| internal_error!("Edge unexpectedly missing"))
474 }
475
476 fn schema(&self) -> Result<&ValidFederationSchema, FederationError> {
477 self.schema_by_source(&self.current_source)
478 }
479
480 pub(crate) fn schema_by_source(
481 &self,
482 source: &str,
483 ) -> Result<&ValidFederationSchema, FederationError> {
484 self.sources
485 .get(source)
486 .ok_or_else(|| internal_error!(r#"Schema for "{source}" unexpectedly missing"#))
487 }
488
489 pub(crate) fn subgraph_schemas(&self) -> &IndexMap<Arc<str>, ValidFederationSchema> {
490 &self.subgraphs_by_name
491 }
492
493 pub(crate) fn subgraphs(&self) -> impl Iterator<Item = (&Arc<str>, &ValidFederationSchema)> {
494 self.subgraphs_by_name.iter()
495 }
496
497 pub(crate) fn nodes_for_type(
499 &self,
500 name: &Name,
501 ) -> Result<&IndexSet<NodeIndex>, FederationError> {
502 self.types_to_nodes()?
503 .get(name)
504 .ok_or_else(|| internal_error!("No nodes unexpectedly found for type"))
505 }
506
507 pub(crate) fn types_to_nodes(
508 &self,
509 ) -> Result<&IndexMap<NamedType, IndexSet<NodeIndex>>, FederationError> {
510 self.types_to_nodes_by_source(&self.current_source)
511 }
512
513 pub(super) fn types_to_nodes_by_source(
514 &self,
515 source: &str,
516 ) -> Result<&IndexMap<NamedType, IndexSet<NodeIndex>>, FederationError> {
517 self.types_to_nodes_by_source.get(source).ok_or_else(|| {
518 SingleFederationError::Internal {
519 message: "Types-to-nodes map unexpectedly missing".to_owned(),
520 }
521 .into()
522 })
523 }
524
525 fn types_to_nodes_mut(
526 &mut self,
527 ) -> Result<&mut IndexMap<NamedType, IndexSet<NodeIndex>>, FederationError> {
528 self.types_to_nodes_by_source
529 .get_mut(&self.current_source)
530 .ok_or_else(|| {
531 SingleFederationError::Internal {
532 message: "Types-to-nodes map unexpectedly missing".to_owned(),
533 }
534 .into()
535 })
536 }
537
538 pub(crate) fn root_kinds_to_nodes(
539 &self,
540 ) -> Result<&IndexMap<SchemaRootDefinitionKind, NodeIndex>, FederationError> {
541 self.root_kinds_to_nodes_by_source
542 .get(&self.current_source)
543 .ok_or_else(|| {
544 SingleFederationError::Internal {
545 message: "Root-kinds-to-nodes map unexpectedly missing".to_owned(),
546 }
547 .into()
548 })
549 }
550
551 pub(crate) fn root_kinds_to_nodes_by_source(
552 &self,
553 source: &str,
554 ) -> Result<&IndexMap<SchemaRootDefinitionKind, NodeIndex>, FederationError> {
555 self.root_kinds_to_nodes_by_source
556 .get(source)
557 .ok_or_else(|| {
558 SingleFederationError::Internal {
559 message: "Root-kinds-to-nodes map unexpectedly missing".to_owned(),
560 }
561 .into()
562 })
563 }
564
565 fn root_kinds_to_nodes_mut(
566 &mut self,
567 ) -> Result<&mut IndexMap<SchemaRootDefinitionKind, NodeIndex>, FederationError> {
568 self.root_kinds_to_nodes_by_source
569 .get_mut(&self.current_source)
570 .ok_or_else(|| {
571 SingleFederationError::Internal {
572 message: "Root-kinds-to-nodes map unexpectedly missing".to_owned(),
573 }
574 .into()
575 })
576 }
577
578 pub(crate) fn context_id_by_source_and_argument(
579 &self,
580 source: &str,
581 argument: &ObjectFieldArgumentDefinitionPosition,
582 ) -> Result<&Name, FederationError> {
583 self.arguments_to_context_ids_by_source
584 .get(source)
585 .and_then(|r| r.get(argument))
586 .ok_or_else(|| {
587 internal_error!("context ID unexpectedly missing for @fromContext argument")
588 })
589 }
590
591 pub(crate) fn is_context_used(&self) -> bool {
592 !self.arguments_to_context_ids_by_source.is_empty()
593 }
594
595 pub(crate) fn non_local_selection_metadata(
596 &self,
597 ) -> &non_local_selections_estimation::QueryGraphMetadata {
598 &self.non_local_selection_metadata
599 }
600
601 pub(crate) fn out_edges_with_federation_self_edges(
605 &self,
606 node: NodeIndex,
607 ) -> Vec<EdgeReference<QueryGraphEdge>> {
608 Self::sorted_edges(self.graph.edges_directed(node, Direction::Outgoing))
609 }
610
611 pub(crate) fn out_edges(&self, node: NodeIndex) -> Vec<EdgeReference<QueryGraphEdge>> {
614 Self::sorted_edges(self.graph.edges_directed(node, Direction::Outgoing).filter(
615 |edge_ref| {
616 !(edge_ref.source() == edge_ref.target()
617 && matches!(
618 edge_ref.weight().transition,
619 QueryGraphEdgeTransition::KeyResolution
620 | QueryGraphEdgeTransition::RootTypeResolution { .. }
621 ))
622 },
623 ))
624 }
625
626 fn sorted_edges<'graph>(
638 edges: impl Iterator<Item = EdgeReference<'graph, QueryGraphEdge>>,
639 ) -> Vec<EdgeReference<'graph, QueryGraphEdge>> {
640 let mut edges: Vec<_> = edges.collect();
641 edges.sort_by_key(|e| -> EdgeIndex { e.id() });
642 edges
643 }
644
645 pub(crate) fn is_self_key_or_root_edge(
646 &self,
647 edge: EdgeIndex,
648 ) -> Result<bool, FederationError> {
649 let edge_weight = self.edge_weight(edge)?;
650 let (head, tail) = self.edge_endpoints(edge)?;
651 let head_weight = self.node_weight(head)?;
652 let tail_weight = self.node_weight(tail)?;
653 Ok(head_weight.source == tail_weight.source
654 && matches!(
655 edge_weight.transition,
656 QueryGraphEdgeTransition::KeyResolution
657 | QueryGraphEdgeTransition::RootTypeResolution { .. }
658 ))
659 }
660
661 pub(crate) fn has_satisfiable_direct_key_edge(
663 &self,
664 from_node: NodeIndex,
665 to_subgraph: &str,
666 condition_resolver: &mut impl ConditionResolver,
667 max_cost: QueryPlanCost,
668 ) -> Result<bool, FederationError> {
669 for edge_ref in self.out_edges(from_node) {
670 let edge_weight = edge_ref.weight();
671 if !matches!(
672 edge_weight.transition,
673 QueryGraphEdgeTransition::KeyResolution
674 ) {
675 continue;
676 }
677
678 let tail = edge_ref.target();
679 let tail_weight = self.node_weight(tail)?;
680 if tail_weight.source.as_ref() != to_subgraph {
681 continue;
682 }
683
684 let condition_resolution = condition_resolver.resolve(
685 edge_ref.id(),
686 &OpGraphPathContext::default(),
687 &ExcludedDestinations::default(),
688 &ExcludedConditions::default(),
689 None,
690 )?;
691 let ConditionResolution::Satisfied { cost, .. } = condition_resolution else {
692 continue;
693 };
694
695 if cost <= max_cost {
697 return Ok(true);
698 }
699 }
700 Ok(false)
701 }
702
703 pub(crate) fn locally_satisfiable_key(
704 &self,
705 edge_index: EdgeIndex,
706 ) -> Result<Option<SelectionSet>, FederationError> {
707 let edge_head = self.edge_head_weight(edge_index)?;
708 let QueryGraphNodeType::SchemaType(type_position) = &edge_head.type_ else {
709 return Err(FederationError::internal(
710 "Unable to compute locally_satisfiable_key. Edge head was unexpectedly pointing to a federated root type",
711 ));
712 };
713 let Some(subgraph_schema) = self.sources.get(&edge_head.source) else {
714 return Err(FederationError::internal(format!(
715 "Could not find subgraph source {}",
716 edge_head.source
717 )));
718 };
719 let Some(metadata) = subgraph_schema.subgraph_metadata() else {
720 return Err(FederationError::internal(format!(
721 "Could not find federation metadata for source {}",
722 edge_head.source
723 )));
724 };
725 let key_directive_definition = metadata
726 .federation_spec_definition()
727 .key_directive_definition(subgraph_schema)?;
728 let external_metadata = metadata.external_metadata();
729 let composite_type_position: CompositeTypeDefinitionPosition =
730 type_position.clone().try_into()?;
731 let type_ = composite_type_position.get(subgraph_schema.schema())?;
732 type_
733 .directives()
734 .get_all(&key_directive_definition.name)
735 .map(|key| {
736 metadata
737 .federation_spec_definition()
738 .key_directive_arguments(key)
739 })
740 .and_then(|key_value| {
741 parse_field_set(
742 subgraph_schema,
743 composite_type_position.type_name().clone(),
744 key_value.fields,
745 )
746 })
747 .find_ok(|selection| !external_metadata.selects_any_external_field(selection))
748 }
749
750 pub(crate) fn edge_for_field(
751 &self,
752 node: NodeIndex,
753 field: &Field,
754 override_conditions: &EnabledOverrideConditions,
755 ) -> Option<EdgeIndex> {
756 let mut candidates = self.out_edges(node).into_iter().filter_map(|edge_ref| {
757 let edge_weight = edge_ref.weight();
758 let QueryGraphEdgeTransition::FieldCollection {
759 field_definition_position,
760 ..
761 } = &edge_weight.transition
762 else {
763 return None;
764 };
765
766 if !edge_weight.satisfies_override_conditions(override_conditions) {
767 return None;
768 }
769
770 if field.field_position.field_name() == field_definition_position.field_name() {
773 Some(edge_ref.id())
774 } else {
775 None
776 }
777 });
778 if let Some(candidate) = candidates.next() {
779 debug_assert!(
783 candidates.next().is_none(),
784 "Unexpectedly found multiple candidates",
785 );
786 Some(candidate)
787 } else {
788 None
789 }
790 }
791
792 pub(crate) fn edge_for_inline_fragment(
793 &self,
794 node: NodeIndex,
795 inline_fragment: &InlineFragment,
796 ) -> Option<EdgeIndex> {
797 let Some(type_condition_pos) = &inline_fragment.type_condition_position else {
798 return None;
800 };
801 let mut candidates = self.out_edges(node).into_iter().filter_map(|edge_ref| {
802 let edge_weight = edge_ref.weight();
803 let QueryGraphEdgeTransition::Downcast {
804 to_type_position, ..
805 } = &edge_weight.transition
806 else {
807 return None;
808 };
809 if type_condition_pos.type_name() == to_type_position.type_name() {
813 Some(edge_ref.id())
814 } else {
815 None
816 }
817 });
818 if let Some(candidate) = candidates.next() {
819 debug_assert!(
823 candidates.next().is_none(),
824 "Unexpectedly found multiple candidates",
825 );
826 Some(candidate)
827 } else {
828 None
829 }
830 }
831
832 pub(crate) fn edge_for_op_graph_path_trigger(
833 &self,
834 node: NodeIndex,
835 op_graph_path_trigger: &OpGraphPathTrigger,
836 override_conditions: &EnabledOverrideConditions,
837 ) -> Option<Option<EdgeIndex>> {
838 let OpGraphPathTrigger::OpPathElement(op_path_element) = op_graph_path_trigger else {
839 return None;
840 };
841 match op_path_element {
842 OpPathElement::Field(field) => self
843 .edge_for_field(node, field, override_conditions)
844 .map(Some),
845 OpPathElement::InlineFragment(inline_fragment) => {
846 if inline_fragment.type_condition_position.is_some() {
847 self.edge_for_inline_fragment(node, inline_fragment)
848 .map(Some)
849 } else {
850 Some(None)
851 }
852 }
853 }
854 }
855
856 pub(crate) fn advance_possible_runtime_types(
860 &self,
861 possible_runtime_types: &IndexSet<ObjectTypeDefinitionPosition>,
862 edge: Option<EdgeIndex>,
863 ) -> Result<IndexSet<ObjectTypeDefinitionPosition>, FederationError> {
864 let Some(edge) = edge else {
865 return Ok(possible_runtime_types.clone());
866 };
867
868 let edge_weight = self.edge_weight(edge)?;
869 let (_, tail) = self.edge_endpoints(edge)?;
870 let tail_weight = self.node_weight(tail)?;
871 let QueryGraphNodeType::SchemaType(tail_type_pos) = &tail_weight.type_ else {
872 return Err(FederationError::internal(
873 "Unexpectedly encountered federation root node as tail node.",
874 ));
875 };
876 match &edge_weight.transition {
877 QueryGraphEdgeTransition::FieldCollection {
878 source,
879 field_definition_position,
880 ..
881 } => {
882 let Ok(_): Result<CompositeTypeDefinitionPosition, _> =
883 tail_type_pos.clone().try_into()
884 else {
885 return Ok(IndexSet::default());
886 };
887 let schema = self.schema_by_source(source)?;
888 let mut new_possible_runtime_types = IndexSet::default();
889 for possible_runtime_type in possible_runtime_types {
890 let field_pos =
891 possible_runtime_type.field(field_definition_position.field_name().clone());
892 let Some(field) = field_pos.try_get(schema.schema()) else {
893 continue;
894 };
895 let field_type_pos: CompositeTypeDefinitionPosition = schema
896 .get_type(field.ty.inner_named_type().clone())?
897 .try_into()?;
898 new_possible_runtime_types
899 .extend(schema.possible_runtime_types(field_type_pos)?);
900 }
901 Ok(new_possible_runtime_types)
902 }
903 QueryGraphEdgeTransition::Downcast {
904 source,
905 to_type_position,
906 ..
907 } => Ok(self
908 .schema_by_source(source)?
909 .possible_runtime_types(to_type_position.clone())?
910 .intersection(possible_runtime_types)
911 .cloned()
912 .collect()),
913 QueryGraphEdgeTransition::KeyResolution => {
914 let tail_type_pos: CompositeTypeDefinitionPosition =
915 tail_type_pos.clone().try_into()?;
916 Ok(self
917 .schema_by_source(&tail_weight.source)?
918 .possible_runtime_types(tail_type_pos)?)
919 }
920 QueryGraphEdgeTransition::RootTypeResolution { .. } => {
921 let OutputTypeDefinitionPosition::Object(tail_type_pos) = tail_type_pos.clone()
922 else {
923 return Err(FederationError::internal(
924 "Unexpectedly encountered non-object root operation type.",
925 ));
926 };
927 Ok(IndexSet::from_iter([tail_type_pos]))
928 }
929 QueryGraphEdgeTransition::SubgraphEnteringTransition => {
930 let OutputTypeDefinitionPosition::Object(tail_type_pos) = tail_type_pos.clone()
931 else {
932 return Err(FederationError::internal(
933 "Unexpectedly encountered non-object root operation type.",
934 ));
935 };
936 Ok(IndexSet::from_iter([tail_type_pos]))
937 }
938 QueryGraphEdgeTransition::InterfaceObjectFakeDownCast { .. } => {
939 Ok(possible_runtime_types.clone())
940 }
941 }
942 }
943
944 pub(crate) fn get_locally_satisfiable_key(
948 &self,
949 node_index: NodeIndex,
950 ) -> Result<Option<SelectionSet>, FederationError> {
951 let node = self.node_weight(node_index)?;
952 let type_name = match &node.type_ {
953 QueryGraphNodeType::SchemaType(ty) => {
954 CompositeTypeDefinitionPosition::try_from(ty.clone())?
955 }
956 QueryGraphNodeType::FederatedRootType(_) => {
957 return Err(FederationError::internal(format!(
958 "get_locally_satisfiable_key must be called on a composite type, got {}",
959 node.type_
960 )));
961 }
962 };
963 let schema = self.schema_by_source(&node.source)?;
964 let Some(metadata) = schema.subgraph_metadata() else {
965 return Err(FederationError::internal(format!(
966 "Could not find subgraph metadata for source {}",
967 node.source
968 )));
969 };
970 let key_directive_definition = metadata
971 .federation_spec_definition()
972 .key_directive_definition(schema)?;
973
974 let ty = type_name.get(schema.schema())?;
975
976 ty.directives()
977 .get_all(&key_directive_definition.name)
978 .filter_map(|key| {
979 key.specified_argument_by_name("fields")
980 .and_then(|arg| arg.as_str())
981 })
982 .map(|value| parse_field_set(schema, ty.name().clone(), value))
983 .find_ok(|selection| {
984 !metadata
985 .external_metadata()
986 .selects_any_external_field(selection)
987 })
988 }
989
990 pub(crate) fn is_cross_subgraph_edge(&self, edge: EdgeIndex) -> Result<bool, FederationError> {
991 let (head, tail) = self.edge_endpoints(edge)?;
992 let head_weight = self.node_weight(head)?;
993 let tail_weight = self.node_weight(tail)?;
994 Ok(head_weight.source != tail_weight.source)
995 }
996
997 pub(crate) fn is_provides_edge(&self, edge: EdgeIndex) -> Result<bool, FederationError> {
998 let edge_weight = self.edge_weight(edge)?;
999 let QueryGraphEdgeTransition::FieldCollection {
1000 is_part_of_provides,
1001 ..
1002 } = &edge_weight.transition
1003 else {
1004 return Ok(false);
1005 };
1006 Ok(*is_part_of_provides)
1007 }
1008
1009 pub(crate) fn has_an_implementation_with_provides(
1010 &self,
1011 source: &Arc<str>,
1012 interface_field_definition_position: InterfaceFieldDefinitionPosition,
1013 ) -> Result<bool, FederationError> {
1014 let schema = self.schema_by_source(source)?;
1015 let Some(metadata) = schema.subgraph_metadata() else {
1016 return Err(FederationError::internal(format!(
1017 "Interface should have come from a federation subgraph {}",
1018 source
1019 )));
1020 };
1021
1022 let provides_directive_definition = metadata
1023 .federation_spec_definition()
1024 .provides_directive_definition(schema)?;
1025
1026 Ok(schema
1027 .possible_runtime_types(interface_field_definition_position.parent().into())?
1028 .into_iter()
1029 .map(|object_type_definition_position| {
1030 let field_pos = object_type_definition_position
1031 .field(interface_field_definition_position.field_name.clone());
1032 field_pos.get(schema.schema())
1033 })
1034 .ok_and_any(|field| field.directives.has(&provides_directive_definition.name))?)
1035 }
1036}