use std::collections::{HashMap, VecDeque};
use petgraph::{graph::NodeIndex, visit::EdgeRef};
use crate::query_planner::{
planner::{
fetch::state::MultiTypeFetchStep,
plan_nodes::PlanNode,
query_plan::optimize::{optimize_root_node, optimize_top_level_sequence},
},
state::supergraph_state::SupergraphState,
utils::cancellation::CancellationToken,
};
use super::{
error::QueryPlanError,
fetch::fetch_graph::FetchGraph,
plan_nodes::{ParallelNode, QueryPlan, SequenceNode},
};
mod optimize;
pub struct InDegree<'a> {
state: HashMap<NodeIndex, usize>,
fetch_graph: &'a FetchGraph<MultiTypeFetchStep>,
}
impl<'a> InDegree<'a> {
pub fn new(fetch_graph: &'a FetchGraph<MultiTypeFetchStep>) -> Result<Self, QueryPlanError> {
let mut state: HashMap<NodeIndex, usize> = HashMap::new();
let root_index = fetch_graph.root_index.ok_or(QueryPlanError::NoRoot)?;
fetch_graph.bfs(root_index, |step_index, _| {
state.insert(
*step_index,
fetch_graph
.parents_of(*step_index)
.filter(|edge| edge.source() != root_index)
.count(),
);
false });
Ok(Self { state, fetch_graph })
}
pub fn mark_as_processed(&mut self, index: NodeIndex) {
for edge in self.fetch_graph.children_of(index) {
let child_index = edge.target();
let current = self.state.get(&child_index);
if let Some(in_degree) = current {
if *in_degree == 0 {
panic!("In-degree was 0");
}
self.state.insert(child_index, in_degree - 1);
} else {
panic!("Attempt to decrease an in-degree of a non-existing step");
}
}
}
pub fn is_fulfilled(&self, child_index: NodeIndex) -> bool {
self.state
.get(&child_index)
.expect("In-degree record missing")
== &0
}
}
pub static QUERY_PLAN_KIND: &str = "QueryPlan";
#[tracing::instrument(level = "trace", skip_all)]
pub fn build_query_plan_from_fetch_graph(
fetch_graph: FetchGraph<MultiTypeFetchStep>,
supergraph: &SupergraphState,
cancellation_token: &CancellationToken,
) -> Result<QueryPlan, QueryPlanError> {
let root_index = fetch_graph.root_index.ok_or(QueryPlanError::NoRoot)?;
let mut in_degrees = InDegree::new(&fetch_graph)?;
let mut queue: VecDeque<NodeIndex> = VecDeque::new();
let mut planned_nodes_count = 0;
for edge in fetch_graph.children_of(root_index) {
let child_index = edge.target();
if in_degrees.is_fulfilled(child_index) {
queue.push_back(child_index);
} else {
return Err(QueryPlanError::Internal(format!(
"Root's child ({}) has more than one parent",
child_index.index()
)));
}
}
let mut overall_plan_sequence: Vec<PlanNode> = Vec::new();
while !queue.is_empty() {
let mut current_wave_nodes: Vec<PlanNode> = Vec::new();
let wave_size = queue.len();
for _ in 0..wave_size {
let step_index = queue
.pop_front()
.ok_or(QueryPlanError::Internal(String::from(
"Failed to pop a step from the queue. Queue should not be empty",
)))?;
let step_data = fetch_graph.get_step_data(step_index)?;
current_wave_nodes.push(PlanNode::from_fetch_step(step_data, supergraph));
planned_nodes_count += 1;
in_degrees.mark_as_processed(step_index);
for child_edge in fetch_graph.children_of(step_index) {
cancellation_token.bail_if_cancelled()?;
let child_index = child_edge.target();
if child_index == root_index {
return Err(QueryPlanError::Internal(String::from(
"Visited child step is a root step. It should not happen",
)));
}
if in_degrees.is_fulfilled(child_index) {
queue.push_back(child_index);
}
}
}
if current_wave_nodes.is_empty() {
return Err(QueryPlanError::Internal(String::from(
"Wave was empty. It should not happen as the queue was non-empty.",
)));
} else if current_wave_nodes.len() == 1 {
overall_plan_sequence.push(current_wave_nodes.into_iter().next().ok_or(
QueryPlanError::Internal(String::from("Was was expected to be of length 1")),
)?);
} else {
overall_plan_sequence.push(PlanNode::Parallel(ParallelNode {
nodes: current_wave_nodes,
}));
}
}
let total_fetch_nodes = fetch_graph
.step_indices()
.filter(|&idx| idx != root_index)
.count();
if planned_nodes_count != total_fetch_nodes {
return Err(QueryPlanError::Internal("Cycle detected".to_string()));
}
if overall_plan_sequence.is_empty() {
if total_fetch_nodes == 0 {
return Err(QueryPlanError::EmptyPlan);
} else {
return Err(QueryPlanError::Internal(
"Plan is empty, but graph reported task nodes that were not planned.".to_string(),
));
}
}
let overall_plan_sequence = optimize_top_level_sequence(overall_plan_sequence);
let root_node = match overall_plan_sequence.len() == 1 {
true => overall_plan_sequence.into_iter().next().unwrap(),
false => PlanNode::Sequence(SequenceNode {
nodes: overall_plan_sequence,
}),
};
let root_node = optimize_root_node(root_node, supergraph)?;
Ok(QueryPlan {
kind: QUERY_PLAN_KIND,
node: Some(root_node),
})
}