data_modelling_sdk/models/
column.rs

1//! Column model for the SDK
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6/// Foreign key reference to another table's column
7#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
8pub struct ForeignKey {
9    /// Target table ID (UUID as string)
10    pub table_id: String,
11    /// Column name in the target table
12    pub column_name: String,
13}
14
15/// ODCS v3.1.0 Relationship at property level
16#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
17pub struct PropertyRelationship {
18    /// Relationship type (e.g., "foreignKey", "parent", "child")
19    #[serde(rename = "type")]
20    pub relationship_type: String,
21    /// Target reference (e.g., "definitions/order_id", "schema/id/properties/id")
22    pub to: String,
23}
24
25/// ODCS v3.1.0 logicalTypeOptions for additional type metadata
26#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
27#[serde(rename_all = "camelCase")]
28pub struct LogicalTypeOptions {
29    /// Minimum length for strings
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub min_length: Option<i64>,
32    /// Maximum length for strings
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub max_length: Option<i64>,
35    /// Regex pattern for strings
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub pattern: Option<String>,
38    /// Format hint (e.g., "email", "uuid", "uri")
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub format: Option<String>,
41    /// Minimum value for numbers/dates
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub minimum: Option<serde_json::Value>,
44    /// Maximum value for numbers/dates
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub maximum: Option<serde_json::Value>,
47    /// Exclusive minimum for numbers
48    #[serde(skip_serializing_if = "Option::is_none")]
49    pub exclusive_minimum: Option<serde_json::Value>,
50    /// Exclusive maximum for numbers
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub exclusive_maximum: Option<serde_json::Value>,
53    /// Precision for decimals
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub precision: Option<i32>,
56    /// Scale for decimals
57    #[serde(skip_serializing_if = "Option::is_none")]
58    pub scale: Option<i32>,
59}
60
61impl LogicalTypeOptions {
62    pub fn is_empty(&self) -> bool {
63        self.min_length.is_none()
64            && self.max_length.is_none()
65            && self.pattern.is_none()
66            && self.format.is_none()
67            && self.minimum.is_none()
68            && self.maximum.is_none()
69            && self.exclusive_minimum.is_none()
70            && self.exclusive_maximum.is_none()
71            && self.precision.is_none()
72            && self.scale.is_none()
73    }
74}
75
76/// Authoritative definition reference (ODCS v3.1.0)
77#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
78pub struct AuthoritativeDefinition {
79    /// Type of the reference (e.g., "businessDefinition", "transformationImplementation")
80    #[serde(rename = "type")]
81    pub definition_type: String,
82    /// URL to the authoritative definition
83    pub url: String,
84}
85
86/// Column model representing a field in a table
87///
88/// A column defines a single field with a data type, constraints, and optional metadata.
89/// This model supports all ODCS v3.1.0 property fields to ensure no data loss during import/export.
90///
91/// # Example
92///
93/// ```rust
94/// use data_modelling_sdk::models::Column;
95///
96/// let column = Column::new("id".to_string(), "INT".to_string());
97/// ```
98#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
99#[serde(rename_all = "camelCase")]
100pub struct Column {
101    // === Core Identity Fields ===
102    /// Stable technical identifier (ODCS: id)
103    #[serde(skip_serializing_if = "Option::is_none")]
104    pub id: Option<String>,
105    /// Column name (ODCS: name)
106    pub name: String,
107    /// Business name for the column (ODCS: businessName)
108    #[serde(skip_serializing_if = "Option::is_none")]
109    pub business_name: Option<String>,
110    /// Column description/documentation (ODCS: description)
111    #[serde(default)]
112    pub description: String,
113
114    // === Type Information ===
115    /// Logical data type (ODCS: logicalType - e.g., "string", "integer", "number")
116    #[serde(rename = "dataType")]
117    pub data_type: String,
118    /// Physical database type (ODCS: physicalType - e.g., "VARCHAR(100)", "BIGINT")
119    #[serde(skip_serializing_if = "Option::is_none")]
120    pub physical_type: Option<String>,
121    /// Physical name in the data source (ODCS: physicalName)
122    #[serde(skip_serializing_if = "Option::is_none")]
123    pub physical_name: Option<String>,
124    /// Additional type options (ODCS: logicalTypeOptions)
125    #[serde(skip_serializing_if = "Option::is_none")]
126    pub logical_type_options: Option<LogicalTypeOptions>,
127
128    // === Key Constraints ===
129    /// Whether this column is part of the primary key (ODCS: primaryKey)
130    #[serde(default)]
131    pub primary_key: bool,
132    /// Position in composite primary key, 1-based (ODCS: primaryKeyPosition)
133    #[serde(skip_serializing_if = "Option::is_none")]
134    pub primary_key_position: Option<i32>,
135    /// Whether the column contains unique values (ODCS: unique)
136    #[serde(default)]
137    pub unique: bool,
138    /// Whether the column allows NULL values (inverse of ODCS: required)
139    #[serde(default = "default_true")]
140    pub nullable: bool,
141
142    // === Partitioning & Clustering ===
143    /// Whether the column is used for partitioning (ODCS: partitioned)
144    #[serde(default)]
145    pub partitioned: bool,
146    /// Position in partition key, 1-based (ODCS: partitionKeyPosition)
147    #[serde(skip_serializing_if = "Option::is_none")]
148    pub partition_key_position: Option<i32>,
149    /// Whether the column is used for clustering
150    #[serde(default)]
151    pub clustered: bool,
152
153    // === Data Classification & Security ===
154    /// Data classification level (ODCS: classification - e.g., "confidential", "public")
155    #[serde(skip_serializing_if = "Option::is_none")]
156    pub classification: Option<String>,
157    /// Whether this is a critical data element (ODCS: criticalDataElement)
158    #[serde(default)]
159    pub critical_data_element: bool,
160    /// Name of the encrypted version of this column (ODCS: encryptedName)
161    #[serde(skip_serializing_if = "Option::is_none")]
162    pub encrypted_name: Option<String>,
163
164    // === Transformation Metadata ===
165    /// Source objects used in transformation (ODCS: transformSourceObjects)
166    #[serde(default, skip_serializing_if = "Vec::is_empty")]
167    pub transform_source_objects: Vec<String>,
168    /// Transformation logic/expression (ODCS: transformLogic)
169    #[serde(skip_serializing_if = "Option::is_none")]
170    pub transform_logic: Option<String>,
171    /// Human-readable transformation description (ODCS: transformDescription)
172    #[serde(skip_serializing_if = "Option::is_none")]
173    pub transform_description: Option<String>,
174
175    // === Examples & Documentation ===
176    /// Example values for this column (ODCS: examples)
177    #[serde(default, skip_serializing_if = "Vec::is_empty")]
178    pub examples: Vec<serde_json::Value>,
179    /// Default value for the column
180    #[serde(skip_serializing_if = "Option::is_none")]
181    pub default_value: Option<serde_json::Value>,
182
183    // === Relationships & References ===
184    /// ODCS v3.1.0 relationships (property-level references)
185    #[serde(default, skip_serializing_if = "Vec::is_empty")]
186    pub relationships: Vec<PropertyRelationship>,
187    /// Authoritative definitions (ODCS: authoritativeDefinitions)
188    #[serde(default, skip_serializing_if = "Vec::is_empty")]
189    pub authoritative_definitions: Vec<AuthoritativeDefinition>,
190
191    // === Quality & Validation ===
192    /// Quality rules and checks (ODCS: quality)
193    #[serde(default)]
194    pub quality: Vec<HashMap<String, serde_json::Value>>,
195    /// Enum values if this column is an enumeration type
196    #[serde(default)]
197    pub enum_values: Vec<String>,
198
199    // === Tags & Custom Properties ===
200    /// Property-level tags (ODCS: tags)
201    #[serde(default, skip_serializing_if = "Vec::is_empty")]
202    pub tags: Vec<String>,
203    /// Custom properties for format-specific metadata not covered by ODCS
204    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
205    pub custom_properties: HashMap<String, serde_json::Value>,
206
207    // === Legacy/Internal Fields ===
208    /// Whether this column is a secondary/business key
209    #[serde(default)]
210    pub secondary_key: bool,
211    /// Composite key name if this column is part of a composite key
212    #[serde(skip_serializing_if = "Option::is_none")]
213    pub composite_key: Option<String>,
214    /// Foreign key reference (legacy - prefer relationships)
215    #[serde(skip_serializing_if = "Option::is_none")]
216    pub foreign_key: Option<ForeignKey>,
217    /// Additional constraints (e.g., "CHECK", "UNIQUE")
218    #[serde(default)]
219    pub constraints: Vec<String>,
220    /// Validation errors and warnings
221    #[serde(default)]
222    pub errors: Vec<HashMap<String, serde_json::Value>>,
223    /// Display order for UI rendering
224    #[serde(default)]
225    pub column_order: i32,
226    /// Nested data type for ARRAY<STRUCT> or MAP types
227    #[serde(skip_serializing_if = "Option::is_none")]
228    pub nested_data: Option<String>,
229}
230
231fn default_true() -> bool {
232    true
233}
234
235impl Default for Column {
236    fn default() -> Self {
237        Self {
238            // Core Identity
239            id: None,
240            name: String::new(),
241            business_name: None,
242            description: String::new(),
243            // Type Information
244            data_type: String::new(),
245            physical_type: None,
246            physical_name: None,
247            logical_type_options: None,
248            // Key Constraints
249            primary_key: false,
250            primary_key_position: None,
251            unique: false,
252            nullable: true, // Default to nullable
253            // Partitioning & Clustering
254            partitioned: false,
255            partition_key_position: None,
256            clustered: false,
257            // Data Classification & Security
258            classification: None,
259            critical_data_element: false,
260            encrypted_name: None,
261            // Transformation Metadata
262            transform_source_objects: Vec::new(),
263            transform_logic: None,
264            transform_description: None,
265            // Examples & Documentation
266            examples: Vec::new(),
267            default_value: None,
268            // Relationships & References
269            relationships: Vec::new(),
270            authoritative_definitions: Vec::new(),
271            // Quality & Validation
272            quality: Vec::new(),
273            enum_values: Vec::new(),
274            // Tags & Custom Properties
275            tags: Vec::new(),
276            custom_properties: HashMap::new(),
277            // Legacy/Internal Fields
278            secondary_key: false,
279            composite_key: None,
280            foreign_key: None,
281            constraints: Vec::new(),
282            errors: Vec::new(),
283            column_order: 0,
284            nested_data: None,
285        }
286    }
287}
288
289impl Column {
290    /// Create a new column with the given name and data type
291    ///
292    /// # Arguments
293    ///
294    /// * `name` - The column name (must be valid according to naming conventions)
295    /// * `data_type` - The data type string (e.g., "INT", "VARCHAR(100)")
296    ///
297    /// # Returns
298    ///
299    /// A new `Column` instance with default values (nullable=true, primary_key=false).
300    ///
301    /// # Example
302    ///
303    /// ```rust
304    /// use data_modelling_sdk::models::Column;
305    ///
306    /// let col = Column::new("user_id".to_string(), "BIGINT".to_string());
307    /// ```
308    #[allow(deprecated)]
309    pub fn new(name: String, data_type: String) -> Self {
310        Self {
311            // Core Identity
312            id: None,
313            name,
314            business_name: None,
315            description: String::new(),
316            // Type Information
317            data_type: normalize_data_type(&data_type),
318            physical_type: None,
319            physical_name: None,
320            logical_type_options: None,
321            // Key Constraints
322            primary_key: false,
323            primary_key_position: None,
324            unique: false,
325            nullable: true,
326            // Partitioning & Clustering
327            partitioned: false,
328            partition_key_position: None,
329            clustered: false,
330            // Data Classification & Security
331            classification: None,
332            critical_data_element: false,
333            encrypted_name: None,
334            // Transformation Metadata
335            transform_source_objects: Vec::new(),
336            transform_logic: None,
337            transform_description: None,
338            // Examples & Documentation
339            examples: Vec::new(),
340            default_value: None,
341            // Relationships & References
342            relationships: Vec::new(),
343            authoritative_definitions: Vec::new(),
344            // Quality & Validation
345            quality: Vec::new(),
346            enum_values: Vec::new(),
347            // Tags & Custom Properties
348            tags: Vec::new(),
349            custom_properties: HashMap::new(),
350            // Legacy/Internal Fields
351            secondary_key: false,
352            composite_key: None,
353            foreign_key: None,
354            constraints: Vec::new(),
355            errors: Vec::new(),
356            column_order: 0,
357            nested_data: None,
358        }
359    }
360}
361
362fn normalize_data_type(data_type: &str) -> String {
363    if data_type.is_empty() {
364        return data_type.to_string();
365    }
366
367    let upper = data_type.to_uppercase();
368
369    // Handle STRUCT<...>, ARRAY<...>, MAP<...> preserving inner content
370    if upper.starts_with("STRUCT") {
371        if let Some(start) = data_type.find('<')
372            && let Some(end) = data_type.rfind('>')
373        {
374            let inner = &data_type[start + 1..end];
375            return format!("STRUCT<{}>", inner);
376        }
377        return format!("STRUCT{}", &data_type[6..]);
378    } else if upper.starts_with("ARRAY") {
379        if let Some(start) = data_type.find('<')
380            && let Some(end) = data_type.rfind('>')
381        {
382            let inner = &data_type[start + 1..end];
383            return format!("ARRAY<{}>", inner);
384        }
385        return format!("ARRAY{}", &data_type[5..]);
386    } else if upper.starts_with("MAP") {
387        if let Some(start) = data_type.find('<')
388            && let Some(end) = data_type.rfind('>')
389        {
390            let inner = &data_type[start + 1..end];
391            return format!("MAP<{}>", inner);
392        }
393        return format!("MAP{}", &data_type[3..]);
394    }
395
396    upper
397}