Skip to main content

reifydb_core/flow/
graph.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use std::{
5	cmp::Reverse,
6	collections::{BTreeMap, BinaryHeap, HashSet},
7};
8
9use crate::interface::catalog::flow::{FlowEdge, FlowEdgeId, OperatorId};
10
11#[derive(Debug, Clone, PartialEq)]
12pub struct DirectedGraph<NodeData> {
13	nodes: BTreeMap<OperatorId, NodeData>,
14	edges: Vec<FlowEdge>,
15	outgoing: BTreeMap<OperatorId, Vec<OperatorId>>,
16	incoming: BTreeMap<OperatorId, Vec<OperatorId>>,
17}
18
19impl<NodeData> DirectedGraph<NodeData> {
20	pub fn new() -> Self {
21		Self {
22			nodes: BTreeMap::new(),
23			edges: Vec::new(),
24			outgoing: BTreeMap::new(),
25			incoming: BTreeMap::new(),
26		}
27	}
28
29	pub fn add_node(&mut self, node_id: OperatorId, data: NodeData) -> OperatorId {
30		self.nodes.insert(node_id, data);
31		self.outgoing.entry(node_id).or_default();
32		self.incoming.entry(node_id).or_default();
33		node_id
34	}
35
36	pub fn add_edge(&mut self, edge: FlowEdge) -> FlowEdgeId {
37		let source = edge.source;
38		let target = edge.target;
39
40		let result = edge.id;
41
42		if !self.nodes.contains_key(&source) {
43			panic!("Source operator {:?} does not exist", source);
44		}
45		if !self.nodes.contains_key(&target) {
46			panic!("Target operator {:?} does not exist", target);
47		}
48
49		if self.creates_cycle(&source, &target) {
50			panic!("Adding edge would create a cycle");
51		}
52
53		self.edges.push(edge);
54
55		self.outgoing.entry(source).or_default().push(target);
56
57		self.incoming.entry(target).or_default().push(source);
58
59		result
60	}
61
62	pub fn get_node(&self, node_id: &OperatorId) -> Option<&NodeData> {
63		self.nodes.get(node_id)
64	}
65
66	pub fn get_node_mut(&mut self, node_id: &OperatorId) -> Option<&mut NodeData> {
67		self.nodes.get_mut(node_id)
68	}
69
70	pub fn node_count(&self) -> usize {
71		self.nodes.len()
72	}
73
74	pub fn edge_count(&self) -> usize {
75		self.edges.len()
76	}
77
78	pub fn neighbors(&self, node_id: &OperatorId) -> Vec<OperatorId> {
79		self.outgoing.get(node_id).cloned().unwrap_or_default()
80	}
81
82	pub fn predecessors(&self, node_id: &OperatorId) -> Vec<OperatorId> {
83		self.incoming.get(node_id).cloned().unwrap_or_default()
84	}
85
86	pub fn topological_sort(&self) -> Vec<OperatorId> {
87		let mut in_degree = BTreeMap::new();
88
89		let mut heap: BinaryHeap<Reverse<OperatorId>> = BinaryHeap::new();
90		let mut result = Vec::new();
91
92		for node_id in self.nodes.keys() {
93			in_degree.insert(*node_id, 0);
94		}
95
96		for edge in &self.edges {
97			*in_degree.get_mut(&edge.target).unwrap() += 1;
98		}
99
100		for (node_id, &degree) in &in_degree {
101			if degree == 0 {
102				heap.push(Reverse(*node_id));
103			}
104		}
105
106		while let Some(Reverse(node_id)) = heap.pop() {
107			result.push(node_id);
108
109			if let Some(neighbors) = self.outgoing.get(&node_id) {
110				for neighbor in neighbors {
111					let degree = in_degree.get_mut(neighbor).unwrap();
112					*degree -= 1;
113					if *degree == 0 {
114						heap.push(Reverse(*neighbor));
115					}
116				}
117			}
118		}
119
120		if result.len() != self.nodes.len() {
121			panic!("Graph contains cycles");
122		}
123
124		result
125	}
126
127	pub fn dfs_from(&self, start: &OperatorId) -> Vec<OperatorId> {
128		let mut visited = HashSet::new();
129		let mut result = Vec::new();
130		let mut stack = vec![*start];
131
132		while let Some(node_id) = stack.pop() {
133			if visited.insert(node_id) {
134				result.push(node_id);
135
136				if let Some(neighbors) = self.outgoing.get(&node_id) {
137					for neighbor in neighbors.iter().rev() {
138						if !visited.contains(neighbor) {
139							stack.push(*neighbor);
140						}
141					}
142				}
143			}
144		}
145
146		result
147	}
148
149	fn creates_cycle(&self, source: &OperatorId, target: &OperatorId) -> bool {
150		let reachable = self.dfs_from(target);
151		reachable.contains(source)
152	}
153
154	pub fn nodes(&self) -> impl Iterator<Item = (&OperatorId, &NodeData)> {
155		self.nodes.iter()
156	}
157
158	pub fn edges(&self) -> impl Iterator<Item = &FlowEdge> {
159		self.edges.iter()
160	}
161
162	pub fn is_empty(&self) -> bool {
163		self.nodes.is_empty()
164	}
165
166	pub fn clear(&mut self) {
167		self.nodes.clear();
168		self.edges.clear();
169		self.outgoing.clear();
170		self.incoming.clear();
171	}
172}
173
174impl<NodeData> Default for DirectedGraph<NodeData> {
175	fn default() -> Self {
176		Self::new()
177	}
178}
179
180#[cfg(test)]
181pub mod tests {
182	use super::*;
183	use crate::interface::catalog::flow::FlowId;
184
185	const FLOW: FlowId = FlowId(1);
186
187	#[test]
188	fn test_basic_graph_operations() {
189		let mut graph = DirectedGraph::new();
190
191		let node1 = graph.add_node(OperatorId(1), "Node 1");
192		let node2 = graph.add_node(OperatorId(2), "Node 2");
193		let node3 = graph.add_node(OperatorId(3), "Node 3");
194
195		assert_eq!(graph.node_count(), 3);
196		assert_eq!(graph.edge_count(), 0);
197
198		graph.add_edge(FlowEdge::new(1, FLOW, node1, node2));
199		graph.add_edge(FlowEdge::new(2, FLOW, node2, node3));
200
201		assert_eq!(graph.edge_count(), 2);
202		assert_eq!(graph.neighbors(&node1), vec![OperatorId(2)]);
203		assert_eq!(graph.neighbors(&node2), vec![OperatorId(3)]);
204		assert_eq!(graph.predecessors(&node3), vec![OperatorId(2)]);
205	}
206
207	#[test]
208	#[should_panic(expected = "Adding edge would create a cycle")]
209	fn test_cycle_detection() {
210		let mut graph = DirectedGraph::new();
211
212		let node1 = graph.add_node(OperatorId(1), "Node 1");
213		let node2 = graph.add_node(OperatorId(2), "Node 2");
214		let node3 = graph.add_node(OperatorId(3), "Node 3");
215
216		graph.add_edge(FlowEdge::new(1, FLOW, node1, node2));
217		graph.add_edge(FlowEdge::new(2, FLOW, node2, node3));
218
219		graph.add_edge(FlowEdge::new(3, FLOW, node3, node1));
220	}
221
222	#[test]
223	fn test_topological_sort() {
224		let mut graph = DirectedGraph::new();
225
226		let node1 = graph.add_node(OperatorId(1), "Node 1");
227		let node2 = graph.add_node(OperatorId(2), "Node 2");
228		let node3 = graph.add_node(OperatorId(3), "Node 3");
229
230		graph.add_edge(FlowEdge::new(1, FLOW, node1, node2));
231		graph.add_edge(FlowEdge::new(2, FLOW, node2, node3));
232
233		let sorted = graph.topological_sort();
234		assert_eq!(sorted, vec![OperatorId(1), OperatorId(2), OperatorId(3)]);
235	}
236
237	#[test]
238	fn test_dfs_traversal() {
239		let mut graph = DirectedGraph::new();
240
241		let node1 = graph.add_node(OperatorId(1), "Node 1");
242		let node2 = graph.add_node(OperatorId(2), "Node 2");
243		let node3 = graph.add_node(OperatorId(3), "Node 3");
244		let node4 = graph.add_node(OperatorId(4), "Node 4");
245
246		graph.add_edge(FlowEdge::new(1, FLOW, node1, node2));
247		graph.add_edge(FlowEdge::new(2, FLOW, node1, node3));
248		graph.add_edge(FlowEdge::new(3, FLOW, node2, node4));
249
250		let dfs_result = graph.dfs_from(&node1);
251		assert!(dfs_result.contains(&OperatorId(1)));
252		assert!(dfs_result.contains(&OperatorId(2)));
253		assert!(dfs_result.contains(&OperatorId(3)));
254		assert!(dfs_result.contains(&OperatorId(4)));
255		assert_eq!(dfs_result.len(), 4);
256	}
257}