data_modelling_sdk/models/data_model.rs
1//! DataModel for the SDK
2
3use super::relationship::Relationship;
4use super::table::Table;
5use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7use uuid::Uuid;
8
9/// Data model representing a complete data model with tables and relationships
10///
11/// A `DataModel` is a container for a collection of tables and their relationships.
12/// It represents a workspace or domain within a larger data modeling system.
13///
14/// # Example
15///
16/// ```rust
17/// use data_modelling_sdk::models::DataModel;
18///
19/// let model = DataModel::new(
20/// "MyModel".to_string(),
21/// "/path/to/git".to_string(),
22/// "control.yaml".to_string(),
23/// );
24/// ```
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct DataModel {
27 /// Unique identifier for the model (UUIDv5 based on name and path)
28 pub id: Uuid,
29 /// Model name
30 pub name: String,
31 /// Optional description of the model
32 #[serde(skip_serializing_if = "Option::is_none")]
33 pub description: Option<String>,
34 /// Path to the Git repository directory
35 pub git_directory_path: String,
36 /// Tables in this model
37 #[serde(default)]
38 pub tables: Vec<Table>,
39 /// Relationships between tables
40 #[serde(default)]
41 pub relationships: Vec<Relationship>,
42 /// Path to the control file (relationships.yaml)
43 pub control_file_path: String,
44 /// Path to diagram file if applicable
45 #[serde(skip_serializing_if = "Option::is_none")]
46 pub diagram_file_path: Option<String>,
47 /// Whether this model is in a subfolder
48 #[serde(default)]
49 pub is_subfolder: bool,
50 /// Parent Git directory if this is a subfolder
51 #[serde(skip_serializing_if = "Option::is_none")]
52 pub parent_git_directory: Option<String>,
53 /// Creation timestamp
54 pub created_at: DateTime<Utc>,
55 /// Last update timestamp
56 pub updated_at: DateTime<Utc>,
57}
58
59impl DataModel {
60 /// Create a new data model with the given name and paths
61 ///
62 /// # Arguments
63 ///
64 /// * `name` - The model name
65 /// * `git_directory_path` - Path to the Git repository directory
66 /// * `control_file_path` - Path to the control file (typically "relationships.yaml")
67 ///
68 /// # Returns
69 ///
70 /// A new `DataModel` instance with a UUIDv5 ID (deterministic based on name and path)
71 /// and current timestamps.
72 ///
73 /// # Example
74 ///
75 /// ```rust
76 /// use data_modelling_sdk::models::DataModel;
77 ///
78 /// let model = DataModel::new(
79 /// "MyModel".to_string(),
80 /// "/workspace/models".to_string(),
81 /// "relationships.yaml".to_string(),
82 /// );
83 /// ```
84 pub fn new(name: String, git_directory_path: String, control_file_path: String) -> Self {
85 let now = Utc::now();
86 // Use deterministic UUID v5 based on model name and git path
87 // This avoids requiring random number generation (getrandom/wasm_js)
88 let key = format!("{}:{}", git_directory_path, name);
89 let id = Uuid::new_v5(&Uuid::NAMESPACE_DNS, key.as_bytes());
90 Self {
91 id,
92 name,
93 description: None,
94 git_directory_path,
95 tables: Vec::new(),
96 relationships: Vec::new(),
97 control_file_path,
98 diagram_file_path: None,
99 is_subfolder: false,
100 parent_git_directory: None,
101 created_at: now,
102 updated_at: now,
103 }
104 }
105
106 /// Get a table by its ID
107 ///
108 /// # Arguments
109 ///
110 /// * `table_id` - The UUID of the table to find
111 ///
112 /// # Returns
113 ///
114 /// A reference to the table if found, `None` otherwise.
115 pub fn get_table_by_id(&self, table_id: Uuid) -> Option<&Table> {
116 self.tables.iter().find(|t| t.id == table_id)
117 }
118
119 /// Get a mutable reference to a table by its ID
120 ///
121 /// # Arguments
122 ///
123 /// * `table_id` - The UUID of the table to find
124 ///
125 /// # Returns
126 ///
127 /// A mutable reference to the table if found, `None` otherwise.
128 pub fn get_table_by_id_mut(&mut self, table_id: Uuid) -> Option<&mut Table> {
129 self.tables.iter_mut().find(|t| t.id == table_id)
130 }
131
132 /// Get a table by its name
133 ///
134 /// # Arguments
135 ///
136 /// * `name` - The name of the table to find
137 ///
138 /// # Returns
139 ///
140 /// A reference to the first table with the given name if found, `None` otherwise.
141 ///
142 /// # Note
143 ///
144 /// If multiple tables have the same name (different database_type/catalog/schema),
145 /// use `get_table_by_unique_key` instead.
146 pub fn get_table_by_name(&self, name: &str) -> Option<&Table> {
147 self.tables.iter().find(|t| t.name == name)
148 }
149
150 /// Get a table by its unique key (database_type, name, catalog, schema)
151 ///
152 /// # Arguments
153 ///
154 /// * `database_type` - Optional database type
155 /// * `name` - Table name
156 /// * `catalog_name` - Optional catalog name
157 /// * `schema_name` - Optional schema name
158 ///
159 /// # Returns
160 ///
161 /// A reference to the table if found, `None` otherwise.
162 ///
163 /// # Example
164 ///
165 /// ```rust
166 /// # use data_modelling_sdk::models::DataModel;
167 /// # let model = DataModel::new("test".to_string(), "/path".to_string(), "control.yaml".to_string());
168 /// // Find table in specific schema
169 /// let table = model.get_table_by_unique_key(
170 /// Some("PostgreSQL"),
171 /// "users",
172 /// Some("mydb"),
173 /// Some("public"),
174 /// );
175 /// ```
176 pub fn get_table_by_unique_key(
177 &self,
178 database_type: Option<&str>,
179 name: &str,
180 catalog_name: Option<&str>,
181 schema_name: Option<&str>,
182 ) -> Option<&Table> {
183 let target_key = (
184 database_type.map(|s| s.to_string()),
185 name.to_string(),
186 catalog_name.map(|s| s.to_string()),
187 schema_name.map(|s| s.to_string()),
188 );
189 self.tables
190 .iter()
191 .find(|t| t.get_unique_key() == target_key)
192 }
193
194 /// Get all relationships involving a specific table
195 ///
196 /// # Arguments
197 ///
198 /// * `table_id` - The UUID of the table
199 ///
200 /// # Returns
201 ///
202 /// A vector of references to relationships where the table is either the source or target.
203 ///
204 /// # Example
205 ///
206 /// ```rust
207 /// # use data_modelling_sdk::models::DataModel;
208 /// # let model = DataModel::new("test".to_string(), "/path".to_string(), "control.yaml".to_string());
209 /// # let table_id = uuid::Uuid::new_v4();
210 /// // Get all relationships for a table
211 /// let relationships = model.get_relationships_for_table(table_id);
212 /// ```
213 pub fn get_relationships_for_table(&self, table_id: Uuid) -> Vec<&Relationship> {
214 self.relationships
215 .iter()
216 .filter(|r| r.source_table_id == table_id || r.target_table_id == table_id)
217 .collect()
218 }
219}