Skip to content

Commit

Permalink
Add query planner configuration option generate_query_fragments (#460)
Browse files Browse the repository at this point in the history
Ref: apollographql/federation#2958

Adopting this in router should take into consideration that both
`reuse_query_fragments` and `generate_query_fragments` should not both
be set to true. Since `reuse_` currently defaults to true, it should be
disabled when `generate_` is enabled.

---------

Co-authored-by: apollo-bot2 <apollo-bot2@users.noreply.github.com>
  • Loading branch information
trevor-scheer and apollo-bot2 authored Mar 21, 2024
1 parent e222c0a commit 640dc3a
Show file tree
Hide file tree
Showing 7 changed files with 251 additions and 0 deletions.
1 change: 1 addition & 0 deletions federation-2/router-bridge/src/introspect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ fragment TypeRef on __Type {
}),
graphql_validation: true,
reuse_query_fragments: None,
generate_query_fragments: None,
debug: Default::default(),
},
)
Expand Down
85 changes: 85 additions & 0 deletions federation-2/router-bridge/src/planner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,12 @@ pub struct QueryPlannerConfig {
/// planner's default.
pub reuse_query_fragments: Option<bool>,

/// If enabled, the query planner will extract inline fragments into
/// fragment definitions before sending queries to subgraphs. This can
/// significantly reduce the size of the query sent to subgraphs, but may
/// increase the time it takes to plan the query.
pub generate_query_fragments: Option<bool>,

/// A sub-set of configurations that are meant for debugging or testing. All the configurations in this
/// sub-set are provided without guarantees of stability (they may be dangerous) or continued support (they
/// may be removed without warning).
Expand All @@ -691,6 +697,7 @@ impl Default for QueryPlannerConfig {
}),
graphql_validation: true,
reuse_query_fragments: None,
generate_query_fragments: None,
debug: Default::default(),
}
}
Expand Down Expand Up @@ -764,6 +771,8 @@ mod tests {
const NO_OPERATION: &str = include_str!("testdata/no_operation.graphql");
const QUERY_REUSE_QUERY_FRAGMENTS: &str =
include_str!("testdata/query_reuse_query_fragments.graphql");
const QUERY_GENERATE_QUERY_FRAGMENTS: &str =
include_str!("testdata/query_generate_query_fragments.graphql");

const MULTIPLE_ANONYMOUS_QUERIES: &str =
include_str!("testdata/query_with_multiple_anonymous_operations.graphql");
Expand All @@ -773,6 +782,8 @@ mod tests {
include_str!("testdata/schema_without_review_body.graphql");
const SCHEMA_REUSE_QUERY_FRAGMENTS: &str =
include_str!("testdata/schema_reuse_query_fragments.graphql");
const SCHEMA_GENERATE_QUERY_FRAGMENTS: &str =
include_str!("testdata/schema_generate_query_fragments.graphql");
const CORE_IN_V0_1: &str = include_str!("testdata/core_in_v0.1.graphql");
const UNSUPPORTED_FEATURE: &str = include_str!("testdata/unsupported_feature.graphql");
const UNSUPPORTED_FEATURE_FOR_EXECUTION: &str =
Expand Down Expand Up @@ -970,6 +981,78 @@ mod tests {
insta::assert_snapshot!(serde_json::to_string_pretty(&payload.data).unwrap());
}

#[tokio::test]
async fn generate_query_fragments_defaults_to_false() {
let planner = Planner::<serde_json::Value>::new(
SCHEMA_GENERATE_QUERY_FRAGMENTS.to_string(),
QueryPlannerConfig::default(),
)
.await
.unwrap();

let payload = planner
.plan(
QUERY_GENERATE_QUERY_FRAGMENTS.to_string(),
None,
PlanOptions::default(),
)
.await
.unwrap()
.into_result()
.unwrap();
insta::assert_snapshot!(serde_json::to_string_pretty(&payload.data).unwrap());
}

#[tokio::test]
async fn generate_query_fragments_explicit_false() {
let planner = Planner::<serde_json::Value>::new(
SCHEMA_GENERATE_QUERY_FRAGMENTS.to_string(),
QueryPlannerConfig {
generate_query_fragments: Some(false),
..Default::default()
},
)
.await
.unwrap();

let payload = planner
.plan(
QUERY_GENERATE_QUERY_FRAGMENTS.to_string(),
None,
PlanOptions::default(),
)
.await
.unwrap()
.into_result()
.unwrap();
insta::assert_snapshot!(serde_json::to_string_pretty(&payload.data).unwrap());
}

#[tokio::test]
async fn generate_query_fragments_true() {
let planner = Planner::<serde_json::Value>::new(
SCHEMA_GENERATE_QUERY_FRAGMENTS.to_string(),
QueryPlannerConfig {
generate_query_fragments: Some(true),
..Default::default()
},
)
.await
.unwrap();

let payload = planner
.plan(
QUERY_GENERATE_QUERY_FRAGMENTS.to_string(),
None,
PlanOptions::default(),
)
.await
.unwrap()
.into_result()
.unwrap();
insta::assert_snapshot!(serde_json::to_string_pretty(&payload.data).unwrap());
}

#[tokio::test]
async fn parse_errors_return_the_right_usage_reporting_data() {
let planner =
Expand Down Expand Up @@ -2034,6 +2117,7 @@ feature https://specs.apollo.dev/unsupported-feature/v0.1 is for: SECURITY but i
}),
graphql_validation: true,
reuse_query_fragments: None,
generate_query_fragments: None,
debug: Default::default(),
},
)
Expand Down Expand Up @@ -2113,6 +2197,7 @@ feature https://specs.apollo.dev/unsupported-feature/v0.1 is for: SECURITY but i
}),
graphql_validation: true,
reuse_query_fragments: None,
generate_query_fragments: None,
debug: Default::default(),
},
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
source: router-bridge/src/planner.rs
assertion_line: 1003
expression: "serde_json::to_string_pretty(&payload.data).unwrap()"
---
{
"queryPlan": {
"kind": "QueryPlan",
"node": {
"kind": "Fetch",
"serviceName": "Subgraph1",
"variableUsages": [],
"operation": "{t{__typename ...on A{x y t{__typename ...on A{x y}...on B{z}}}}}",
"operationKind": "query"
}
},
"formattedQueryPlan": "QueryPlan {\n Fetch(service: \"Subgraph1\") {\n {\n t {\n __typename\n ... on A {\n x\n y\n t {\n __typename\n ... on A {\n x\n y\n }\n ... on B {\n z\n }\n }\n }\n }\n }\n },\n}"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
source: router-bridge/src/planner.rs
assertion_line: 1028
expression: "serde_json::to_string_pretty(&payload.data).unwrap()"
---
{
"queryPlan": {
"kind": "QueryPlan",
"node": {
"kind": "Fetch",
"serviceName": "Subgraph1",
"variableUsages": [],
"operation": "{t{__typename ...on A{x y t{__typename ...on A{x y}...on B{z}}}}}",
"operationKind": "query"
}
},
"formattedQueryPlan": "QueryPlan {\n Fetch(service: \"Subgraph1\") {\n {\n t {\n __typename\n ... on A {\n x\n y\n t {\n __typename\n ... on A {\n x\n y\n }\n ... on B {\n z\n }\n }\n }\n }\n }\n },\n}"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
source: router-bridge/src/planner.rs
assertion_line: 1054
expression: "serde_json::to_string_pretty(&payload.data).unwrap()"
---
{
"queryPlan": {
"kind": "QueryPlan",
"node": {
"kind": "Fetch",
"serviceName": "Subgraph1",
"variableUsages": [],
"operation": "{t{__typename ..._generated_onA3_0}}fragment _generated_onA2_0 on A{x y}fragment _generated_onA3_0 on A{x y t{__typename ..._generated_onA2_0 ...on B{z}}}",
"operationKind": "query"
}
},
"formattedQueryPlan": "QueryPlan {\n Fetch(service: \"Subgraph1\") {\n {\n t {\n __typename\n ..._generated_onA3_0\n }\n }\n \n fragment _generated_onA2_0 on A {\n x\n y\n }\n \n fragment _generated_onA3_0 on A {\n x\n y\n t {\n __typename\n ..._generated_onA2_0\n ... on B {\n z\n }\n }\n }\n },\n}"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
query {
t {
... on A {
x
y
t {
... on A {
x
y
}
... on B {
z
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
schema
@link(url: "https://specs.apollo.dev/link/v1.0")
@link(url: "https://specs.apollo.dev/join/v0.4", for: EXECUTION) {
query: Query
}

directive @join__directive(
graphs: [join__Graph!]
name: String!
args: join__DirectiveArguments
) repeatable on SCHEMA | OBJECT | INTERFACE | FIELD_DEFINITION

directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE

directive @join__field(
graph: join__Graph
requires: join__FieldSet
provides: join__FieldSet
type: String
external: Boolean
override: String
usedOverridden: Boolean
overrideLabel: String
) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION

directive @join__graph(name: String!, url: String!) on ENUM_VALUE

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

directive @link(
url: String
as: String
for: link__Purpose
import: [link__Import]
) repeatable on SCHEMA

type A @join__type(graph: SUBGRAPH1) {
x: Int
y: Int
t: T
}

type B @join__type(graph: SUBGRAPH1) {
z: Int
}

scalar join__DirectiveArguments

scalar join__FieldSet

enum join__Graph {
SUBGRAPH1 @join__graph(name: "Subgraph1", url: "")
}

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
}

type Query @join__type(graph: SUBGRAPH1) {
t: T
t2: T
}

union T
@join__type(graph: SUBGRAPH1)
@join__unionMember(graph: SUBGRAPH1, member: "A")
@join__unionMember(graph: SUBGRAPH1, member: "B") =
A
| B

0 comments on commit 640dc3a

Please sign in to comment.