From e4ed4bb40ea7b344df43cc0d8ed9a5a0f92c33b8 Mon Sep 17 00:00:00 2001 From: Priec Date: Tue, 21 Jul 2026 18:18:32 +0200 Subject: [PATCH] print page poggers2 --- client | 2 +- common/src/lib.rs | 1 + common/src/relationship.rs | 97 ++++++++++++++++++++++++++++++++++++++ server | 2 +- 4 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 common/src/relationship.rs diff --git a/client b/client index 2465eb9..f85bc82 160000 --- a/client +++ b/client @@ -1 +1 @@ -Subproject commit 2465eb9ca99946aab8e2b435d0e73907a989c163 +Subproject commit f85bc82e85d14c2828e450ceea302d2b6581c950 diff --git a/common/src/lib.rs b/common/src/lib.rs index 56c584c..44154e1 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -2,6 +2,7 @@ pub mod search; pub mod grpc_error; +pub mod relationship; pub mod proto { pub mod komp_ac { diff --git a/common/src/relationship.rs b/common/src/relationship.rs new file mode 100644 index 0000000..5b5f347 --- /dev/null +++ b/common/src/relationship.rs @@ -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, String> { + if start == end { + return Ok(Vec::new()); + } + let mut queue = VecDeque::from([(start.to_string(), Vec::new())]); + let mut best_depth: HashMap = 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, 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) +} diff --git a/server b/server index af3e1cd..56c988c 160000 --- a/server +++ b/server @@ -1 +1 @@ -Subproject commit af3e1cd2b371f869cf057b0f99d98d2569e3d039 +Subproject commit 56c988c2e397baadf2eefce9204073c32d17ccc2