print page poggers2

This commit is contained in:
Priec
2026-07-21 18:18:32 +02:00
parent e4ce518427
commit e4ed4bb40e
4 changed files with 100 additions and 2 deletions

2
client

Submodule client updated: 2465eb9ca9...f85bc82e85

View File

@@ -2,6 +2,7 @@
pub mod search; pub mod search;
pub mod grpc_error; pub mod grpc_error;
pub mod relationship;
pub mod proto { pub mod proto {
pub mod komp_ac { pub mod komp_ac {

View 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)
}

2
server

Submodule server updated: af3e1cd2b3...56c988c2e3