Skip to main content

toolu_orm_core/
relation.rs

1//! Core types for table relations used by relational query building.
2//!
3//! # Public API
4//!
5//! - [`RelationKind`], [`JoinColumn`], [`ThroughDef`], [`RelationDef`]
6//! - [`RelationRegistry`], helpers [`one`], [`many`], [`many_through`]
7
8use std::collections::BTreeMap;
9
10/// The kind of relation between two tables.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum RelationKind {
13  /// Exactly zero or one related row (foreign key on the source table).
14  One,
15  /// Zero or more related rows (foreign key on the target table).
16  Many,
17}
18
19/// A single column pair in a join condition.
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct JoinColumn {
22  /// Column name on the source (local) table.
23  pub local: String,
24  /// Column name on the target (foreign) table.
25  pub foreign: String,
26}
27
28/// Configuration for many-to-many relations via a junction table.
29#[derive(Debug, Clone, PartialEq, Eq)]
30pub struct ThroughDef {
31  /// The junction (pivot) table name.
32  pub junction_table: String,
33  /// Column in the junction table that references the source table.
34  pub local_column: String,
35  /// Column in the junction table that references the target table.
36  pub foreign_column: String,
37}
38
39/// A complete relation definition between two tables.
40#[derive(Debug, Clone, PartialEq, Eq)]
41pub struct RelationDef {
42  /// The field name used in result structs (e.g. "posts", "author").
43  pub field_name: String,
44  /// Whether this is a one-to-one/many-to-one or one-to-many relation.
45  pub kind: RelationKind,
46  /// The target table name.
47  pub target_table: String,
48  /// The join column pairs (local column -> foreign column).
49  pub join_columns: Vec<JoinColumn>,
50  /// For many-to-many: the junction table configuration.
51  pub through: Option<ThroughDef>,
52}
53
54/// Create a `One` (belongs-to / has-one) relation.
55///
56/// `columns` is a slice of `(local_column, foreign_column)` pairs.
57pub fn one(field_name: &str, target_table: &str, columns: &[(&str, &str)]) -> RelationDef {
58  RelationDef {
59    field_name: field_name.to_owned(),
60    kind: RelationKind::One,
61    target_table: target_table.to_owned(),
62    join_columns: columns
63      .iter()
64      .map(|(local, foreign)| JoinColumn {
65        local: (*local).to_owned(),
66        foreign: (*foreign).to_owned(),
67      })
68      .collect(),
69    through: None,
70  }
71}
72
73/// Create a `Many` (has-many) relation.
74///
75/// `columns` is a slice of `(local_column, foreign_column)` pairs.
76pub fn many(field_name: &str, target_table: &str, columns: &[(&str, &str)]) -> RelationDef {
77  RelationDef {
78    field_name: field_name.to_owned(),
79    kind: RelationKind::Many,
80    target_table: target_table.to_owned(),
81    join_columns: columns
82      .iter()
83      .map(|(local, foreign)| JoinColumn {
84        local: (*local).to_owned(),
85        foreign: (*foreign).to_owned(),
86      })
87      .collect(),
88    through: None,
89  }
90}
91
92/// Create a `Many` relation through a junction table (many-to-many).
93pub fn many_through(
94  field_name: &str,
95  target_table: &str,
96  columns: &[(&str, &str)],
97  junction_table: &str,
98  junction_local: &str,
99  junction_foreign: &str,
100) -> RelationDef {
101  RelationDef {
102    field_name: field_name.to_owned(),
103    kind: RelationKind::Many,
104    target_table: target_table.to_owned(),
105    join_columns: columns
106      .iter()
107      .map(|(local, foreign)| JoinColumn {
108        local: (*local).to_owned(),
109        foreign: (*foreign).to_owned(),
110      })
111      .collect(),
112    through: Some(ThroughDef {
113      junction_table: junction_table.to_owned(),
114      local_column: junction_local.to_owned(),
115      foreign_column: junction_foreign.to_owned(),
116    }),
117  }
118}
119
120/// Registry of relation definitions, keyed by source table name.
121#[derive(Debug, Clone)]
122pub struct RelationRegistry {
123  entries: BTreeMap<String, Vec<RelationDef>>,
124}
125
126impl RelationRegistry {
127  /// Create an empty registry.
128  pub fn new() -> Self {
129    Self {
130      entries: BTreeMap::new(),
131    }
132  }
133
134  /// Register relations for a source table (overwrites prior entry).
135  pub fn register(&mut self, source_table: &str, relations: Vec<RelationDef>) {
136    self.entries.insert(source_table.to_owned(), relations);
137  }
138
139  /// Relations for a source table, if any.
140  pub fn get(&self, source_table: &str) -> Option<&[RelationDef]> {
141    self.entries.get(source_table).map(Vec::as_slice)
142  }
143}
144
145impl Default for RelationRegistry {
146  fn default() -> Self {
147    Self::new()
148  }
149}