print page poggers2
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
pub mod search;
|
||||
pub mod grpc_error;
|
||||
pub mod relationship;
|
||||
|
||||
pub mod proto {
|
||||
pub mod komp_ac {
|
||||
|
||||
97
common/src/relationship.rs
Normal file
97
common/src/relationship.rs
Normal file
@@ -0,0 +1,97 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{HashMap, HashSet, VecDeque};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct RelationshipHop {
|
||||
pub from_table: String,
|
||||
pub to_table: String,
|
||||
pub fk_source_table: String,
|
||||
pub fk_column: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct RelationshipLink {
|
||||
pub source_table: String,
|
||||
pub target_table: String,
|
||||
}
|
||||
|
||||
pub fn resolve_relationship_path(
|
||||
start: &str,
|
||||
end: &str,
|
||||
links: &[RelationshipLink],
|
||||
) -> Result<Vec<RelationshipHop>, String> {
|
||||
if start == end {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
let mut queue = VecDeque::from([(start.to_string(), Vec::new())]);
|
||||
let mut best_depth: HashMap<String, usize> = HashMap::from([(start.to_string(), 0)]);
|
||||
let mut matches = Vec::new();
|
||||
let mut match_depth = None;
|
||||
|
||||
while let Some((table, path)) = queue.pop_front() {
|
||||
if match_depth.is_some_and(|depth| path.len() >= depth) {
|
||||
continue;
|
||||
}
|
||||
for link in links {
|
||||
let next = if link.source_table == table {
|
||||
Some(link.target_table.as_str())
|
||||
} else if link.target_table == table {
|
||||
Some(link.source_table.as_str())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let Some(next) = next else { continue };
|
||||
if path
|
||||
.iter()
|
||||
.any(|hop: &RelationshipHop| hop.from_table == next)
|
||||
|| next == start
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let mut next_path = path.clone();
|
||||
next_path.push(RelationshipHop {
|
||||
from_table: table.clone(),
|
||||
to_table: next.to_string(),
|
||||
fk_source_table: link.source_table.clone(),
|
||||
fk_column: format!("{}_id", link.target_table),
|
||||
});
|
||||
if next == end {
|
||||
match_depth = Some(next_path.len());
|
||||
matches.push(next_path);
|
||||
continue;
|
||||
}
|
||||
let depth = next_path.len();
|
||||
if best_depth.get(next).is_none_or(|known| depth <= *known) {
|
||||
best_depth.insert(next.to_string(), depth);
|
||||
queue.push_back((next.to_string(), next_path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match matches.as_slice() {
|
||||
[path] => Ok(path.clone()),
|
||||
[] => Err(format!("No FK path from '{}' to '{}'", start, end)),
|
||||
_ => Err(format!(
|
||||
"Multiple equally short FK paths from '{}' to '{}'",
|
||||
start, end
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve_anchored_relationship_path(
|
||||
source: &str,
|
||||
target: &str,
|
||||
anchor: &str,
|
||||
links: &[RelationshipLink],
|
||||
) -> Result<Vec<RelationshipHop>, String> {
|
||||
let mut path = resolve_relationship_path(source, anchor, links)?;
|
||||
path.extend(resolve_relationship_path(anchor, target, links)?);
|
||||
let mut tables = HashSet::from([source.to_string()]);
|
||||
if path.iter().any(|hop| !tables.insert(hop.to_table.clone())) {
|
||||
return Err(format!(
|
||||
"The FK route from '{}' to '{}' via '{}' revisits a table",
|
||||
source, target, anchor
|
||||
));
|
||||
}
|
||||
Ok(path)
|
||||
}
|
||||
Reference in New Issue
Block a user