Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .changeset/root_types_hardcoded.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
hive-router-plan-executor: patch
hive-router-query-planner: patch
hive-router: patch
graphql-tools: patch
node-addon: patch
hive-console-sdk: patch
hive-router-internal: patch
hive-apollo-router-plugin: patch
---

# Support custom GraphQL root type names

Hive Router now reads `query`, `mutation`, and `subscription` root type names from the schema instead of assuming they are named `Query`, `Mutation`, and `Subscription`.
6 changes: 5 additions & 1 deletion bin/differential/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,11 @@ impl<'a> QueryGenerator<'a> {

pub fn generate(mut self) -> QueryCase {
let operation_name = "GeneratedQuery".to_string();
let root = self.schema.query_type_name().to_string();
let root = self
.schema
.query_type_name()
.expect("schema must have a query type")
.to_string();
let selections = self.selection_set_for_type(&root, 0, SelectionContext::Root);

let variables_json = self.render_variables_json();
Expand Down
7 changes: 7 additions & 0 deletions bin/router/benches/router_benches.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ fn authorization_benchmark(c: &mut Criterion) {
let normalized = normalize_operation(supergraph, &parsed, None).unwrap();
let (root_type_name, projection_plan) =
FieldProjectionPlan::from_operation(&normalized.operation, &metadata);
let root_type_name = root_type_name.to_string();
let operation_kind = normalized
.operation
.operation_kind
.clone()
.unwrap_or(OperationKind::Query);
let partitioned_operation = partition_operation(normalized.operation);
let hashes = hash_normalized_operation(
&partitioned_operation.downstream_operation,
Expand All @@ -57,6 +63,7 @@ fn authorization_benchmark(c: &mut Criterion) {

GraphQLNormalizationPayload {
root_type_name,
operation_kind,
projection_plan: Arc::new(projection_plan),
operation_for_plan: Arc::new(partitioned_operation.downstream_operation),
operation_for_introspection: partitioned_operation
Expand Down
2 changes: 1 addition & 1 deletion bin/router/src/pipeline/authorization/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ pub fn apply_authorization_to_operation(
};

let operation_filter_output = OperationFilter::new(schema_metadata).filter(
normalized_payload.root_type_name,
&normalized_payload.root_type_name,
&normalized_payload.operation_for_plan.selection_set,
variable_payload,
|selection| match selection {
Expand Down
6 changes: 6 additions & 0 deletions bin/router/src/pipeline/authorization/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,13 @@ impl SupergraphTestData {
let parsed_query = parse_query(operation).unwrap();
let doc = normalize_operation(&self.supergraph_state, &parsed_query, None).unwrap();
let operation = doc.operation;
let operation_kind = operation
.operation_kind
.clone()
.unwrap_or(OperationKind::Query);
let (root_type_name, projection_plan) =
FieldProjectionPlan::from_operation(&operation, &self.schema_metadata);
let root_type_name = root_type_name.to_string();
let partitioned_operation = partition_operation(operation);
let operation_for_plan = Arc::new(partitioned_operation.downstream_operation);
let operation_for_introspection =
Expand All @@ -90,6 +95,7 @@ impl SupergraphTestData {

let payload = GraphQLNormalizationPayload {
root_type_name,
operation_kind,
projection_plan: Arc::new(projection_plan),
operation_for_plan,
operation_for_plan_hash: hashes.operation_for_plan_hash,
Expand Down
2 changes: 1 addition & 1 deletion bin/router/src/pipeline/demand_control/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ impl DemandControlRuntime {
actual_plans_by_fetch_hash: &mut Option<AHashMap<u64, CompiledSubgraphActualCostPlan>>,
) -> FormulaFetchNode {
let default_list_size = self.default_list_size_for_subgraph(service_name);
let root_type = supergraph_state.root_type_name(operation_kind);
let root_type = supergraph_state.expect_root_type_name(operation_kind);
Comment thread
dotansimha marked this conversation as resolved.
if let Some(actual_plans_by_fetch_hash) = actual_plans_by_fetch_hash {
actual_plans_by_fetch_hash
.entry(operation.hash)
Expand Down
3 changes: 2 additions & 1 deletion bin/router/src/pipeline/execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,8 @@ pub async fn execute_plan<'exec>(
extensions,
client_request: planned_request.client_request_details,
introspection_context: introspection_context.into(),
operation_type_name: planned_request.normalized_payload.root_type_name,
operation_type_name: planned_request.normalized_payload.root_type_name.clone(),
operation_kind: planned_request.normalized_payload.operation_kind.clone(),
jwt_auth_forwarding: jwt_auth_forwarding.map(|j| j.into()),
graphql_error_recorder: app_state.telemetry_context.metrics.graphql.error_recorder(),
demand_control_context: planned_request
Expand Down
5 changes: 2 additions & 3 deletions bin/router/src/pipeline/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -491,10 +491,9 @@ pub async fn execute_planned_request<'exec>(
operation: OperationDetails {
name: normalize_payload.operation_for_plan.name.as_deref(),
kind: match normalize_payload.operation_for_plan.operation_kind {
Some(OperationKind::Query) => "query",
None | Some(OperationKind::Query) => "query",
Some(OperationKind::Mutation) => "mutation",
Some(OperationKind::Subscription) => "subscription",
None => "query",
},
query: graphql_params.get_query()?,
},
Expand Down Expand Up @@ -739,7 +738,7 @@ pub async fn execute_pipeline<'exec>(
&variable_payload,
&query_plan_payload,
normalize_payload.operation_for_plan.as_ref(),
normalize_payload.root_type_name,
normalize_payload.root_type_name.as_str(),
normalize_payload.normalized_operation_hash,
(&normalize_payload.operation_identity).into(),
)
Expand Down
14 changes: 12 additions & 2 deletions bin/router/src/pipeline/normalize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ pub struct GraphQLNormalizationPayload {
pub operation_for_introspection: Option<Arc<OperationDefinition>>,
pub operation_for_introspection_hash: Option<u64>,
pub normalized_operation_hash: u64,
pub root_type_name: &'static str,
pub root_type_name: String,
pub operation_kind: OperationKind,
pub projection_plan: Arc<Vec<FieldProjectionPlan>>,
pub operation_identity: OperationIdentity,
}
Expand Down Expand Up @@ -65,13 +66,16 @@ impl GraphQLNormalizationPayload {
) -> Arc<GraphQLNormalizationPayload> {
let hashes =
hash_normalized_operation(&new_operation, self.operation_for_introspection.as_deref());

Arc::new(GraphQLNormalizationPayload {
operation_for_plan: Arc::new(new_operation),
operation_for_plan_hash: hashes.operation_for_plan_hash,
// These are cheap Arc clones
operation_for_introspection: self.operation_for_introspection.clone(),
operation_for_introspection_hash: hashes.operation_for_introspection_hash,
normalized_operation_hash: hashes.combined_operation_hash,
root_type_name: self.root_type_name,
root_type_name: self.root_type_name.clone(),
operation_kind: self.operation_kind.clone(),
projection_plan: Arc::new(new_projection_plan),
operation_identity: self.operation_identity.clone(),
})
Expand Down Expand Up @@ -166,8 +170,13 @@ pub async fn normalize_request_with_cache(
);

let operation = doc.operation;
let operation_kind = operation
.operation_kind
.clone()
.unwrap_or(OperationKind::Query);
let (root_type_name, projection_plan) =
FieldProjectionPlan::from_operation(&operation, &supergraph.metadata);
let root_type_name = root_type_name.to_string();
let partitioned_operation = partition_operation(operation);

let operation_for_plan = Arc::new(partitioned_operation.downstream_operation);
Expand All @@ -181,6 +190,7 @@ pub async fn normalize_request_with_cache(

let payload = GraphQLNormalizationPayload {
root_type_name,
operation_kind,
projection_plan: Arc::new(projection_plan),
operation_for_plan,
operation_for_plan_hash: hashes.operation_for_plan_hash,
Expand Down
3 changes: 2 additions & 1 deletion lib/executor/src/execution/demand_control/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,8 @@ pub fn compile_actual_subgraph_cost_plan(
supergraph_state: &SupergraphState,
) -> CompiledSubgraphActualCostPlan {
let operation_def = &operation.document.operation;
let root_type_name = supergraph_state.root_type_name(operation_def.operation_kind.as_ref());
let root_type_name =
supergraph_state.expect_root_type_name(operation_def.operation_kind.as_ref());

// Detect if every top-level selection is a `_entities` field (with or without alias).
// This covers FlattenFetch (single `_entities`) and BatchFetch (multiple `_eN: _entities`).
Expand Down
12 changes: 7 additions & 5 deletions lib/executor/src/execution/plan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,8 @@ pub struct QueryPlanExecutionOpts<'exec> {
pub extensions: ExecutionResultExtensions<'exec>,
pub client_request: Arc<ClientRequestDetails<'exec>>,
pub introspection_context: Arc<IntrospectionContext>,
pub operation_type_name: &'static str,
pub operation_type_name: String,
pub operation_kind: OperationKind,
pub executors: Arc<SubgraphExecutorMap>,
pub jwt_auth_forwarding: Option<Arc<JwtAuthForwardingPlan>>,
pub graphql_error_recorder: Option<GraphQLErrorMetricsRecorder>,
Expand Down Expand Up @@ -380,7 +381,8 @@ pub async fn execute_query_plan<'exec>(
path_params: client_path_params.clone(),
}.into(),
introspection_context: opts.introspection_context.clone(),
operation_type_name: opts.operation_type_name,
operation_type_name: opts.operation_type_name.clone(),
operation_kind: opts.operation_kind.clone(),
executors: opts.executors.clone(),
jwt_auth_forwarding: opts.jwt_auth_forwarding.clone(),
initial_errors,
Expand Down Expand Up @@ -434,7 +436,7 @@ async fn execute_query_plan_with_data<'exec>(
) -> Result<PlanExecutionOutput, PlanExecutionError> {
let mut errors = opts.initial_errors;

let dedupe_subgraph_requests = opts.operation_type_name == "Query";
let dedupe_subgraph_requests = opts.operation_kind.is_query();
Comment thread
dotansimha marked this conversation as resolved.

let mut on_end_callbacks = vec![];

Expand Down Expand Up @@ -564,7 +566,7 @@ async fn execute_query_plan_with_data<'exec>(
cache_control::finalize(
&mut exec_ctx.response_headers_aggregator,
// force no-store for mutations and errors (execution or graphql errors)
opts.operation_type_name == "Mutation" || !errors.is_empty(),
opts.operation_kind.is_mutation() || !errors.is_empty(),
// response_storage.len() counts subgraphs that returned bytes, which is
// exactly what we need: a plugin hook can short-circuit with a bytes-less
// SubgraphResponse, but in that case it also produces no cache-control header,
Expand Down Expand Up @@ -641,7 +643,7 @@ async fn execute_query_plan_with_data<'exec>(
&data,
errors,
&opts.extensions,
opts.operation_type_name,
opts.operation_type_name.as_str(),
&opts.projection_plan,
&opts.variable_values.variables_map,
response_size_estimate,
Expand Down
27 changes: 10 additions & 17 deletions lib/executor/src/introspection/resolve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ use hive_router_query_planner::ast::{
selection_set::{FieldSelection, SelectionSet},
value::Value as AstValue,
};
use hive_router_query_planner::state::supergraph_state::OperationKind;
use sonic_rs::JsonValueTrait;

use crate::execution::plan::CoerceVariablesPayload;
Expand Down Expand Up @@ -559,9 +558,13 @@ fn resolve_schema_selections<'exec>(
}
"queryType" => {
let query_type = ctx
.schema
.type_by_name(ctx.schema.query_type_name())
.expect("Query type not found");
.metadata
.query_type_name
.as_ref()
.and_then(|name| ctx.schema.type_by_name(name))
// SAFETY: The query type is guaranteed to exist,
// every schema has a query type.
.expect("invariant violation: query type is guaranteed to exist because every schema must have a query type");
resolve_type_definition(query_type, &inner_field.selections, ctx)
}
"mutationType" => ctx
Expand Down Expand Up @@ -611,19 +614,9 @@ pub fn resolve_introspection<'exec>(
ctx: &'exec IntrospectionContext,
) -> Value<'exec> {
let root_selection_set = &operation_definition.selection_set;

let root_type_name = operation_definition
.operation_kind
.as_ref()
.map(|kind| match kind {
OperationKind::Query => ctx.schema.query_type_name(),
OperationKind::Mutation => ctx.schema.mutation_type_name().unwrap_or("Mutation"),
OperationKind::Subscription => ctx
.schema
.subscription_type_name()
.unwrap_or("Subscription"),
})
.unwrap_or_else(|| ctx.schema.query_type_name());
let root_type_name = ctx
.metadata
.expect_root_type_name(operation_definition.operation_kind.as_ref());

let mut data =
resolve_root_introspection_selections(root_type_name, &root_selection_set.items, ctx);
Expand Down
Loading
Loading