use std::sync::Arc;
use router_bridge::planner::UsageReporting;
use serde::Deserialize;
use serde::Serialize;
pub(crate) use self::fetch::OperationKind;
use super::fetch;
use super::subscription::SubscriptionNode;
use crate::json_ext::Object;
use crate::json_ext::Path;
use crate::json_ext::Value;
use crate::plugins::authorization::CacheKeyMetadata;
use crate::spec::Query;
#[derive(Clone)]
pub(crate) struct QueryKey {
pub(crate) filtered_query: String,
pub(crate) original_query: String,
pub(crate) operation_name: Option<String>,
pub(crate) metadata: CacheKeyMetadata,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct QueryPlan {
pub(crate) usage_reporting: UsageReporting,
pub(crate) root: PlanNode,
pub(crate) formatted_query_plan: Option<String>,
pub(crate) query: Arc<Query>,
}
#[buildstructor::buildstructor]
impl QueryPlan {
#[builder]
pub(crate) fn fake_new(
root: Option<PlanNode>,
usage_reporting: Option<UsageReporting>,
) -> Self {
Self {
usage_reporting: usage_reporting.unwrap_or_else(|| UsageReporting {
stats_report_key: "this is a test report key".to_string(),
referenced_fields_by_type: Default::default(),
}),
root: root.unwrap_or_else(|| PlanNode::Sequence { nodes: Vec::new() }),
formatted_query_plan: Default::default(),
query: Arc::new(Query::empty()),
}
}
}
impl QueryPlan {
pub(crate) fn is_deferred(&self, operation: Option<&str>, variables: &Object) -> bool {
self.root.is_deferred(operation, variables, &self.query)
}
pub(crate) fn is_subscription(&self, operation: Option<&str>) -> bool {
match self.query.operation(operation) {
Some(op) => matches!(op.kind(), OperationKind::Subscription),
None => false,
}
}
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "PascalCase", tag = "kind")]
pub(crate) enum PlanNode {
Sequence {
nodes: Vec<PlanNode>,
},
Parallel {
nodes: Vec<PlanNode>,
},
Fetch(fetch::FetchNode),
Flatten(FlattenNode),
Defer {
primary: Primary,
deferred: Vec<DeferredNode>,
},
Subscription {
primary: SubscriptionNode,
rest: Option<Box<PlanNode>>,
},
#[serde(rename_all = "camelCase")]
Condition {
condition: String,
if_clause: Option<Box<PlanNode>>,
else_clause: Option<Box<PlanNode>>,
},
}
impl PlanNode {
pub(crate) fn contains_mutations(&self) -> bool {
match self {
Self::Sequence { nodes } => nodes.iter().any(|n| n.contains_mutations()),
Self::Parallel { nodes } => nodes.iter().any(|n| n.contains_mutations()),
Self::Fetch(fetch_node) => fetch_node.operation_kind() == &OperationKind::Mutation,
Self::Defer { primary, .. } => primary
.node
.as_ref()
.map(|n| n.contains_mutations())
.unwrap_or(false),
Self::Subscription { .. } => false,
Self::Flatten(_) => false,
Self::Condition {
if_clause,
else_clause,
..
} => {
if let Some(node) = if_clause {
if node.contains_mutations() {
return true;
}
}
if let Some(node) = else_clause {
if node.contains_mutations() {
return true;
}
}
false
}
}
}
pub(crate) fn is_deferred(
&self,
operation: Option<&str>,
variables: &Object,
query: &Query,
) -> bool {
match self {
Self::Sequence { nodes } => nodes
.iter()
.any(|n| n.is_deferred(operation, variables, query)),
Self::Parallel { nodes } => nodes
.iter()
.any(|n| n.is_deferred(operation, variables, query)),
Self::Flatten(node) => node.node.is_deferred(operation, variables, query),
Self::Fetch(..) => false,
Self::Defer { .. } => true,
Self::Subscription { .. } => false,
Self::Condition {
if_clause,
else_clause,
condition,
} => {
if query
.variable_value(operation, condition.as_str(), variables)
.map(|v| *v == Value::Bool(true))
.unwrap_or(true)
{
if let Some(node) = if_clause {
if node.is_deferred(operation, variables, query) {
return true;
}
}
} else if let Some(node) = else_clause {
if node.is_deferred(operation, variables, query) {
return true;
}
}
false
}
}
}
pub(crate) fn subgraph_fetches(&self) -> usize {
match self {
PlanNode::Sequence { nodes } => nodes.iter().map(|n| n.subgraph_fetches()).sum(),
PlanNode::Parallel { nodes } => nodes.iter().map(|n| n.subgraph_fetches()).sum(),
PlanNode::Fetch(_) => 1,
PlanNode::Flatten(node) => node.node.subgraph_fetches(),
PlanNode::Defer { primary, deferred } => {
primary.node.as_ref().map_or(0, |n| n.subgraph_fetches())
+ deferred
.iter()
.map(|n| n.node.as_ref().map_or(0, |n| n.subgraph_fetches()))
.sum::<usize>()
}
PlanNode::Subscription { rest, .. } => {
rest.as_ref().map_or(0, |n| n.subgraph_fetches()) + 1
}
PlanNode::Condition {
if_clause,
else_clause,
..
} => std::cmp::max(
if_clause
.as_ref()
.map(|n| n.subgraph_fetches())
.unwrap_or(0),
else_clause
.as_ref()
.map(|n| n.subgraph_fetches())
.unwrap_or(0),
),
}
}
#[cfg(test)]
pub(crate) fn service_usage<'a>(&'a self) -> Box<dyn Iterator<Item = &'a str> + 'a> {
match self {
Self::Sequence { nodes } | Self::Parallel { nodes } => {
Box::new(nodes.iter().flat_map(|x| x.service_usage()))
}
Self::Fetch(fetch) => Box::new(Some(fetch.service_name()).into_iter()),
Self::Subscription { primary, rest } => match rest {
Some(rest) => Box::new(
rest.service_usage()
.chain(Some(primary.service_name.as_str())),
) as Box<dyn Iterator<Item = &'a str> + 'a>,
None => Box::new(Some(primary.service_name.as_str()).into_iter()),
},
Self::Flatten(flatten) => flatten.node.service_usage(),
Self::Defer { primary, deferred } => primary
.node
.as_ref()
.map(|n| {
Box::new(
n.service_usage().chain(
deferred
.iter()
.flat_map(|d| d.node.iter().flat_map(|node| node.service_usage())),
),
) as Box<dyn Iterator<Item = &'a str> + 'a>
})
.unwrap_or_else(|| {
Box::new(std::iter::empty()) as Box<dyn Iterator<Item = &'a str> + 'a>
}),
Self::Condition {
if_clause,
else_clause,
..
} => match (if_clause, else_clause) {
(None, None) => Box::new(None.into_iter()),
(None, Some(node)) => node.service_usage(),
(Some(node), None) => node.service_usage(),
(Some(if_node), Some(else_node)) => {
Box::new(if_node.service_usage().chain(else_node.service_usage()))
}
},
}
}
pub(crate) fn extract_authorization_metadata(
&mut self,
schema: &apollo_compiler::Schema,
key: &CacheKeyMetadata,
) {
match self {
PlanNode::Fetch(fetch_node) => {
fetch_node.extract_authorization_metadata(schema, key);
}
PlanNode::Sequence { nodes } => {
for node in nodes {
node.extract_authorization_metadata(schema, key);
}
}
PlanNode::Parallel { nodes } => {
for node in nodes {
node.extract_authorization_metadata(schema, key);
}
}
PlanNode::Flatten(flatten) => flatten.node.extract_authorization_metadata(schema, key),
PlanNode::Defer { primary, deferred } => {
if let Some(node) = primary.node.as_mut() {
node.extract_authorization_metadata(schema, key);
}
for deferred_node in deferred {
if let Some(node) = deferred_node.node.take() {
let mut new_node = (*node).clone();
new_node.extract_authorization_metadata(schema, key);
deferred_node.node = Some(Arc::new(new_node));
}
}
}
PlanNode::Subscription { primary: _, rest } => {
if let Some(node) = rest.as_mut() {
node.extract_authorization_metadata(schema, key);
}
}
PlanNode::Condition {
condition: _,
if_clause,
else_clause,
} => {
if let Some(node) = if_clause.as_mut() {
node.extract_authorization_metadata(schema, key);
}
if let Some(node) = else_clause.as_mut() {
node.extract_authorization_metadata(schema, key);
}
}
}
}
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct FlattenNode {
pub(crate) path: Path,
pub(crate) node: Box<PlanNode>,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct Primary {
pub(crate) path: Option<Path>,
pub(crate) subselection: Option<String>,
pub(crate) node: Option<Box<PlanNode>>,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct DeferredNode {
pub(crate) depends: Vec<Depends>,
pub(crate) label: Option<String>,
pub(crate) query_path: Path,
pub(crate) subselection: Option<String>,
pub(crate) node: Option<Arc<PlanNode>>,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct Depends {
pub(crate) id: String,
pub(crate) defer_label: Option<String>,
}