1use indexmap::IndexMap;
18use serde::{Deserialize, Serialize};
19
20use crate::error::{Error, Result};
21use crate::models::types::DataType;
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
25pub struct TableId(pub u32);
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
29pub struct ColumnId(pub u32);
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
33pub struct ConstraintId(pub u32);
34
35#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
37pub struct Column {
38 pub id: ColumnId,
40 pub name: String,
42 pub data_type: DataType,
44 pub nullable: bool,
46 pub is_primary: bool,
48 pub is_foreign: bool,
50 pub is_unique: bool,
52 pub default: Option<String>,
54 pub comment: String,
56}
57
58impl Column {
59 pub fn new(id: ColumnId, name: impl Into<String>, data_type: DataType) -> Self {
61 Self {
62 id,
63 name: name.into(),
64 data_type,
65 nullable: false,
66 is_primary: false,
67 is_foreign: false,
68 is_unique: false,
69 default: None,
70 comment: String::new(),
71 }
72 }
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
77pub enum ReferentialAction {
78 #[default]
81 NoAction,
82 Restrict,
84 Cascade,
86 SetNull,
88 SetDefault,
90}
91
92#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
95pub struct ForeignKey {
96 pub columns: Vec<ColumnId>,
98 pub references_table: TableId,
100 pub references_columns: Vec<ColumnId>,
102 pub on_update: ReferentialAction,
104 pub on_delete: ReferentialAction,
106}
107
108#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
110pub enum ConstraintKind {
111 PrimaryKey {
113 columns: Vec<ColumnId>,
115 },
116 Unique {
118 columns: Vec<ColumnId>,
120 },
121 ForeignKey(ForeignKey),
123 Check {
125 expression: String,
127 },
128}
129
130#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
132pub struct Constraint {
133 pub id: ConstraintId,
135 pub name: Option<String>,
138 pub kind: ConstraintKind,
140}
141
142impl Constraint {
143 pub fn is_primary_key(&self) -> bool {
145 matches!(self.kind, ConstraintKind::PrimaryKey { .. })
146 }
147
148 pub fn is_foreign_key(&self) -> bool {
150 matches!(self.kind, ConstraintKind::ForeignKey(_))
151 }
152}
153
154#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
156pub struct Table {
157 pub id: TableId,
159 pub name: String,
161 pub columns: IndexMap<ColumnId, Column>,
163 pub constraints: IndexMap<ConstraintId, Constraint>,
165 pub comment: String,
167}
168
169impl Table {
170 pub fn columns_iter(&self) -> impl Iterator<Item = &Column> {
172 self.columns.values()
173 }
174
175 pub fn primary_key(&self) -> Option<&Constraint> {
177 self.constraints.values().find(|c| c.is_primary_key())
178 }
179
180 pub fn primary_key_columns(&self) -> &[ColumnId] {
183 match self.primary_key().map(|c| &c.kind) {
184 Some(ConstraintKind::PrimaryKey { columns }) => columns,
185 _ => &[],
186 }
187 }
188
189 pub fn column(&self, id: ColumnId) -> Option<&Column> {
191 self.columns.get(&id)
192 }
193}
194
195#[derive(Debug, Clone, Default, Serialize, Deserialize)]
197pub struct LogicalModel {
198 pub name: String,
200 next_id: u32,
202 pub tables: IndexMap<TableId, Table>,
204}
205
206impl LogicalModel {
207 pub fn new(name: impl Into<String>) -> Self {
209 Self { name: name.into(), ..Self::default() }
210 }
211
212 pub(crate) fn mint(&mut self) -> u32 {
213 self.next_id = self.next_id.checked_add(1).expect("ID space exhausted");
214 self.next_id
215 }
216
217 pub fn add_table(&mut self, name: impl Into<String>) -> TableId {
219 let id = TableId(self.mint());
220 self.tables.insert(
221 id,
222 Table {
223 id,
224 name: name.into(),
225 columns: IndexMap::new(),
226 constraints: IndexMap::new(),
227 comment: String::new(),
228 },
229 );
230 id
231 }
232
233 pub fn table(&self, id: TableId) -> Result<&Table> {
235 self.tables
236 .get(&id)
237 .ok_or_else(|| Error::UnknownReference { kind: "table", id: format!("{}", id.0) })
238 }
239
240 pub fn table_mut(&mut self, id: TableId) -> Result<&mut Table> {
242 self.tables
243 .get_mut(&id)
244 .ok_or_else(|| Error::UnknownReference { kind: "table", id: format!("{}", id.0) })
245 }
246
247 pub fn add_column(
249 &mut self,
250 table: TableId,
251 name: impl Into<String>,
252 data_type: DataType,
253 ) -> Result<ColumnId> {
254 let id = ColumnId(self.mint());
255 let column = Column::new(id, name, data_type);
256 self.table_mut(table)?.columns.insert(id, column);
257 Ok(id)
258 }
259
260 pub fn set_primary_key(&mut self, table: TableId, columns: Vec<ColumnId>) -> Result<ConstraintId> {
262 {
263 let t = self.table_mut(table)?;
264 for c in &columns {
265 let col = t.columns.get_mut(c).ok_or_else(|| Error::UnknownReference {
266 kind: "column",
267 id: format!("{}", c.0),
268 })?;
269 col.is_primary = true;
270 col.nullable = false;
271 }
272 let existing_pk: Vec<ConstraintId> = t
273 .constraints
274 .iter()
275 .filter_map(|(id, c)| c.is_primary_key().then_some(*id))
276 .collect();
277 for id in existing_pk {
278 t.constraints.shift_remove(&id);
279 }
280 }
281 let id = ConstraintId(self.mint());
282 let t = self.table_mut(table)?;
283 t.constraints
284 .insert(id, Constraint { id, name: None, kind: ConstraintKind::PrimaryKey { columns } });
285 Ok(id)
286 }
287
288 pub fn add_foreign_key(&mut self, table: TableId, fk: ForeignKey) -> Result<ConstraintId> {
290 {
291 let t = self.table_mut(table)?;
292 for c in &fk.columns {
293 let col = t.columns.get_mut(c).ok_or_else(|| Error::UnknownReference {
294 kind: "column",
295 id: format!("{}", c.0),
296 })?;
297 col.is_foreign = true;
298 }
299 }
300 let id = ConstraintId(self.mint());
301 let t = self.table_mut(table)?;
302 t.constraints
303 .insert(id, Constraint { id, name: None, kind: ConstraintKind::ForeignKey(fk) });
304 Ok(id)
305 }
306
307 pub fn add_unique(&mut self, table: TableId, columns: Vec<ColumnId>) -> Result<ConstraintId> {
309 let id = ConstraintId(self.mint());
310 let t = self.table_mut(table)?;
311 if columns.len() == 1 {
312 if let Some(col) = t.columns.get_mut(&columns[0]) {
313 col.is_unique = true;
314 }
315 }
316 t.constraints
317 .insert(id, Constraint { id, name: None, kind: ConstraintKind::Unique { columns } });
318 Ok(id)
319 }
320}
321
322#[cfg(test)]
323mod tests {
324 use super::*;
325
326 #[test]
327 fn build_table_with_pk_and_fk() {
328 let mut m = LogicalModel::new("shop");
329 let customer = m.add_table("Customer");
330 let cid = m.add_column(customer, "id", DataType::Integer).unwrap();
331 m.add_column(customer, "name", DataType::Varchar(120)).unwrap();
332 m.set_primary_key(customer, vec![cid]).unwrap();
333
334 let order = m.add_table("Order");
335 let oid = m.add_column(order, "id", DataType::Integer).unwrap();
336 let ocust = m.add_column(order, "customer_id", DataType::Integer).unwrap();
337 m.set_primary_key(order, vec![oid]).unwrap();
338 m.add_foreign_key(
339 order,
340 ForeignKey {
341 columns: vec![ocust],
342 references_table: customer,
343 references_columns: vec![cid],
344 on_update: ReferentialAction::NoAction,
345 on_delete: ReferentialAction::NoAction,
346 },
347 )
348 .unwrap();
349
350 let order_t = m.table(order).unwrap();
351 assert_eq!(order_t.primary_key_columns(), &[oid]);
352 assert!(order_t.column(ocust).unwrap().is_foreign);
353 assert!(!order_t.column(ocust).unwrap().nullable);
354 }
355}