Skip to main content

reinhardt_db/migrations/
graph.rs

1//! Migration dependency graph
2//!
3//! This module provides the MigrationGraph structure for managing migration dependencies
4//! and determining execution order through topological sorting.
5//!
6//! # Example
7//!
8//! ```rust
9//! use reinhardt_db::migrations::graph::{MigrationGraph, MigrationKey};
10//!
11//! let mut graph = MigrationGraph::new();
12//!
13//! // Add migrations with dependencies
14//! let key1 = MigrationKey::new("myapp", "0001_initial");
15//! let key2 = MigrationKey::new("myapp", "0002_add_field");
16//!
17//! graph.add_migration(key1.clone(), vec![]);
18//! graph.add_migration(key2.clone(), vec![key1.clone()]);
19//!
20//! // Get execution order
21//! let order = graph.topological_sort().unwrap();
22//! assert_eq!(order.len(), 2);
23//! assert_eq!(order[0], key1);
24//! assert_eq!(order[1], key2);
25//! ```
26
27use super::dependency::{DependencyResolutionContext, DependencyResolver, MigrationDependency};
28use super::{Migration, MigrationError, Result};
29use serde::{Deserialize, Serialize};
30use std::collections::{HashMap, HashSet, VecDeque};
31
32/// Key identifying a migration (app_label, migration_name)
33///
34/// # Example
35///
36/// ```rust
37/// use reinhardt_db::migrations::graph::MigrationKey;
38///
39/// let key = MigrationKey::new("auth", "0001_initial");
40/// assert_eq!(key.app_label, "auth");
41/// assert_eq!(key.name, "0001_initial");
42/// ```
43#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
44pub struct MigrationKey {
45	/// The app label.
46	pub app_label: String,
47	/// The name.
48	pub name: String,
49}
50
51impl MigrationKey {
52	/// Create a new migration key
53	///
54	/// # Example
55	///
56	/// ```rust
57	/// use reinhardt_db::migrations::graph::MigrationKey;
58	///
59	/// let key = MigrationKey::new("users", "0001_initial");
60	/// assert_eq!(key.app_label, "users");
61	/// assert_eq!(key.name, "0001_initial");
62	/// ```
63	pub fn new(app_label: impl Into<String>, name: impl Into<String>) -> Self {
64		Self {
65			app_label: app_label.into(),
66			name: name.into(),
67		}
68	}
69
70	/// Get a string representation of this key
71	///
72	/// # Example
73	///
74	/// ```rust
75	/// use reinhardt_db::migrations::graph::MigrationKey;
76	///
77	/// let key = MigrationKey::new("auth", "0001_initial");
78	/// assert_eq!(key.id(), "auth.0001_initial");
79	/// ```
80	pub fn id(&self) -> String {
81		format!("{}.{}", self.app_label, self.name)
82	}
83}
84
85impl std::fmt::Display for MigrationKey {
86	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87		write!(f, "{}", self.id())
88	}
89}
90
91/// Migration graph node
92#[derive(Debug, Clone)]
93pub struct MigrationNode {
94	/// The key.
95	pub key: MigrationKey,
96	/// The dependencies.
97	pub dependencies: Vec<MigrationKey>,
98	/// The replaces.
99	pub replaces: Vec<MigrationKey>,
100}
101
102impl MigrationNode {
103	/// Create a new migration node
104	pub fn new(key: MigrationKey, dependencies: Vec<MigrationKey>) -> Self {
105		Self {
106			key,
107			dependencies,
108			replaces: Vec::new(),
109		}
110	}
111
112	/// Create a new migration node with replaces
113	pub fn with_replaces(
114		key: MigrationKey,
115		dependencies: Vec<MigrationKey>,
116		replaces: Vec<MigrationKey>,
117	) -> Self {
118		Self {
119			key,
120			dependencies,
121			replaces,
122		}
123	}
124}
125
126/// Migration dependency graph
127///
128/// Manages migration dependencies and determines execution order.
129///
130/// # Example
131///
132/// ```rust
133/// use reinhardt_db::migrations::graph::{MigrationGraph, MigrationKey};
134///
135/// let mut graph = MigrationGraph::new();
136///
137/// let initial = MigrationKey::new("users", "0001_initial");
138/// let add_field = MigrationKey::new("users", "0002_add_email");
139///
140/// graph.add_migration(initial.clone(), vec![]);
141/// graph.add_migration(add_field.clone(), vec![initial.clone()]);
142///
143/// let order = graph.topological_sort().unwrap();
144/// assert_eq!(order.len(), 2);
145/// ```
146pub struct MigrationGraph {
147	nodes: HashMap<MigrationKey, MigrationNode>,
148}
149
150impl MigrationGraph {
151	/// Create a new empty migration graph
152	///
153	/// # Example
154	///
155	/// ```rust
156	/// use reinhardt_db::migrations::graph::MigrationGraph;
157	///
158	/// let graph = MigrationGraph::new();
159	/// assert!(graph.is_empty());
160	/// ```
161	pub fn new() -> Self {
162		Self {
163			nodes: HashMap::new(),
164		}
165	}
166
167	/// Add a migration to the graph
168	///
169	/// # Example
170	///
171	/// ```rust
172	/// use reinhardt_db::migrations::graph::{MigrationGraph, MigrationKey};
173	///
174	/// let mut graph = MigrationGraph::new();
175	/// let key = MigrationKey::new("auth", "0001_initial");
176	///
177	/// graph.add_migration(key.clone(), vec![]);
178	/// assert!(graph.has_migration(&key));
179	/// ```
180	pub fn add_migration(&mut self, key: MigrationKey, dependencies: Vec<MigrationKey>) {
181		let node = MigrationNode::new(key.clone(), dependencies);
182		self.nodes.insert(key, node);
183	}
184
185	/// Check if a migration exists in the graph
186	///
187	/// # Example
188	///
189	/// ```rust
190	/// use reinhardt_db::migrations::graph::{MigrationGraph, MigrationKey};
191	///
192	/// let mut graph = MigrationGraph::new();
193	/// let key = MigrationKey::new("auth", "0001_initial");
194	///
195	/// assert!(!graph.has_migration(&key));
196	/// graph.add_migration(key.clone(), vec![]);
197	/// assert!(graph.has_migration(&key));
198	/// ```
199	pub fn has_migration(&self, key: &MigrationKey) -> bool {
200		self.nodes.contains_key(key)
201	}
202
203	/// Get a migration node
204	pub fn get_node(&self, key: &MigrationKey) -> Option<&MigrationNode> {
205		self.nodes.get(key)
206	}
207
208	/// Check if the graph is empty
209	///
210	/// # Example
211	///
212	/// ```rust
213	/// use reinhardt_db::migrations::graph::{MigrationGraph, MigrationKey};
214	///
215	/// let mut graph = MigrationGraph::new();
216	/// assert!(graph.is_empty());
217	///
218	/// graph.add_migration(MigrationKey::new("auth", "0001_initial"), vec![]);
219	/// assert!(!graph.is_empty());
220	/// ```
221	pub fn is_empty(&self) -> bool {
222		self.nodes.is_empty()
223	}
224
225	/// Get the number of migrations in the graph
226	pub fn len(&self) -> usize {
227		self.nodes.len()
228	}
229
230	/// Get all migrations in the graph
231	pub fn all_migrations(&self) -> Vec<&MigrationKey> {
232		self.nodes.keys().collect()
233	}
234
235	/// Get direct dependencies of a migration
236	///
237	/// # Example
238	///
239	/// ```rust
240	/// use reinhardt_db::migrations::graph::{MigrationGraph, MigrationKey};
241	///
242	/// let mut graph = MigrationGraph::new();
243	/// let key1 = MigrationKey::new("auth", "0001_initial");
244	/// let key2 = MigrationKey::new("auth", "0002_add_field");
245	///
246	/// graph.add_migration(key1.clone(), vec![]);
247	/// graph.add_migration(key2.clone(), vec![key1.clone()]);
248	///
249	/// let deps = graph.get_dependencies(&key2).unwrap();
250	/// assert_eq!(deps.len(), 1);
251	/// assert_eq!(deps[0], key1);
252	/// ```
253	pub fn get_dependencies(&self, key: &MigrationKey) -> Option<&[MigrationKey]> {
254		self.nodes.get(key).map(|node| node.dependencies.as_slice())
255	}
256
257	/// Get all migrations that depend on this migration
258	///
259	/// # Example
260	///
261	/// ```rust
262	/// use reinhardt_db::migrations::graph::{MigrationGraph, MigrationKey};
263	///
264	/// let mut graph = MigrationGraph::new();
265	/// let key1 = MigrationKey::new("auth", "0001_initial");
266	/// let key2 = MigrationKey::new("auth", "0002_add_field");
267	///
268	/// graph.add_migration(key1.clone(), vec![]);
269	/// graph.add_migration(key2.clone(), vec![key1.clone()]);
270	///
271	/// let dependents = graph.get_dependents(&key1);
272	/// assert_eq!(dependents.len(), 1);
273	/// assert_eq!(dependents[0], &key2);
274	/// ```
275	pub fn get_dependents(&self, key: &MigrationKey) -> Vec<&MigrationKey> {
276		self.nodes
277			.iter()
278			.filter(|(_, node)| node.dependencies.contains(key))
279			.map(|(k, _)| k)
280			.collect()
281	}
282
283	/// Perform topological sort to determine migration execution order
284	///
285	/// Returns migrations in the order they should be executed.
286	///
287	/// # Example
288	///
289	/// ```rust
290	/// use reinhardt_db::migrations::graph::{MigrationGraph, MigrationKey};
291	///
292	/// let mut graph = MigrationGraph::new();
293	///
294	/// let key1 = MigrationKey::new("auth", "0001_initial");
295	/// let key2 = MigrationKey::new("auth", "0002_add_field");
296	/// let key3 = MigrationKey::new("auth", "0003_alter_field");
297	///
298	/// graph.add_migration(key1.clone(), vec![]);
299	/// graph.add_migration(key2.clone(), vec![key1.clone()]);
300	/// graph.add_migration(key3.clone(), vec![key2.clone()]);
301	///
302	/// let order = graph.topological_sort().unwrap();
303	/// assert_eq!(order.len(), 3);
304	/// assert_eq!(order[0], key1);
305	/// assert_eq!(order[1], key2);
306	/// assert_eq!(order[2], key3);
307	/// ```
308	pub fn topological_sort(&self) -> Result<Vec<MigrationKey>> {
309		// Calculate in-degree for each node
310		let mut in_degree: HashMap<MigrationKey, usize> = HashMap::new();
311		let mut dependents: HashMap<MigrationKey, Vec<MigrationKey>> = HashMap::new();
312
313		for key in self.nodes.keys() {
314			in_degree.insert(key.clone(), 0);
315			dependents.insert(key.clone(), Vec::new());
316		}
317
318		for node in self.nodes.values() {
319			for dep in &node.dependencies {
320				// Only count dependencies that are within the graph.
321				// Dependencies outside the graph are assumed to be already applied.
322				if self.nodes.contains_key(dep) {
323					*in_degree
324						.get_mut(&node.key)
325						.expect("node key must be initialized") += 1;
326					dependents
327						.get_mut(dep)
328						.expect("internal dependency key must be initialized")
329						.push(node.key.clone());
330				}
331			}
332		}
333
334		for dependent_keys in dependents.values_mut() {
335			dependent_keys.sort_by(|a, b| {
336				a.app_label
337					.cmp(&b.app_label)
338					.then_with(|| a.name.cmp(&b.name))
339			});
340		}
341
342		// Find all nodes with in-degree 0 (no dependencies within the graph)
343		// Sort deterministically by (app_label, name) to ensure consistent ordering
344		let mut zero_degree: Vec<MigrationKey> = in_degree
345			.iter()
346			.filter(|&(_, &degree)| degree == 0)
347			.map(|(key, _)| key.clone())
348			.collect();
349		zero_degree.sort_by(|a, b| {
350			a.app_label
351				.cmp(&b.app_label)
352				.then_with(|| a.name.cmp(&b.name))
353		});
354		let mut queue: VecDeque<MigrationKey> = zero_degree.into_iter().collect();
355
356		let mut result = Vec::with_capacity(self.nodes.len());
357
358		while let Some(key) = queue.pop_front() {
359			result.push(key.clone());
360
361			// Reduce in-degree for all dependents and collect newly freed nodes
362			let mut newly_freed = Vec::new();
363			if let Some(dependent_keys) = dependents.get(&key) {
364				for other_key in dependent_keys {
365					if let Some(degree) = in_degree.get_mut(other_key) {
366						*degree -= 1;
367						if *degree == 0 {
368							newly_freed.push(other_key.clone());
369						}
370					}
371				}
372			}
373			queue.extend(newly_freed);
374		}
375
376		// Check for circular dependencies
377		if result.len() != self.nodes.len() {
378			let remaining: Vec<String> = self
379				.nodes
380				.keys()
381				.filter(|k| !result.contains(k))
382				.map(|k| k.id())
383				.collect();
384
385			return Err(MigrationError::CircularDependency {
386				cycle: format!("Circular dependency detected: {}", remaining.join(", ")),
387			});
388		}
389
390		Ok(result)
391	}
392
393	/// Get leaf nodes (migrations with no dependents)
394	///
395	/// # Example
396	///
397	/// ```rust
398	/// use reinhardt_db::migrations::graph::{MigrationGraph, MigrationKey};
399	///
400	/// let mut graph = MigrationGraph::new();
401	///
402	/// let key1 = MigrationKey::new("auth", "0001_initial");
403	/// let key2 = MigrationKey::new("auth", "0002_add_field");
404	///
405	/// graph.add_migration(key1.clone(), vec![]);
406	/// graph.add_migration(key2.clone(), vec![key1.clone()]);
407	///
408	/// let leaves = graph.get_leaf_nodes();
409	/// assert_eq!(leaves.len(), 1);
410	/// assert_eq!(leaves[0], &key2);
411	/// ```
412	pub fn get_leaf_nodes(&self) -> Vec<&MigrationKey> {
413		self.nodes
414			.keys()
415			.filter(|key| self.get_dependents(key).is_empty())
416			.collect()
417	}
418
419	/// Get leaf nodes for a specific app
420	///
421	/// Returns all leaf nodes (migrations with no dependents within the graph)
422	/// that belong to the specified app label.
423	///
424	/// # Example
425	///
426	/// ```rust
427	/// use reinhardt_db::migrations::graph::{MigrationGraph, MigrationKey};
428	///
429	/// let mut graph = MigrationGraph::new();
430	///
431	/// let key1 = MigrationKey::new("auth", "0001_initial");
432	/// let key2 = MigrationKey::new("auth", "0002_add_field");
433	/// let key3 = MigrationKey::new("users", "0001_initial");
434	///
435	/// graph.add_migration(key1.clone(), vec![]);
436	/// graph.add_migration(key2.clone(), vec![key1.clone()]);
437	/// graph.add_migration(key3.clone(), vec![]);
438	///
439	/// let auth_leaves = graph.get_leaf_nodes_for_app("auth");
440	/// assert_eq!(auth_leaves.len(), 1);
441	/// assert_eq!(auth_leaves[0], &key2);
442	/// ```
443	pub fn get_leaf_nodes_for_app(&self, app_label: &str) -> Vec<&MigrationKey> {
444		let mut leaves: Vec<&MigrationKey> = self
445			.get_leaf_nodes()
446			.into_iter()
447			.filter(|key| key.app_label == app_label)
448			.collect();
449		leaves.sort_by(|a, b| a.name.cmp(&b.name));
450		leaves
451	}
452
453	/// Detect migration conflicts (apps with multiple leaf nodes)
454	///
455	/// Returns a map of app labels to their conflicting leaf nodes.
456	/// Only apps with 2 or more leaf nodes are included.
457	/// Results are sorted by leaf name for deterministic output.
458	///
459	/// # Example
460	///
461	/// ```rust
462	/// use reinhardt_db::migrations::graph::{MigrationGraph, MigrationKey};
463	///
464	/// let mut graph = MigrationGraph::new();
465	///
466	/// let key1 = MigrationKey::new("auth", "0001_initial");
467	/// let key2a = MigrationKey::new("auth", "0002_add_field");
468	/// let key2b = MigrationKey::new("auth", "0002_add_index");
469	///
470	/// graph.add_migration(key1.clone(), vec![]);
471	/// graph.add_migration(key2a.clone(), vec![key1.clone()]);
472	/// graph.add_migration(key2b.clone(), vec![key1.clone()]);
473	///
474	/// let conflicts = graph.detect_conflicts();
475	/// assert_eq!(conflicts.len(), 1);
476	/// assert!(conflicts.contains_key("auth"));
477	/// assert_eq!(conflicts["auth"].len(), 2);
478	/// ```
479	pub fn detect_conflicts(&self) -> HashMap<String, Vec<&MigrationKey>> {
480		let leaves = self.get_leaf_nodes();
481		let mut app_leaves: HashMap<String, Vec<&MigrationKey>> = HashMap::new();
482
483		for leaf in leaves {
484			app_leaves
485				.entry(leaf.app_label.clone())
486				.or_default()
487				.push(leaf);
488		}
489
490		// Sort leaves by name for deterministic output and filter to conflicts only
491		let mut conflicts: HashMap<String, Vec<&MigrationKey>> = HashMap::new();
492		for (app, mut leaves) in app_leaves {
493			if leaves.len() >= 2 {
494				leaves.sort_by(|a, b| a.name.cmp(&b.name));
495				conflicts.insert(app, leaves);
496			}
497		}
498
499		conflicts
500	}
501
502	/// Get root nodes (migrations with no dependencies)
503	///
504	/// # Example
505	///
506	/// ```rust
507	/// use reinhardt_db::migrations::graph::{MigrationGraph, MigrationKey};
508	///
509	/// let mut graph = MigrationGraph::new();
510	///
511	/// let key1 = MigrationKey::new("auth", "0001_initial");
512	/// let key2 = MigrationKey::new("auth", "0002_add_field");
513	///
514	/// graph.add_migration(key1.clone(), vec![]);
515	/// graph.add_migration(key2.clone(), vec![key1.clone()]);
516	///
517	/// let roots = graph.get_root_nodes();
518	/// assert_eq!(roots.len(), 1);
519	/// assert_eq!(roots[0], &key1);
520	/// ```
521	pub fn get_root_nodes(&self) -> Vec<&MigrationKey> {
522		self.nodes
523			.values()
524			.filter(|node| node.dependencies.is_empty())
525			.map(|node| &node.key)
526			.collect()
527	}
528
529	/// Remove a migration from the graph
530	pub fn remove_migration(&mut self, key: &MigrationKey) {
531		self.nodes.remove(key);
532	}
533
534	/// Clear all migrations from the graph
535	pub fn clear(&mut self) {
536		self.nodes.clear();
537	}
538
539	/// Add a migration with replaces information
540	///
541	/// # Example
542	///
543	/// ```rust
544	/// use reinhardt_db::migrations::graph::{MigrationGraph, MigrationKey};
545	///
546	/// let mut graph = MigrationGraph::new();
547	/// let squashed = MigrationKey::new("auth", "0001_squashed_0003");
548	/// let old1 = MigrationKey::new("auth", "0001_initial");
549	/// let old2 = MigrationKey::new("auth", "0002_add_field");
550	///
551	/// graph.add_migration_with_replaces(
552	///     squashed.clone(),
553	///     vec![],
554	///     vec![old1.clone(), old2.clone()],
555	/// );
556	/// ```
557	pub fn add_migration_with_replaces(
558		&mut self,
559		key: MigrationKey,
560		dependencies: Vec<MigrationKey>,
561		replaces: Vec<MigrationKey>,
562	) {
563		let node = MigrationNode::with_replaces(key.clone(), dependencies, replaces);
564		self.nodes.insert(key, node);
565	}
566
567	/// Find a path between two migration states
568	///
569	/// Uses breadth-first search to find the shortest path from `from` to `to`.
570	///
571	/// # Example
572	///
573	/// ```rust
574	/// use reinhardt_db::migrations::graph::{MigrationGraph, MigrationKey};
575	///
576	/// let mut graph = MigrationGraph::new();
577	/// let key1 = MigrationKey::new("auth", "0001_initial");
578	/// let key2 = MigrationKey::new("auth", "0002_add_field");
579	/// let key3 = MigrationKey::new("auth", "0003_alter_field");
580	///
581	/// graph.add_migration(key1.clone(), vec![]);
582	/// graph.add_migration(key2.clone(), vec![key1.clone()]);
583	/// graph.add_migration(key3.clone(), vec![key2.clone()]);
584	///
585	/// let path = graph.find_migration_path(&key1, &key3).unwrap();
586	/// assert_eq!(path.len(), 3);
587	/// assert_eq!(path[0], key1);
588	/// assert_eq!(path[1], key2);
589	/// assert_eq!(path[2], key3);
590	/// ```
591	pub fn find_migration_path(
592		&self,
593		from: &MigrationKey,
594		to: &MigrationKey,
595	) -> Result<Vec<MigrationKey>> {
596		use std::collections::VecDeque;
597
598		if from == to {
599			return Ok(vec![from.clone()]);
600		}
601
602		if !self.has_migration(from) {
603			return Err(MigrationError::NodeNotFound {
604				message: "Source migration not found".to_string(),
605				node: from.id(),
606			});
607		}
608
609		if !self.has_migration(to) {
610			return Err(MigrationError::NodeNotFound {
611				message: "Target migration not found".to_string(),
612				node: to.id(),
613			});
614		}
615
616		// BFS to find shortest path
617		let mut queue = VecDeque::new();
618		let mut visited = HashMap::new();
619		let mut parent: HashMap<MigrationKey, MigrationKey> = HashMap::new();
620
621		queue.push_back(from.clone());
622		visited.insert(from.clone(), true);
623
624		while let Some(current) = queue.pop_front() {
625			if &current == to {
626				// Reconstruct path
627				let mut path = vec![to.clone()];
628				let mut node = to.clone();
629
630				while let Some(p) = parent.get(&node) {
631					path.push(p.clone());
632					node = p.clone();
633				}
634
635				path.reverse();
636				return Ok(path);
637			}
638
639			// Visit all dependents (forward direction)
640			for dependent in self.get_dependents(&current) {
641				if !visited.contains_key(dependent) {
642					visited.insert(dependent.clone(), true);
643					parent.insert(dependent.clone(), current.clone());
644					queue.push_back(dependent.clone());
645				}
646			}
647		}
648
649		Err(MigrationError::DependencyError(format!(
650			"No path found from {} to {}",
651			from.id(),
652			to.id()
653		)))
654	}
655
656	/// Find a backward path (for rollback) between two migration states
657	///
658	/// # Example
659	///
660	/// ```rust
661	/// use reinhardt_db::migrations::graph::{MigrationGraph, MigrationKey};
662	///
663	/// let mut graph = MigrationGraph::new();
664	/// let key1 = MigrationKey::new("auth", "0001_initial");
665	/// let key2 = MigrationKey::new("auth", "0002_add_field");
666	/// let key3 = MigrationKey::new("auth", "0003_alter_field");
667	///
668	/// graph.add_migration(key1.clone(), vec![]);
669	/// graph.add_migration(key2.clone(), vec![key1.clone()]);
670	/// graph.add_migration(key3.clone(), vec![key2.clone()]);
671	///
672	/// let path = graph.find_backward_path(&key3, &key1).unwrap();
673	/// assert_eq!(path.len(), 3);
674	/// assert_eq!(path[0], key3);
675	/// assert_eq!(path[1], key2);
676	/// assert_eq!(path[2], key1);
677	/// ```
678	pub fn find_backward_path(
679		&self,
680		from: &MigrationKey,
681		to: &MigrationKey,
682	) -> Result<Vec<MigrationKey>> {
683		use std::collections::VecDeque;
684
685		if from == to {
686			return Ok(vec![from.clone()]);
687		}
688
689		if !self.has_migration(from) {
690			return Err(MigrationError::NodeNotFound {
691				message: "Source migration not found".to_string(),
692				node: from.id(),
693			});
694		}
695
696		if !self.has_migration(to) {
697			return Err(MigrationError::NodeNotFound {
698				message: "Target migration not found".to_string(),
699				node: to.id(),
700			});
701		}
702
703		// BFS to find path following dependencies backward
704		let mut queue = VecDeque::new();
705		let mut visited = HashMap::new();
706		let mut parent: HashMap<MigrationKey, MigrationKey> = HashMap::new();
707
708		queue.push_back(from.clone());
709		visited.insert(from.clone(), true);
710
711		while let Some(current) = queue.pop_front() {
712			if &current == to {
713				// Reconstruct path
714				let mut path = vec![to.clone()];
715				let mut node = to.clone();
716
717				while let Some(p) = parent.get(&node) {
718					path.push(p.clone());
719					node = p.clone();
720				}
721
722				path.reverse();
723				return Ok(path);
724			}
725
726			// Visit all dependencies (backward direction)
727			if let Some(deps) = self.get_dependencies(&current) {
728				for dep in deps {
729					if !visited.contains_key(dep) {
730						visited.insert(dep.clone(), true);
731						parent.insert(dep.clone(), current.clone());
732						queue.push_back(dep.clone());
733					}
734				}
735			}
736		}
737
738		Err(MigrationError::DependencyError(format!(
739			"No backward path found from {} to {}",
740			from.id(),
741			to.id()
742		)))
743	}
744
745	/// Resolve execution order with replaces support
746	///
747	/// This method handles migrations that replace others (squashed migrations).
748	/// When a migration replaces others, the replaced migrations are excluded
749	/// from the execution order.
750	///
751	/// # Example
752	///
753	/// ```rust
754	/// use reinhardt_db::migrations::graph::{MigrationGraph, MigrationKey};
755	///
756	/// let mut graph = MigrationGraph::new();
757	/// let old1 = MigrationKey::new("auth", "0001_initial");
758	/// let old2 = MigrationKey::new("auth", "0002_add_field");
759	/// let squashed = MigrationKey::new("auth", "0001_squashed_0002");
760	///
761	/// graph.add_migration(old1.clone(), vec![]);
762	/// graph.add_migration(old2.clone(), vec![old1.clone()]);
763	/// graph.add_migration_with_replaces(
764	///     squashed.clone(),
765	///     vec![],
766	///     vec![old1.clone(), old2.clone()],
767	/// );
768	///
769	/// let order = graph.resolve_execution_order_with_replaces().unwrap();
770	/// // Should contain only the squashed migration, not the old ones
771	/// assert_eq!(order.len(), 1);
772	/// assert_eq!(order[0], squashed);
773	/// ```
774	pub fn resolve_execution_order_with_replaces(&self) -> Result<Vec<MigrationKey>> {
775		// Find all migrations that are replaced by others
776		let mut replaced: HashSet<MigrationKey> = HashSet::new();
777		let mut replacement_map: HashMap<MigrationKey, MigrationKey> = HashMap::new();
778
779		for (key, node) in &self.nodes {
780			for replaced_key in &node.replaces {
781				replaced.insert(replaced_key.clone());
782				replacement_map.insert(replaced_key.clone(), key.clone());
783			}
784		}
785
786		// Create a filtered graph without replaced migrations
787		let mut filtered_graph = MigrationGraph::new();
788		for (key, node) in &self.nodes {
789			if !replaced.contains(key) {
790				// Transform dependencies: replace old migrations with their replacements
791				let mut filtered_deps: HashSet<MigrationKey> = HashSet::new();
792
793				for dep in &node.dependencies {
794					if let Some(replacement) = replacement_map.get(dep) {
795						// This dependency is replaced, use the replacement instead
796						filtered_deps.insert(replacement.clone());
797					} else if !replaced.contains(dep) {
798						// This dependency is not replaced, keep it
799						filtered_deps.insert(dep.clone());
800					}
801					// If dependency is replaced but not in map, skip it
802				}
803
804				filtered_graph.add_migration(key.clone(), filtered_deps.into_iter().collect());
805			}
806		}
807
808		// Perform topological sort on filtered graph
809		filtered_graph.topological_sort()
810	}
811
812	/// Check if a migration is replaced by another migration
813	///
814	/// # Example
815	///
816	/// ```rust
817	/// use reinhardt_db::migrations::graph::{MigrationGraph, MigrationKey};
818	///
819	/// let mut graph = MigrationGraph::new();
820	/// let old = MigrationKey::new("auth", "0001_initial");
821	/// let squashed = MigrationKey::new("auth", "0001_squashed_0002");
822	///
823	/// graph.add_migration_with_replaces(
824	///     squashed.clone(),
825	///     vec![],
826	///     vec![old.clone()],
827	/// );
828	///
829	/// assert!(graph.is_replaced(&old));
830	/// assert!(!graph.is_replaced(&squashed));
831	/// ```
832	pub fn is_replaced(&self, key: &MigrationKey) -> bool {
833		self.nodes.values().any(|node| node.replaces.contains(key))
834	}
835
836	/// Get the migration that replaces a given migration
837	///
838	/// # Example
839	///
840	/// ```rust
841	/// use reinhardt_db::migrations::graph::{MigrationGraph, MigrationKey};
842	///
843	/// let mut graph = MigrationGraph::new();
844	/// let old = MigrationKey::new("auth", "0001_initial");
845	/// let squashed = MigrationKey::new("auth", "0001_squashed_0002");
846	///
847	/// graph.add_migration_with_replaces(
848	///     squashed.clone(),
849	///     vec![],
850	///     vec![old.clone()],
851	/// );
852	///
853	/// let replacement = graph.get_replacement(&old);
854	/// assert_eq!(replacement, Some(&squashed));
855	/// ```
856	pub fn get_replacement(&self, key: &MigrationKey) -> Option<&MigrationKey> {
857		self.nodes
858			.iter()
859			.find(|(_, node)| node.replaces.contains(key))
860			.map(|(k, _)| k)
861	}
862
863	/// Detect cycles considering replaces relationships
864	///
865	/// This is more comprehensive than the basic topological sort cycle detection,
866	/// as it also checks for cycles that might be introduced through replaces.
867	///
868	/// # Example
869	///
870	/// ```rust
871	/// use reinhardt_db::migrations::graph::{MigrationGraph, MigrationKey};
872	///
873	/// let mut graph = MigrationGraph::new();
874	/// let key1 = MigrationKey::new("auth", "0001_initial");
875	/// let key2 = MigrationKey::new("auth", "0002_add_field");
876	///
877	/// graph.add_migration(key1.clone(), vec![]);
878	/// graph.add_migration(key2.clone(), vec![key1.clone()]);
879	///
880	/// let cycles = graph.detect_all_cycles();
881	/// assert!(cycles.is_empty());
882	/// ```
883	pub fn detect_all_cycles(&self) -> Vec<Vec<MigrationKey>> {
884		let mut cycles = Vec::new();
885		let mut visited = HashSet::new();
886		let mut rec_stack = HashSet::new();
887		let mut path = Vec::new();
888
889		for key in self.nodes.keys() {
890			if !visited.contains(key) {
891				self.detect_cycle_dfs(key, &mut visited, &mut rec_stack, &mut path, &mut cycles);
892			}
893		}
894
895		cycles
896	}
897
898	fn detect_cycle_dfs(
899		&self,
900		key: &MigrationKey,
901		visited: &mut HashSet<MigrationKey>,
902		rec_stack: &mut HashSet<MigrationKey>,
903		path: &mut Vec<MigrationKey>,
904		cycles: &mut Vec<Vec<MigrationKey>>,
905	) {
906		visited.insert(key.clone());
907		rec_stack.insert(key.clone());
908		path.push(key.clone());
909
910		if let Some(node) = self.nodes.get(key) {
911			for dep in &node.dependencies {
912				if !visited.contains(dep) {
913					self.detect_cycle_dfs(dep, visited, rec_stack, path, cycles);
914				} else if rec_stack.contains(dep) {
915					// Found a cycle
916					let cycle_start = path.iter().position(|k| k == dep).unwrap();
917					let cycle: Vec<MigrationKey> = path[cycle_start..].to_vec();
918					cycles.push(cycle);
919				}
920			}
921		}
922
923		path.pop();
924		rec_stack.remove(key);
925	}
926
927	/// Add a migration to the graph, resolving all dependency types.
928	///
929	/// This method handles:
930	/// - Required dependencies (standard)
931	/// - Swappable dependencies (resolved via settings)
932	/// - Optional dependencies (only if condition is met)
933	///
934	/// # Example
935	///
936	/// ```rust
937	/// use reinhardt_db::migrations::graph::{MigrationGraph, MigrationKey};
938	/// use reinhardt_db::migrations::Migration;
939	/// use reinhardt_db::migrations::dependency::{
940	///     DependencyResolutionContext, SwappableDependency, OptionalDependency, DependencyCondition
941	/// };
942	///
943	/// let mut graph = MigrationGraph::new();
944	///
945	/// // Set up context with custom user model
946	/// let context = DependencyResolutionContext::new()
947	///     .with_app("auth")
948	///     .with_app("custom_auth")
949	///     .with_setting("AUTH_USER_MODEL", "custom_auth.CustomUser");
950	///
951	/// // Create a migration with swappable dependency
952	/// let migration = Migration::new("0001_create_profile", "profiles")
953	///     .add_swappable_dependency(SwappableDependency::new(
954	///         "AUTH_USER_MODEL",
955	///         "auth",
956	///         "User",
957	///         "0001_initial",
958	///     ));
959	///
960	/// graph.add_migration_with_context(&migration, &context);
961	///
962	/// // The dependency should be resolved to custom_auth
963	/// let key = MigrationKey::new("profiles", "0001_create_profile");
964	/// let deps = graph.get_dependencies(&key).unwrap();
965	/// assert_eq!(deps.len(), 1);
966	/// assert_eq!(deps[0].app_label, "custom_auth");
967	/// ```
968	pub fn add_migration_with_context(
969		&mut self,
970		migration: &Migration,
971		context: &DependencyResolutionContext,
972	) {
973		let key = MigrationKey::new(migration.app_label.clone(), migration.name.clone());
974		let resolver = DependencyResolver::new(context);
975
976		// Collect all dependencies
977		let mut all_dependencies: Vec<MigrationKey> = Vec::new();
978
979		// Add required dependencies
980		for (app_label, migration_name) in &migration.dependencies {
981			all_dependencies.push(MigrationKey::new(app_label.clone(), migration_name.clone()));
982		}
983
984		// Resolve swappable dependencies
985		for swappable in &migration.swappable_dependencies {
986			let dep = MigrationDependency::Swappable(swappable.clone());
987			if let Some((app_label, migration_name)) = resolver.resolve(&dep) {
988				all_dependencies.push(MigrationKey::new(app_label, migration_name));
989			}
990		}
991
992		// Resolve optional dependencies (only if condition is met)
993		for optional in &migration.optional_dependencies {
994			let dep = MigrationDependency::Optional(optional.clone());
995			if let Some((app_label, migration_name)) = resolver.resolve(&dep) {
996				all_dependencies.push(MigrationKey::new(app_label, migration_name));
997			}
998		}
999
1000		// Handle replaces
1001		let replaces: Vec<MigrationKey> = migration
1002			.replaces
1003			.iter()
1004			.map(|(app, name)| MigrationKey::new(app.clone(), name.clone()))
1005			.collect();
1006
1007		if replaces.is_empty() {
1008			self.add_migration(key, all_dependencies);
1009		} else {
1010			self.add_migration_with_replaces(key, all_dependencies, replaces);
1011		}
1012	}
1013
1014	/// Build a migration graph from a list of migrations.
1015	///
1016	/// This method creates a complete graph with all dependencies resolved
1017	/// using the provided context.
1018	///
1019	/// # Example
1020	///
1021	/// ```rust
1022	/// use reinhardt_db::migrations::graph::MigrationGraph;
1023	/// use reinhardt_db::migrations::Migration;
1024	/// use reinhardt_db::migrations::dependency::DependencyResolutionContext;
1025	///
1026	/// let migrations = vec![
1027	///     Migration::new("0001_initial", "auth"),
1028	///     Migration::new("0002_add_field", "auth")
1029	///         .add_dependency("auth", "0001_initial"),
1030	/// ];
1031	///
1032	/// let context = DependencyResolutionContext::new()
1033	///     .with_app("auth");
1034	///
1035	/// let graph = MigrationGraph::from_migrations(&migrations, &context);
1036	/// assert_eq!(graph.len(), 2);
1037	/// ```
1038	pub fn from_migrations(
1039		migrations: &[Migration],
1040		context: &DependencyResolutionContext,
1041	) -> Self {
1042		let mut graph = Self::new();
1043		for migration in migrations {
1044			graph.add_migration_with_context(migration, context);
1045		}
1046		graph
1047	}
1048
1049	/// Resolve execution order considering swappable and optional dependencies.
1050	///
1051	/// This is a convenience method that combines `from_migrations` with
1052	/// `topological_sort` or `resolve_execution_order_with_replaces`.
1053	///
1054	/// # Example
1055	///
1056	/// ```rust
1057	/// use reinhardt_db::migrations::graph::MigrationGraph;
1058	/// use reinhardt_db::migrations::Migration;
1059	/// use reinhardt_db::migrations::dependency::DependencyResolutionContext;
1060	///
1061	/// let migrations = vec![
1062	///     Migration::new("0001_initial", "auth"),
1063	///     Migration::new("0001_initial", "users")
1064	///         .add_dependency("auth", "0001_initial"),
1065	/// ];
1066	///
1067	/// let context = DependencyResolutionContext::new()
1068	///     .with_apps(["auth", "users"]);
1069	///
1070	/// let order = MigrationGraph::resolve_execution_order(&migrations, &context).unwrap();
1071	/// assert_eq!(order.len(), 2);
1072	/// assert_eq!(order[0].app_label, "auth");
1073	/// ```
1074	pub fn resolve_execution_order(
1075		migrations: &[Migration],
1076		context: &DependencyResolutionContext,
1077	) -> Result<Vec<MigrationKey>> {
1078		let graph = Self::from_migrations(migrations, context);
1079
1080		// Check if any migration has replaces
1081		let has_replaces = migrations.iter().any(|m| !m.replaces.is_empty());
1082
1083		if has_replaces {
1084			graph.resolve_execution_order_with_replaces()
1085		} else {
1086			graph.topological_sort()
1087		}
1088	}
1089}
1090
1091impl Default for MigrationGraph {
1092	fn default() -> Self {
1093		Self::new()
1094	}
1095}
1096
1097#[cfg(test)]
1098mod tests {
1099	use super::*;
1100	use rstest::rstest;
1101
1102	#[test]
1103	fn test_migration_key_creation() {
1104		let key = MigrationKey::new("auth", "0001_initial");
1105		assert_eq!(key.app_label, "auth");
1106		assert_eq!(key.name, "0001_initial");
1107		assert_eq!(key.id(), "auth.0001_initial");
1108	}
1109
1110	#[test]
1111	fn test_migration_key_display() {
1112		let key = MigrationKey::new("users", "0002_add_email");
1113		assert_eq!(format!("{}", key), "users.0002_add_email");
1114	}
1115
1116	#[test]
1117	fn test_graph_creation() {
1118		let graph = MigrationGraph::new();
1119		assert!(graph.is_empty());
1120		assert_eq!(graph.len(), 0);
1121	}
1122
1123	#[test]
1124	fn test_add_migration() {
1125		let mut graph = MigrationGraph::new();
1126		let key = MigrationKey::new("auth", "0001_initial");
1127
1128		graph.add_migration(key.clone(), vec![]);
1129		assert!(!graph.is_empty());
1130		assert_eq!(graph.len(), 1);
1131		assert!(graph.has_migration(&key));
1132	}
1133
1134	#[test]
1135	fn test_get_dependencies() {
1136		let mut graph = MigrationGraph::new();
1137		let key1 = MigrationKey::new("auth", "0001_initial");
1138		let key2 = MigrationKey::new("auth", "0002_add_field");
1139
1140		graph.add_migration(key1.clone(), vec![]);
1141		graph.add_migration(key2.clone(), vec![key1.clone()]);
1142
1143		let deps = graph.get_dependencies(&key2).unwrap();
1144		assert_eq!(deps.len(), 1);
1145		assert_eq!(deps[0], key1);
1146	}
1147
1148	#[test]
1149	fn test_get_dependents() {
1150		let mut graph = MigrationGraph::new();
1151		let key1 = MigrationKey::new("auth", "0001_initial");
1152		let key2 = MigrationKey::new("auth", "0002_add_field");
1153		let key3 = MigrationKey::new("auth", "0003_alter_field");
1154
1155		graph.add_migration(key1.clone(), vec![]);
1156		graph.add_migration(key2.clone(), vec![key1.clone()]);
1157		graph.add_migration(key3.clone(), vec![key2.clone()]);
1158
1159		let dependents = graph.get_dependents(&key1);
1160		assert_eq!(dependents.len(), 1);
1161		assert_eq!(dependents[0], &key2);
1162
1163		let dependents2 = graph.get_dependents(&key2);
1164		assert_eq!(dependents2.len(), 1);
1165		assert_eq!(dependents2[0], &key3);
1166	}
1167
1168	#[test]
1169	fn test_topological_sort_simple() {
1170		let mut graph = MigrationGraph::new();
1171		let key1 = MigrationKey::new("auth", "0001_initial");
1172		let key2 = MigrationKey::new("auth", "0002_add_field");
1173
1174		graph.add_migration(key1.clone(), vec![]);
1175		graph.add_migration(key2.clone(), vec![key1.clone()]);
1176
1177		let order = graph.topological_sort().unwrap();
1178		assert_eq!(order.len(), 2);
1179		assert_eq!(order[0], key1);
1180		assert_eq!(order[1], key2);
1181	}
1182
1183	#[test]
1184	fn test_topological_sort_complex() {
1185		let mut graph = MigrationGraph::new();
1186
1187		let key1 = MigrationKey::new("auth", "0001_initial");
1188		let key2 = MigrationKey::new("auth", "0002_add_field");
1189		let key3 = MigrationKey::new("users", "0001_initial");
1190		let key4 = MigrationKey::new("users", "0002_add_auth");
1191
1192		graph.add_migration(key1.clone(), vec![]);
1193		graph.add_migration(key2.clone(), vec![key1.clone()]);
1194		graph.add_migration(key3.clone(), vec![]);
1195		graph.add_migration(key4.clone(), vec![key2.clone(), key3.clone()]);
1196
1197		let order = graph.topological_sort().unwrap();
1198		assert_eq!(order.len(), 4);
1199
1200		// key1 must come before key2
1201		let pos1 = order.iter().position(|k| k == &key1).unwrap();
1202		let pos2 = order.iter().position(|k| k == &key2).unwrap();
1203		assert!(pos1 < pos2);
1204
1205		// key2 and key3 must come before key4
1206		let pos4 = order.iter().position(|k| k == &key4).unwrap();
1207		assert!(pos2 < pos4);
1208		let pos3 = order.iter().position(|k| k == &key3).unwrap();
1209		assert!(pos3 < pos4);
1210	}
1211
1212	#[test]
1213	fn test_circular_dependency_detection() {
1214		let mut graph = MigrationGraph::new();
1215
1216		let key1 = MigrationKey::new("auth", "0001_initial");
1217		let key2 = MigrationKey::new("auth", "0002_add_field");
1218
1219		// Create circular dependency
1220		graph.add_migration(key1.clone(), vec![key2.clone()]);
1221		graph.add_migration(key2.clone(), vec![key1.clone()]);
1222
1223		let result = graph.topological_sort();
1224		assert!(result.is_err());
1225
1226		if let Err(MigrationError::CircularDependency { cycle }) = result {
1227			assert!(cycle.contains("Circular dependency"));
1228		} else {
1229			panic!("Expected CircularDependency error");
1230		}
1231	}
1232
1233	#[test]
1234	fn test_get_leaf_nodes() {
1235		let mut graph = MigrationGraph::new();
1236
1237		let key1 = MigrationKey::new("auth", "0001_initial");
1238		let key2 = MigrationKey::new("auth", "0002_add_field");
1239		let key3 = MigrationKey::new("users", "0001_initial");
1240
1241		graph.add_migration(key1.clone(), vec![]);
1242		graph.add_migration(key2.clone(), vec![key1.clone()]);
1243		graph.add_migration(key3.clone(), vec![]);
1244
1245		let leaves = graph.get_leaf_nodes();
1246		assert_eq!(leaves.len(), 2);
1247		assert!(leaves.contains(&&key2));
1248		assert!(leaves.contains(&&key3));
1249	}
1250
1251	#[test]
1252	fn test_get_root_nodes() {
1253		let mut graph = MigrationGraph::new();
1254
1255		let key1 = MigrationKey::new("auth", "0001_initial");
1256		let key2 = MigrationKey::new("auth", "0002_add_field");
1257		let key3 = MigrationKey::new("users", "0001_initial");
1258
1259		graph.add_migration(key1.clone(), vec![]);
1260		graph.add_migration(key2.clone(), vec![key1.clone()]);
1261		graph.add_migration(key3.clone(), vec![]);
1262
1263		let roots = graph.get_root_nodes();
1264		assert_eq!(roots.len(), 2);
1265		assert!(roots.contains(&&key1));
1266		assert!(roots.contains(&&key3));
1267	}
1268
1269	#[test]
1270	fn test_remove_migration() {
1271		let mut graph = MigrationGraph::new();
1272		let key = MigrationKey::new("auth", "0001_initial");
1273
1274		graph.add_migration(key.clone(), vec![]);
1275		assert!(graph.has_migration(&key));
1276
1277		graph.remove_migration(&key);
1278		assert!(!graph.has_migration(&key));
1279		assert!(graph.is_empty());
1280	}
1281
1282	#[test]
1283	fn test_clear() {
1284		let mut graph = MigrationGraph::new();
1285
1286		graph.add_migration(MigrationKey::new("auth", "0001_initial"), vec![]);
1287		graph.add_migration(MigrationKey::new("users", "0001_initial"), vec![]);
1288
1289		assert_eq!(graph.len(), 2);
1290
1291		graph.clear();
1292		assert!(graph.is_empty());
1293		assert_eq!(graph.len(), 0);
1294	}
1295
1296	#[test]
1297	fn test_multiple_root_nodes_sort() {
1298		let mut graph = MigrationGraph::new();
1299
1300		let auth_0001 = MigrationKey::new("auth", "0001_initial");
1301		let users_0001 = MigrationKey::new("users", "0001_initial");
1302		let posts_0001 = MigrationKey::new("posts", "0001_initial");
1303
1304		graph.add_migration(auth_0001.clone(), vec![]);
1305		graph.add_migration(users_0001.clone(), vec![]);
1306		graph.add_migration(posts_0001.clone(), vec![]);
1307
1308		let order = graph.topological_sort().unwrap();
1309		assert_eq!(order.len(), 3);
1310		// All three should be in the result
1311		assert!(order.contains(&auth_0001));
1312		assert!(order.contains(&users_0001));
1313		assert!(order.contains(&posts_0001));
1314	}
1315
1316	#[test]
1317	fn test_complex_dependency_chain() {
1318		let mut graph = MigrationGraph::new();
1319
1320		// Create a diamond-shaped dependency graph
1321		//     A
1322		//    / \
1323		//   B   C
1324		//    \ /
1325		//     D
1326		let a = MigrationKey::new("app", "0001_a");
1327		let b = MigrationKey::new("app", "0002_b");
1328		let c = MigrationKey::new("app", "0003_c");
1329		let d = MigrationKey::new("app", "0004_d");
1330
1331		graph.add_migration(a.clone(), vec![]);
1332		graph.add_migration(b.clone(), vec![a.clone()]);
1333		graph.add_migration(c.clone(), vec![a.clone()]);
1334		graph.add_migration(d.clone(), vec![b.clone(), c.clone()]);
1335
1336		let order = graph.topological_sort().unwrap();
1337		assert_eq!(order.len(), 4);
1338
1339		let pos_a = order.iter().position(|k| k == &a).unwrap();
1340		let pos_b = order.iter().position(|k| k == &b).unwrap();
1341		let pos_c = order.iter().position(|k| k == &c).unwrap();
1342		let pos_d = order.iter().position(|k| k == &d).unwrap();
1343
1344		// A must come before B and C
1345		assert!(pos_a < pos_b);
1346		assert!(pos_a < pos_c);
1347
1348		// B and C must come before D
1349		assert!(pos_b < pos_d);
1350		assert!(pos_c < pos_d);
1351	}
1352
1353	#[test]
1354	fn test_migration_replaces() {
1355		let mut graph = MigrationGraph::new();
1356
1357		let old1 = MigrationKey::new("auth", "0001_initial");
1358		let old2 = MigrationKey::new("auth", "0002_add_field");
1359		let squashed = MigrationKey::new("auth", "0001_squashed_0002");
1360
1361		graph.add_migration(old1.clone(), vec![]);
1362		graph.add_migration(old2.clone(), vec![old1.clone()]);
1363		graph.add_migration_with_replaces(
1364			squashed.clone(),
1365			vec![],
1366			vec![old1.clone(), old2.clone()],
1367		);
1368
1369		// Check replaces detection
1370		assert!(graph.is_replaced(&old1));
1371		assert!(graph.is_replaced(&old2));
1372		assert!(!graph.is_replaced(&squashed));
1373
1374		// Check replacement retrieval
1375		assert_eq!(graph.get_replacement(&old1), Some(&squashed));
1376		assert_eq!(graph.get_replacement(&old2), Some(&squashed));
1377		assert_eq!(graph.get_replacement(&squashed), None);
1378	}
1379
1380	#[test]
1381	fn test_resolve_execution_order_with_replaces() {
1382		let mut graph = MigrationGraph::new();
1383
1384		let old1 = MigrationKey::new("auth", "0001_initial");
1385		let old2 = MigrationKey::new("auth", "0002_add_field");
1386		let old3 = MigrationKey::new("auth", "0003_alter_field");
1387		let squashed = MigrationKey::new("auth", "0001_squashed_0002");
1388
1389		graph.add_migration(old1.clone(), vec![]);
1390		graph.add_migration(old2.clone(), vec![old1.clone()]);
1391		graph.add_migration(old3.clone(), vec![old2.clone()]);
1392		graph.add_migration_with_replaces(
1393			squashed.clone(),
1394			vec![],
1395			vec![old1.clone(), old2.clone()],
1396		);
1397
1398		let order = graph.resolve_execution_order_with_replaces().unwrap();
1399
1400		// Should contain squashed and old3, but not old1 or old2
1401		assert_eq!(order.len(), 2);
1402		assert!(order.contains(&squashed));
1403		assert!(order.contains(&old3));
1404		assert!(!order.contains(&old1));
1405		assert!(!order.contains(&old2));
1406
1407		// Squashed should come before old3
1408		let pos_squashed = order.iter().position(|k| k == &squashed).unwrap();
1409		let pos_old3 = order.iter().position(|k| k == &old3).unwrap();
1410		assert!(pos_squashed < pos_old3);
1411	}
1412
1413	#[test]
1414	fn test_find_migration_path_simple() {
1415		let mut graph = MigrationGraph::new();
1416
1417		let key1 = MigrationKey::new("auth", "0001_initial");
1418		let key2 = MigrationKey::new("auth", "0002_add_field");
1419		let key3 = MigrationKey::new("auth", "0003_alter_field");
1420
1421		graph.add_migration(key1.clone(), vec![]);
1422		graph.add_migration(key2.clone(), vec![key1.clone()]);
1423		graph.add_migration(key3.clone(), vec![key2.clone()]);
1424
1425		let path = graph.find_migration_path(&key1, &key3).unwrap();
1426		assert_eq!(path.len(), 3);
1427		assert_eq!(path[0], key1);
1428		assert_eq!(path[1], key2);
1429		assert_eq!(path[2], key3);
1430	}
1431
1432	#[test]
1433	fn test_find_migration_path_same_node() {
1434		let mut graph = MigrationGraph::new();
1435
1436		let key = MigrationKey::new("auth", "0001_initial");
1437		graph.add_migration(key.clone(), vec![]);
1438
1439		let path = graph.find_migration_path(&key, &key).unwrap();
1440		assert_eq!(path.len(), 1);
1441		assert_eq!(path[0], key);
1442	}
1443
1444	#[test]
1445	fn test_find_migration_path_no_path() {
1446		let mut graph = MigrationGraph::new();
1447
1448		let key1 = MigrationKey::new("auth", "0001_initial");
1449		let key2 = MigrationKey::new("users", "0001_initial");
1450
1451		graph.add_migration(key1.clone(), vec![]);
1452		graph.add_migration(key2.clone(), vec![]);
1453
1454		let result = graph.find_migration_path(&key1, &key2);
1455		assert!(result.is_err());
1456	}
1457
1458	#[test]
1459	fn test_find_backward_path() {
1460		let mut graph = MigrationGraph::new();
1461
1462		let key1 = MigrationKey::new("auth", "0001_initial");
1463		let key2 = MigrationKey::new("auth", "0002_add_field");
1464		let key3 = MigrationKey::new("auth", "0003_alter_field");
1465
1466		graph.add_migration(key1.clone(), vec![]);
1467		graph.add_migration(key2.clone(), vec![key1.clone()]);
1468		graph.add_migration(key3.clone(), vec![key2.clone()]);
1469
1470		let path = graph.find_backward_path(&key3, &key1).unwrap();
1471		assert_eq!(path.len(), 3);
1472		assert_eq!(path[0], key3);
1473		assert_eq!(path[1], key2);
1474		assert_eq!(path[2], key1);
1475	}
1476
1477	#[test]
1478	fn test_detect_all_cycles_no_cycle() {
1479		let mut graph = MigrationGraph::new();
1480
1481		let key1 = MigrationKey::new("auth", "0001_initial");
1482		let key2 = MigrationKey::new("auth", "0002_add_field");
1483		let key3 = MigrationKey::new("auth", "0003_alter_field");
1484
1485		graph.add_migration(key1.clone(), vec![]);
1486		graph.add_migration(key2.clone(), vec![key1.clone()]);
1487		graph.add_migration(key3.clone(), vec![key2.clone()]);
1488
1489		let cycles = graph.detect_all_cycles();
1490		assert!(cycles.is_empty());
1491	}
1492
1493	#[test]
1494	fn test_detect_all_cycles_with_cycle() {
1495		let mut graph = MigrationGraph::new();
1496
1497		let key1 = MigrationKey::new("auth", "0001_initial");
1498		let key2 = MigrationKey::new("auth", "0002_add_field");
1499
1500		// Create a cycle
1501		graph.add_migration(key1.clone(), vec![key2.clone()]);
1502		graph.add_migration(key2.clone(), vec![key1.clone()]);
1503
1504		let cycles = graph.detect_all_cycles();
1505		assert!(!cycles.is_empty());
1506		assert_eq!(cycles.len(), 1);
1507	}
1508
1509	#[test]
1510	fn test_complex_replaces_scenario() {
1511		let mut graph = MigrationGraph::new();
1512
1513		// App1 migrations
1514		let app1_0001 = MigrationKey::new("app1", "0001_initial");
1515		let app1_0002 = MigrationKey::new("app1", "0002_add_field");
1516		let app1_squashed = MigrationKey::new("app1", "0001_squashed_0002");
1517
1518		// App2 migrations depending on app1
1519		let app2_0001 = MigrationKey::new("app2", "0001_initial");
1520		let app2_0002 = MigrationKey::new("app2", "0002_add_relation");
1521
1522		graph.add_migration(app1_0001.clone(), vec![]);
1523		graph.add_migration(app1_0002.clone(), vec![app1_0001.clone()]);
1524		graph.add_migration_with_replaces(
1525			app1_squashed.clone(),
1526			vec![],
1527			vec![app1_0001.clone(), app1_0002.clone()],
1528		);
1529		graph.add_migration(app2_0001.clone(), vec![app1_0002.clone()]);
1530		graph.add_migration(app2_0002.clone(), vec![app2_0001.clone()]);
1531
1532		let order = graph.resolve_execution_order_with_replaces().unwrap();
1533
1534		// Should not contain replaced migrations
1535		assert!(!order.contains(&app1_0001));
1536		assert!(!order.contains(&app1_0002));
1537
1538		// Should contain squashed and app2 migrations
1539		assert!(order.contains(&app1_squashed));
1540		assert!(order.contains(&app2_0001));
1541		assert!(order.contains(&app2_0002));
1542
1543		// Squashed should come before app2_0001
1544		let pos_squashed = order.iter().position(|k| k == &app1_squashed).unwrap();
1545		let pos_app2_0001 = order.iter().position(|k| k == &app2_0001).unwrap();
1546		let pos_app2_0002 = order.iter().position(|k| k == &app2_0002).unwrap();
1547
1548		assert!(pos_squashed < pos_app2_0001);
1549		assert!(pos_app2_0001 < pos_app2_0002);
1550	}
1551
1552	#[test]
1553	fn test_multi_app_diamond_dependency() {
1554		let mut graph = MigrationGraph::new();
1555
1556		// Create a diamond dependency across multiple apps
1557		//      auth.0001
1558		//      /        \
1559		// users.0001   posts.0001
1560		//      \        /
1561		//    comments.0001
1562
1563		let auth_0001 = MigrationKey::new("auth", "0001_initial");
1564		let users_0001 = MigrationKey::new("users", "0001_initial");
1565		let posts_0001 = MigrationKey::new("posts", "0001_initial");
1566		let comments_0001 = MigrationKey::new("comments", "0001_initial");
1567
1568		graph.add_migration(auth_0001.clone(), vec![]);
1569		graph.add_migration(users_0001.clone(), vec![auth_0001.clone()]);
1570		graph.add_migration(posts_0001.clone(), vec![auth_0001.clone()]);
1571		graph.add_migration(
1572			comments_0001.clone(),
1573			vec![users_0001.clone(), posts_0001.clone()],
1574		);
1575
1576		let order = graph.topological_sort().unwrap();
1577		assert_eq!(order.len(), 4);
1578
1579		let pos_auth = order.iter().position(|k| k == &auth_0001).unwrap();
1580		let pos_users = order.iter().position(|k| k == &users_0001).unwrap();
1581		let pos_posts = order.iter().position(|k| k == &posts_0001).unwrap();
1582		let pos_comments = order.iter().position(|k| k == &comments_0001).unwrap();
1583
1584		// auth must come before users and posts
1585		assert!(pos_auth < pos_users);
1586		assert!(pos_auth < pos_posts);
1587
1588		// users and posts must come before comments
1589		assert!(pos_users < pos_comments);
1590		assert!(pos_posts < pos_comments);
1591	}
1592
1593	#[test]
1594	fn test_path_with_multiple_routes() {
1595		let mut graph = MigrationGraph::new();
1596
1597		// Create a graph with multiple possible routes
1598		//     A
1599		//    / \
1600		//   B   C
1601		//   |   |
1602		//   D   E
1603		//    \ /
1604		//     F
1605
1606		let a = MigrationKey::new("app", "0001_a");
1607		let b = MigrationKey::new("app", "0002_b");
1608		let c = MigrationKey::new("app", "0003_c");
1609		let d = MigrationKey::new("app", "0004_d");
1610		let e = MigrationKey::new("app", "0005_e");
1611		let f = MigrationKey::new("app", "0006_f");
1612
1613		graph.add_migration(a.clone(), vec![]);
1614		graph.add_migration(b.clone(), vec![a.clone()]);
1615		graph.add_migration(c.clone(), vec![a.clone()]);
1616		graph.add_migration(d.clone(), vec![b.clone()]);
1617		graph.add_migration(e.clone(), vec![c.clone()]);
1618		graph.add_migration(f.clone(), vec![d.clone(), e.clone()]);
1619
1620		// Find path from A to F (should find one of the valid paths)
1621		let path = graph.find_migration_path(&a, &f).unwrap();
1622		assert!(path.len() >= 3);
1623		assert_eq!(path[0], a);
1624		assert_eq!(path[path.len() - 1], f);
1625	}
1626
1627	#[rstest]
1628	fn test_get_leaf_nodes_for_app_single_leaf() {
1629		// Arrange
1630		let mut graph = MigrationGraph::new();
1631		let key1 = MigrationKey::new("auth", "0001_initial");
1632		let key2 = MigrationKey::new("auth", "0002_add_field");
1633		graph.add_migration(key1.clone(), vec![]);
1634		graph.add_migration(key2.clone(), vec![key1]);
1635
1636		// Act
1637		let leaves = graph.get_leaf_nodes_for_app("auth");
1638
1639		// Assert
1640		assert_eq!(leaves.len(), 1);
1641		assert_eq!(leaves[0], &key2);
1642	}
1643
1644	#[rstest]
1645	fn test_get_leaf_nodes_for_app_two_branches() {
1646		// Arrange
1647		let mut graph = MigrationGraph::new();
1648		let key1 = MigrationKey::new("auth", "0001_initial");
1649		let key2a = MigrationKey::new("auth", "0002_add_field");
1650		let key2b = MigrationKey::new("auth", "0002_add_index");
1651		graph.add_migration(key1.clone(), vec![]);
1652		graph.add_migration(key2a.clone(), vec![key1.clone()]);
1653		graph.add_migration(key2b.clone(), vec![key1]);
1654
1655		// Act
1656		let leaves = graph.get_leaf_nodes_for_app("auth");
1657
1658		// Assert
1659		assert_eq!(leaves.len(), 2);
1660		assert_eq!(leaves[0].name, "0002_add_field");
1661		assert_eq!(leaves[1].name, "0002_add_index");
1662	}
1663
1664	#[rstest]
1665	fn test_get_leaf_nodes_for_app_three_branches() {
1666		// Arrange
1667		let mut graph = MigrationGraph::new();
1668		let key1 = MigrationKey::new("auth", "0001_initial");
1669		let key2a = MigrationKey::new("auth", "0002_a");
1670		let key2b = MigrationKey::new("auth", "0002_b");
1671		let key2c = MigrationKey::new("auth", "0002_c");
1672		graph.add_migration(key1.clone(), vec![]);
1673		graph.add_migration(key2a.clone(), vec![key1.clone()]);
1674		graph.add_migration(key2b.clone(), vec![key1.clone()]);
1675		graph.add_migration(key2c.clone(), vec![key1]);
1676
1677		// Act
1678		let leaves = graph.get_leaf_nodes_for_app("auth");
1679
1680		// Assert
1681		assert_eq!(leaves.len(), 3);
1682	}
1683
1684	#[rstest]
1685	fn test_get_leaf_nodes_for_app_nonexistent() {
1686		// Arrange
1687		let mut graph = MigrationGraph::new();
1688		graph.add_migration(MigrationKey::new("auth", "0001_initial"), vec![]);
1689
1690		// Act
1691		let leaves = graph.get_leaf_nodes_for_app("nonexistent");
1692
1693		// Assert
1694		assert!(leaves.is_empty());
1695	}
1696
1697	#[rstest]
1698	fn test_get_leaf_nodes_for_app_empty_graph() {
1699		// Arrange
1700		let graph = MigrationGraph::new();
1701
1702		// Act
1703		let leaves = graph.get_leaf_nodes_for_app("auth");
1704
1705		// Assert
1706		assert!(leaves.is_empty());
1707	}
1708
1709	#[rstest]
1710	fn test_detect_conflicts_no_conflicts() {
1711		// Arrange
1712		let mut graph = MigrationGraph::new();
1713		let key1 = MigrationKey::new("auth", "0001_initial");
1714		let key2 = MigrationKey::new("auth", "0002_add_field");
1715		graph.add_migration(key1.clone(), vec![]);
1716		graph.add_migration(key2.clone(), vec![key1]);
1717
1718		// Act
1719		let conflicts = graph.detect_conflicts();
1720
1721		// Assert
1722		assert!(conflicts.is_empty());
1723	}
1724
1725	#[rstest]
1726	fn test_detect_conflicts_single_app() {
1727		// Arrange
1728		let mut graph = MigrationGraph::new();
1729		let key1 = MigrationKey::new("auth", "0001_initial");
1730		let key2a = MigrationKey::new("auth", "0002_add_field");
1731		let key2b = MigrationKey::new("auth", "0002_add_index");
1732		graph.add_migration(key1.clone(), vec![]);
1733		graph.add_migration(key2a.clone(), vec![key1.clone()]);
1734		graph.add_migration(key2b.clone(), vec![key1]);
1735
1736		// Act
1737		let conflicts = graph.detect_conflicts();
1738
1739		// Assert
1740		assert_eq!(conflicts.len(), 1);
1741		assert!(conflicts.contains_key("auth"));
1742		assert_eq!(conflicts["auth"].len(), 2);
1743	}
1744
1745	#[rstest]
1746	fn test_detect_conflicts_multiple_apps() {
1747		// Arrange
1748		let mut graph = MigrationGraph::new();
1749		let auth1 = MigrationKey::new("auth", "0001_initial");
1750		let auth2a = MigrationKey::new("auth", "0002_a");
1751		let auth2b = MigrationKey::new("auth", "0002_b");
1752		let users1 = MigrationKey::new("users", "0001_initial");
1753		let users2a = MigrationKey::new("users", "0002_a");
1754		let users2b = MigrationKey::new("users", "0002_b");
1755		graph.add_migration(auth1.clone(), vec![]);
1756		graph.add_migration(auth2a.clone(), vec![auth1.clone()]);
1757		graph.add_migration(auth2b.clone(), vec![auth1]);
1758		graph.add_migration(users1.clone(), vec![]);
1759		graph.add_migration(users2a.clone(), vec![users1.clone()]);
1760		graph.add_migration(users2b.clone(), vec![users1]);
1761
1762		// Act
1763		let conflicts = graph.detect_conflicts();
1764
1765		// Assert
1766		assert_eq!(conflicts.len(), 2);
1767		assert!(conflicts.contains_key("auth"));
1768		assert!(conflicts.contains_key("users"));
1769	}
1770
1771	#[rstest]
1772	fn test_detect_conflicts_mixed() {
1773		// Arrange: auth has conflict, users does not
1774		let mut graph = MigrationGraph::new();
1775		let auth1 = MigrationKey::new("auth", "0001_initial");
1776		let auth2a = MigrationKey::new("auth", "0002_a");
1777		let auth2b = MigrationKey::new("auth", "0002_b");
1778		let users1 = MigrationKey::new("users", "0001_initial");
1779		let users2 = MigrationKey::new("users", "0002_add_field");
1780		graph.add_migration(auth1.clone(), vec![]);
1781		graph.add_migration(auth2a.clone(), vec![auth1.clone()]);
1782		graph.add_migration(auth2b.clone(), vec![auth1]);
1783		graph.add_migration(users1.clone(), vec![]);
1784		graph.add_migration(users2.clone(), vec![users1]);
1785
1786		// Act
1787		let conflicts = graph.detect_conflicts();
1788
1789		// Assert
1790		assert_eq!(conflicts.len(), 1);
1791		assert!(conflicts.contains_key("auth"));
1792		assert!(!conflicts.contains_key("users"));
1793	}
1794
1795	#[rstest]
1796	fn test_detect_conflicts_empty_graph() {
1797		// Arrange
1798		let graph = MigrationGraph::new();
1799
1800		// Act
1801		let conflicts = graph.detect_conflicts();
1802
1803		// Assert
1804		assert!(conflicts.is_empty());
1805	}
1806
1807	#[rstest]
1808	fn test_detect_conflicts_cross_app_deps() {
1809		// Arrange: auth branches with cross-app dependency on users
1810		let mut graph = MigrationGraph::new();
1811		let users1 = MigrationKey::new("users", "0001_initial");
1812		let auth1 = MigrationKey::new("auth", "0001_initial");
1813		let auth2a = MigrationKey::new("auth", "0002_add_field");
1814		let auth2b = MigrationKey::new("auth", "0002_add_fk");
1815		graph.add_migration(users1.clone(), vec![]);
1816		graph.add_migration(auth1.clone(), vec![]);
1817		graph.add_migration(auth2a.clone(), vec![auth1.clone()]);
1818		graph.add_migration(auth2b.clone(), vec![auth1, users1]);
1819
1820		// Act
1821		let conflicts = graph.detect_conflicts();
1822
1823		// Assert
1824		assert_eq!(conflicts.len(), 1);
1825		assert!(conflicts.contains_key("auth"));
1826		assert_eq!(conflicts["auth"].len(), 2);
1827	}
1828
1829	#[rstest]
1830	fn test_detect_conflicts_sorted_output() {
1831		// Arrange
1832		let mut graph = MigrationGraph::new();
1833		let key1 = MigrationKey::new("auth", "0001_initial");
1834		let key2c = MigrationKey::new("auth", "0002_c_zebra");
1835		let key2a = MigrationKey::new("auth", "0002_a_alpha");
1836		let key2b = MigrationKey::new("auth", "0002_b_beta");
1837		graph.add_migration(key1.clone(), vec![]);
1838		graph.add_migration(key2c.clone(), vec![key1.clone()]);
1839		graph.add_migration(key2a.clone(), vec![key1.clone()]);
1840		graph.add_migration(key2b.clone(), vec![key1]);
1841
1842		// Act
1843		let conflicts = graph.detect_conflicts();
1844
1845		// Assert
1846		let auth_conflicts = &conflicts["auth"];
1847		assert_eq!(auth_conflicts[0].name, "0002_a_alpha");
1848		assert_eq!(auth_conflicts[1].name, "0002_b_beta");
1849		assert_eq!(auth_conflicts[2].name, "0002_c_zebra");
1850	}
1851}