Skip to main content

vantage_table/table/impls/
references.rs

1//! Table relationship methods for defining and traversing references.
2
3use std::sync::Arc;
4
5use indexmap::IndexMap;
6use vantage_core::{Result, error};
7use vantage_types::Entity;
8
9use crate::{
10    references::{HasMany, HasOne, Reference},
11    table::Table,
12    traits::table_source::TableSource,
13};
14
15mod contained;
16mod traversal;
17
18impl<T: TableSource + 'static, E: Entity<T::Value> + 'static> Table<T, E> {
19    /// Define a one-to-one relationship.
20    ///
21    /// ```rust,ignore
22    /// .with_one("bakery", "bakery_id", Bakery::postgres_table)
23    /// ```
24    pub fn with_one<E2: Entity<T::Value> + 'static>(
25        mut self,
26        relation: &str,
27        foreign_key: &str,
28        build_target: impl Fn(T) -> Table<T, E2> + Send + Sync + 'static,
29    ) -> Self
30    where
31        T::Value: Into<ciborium::Value> + From<ciborium::Value>,
32        T::Id: std::fmt::Display + From<String>,
33    {
34        let reference = HasOne::<T, E, E2>::new(foreign_key, build_target);
35        self.add_ref(relation, Box::new(reference));
36        self
37    }
38
39    /// Define a one-to-many relationship.
40    ///
41    /// ```rust,ignore
42    /// .with_many("orders", "client_id", Order::postgres_table)
43    /// ```
44    pub fn with_many<E2: Entity<T::Value> + 'static>(
45        mut self,
46        relation: &str,
47        foreign_key: &str,
48        build_target: impl Fn(T) -> Table<T, E2> + Send + Sync + 'static,
49    ) -> Self
50    where
51        T::Value: Into<ciborium::Value> + From<ciborium::Value>,
52        T::Id: std::fmt::Display + From<String>,
53    {
54        let reference = HasMany::<T, E, E2>::new(foreign_key, build_target);
55        self.add_ref(relation, Box::new(reference));
56        self
57    }
58
59    pub(crate) fn add_ref(&mut self, relation: &str, reference: Box<dyn Reference>) {
60        self.add_ref_arc(relation, Arc::from(reference));
61    }
62
63    /// Insert an already-shared reference. Used to inherit relations when
64    /// deriving a table from another (the same `Arc` is shared, not rebuilt).
65    pub(crate) fn add_ref_arc(&mut self, relation: &str, reference: Arc<dyn Reference>) {
66        self.refs
67            .get_or_insert_with(IndexMap::new)
68            .insert(relation.to_string(), reference);
69    }
70
71    /// Copy relations from another table, sharing the underlying `Arc`s. With
72    /// `names = None`, copies all relations; otherwise only the listed ones.
73    /// An inherited relation keeps working as long as the derived table still
74    /// projects the column its foreign key references.
75    pub fn copy_relations_from<E2: Entity<T::Value> + 'static>(
76        &mut self,
77        other: &Table<T, E2>,
78        names: Option<&[&str]>,
79    ) {
80        let Some(refs) = &other.refs else {
81            return;
82        };
83        for (name, reference) in refs {
84            if names.is_some_and(|ns| !ns.contains(&name.as_str())) {
85                continue;
86            }
87            self.add_ref_arc(name, reference.clone());
88        }
89    }
90
91    pub fn references(&self) -> Vec<String> {
92        self.refs
93            .as_ref()
94            .map(|refs| refs.keys().cloned().collect())
95            .unwrap_or_default()
96    }
97
98    fn lookup_ref(&self, relation: &str) -> Result<&dyn Reference> {
99        let table_name = self.table_name();
100        let refs = self
101            .refs
102            .as_ref()
103            .ok_or_else(|| error!("No references defined on table", table = table_name))?;
104
105        let reference = refs.get(relation).ok_or_else(|| {
106            error!(
107                "Reference not found on table",
108                relation = relation,
109                table = table_name
110            )
111        })?;
112
113        Ok(reference.as_ref())
114    }
115
116    /// A registered relation as Vista metadata. Kind and key questions are
117    /// read off this, so they can't disagree with what `vista_references`
118    /// publishes. The target name stays empty: nothing here reads it, and
119    /// naming it would build the target.
120    fn ref_shape(&self, relation: &str) -> Result<vantage_vista::Reference> {
121        let reference = self.lookup_ref(relation)?;
122        Ok(reference.vista_reference(relation, String::new(), &self.id_field_name()))
123    }
124
125    /// Look up cardinality for a registered relation.
126    pub fn ref_cardinality(&self, relation: &str) -> Result<vantage_vista::ReferenceKind> {
127        Ok(self.ref_shape(relation)?.kind)
128    }
129
130    /// Look up the foreign-key/link field for a registered relation. For a
131    /// `has_one` this is the source-side column carrying the link — the field
132    /// a backend-native traversal (e.g. a SurrealDB idiom path) descends
133    /// through, which need not match the relation's registry name. For a
134    /// `has_many` it is the target-side column carrying the parent's id.
135    pub fn ref_foreign_key(&self, relation: &str) -> Result<String> {
136        let shape = self.ref_shape(relation)?;
137        let key = shape
138            .keys
139            .first()
140            .ok_or_else(|| error!("Reference has no join key", relation = relation))?;
141        Ok(match shape.kind {
142            vantage_vista::ReferenceKind::HasMany => key.to.clone().unwrap_or_default(),
143            _ => key.from.clone(),
144        })
145    }
146
147    /// List all registered relations with their cardinality.
148    pub fn ref_kinds(&self) -> Vec<(String, vantage_vista::ReferenceKind)> {
149        let Some(refs) = &self.refs else {
150            return Vec::new();
151        };
152        let source_id = self.id_field_name();
153        refs.iter()
154            .map(|(name, r)| {
155                let kind = r.vista_reference(name, String::new(), &source_id).kind;
156                (name.clone(), kind)
157            })
158            .collect()
159    }
160}