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}