Skip to content

Commit

Permalink
FED-191: [Operation Processing] port operation.optimize (#5207)
Browse files Browse the repository at this point in the history
Co-authored-by: Iryna Shestak <shestak.irina@gmail.com>
  • Loading branch information
duckki and lrlna authored May 29, 2024
1 parent 4a2d32e commit a9b8d0e
Show file tree
Hide file tree
Showing 10 changed files with 1,307 additions and 537 deletions.
2 changes: 1 addition & 1 deletion apollo-federation/src/query_plan/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ fn write_operation(
)?
}
for fragment in operation_document.fragments.values() {
state.new_line()?;
state.write("\n\n")?; // new line without indentation (since `fragment` adds indentation)
state.write(
fragment
.serialize()
Expand Down
8 changes: 5 additions & 3 deletions apollo-federation/src/query_plan/fetch_dependency_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1301,9 +1301,11 @@ impl FetchDependencyGraphNode {
&operation_name,
)?
};
let fragments = fragments
.map(|rebased| rebased.for_subgraph(self.subgraph_name.clone(), subgraph_schema));
operation.optimize(fragments, Default::default());
if let Some(fragments) = fragments
.map(|rebased| rebased.for_subgraph(self.subgraph_name.clone(), subgraph_schema))
{
operation.optimize(fragments)?;
}
let operation_document = operation.try_into()?;

let node = super::PlanNode::Fetch(Box::new(super::FetchNode {
Expand Down
152 changes: 91 additions & 61 deletions apollo-federation/src/query_plan/operation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1049,14 +1049,14 @@ impl Fragment {
}

// PORT NOTE: in JS code this is stored on the fragment
fn fragment_usages(&self) -> HashMap<Name, i32> {
pub(crate) fn fragment_usages(&self) -> HashMap<Name, i32> {
let mut usages = HashMap::new();
self.selection_set.collect_used_fragment_names(&mut usages);
usages
}

// PORT NOTE: in JS code this is stored on the fragment
fn collect_used_fragment_names(&self, aggregator: &mut HashMap<Name, i32>) {
pub(crate) fn collect_used_fragment_names(&self, aggregator: &mut HashMap<Name, i32>) {
self.selection_set.collect_used_fragment_names(aggregator)
}

Expand All @@ -1069,6 +1069,7 @@ mod normalized_field_selection {
use std::collections::HashSet;
use std::sync::Arc;

use apollo_compiler::ast;
use apollo_compiler::executable;
use apollo_compiler::executable::Name;
use apollo_compiler::Node;
Expand Down Expand Up @@ -1296,6 +1297,10 @@ mod normalized_field_selection {
self.alias.clone().unwrap_or_else(|| self.name().clone())
}

fn output_ast_type(&self) -> Result<&ast::Type, FederationError> {
Ok(&self.field_position.get(self.schema.schema())?.ty)
}

pub(crate) fn output_base_type(&self) -> Result<TypeDefinitionPosition, FederationError> {
let definition = self.field_position.get(self.schema.schema())?;
self.schema
Expand Down Expand Up @@ -1856,27 +1861,6 @@ pub(crate) use normalized_inline_fragment_selection::InlineFragmentSelection;

use crate::schema::position::INTROSPECTION_TYPENAME_FIELD_NAME;

// TODO(@goto-bus-stop): merge this with the other Operation impl block.
impl Operation {
pub(crate) fn optimize(
&mut self,
fragments: Option<&NamedFragments>,
min_usages_to_optimize: Option<u32>,
) {
let min_usages_to_optimize = min_usages_to_optimize.unwrap_or(2);
let Some(fragments) = fragments else { return };
if fragments.is_empty() {
return;
}
assert!(
min_usages_to_optimize >= 1,
"Expected 'min_usages_to_optimize' to be at least 1, but got {min_usages_to_optimize}"
);

todo!(); // TODO: port JS `Operation.optimize` from `operations.ts`
}
}

/// A simple MultiMap implementation using IndexMap with Vec<V> as its value type.
/// - Preserves the insertion order of keys and values.
struct MultiIndexMap<K, V>(IndexMap<K, Vec<V>>);
Expand Down Expand Up @@ -1910,7 +1894,7 @@ where

/// the return type of `lazy_map` function's `mapper` closure argument
#[derive(derive_more::From)]
enum SelectionMapperReturn {
pub(crate) enum SelectionMapperReturn {
None,
Selection(Selection),
SelectionList(Vec<Selection>),
Expand Down Expand Up @@ -2348,6 +2332,7 @@ impl SelectionSet {
} else {
// convert to inline fragment
let expanded = InlineFragmentSelection::from_fragment_spread_selection(
selection_set.type_position.clone(), // the parent type of this inline selection
spread_selection,
)?;
destination.push(Selection::InlineFragment(Arc::new(expanded)));
Expand Down Expand Up @@ -2619,8 +2604,9 @@ impl SelectionSet {
// version, but its Rust version doesn't use `lazy_map`.
// PORT_NOTE #2: Taking ownership of `self` in this method was considered. However, calling
// `Arc::make_mut` on the `Arc` fields of `self` didn't seem better than cloning Arc instances.
fn lazy_map(
pub(crate) fn lazy_map(
&self,
named_fragments: &NamedFragments,
mut mapper: impl FnMut(&Selection) -> Result<SelectionMapperReturn, FederationError>,
) -> Result<SelectionSet, FederationError> {
let mut iter = self.selections.values();
Expand Down Expand Up @@ -2669,12 +2655,12 @@ impl SelectionSet {
&self.schema,
&self.type_position,
updated_selections.values().map(|v| v.iter()),
/*named_fragments*/ &Default::default(),
named_fragments,
)
}

pub(crate) fn add_back_typename_in_attachments(&self) -> Result<SelectionSet, FederationError> {
self.lazy_map(|selection| {
self.lazy_map(/*named_fragments*/ &Default::default(), |selection| {
let selection_element = selection.element()?;
let updated = selection
.map_selection_set(|ss| ss.add_back_typename_in_attachments().map(Some))?;
Expand Down Expand Up @@ -2870,7 +2856,7 @@ impl SelectionSet {
Ok(())
}

fn collect_used_fragment_names(&self, aggregator: &mut HashMap<Name, i32>) {
pub(crate) fn collect_used_fragment_names(&self, aggregator: &mut HashMap<Name, i32>) {
self.selections
.iter()
.for_each(|(_, s)| s.collect_used_fragment_names(aggregator));
Expand Down Expand Up @@ -4012,13 +3998,14 @@ impl InlineFragmentSelection {
}

pub(crate) fn from_fragment_spread_selection(
parent_type_position: CompositeTypeDefinitionPosition,
fragment_spread_selection: &Arc<FragmentSpreadSelection>,
) -> Result<InlineFragmentSelection, FederationError> {
let fragment_spread_data = fragment_spread_selection.spread.data();
Ok(InlineFragmentSelection {
inline_fragment: InlineFragment::new(InlineFragmentData {
schema: fragment_spread_data.schema.clone(),
parent_type_position: fragment_spread_data.type_condition_position.clone(),
parent_type_position,
type_condition_position: Some(fragment_spread_data.type_condition_position.clone()),
directives: fragment_spread_data.directives.clone(),
selection_id: SelectionId::new(),
Expand All @@ -4029,6 +4016,26 @@ impl InlineFragmentSelection {
})
}

/// Construct a new InlineFragmentSelection out of a selection set.
/// - The new type condition will be the same as the selection set's type.
pub(crate) fn from_selection_set(
parent_type_position: CompositeTypeDefinitionPosition,
selection_set: SelectionSet,
directives: Arc<executable::DirectiveList>,
) -> Self {
let inline_fragment_data = InlineFragmentData {
schema: selection_set.schema.clone(),
parent_type_position,
type_condition_position: selection_set.type_position.clone().into(),
directives,
selection_id: SelectionId::new(),
};
Self {
inline_fragment: InlineFragment::new(inline_fragment_data),
selection_set,
}
}

pub(crate) fn normalize(
&self,
parent_type: &CompositeTypeDefinitionPosition,
Expand Down Expand Up @@ -4546,11 +4553,24 @@ impl NamedFragments {
self.fragments.values()
}

pub(crate) fn insert(&mut self, fragment: Fragment) {
pub(crate) fn iter_rev(&self) -> impl Iterator<Item = &Node<Fragment>> {
self.fragments.values().rev()
}

pub(crate) fn iter_mut(&mut self) -> indexmap::map::IterMut<'_, Name, Node<Fragment>> {
Arc::make_mut(&mut self.fragments).iter_mut()
}

// Calls `retain` on the underlying `IndexMap`.
pub(crate) fn retain(&mut self, mut predicate: impl FnMut(&Name, &Node<Fragment>) -> bool) {
Arc::make_mut(&mut self.fragments).retain(|name, fragment| predicate(name, fragment));
}

fn insert(&mut self, fragment: Fragment) {
Arc::make_mut(&mut self.fragments).insert(fragment.name.clone(), Node::new(fragment));
}

pub(crate) fn try_insert(&mut self, fragment: Fragment) -> Result<(), FederationError> {
fn try_insert(&mut self, fragment: Fragment) -> Result<(), FederationError> {
match Arc::make_mut(&mut self.fragments).entry(fragment.name.clone()) {
indexmap::map::Entry::Occupied(_) => {
Err(FederationError::internal("Duplicate fragment name"))
Expand Down Expand Up @@ -4597,7 +4617,9 @@ impl NamedFragments {
depends_on: Vec<Name>,
}

let mut fragments_map: HashMap<Name, FragmentDependencies> = HashMap::new();
// Note: We use IndexMap to stabilize the ordering of the result, which influences
// the outcome of `map_to_expanded_selection_sets`.
let mut fragments_map: IndexMap<Name, FragmentDependencies> = IndexMap::new();
for fragment in fragments.values() {
let mut fragment_usages: HashMap<Name, i32> = HashMap::new();
NamedFragments::collect_fragment_usages(&fragment.selection_set, &mut fragment_usages);
Expand Down Expand Up @@ -4744,10 +4766,10 @@ impl NamedFragments {
&fragment.schema,
NormalizeSelectionOption::NormalizeRecursively,
)?;
let mapped_selection_set = mapper(&expanded_selection_set)?;
let optimized_selection_set = mapped_selection_set; // TODO: call SelectionSet::optimize (FED-191)
let mut mapped_selection_set = mapper(&expanded_selection_set)?;
mapped_selection_set.optimize_at_root(&result)?;
let updated = Fragment {
selection_set: optimized_selection_set,
selection_set: mapped_selection_set,
schema: fragment.schema.clone(),
name: fragment.name.clone(),
type_condition_position: fragment.type_condition_position.clone(),
Expand Down Expand Up @@ -4848,9 +4870,9 @@ pub(crate) struct RebasedFragments {
}

impl RebasedFragments {
pub(crate) fn new(fragments: &NamedFragments) -> Self {
pub(crate) fn new(fragments: NamedFragments) -> Self {
Self {
original_fragments: fragments.clone(),
original_fragments: fragments,
rebased_fragments: Arc::new(HashMap::new()),
}
}
Expand Down Expand Up @@ -5067,6 +5089,10 @@ impl Display for Operation {
Ok(operation) => operation,
Err(_) => return Err(std::fmt::Error),
};
for fragment_def in self.named_fragments.iter() {
fragment_def.fmt(f)?;
f.write_str("\n\n")?;
}
operation.serialize().fmt(f)
}
}
Expand Down Expand Up @@ -5320,23 +5346,23 @@ type Foo {
.named_operations
.get_mut("NamedFragmentQuery")
{
let normalized_operation = normalize_operation(
let mut normalized_operation = normalize_operation(
operation,
NamedFragments::new(&executable_document.fragments, &schema),
&schema,
&IndexSet::new(),
)
.unwrap();

let expected = r#"query NamedFragmentQuery {
foo {
id
bar
baz
}
}"#;
let actual = normalized_operation.to_string();
assert_eq!(expected, actual);
normalized_operation.named_fragments = Default::default();
insta::assert_snapshot!(normalized_operation, @r###"
query NamedFragmentQuery {
foo {
id
bar
baz
}
}
"###);
}
}

Expand Down Expand Up @@ -5374,23 +5400,23 @@ type Foo {
let (schema, mut executable_document) =
parse_schema_and_operation(operation_with_named_fragment);
if let Some((_, operation)) = executable_document.named_operations.first_mut() {
let normalized_operation = normalize_operation(
let mut normalized_operation = normalize_operation(
operation,
NamedFragments::new(&executable_document.fragments, &schema),
&schema,
&IndexSet::new(),
)
.unwrap();

let expected = r#"query NestedFragmentQuery {
foo {
id
bar
baz
}
}"#;
let actual = normalized_operation.to_string();
assert_eq!(expected, actual);
normalized_operation.named_fragments = Default::default();
insta::assert_snapshot!(normalized_operation, @r###"
query NestedFragmentQuery {
foo {
id
bar
baz
}
}
"###);
}
}

Expand Down Expand Up @@ -7105,6 +7131,10 @@ type T {
None,
)
.unwrap(), @r###"
fragment frag on HasA {
intfField
}
{
intf {
... on HasA {
Expand Down Expand Up @@ -7232,7 +7262,7 @@ type T {
ss: &SelectionSet,
pred: &impl Fn(&Selection) -> bool,
) -> Result<SelectionSet, FederationError> {
ss.lazy_map(|s| {
ss.lazy_map(/*named_fragments*/ &Default::default(), |s| {
if !pred(s) {
return Ok(SelectionMapperReturn::None);
}
Expand Down Expand Up @@ -7319,7 +7349,7 @@ type T {
ss: &SelectionSet,
pred: &impl Fn(&Selection) -> bool,
) -> Result<SelectionSet, FederationError> {
ss.lazy_map(|s| {
ss.lazy_map(/*named_fragments*/ &Default::default(), |s| {
let to_add_typename = pred(s);
let updated = s.map_selection_set(|ss| add_typename_if(ss, pred).map(Some))?;
if !to_add_typename {
Expand Down
Loading

0 comments on commit a9b8d0e

Please sign in to comment.