use std::collections::VecDeque;
use std::rc::Rc;
use std::{cmp, fmt::Debug};
use petgraph::{
graph::{EdgeIndex, NodeIndex},
visit::EdgeRef,
};
use crate::query_planner::ast::merge_path::Condition;
use crate::query_planner::planner::walker::pathfinder::NavigationTarget;
use crate::query_planner::{
ast::arguments::ArgumentsMap,
graph::{edge::Edge, edge::EdgeReference, Graph},
planner::tree::query_tree_node::QueryTreeNode,
};
#[derive(Debug, Clone, Default)]
pub struct SelectionAttributes {
pub alias: Option<String>,
pub arguments: Option<ArgumentsMap>,
}
impl PartialEq for SelectionAttributes {
fn eq(&self, other: &Self) -> bool {
self.alias == other.alias && self.arguments == other.arguments
}
}
#[derive(Debug, Clone)]
pub struct PathSegment {
prev: Option<Rc<PathSegment>>,
pub edge_index: EdgeIndex,
tail_node: NodeIndex,
cumulative_cost: u64,
pub requirement_tree: Option<Rc<QueryTreeNode>>,
pub selection_attributes: Option<SelectionAttributes>,
pub condition: Option<Condition>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UnionContext<'graph> {
pub parent_type_name: &'graph str,
pub field_name: &'graph str,
pub graph_id: &'graph str,
pub member_name: &'graph str,
pub possible_members: Vec<&'graph str>,
}
impl<'graph> UnionContext<'graph> {
fn can_resolve_member(&self, member_type_name: &str) -> bool {
self.possible_members.contains(&member_type_name)
}
pub fn eq_field(&self, other: &Self) -> bool {
self.parent_type_name == other.parent_type_name && self.field_name == other.field_name
}
fn narrow_to_member(&self, member_type_name: &'graph str) -> Self {
Self {
parent_type_name: self.parent_type_name,
field_name: self.field_name,
graph_id: self.graph_id,
member_name: member_type_name,
possible_members: vec![member_type_name],
}
}
}
impl PathSegment {
pub fn new_root(edge: &EdgeReference) -> Self {
Self {
prev: None,
edge_index: edge.id(),
tail_node: edge.target(),
cumulative_cost: edge.weight().cost(),
requirement_tree: None,
selection_attributes: None,
condition: None,
}
}
}
#[derive(Clone)]
pub struct OperationPath<'graph> {
pub root_node: NodeIndex,
pub last_segment: Option<Rc<PathSegment>>,
pub cost: u64,
pub union_context: Option<UnionContext<'graph>>,
}
impl Debug for OperationPath<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut out = f.debug_struct("");
let mut out = out.field("cost", &self.cost);
let edges = self.get_edges();
if edges.is_empty() {
out = out.field("empty", &true).field("head", &self.root_node);
} else {
out = out.field(
"egdes",
&edges
.iter()
.map(|i| format!("{:?}", i))
.collect::<Vec<String>>()
.join(" --> "),
);
}
out.finish()
}
}
impl<'graph> OperationPath<'graph> {
pub fn new(root_node_index: NodeIndex, last_segment: Option<Rc<PathSegment>>) -> Self {
Self {
root_node: root_node_index,
cost: last_segment
.as_ref()
.map_or(0, |segment| segment.cumulative_cost),
last_segment,
union_context: None,
}
}
pub fn new_entrypoint(edge: &EdgeReference<'_>) -> Self {
let path_segment = PathSegment::new_root(edge);
let arc_path_segment = Rc::new(path_segment);
OperationPath::new(edge.source(), Some(arc_path_segment))
}
pub fn advance(
&self,
graph: &'graph Graph,
edge_ref: &EdgeReference<'graph>,
requirement: Option<Rc<QueryTreeNode>>,
target: &NavigationTarget,
) -> OperationPath<'graph> {
let prev_cost = self.cost;
let edge_cost = edge_ref.weight().cost();
let new_cost = prev_cost + edge_cost;
let new_segment_data = PathSegment {
prev: self.last_segment.clone(),
tail_node: edge_ref.target(),
edge_index: edge_ref.id(),
cumulative_cost: new_cost,
requirement_tree: requirement,
selection_attributes: match target {
NavigationTarget::Field { field, .. } => Some(SelectionAttributes {
alias: field.alias.clone(),
arguments: field.arguments.clone(),
}),
NavigationTarget::ConcreteType(_, _) => None,
},
condition: match target {
NavigationTarget::Field { field, .. } => (*field).into(),
NavigationTarget::ConcreteType(_, condition) => condition.clone(),
},
};
let new_segment = Rc::new(new_segment_data);
let union_context = match edge_ref.weight() {
Edge::FieldMove(_) => {
let tail = graph.node(edge_ref.target()).ok();
let union_data = tail.and_then(|tail| tail.union_members_data());
let graph_id = tail.and_then(|tail| tail.graph_id());
match (union_data, graph_id) {
(Some(union_data), Some(graph_id)) => Some(UnionContext {
parent_type_name: &union_data.type_name,
field_name: &union_data.field_name,
graph_id,
member_name: &union_data.object_type_name,
possible_members: union_data
.possible_members
.iter()
.map(|member| member.as_str())
.collect(),
}),
_ => None,
}
}
Edge::AbstractMove(member_type_name) => self
.union_context
.as_ref()
.map(|scope| scope.narrow_to_member(member_type_name)),
Edge::EntityMove(_) | Edge::InterfaceObjectTypeMove(_) | Edge::ReentryMove(_) => None,
_ => self.union_context.clone(),
};
OperationPath {
root_node: self.root_node,
cost: new_cost,
last_segment: Some(new_segment),
union_context,
}
}
pub fn can_resolve_union_member(&self, member_type_name: &str) -> bool {
self.union_context
.as_ref()
.is_none_or(|scope| scope.can_resolve_member(member_type_name))
}
pub fn tail(&self) -> NodeIndex {
self.last_segment
.as_ref()
.map_or(self.root_node, |segment| segment.tail_node)
}
pub fn get_segments(&self) -> Vec<Rc<PathSegment>> {
let mut segments: VecDeque<Rc<PathSegment>> = VecDeque::new();
let mut current: Option<Rc<PathSegment>> = self.last_segment.clone();
while let Some(segment) = current {
segments.push_front(segment.clone());
current = segment.prev.clone();
}
segments.into_iter().collect()
}
pub fn get_edges(&self) -> Vec<EdgeIndex> {
let mut edges: VecDeque<EdgeIndex> = VecDeque::new();
let mut current: Option<Rc<PathSegment>> = self.last_segment.clone();
while let Some(segment) = current {
edges.push_front(segment.edge_index);
current = segment.prev.clone();
}
edges.into_iter().collect()
}
pub fn get_requirement_tree(&self) -> Vec<Option<Rc<QueryTreeNode>>> {
let mut requirement_tree_vec: VecDeque<Option<Rc<QueryTreeNode>>> = VecDeque::new();
let mut current: Option<Rc<PathSegment>> = self.last_segment.clone();
while let Some(segment) = current {
requirement_tree_vec.push_front(segment.requirement_tree.clone());
current = segment.prev.clone();
}
requirement_tree_vec.into_iter().collect()
}
pub fn pretty_print(&self, graph: &Graph) -> String {
let edges = self.get_edges();
if edges.is_empty() {
graph.node(self.root_node).unwrap().display_name()
} else {
edges
.iter()
.enumerate()
.map(|(vec_index, edge_index)| graph.pretty_print_edge(*edge_index, vec_index > 0))
.collect::<Vec<String>>()
.join(" ")
}
}
pub fn build_requirement_continuation_path(&self, other: &OperationPath<'graph>) -> Self {
let source_segments: Vec<Rc<PathSegment>> = self.get_segments();
let target_segments: Vec<Rc<PathSegment>> = other.get_segments();
let mut common_index: Option<usize> = None;
let len = cmp::min(source_segments.len(), target_segments.len());
for index in 0..len {
if source_segments[index].edge_index == target_segments[index].edge_index {
common_index = Some(index);
} else {
break;
}
}
let new_root_node: Option<NodeIndex>;
let cost_offset: Option<u64>;
match common_index {
None => {
new_root_node = Some(self.root_node);
cost_offset = Some(0);
}
Some(common_idx) => {
let last_common_segment = &target_segments[common_idx];
new_root_node = Some(last_common_segment.tail_node);
cost_offset = Some(last_common_segment.cumulative_cost);
}
}
let mut previous_new_segment: Option<Rc<PathSegment>> = None;
for original_segment in target_segments
.iter()
.skip(common_index.map(|v| v + 1).unwrap_or(0))
{
let new_cumulative_cost = original_segment.cumulative_cost - cost_offset.unwrap_or(0);
let new_segment_data = PathSegment {
prev: previous_new_segment.take(),
cumulative_cost: new_cumulative_cost,
edge_index: original_segment.edge_index,
requirement_tree: original_segment.requirement_tree.clone(),
tail_node: original_segment.tail_node,
selection_attributes: original_segment.selection_attributes.clone(),
condition: original_segment.condition.clone(),
};
previous_new_segment = Some(Rc::new(new_segment_data));
}
OperationPath::new(new_root_node.unwrap(), previous_new_segment)
.with_union_context(other.union_context.clone())
}
fn with_union_context(mut self, scope: Option<UnionContext<'graph>>) -> Self {
self.union_context = scope;
self
}
}