use std::collections::HashSet;
use crate::DbError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum CyclePolicy {
Error,
#[default]
Truncate,
AllowWithDepthLimit(usize),
}
pub struct CycleDetector {
policy: CyclePolicy,
visited: HashSet<String>,
current_depth: usize,
path: Vec<String>,
}
impl CycleDetector {
pub fn new(policy: CyclePolicy) -> Self {
Self {
policy,
visited: HashSet::new(),
current_depth: 0,
path: Vec::new(),
}
}
pub fn check(&mut self, entity_type: &str, relation_name: &str) -> Result<bool, DbError> {
let key = format!("{}::{}", entity_type, relation_name);
if self.visited.contains(&key) {
return match self.policy {
CyclePolicy::Error => {
self.path.push(key.clone());
Err(DbError::InvalidInput(format!(
"检测到循环引用: {}",
self.path.join(" → ")
)))
}
CyclePolicy::Truncate => Ok(false),
CyclePolicy::AllowWithDepthLimit(max_depth) => {
if self.current_depth >= max_depth {
Ok(false)
} else {
Ok(true)
}
}
};
}
if let CyclePolicy::AllowWithDepthLimit(max_depth) = self.policy {
if self.current_depth >= max_depth {
return Ok(false);
}
}
Ok(true)
}
pub fn enter(&mut self, entity_type: &str, relation_name: &str) {
let key = format!("{}::{}", entity_type, relation_name);
self.visited.insert(key.clone());
self.path.push(key);
self.current_depth += 1;
}
pub fn leave(&mut self) {
self.path.pop();
self.current_depth = self.current_depth.saturating_sub(1);
}
pub fn depth(&self) -> usize {
self.current_depth
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cycle_policy_default() {
assert_eq!(CyclePolicy::default(), CyclePolicy::Truncate);
}
#[test]
fn test_cycle_detector_no_cycle() {
let mut detector = CycleDetector::new(CyclePolicy::Error);
assert!(detector.check("User", "orders").unwrap());
detector.enter("User", "orders");
assert!(detector.check("Order", "items").unwrap());
detector.enter("Order", "items");
detector.leave();
detector.leave();
}
#[test]
fn test_cycle_detector_error_policy() {
let mut detector = CycleDetector::new(CyclePolicy::Error);
detector.enter("User", "orders");
assert!(detector.check("Order", "user").unwrap());
detector.enter("Order", "user");
let result = detector.check("User", "orders");
assert!(result.is_err());
}
#[test]
fn test_cycle_detector_truncate_policy() {
let mut detector = CycleDetector::new(CyclePolicy::Truncate);
detector.enter("User", "orders");
detector.enter("Order", "user");
let result = detector.check("User", "orders").unwrap();
assert!(!result);
}
#[test]
fn test_cycle_detector_depth_limit() {
let mut detector = CycleDetector::new(CyclePolicy::AllowWithDepthLimit(3));
detector.enter("User", "orders");
assert_eq!(detector.depth(), 1);
detector.enter("Order", "items");
assert_eq!(detector.depth(), 2);
detector.enter("OrderItem", "product");
assert_eq!(detector.depth(), 3);
let result = detector.check("Product", "category").unwrap();
assert!(!result);
}
#[test]
fn test_cycle_detector_different_relation_no_false_positive() {
let mut detector = CycleDetector::new(CyclePolicy::Error);
detector.enter("User", "orders");
let result = detector.check("User", "manager");
assert!(result.unwrap());
}
}