Skip to main content

wasm_dbms_api/dbms/table/
column_def.rs

1use serde::{Deserialize, Serialize};
2
3use crate::dbms::types::DataTypeKind;
4
5/// Defines a column in a database table.
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7pub struct ColumnDef {
8    /// The name of the column.
9    pub name: &'static str,
10    /// The data type of the column.
11    pub data_type: DataTypeKind,
12    /// Indicates if this column can contain NULL values.
13    pub nullable: bool,
14    /// Indicates if this column is part of the primary key.
15    pub primary_key: bool,
16    /// Foreign key definition, if any.
17    pub foreign_key: Option<ForeignKeyDef>,
18}
19
20/// Defines a foreign key relationship for a column.
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22pub struct ForeignKeyDef {
23    /// Name of the local column that holds the foreign key (es: "user_id")
24    pub local_column: &'static str,
25    /// Name of the foreign table (e.g., "users")
26    pub foreign_table: &'static str,
27    /// Name of the foreign column that the FK points to (e.g., "id")
28    pub foreign_column: &'static str,
29}
30
31/// Serializable data type kind for API boundaries.
32///
33/// Mirrors [`DataTypeKind`] but uses owned `String` for the `Custom` variant,
34/// making it suitable for serialization across API boundaries.
35#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
36#[cfg_attr(feature = "candid", derive(candid::CandidType))]
37pub enum CandidDataTypeKind {
38    Blob,
39    Boolean,
40    Date,
41    DateTime,
42    Decimal,
43    Int32,
44    Int64,
45    Json,
46    Text,
47    Uint32,
48    Uint64,
49    Uuid,
50    Custom(String),
51}
52
53impl From<DataTypeKind> for CandidDataTypeKind {
54    fn from(kind: DataTypeKind) -> Self {
55        match kind {
56            DataTypeKind::Blob => Self::Blob,
57            DataTypeKind::Boolean => Self::Boolean,
58            DataTypeKind::Date => Self::Date,
59            DataTypeKind::DateTime => Self::DateTime,
60            DataTypeKind::Decimal => Self::Decimal,
61            DataTypeKind::Int32 => Self::Int32,
62            DataTypeKind::Int64 => Self::Int64,
63            DataTypeKind::Json => Self::Json,
64            DataTypeKind::Text => Self::Text,
65            DataTypeKind::Uint32 => Self::Uint32,
66            DataTypeKind::Uint64 => Self::Uint64,
67            DataTypeKind::Uuid => Self::Uuid,
68            DataTypeKind::Custom(s) => Self::Custom(s.to_string()),
69        }
70    }
71}
72
73/// Serializable column definition for API boundaries.
74///
75/// This type mirrors [`ColumnDef`] but uses owned `String` fields instead
76/// of `&'static str`, making it suitable for serialization across API boundaries.
77#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
78#[cfg_attr(feature = "candid", derive(candid::CandidType))]
79pub struct CandidColumnDef {
80    /// The source table name. `Some` for join results, `None` for single-table queries.
81    pub table: Option<String>,
82    /// The name of the column.
83    pub name: String,
84    /// The data type of the column.
85    pub data_type: CandidDataTypeKind,
86    /// Indicates if this column can contain NULL values.
87    pub nullable: bool,
88    /// Indicates if this column is part of the primary key.
89    pub primary_key: bool,
90    /// Foreign key definition, if any.
91    pub foreign_key: Option<CandidForeignKeyDef>,
92}
93
94/// Serializable foreign key definition for API boundaries.
95///
96/// This type mirrors [`ForeignKeyDef`] but uses owned `String` fields instead
97/// of `&'static str`, making it suitable for serialization across API boundaries.
98#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
99#[cfg_attr(feature = "candid", derive(candid::CandidType))]
100pub struct CandidForeignKeyDef {
101    /// Name of the local column that holds the foreign key (e.g., "user_id").
102    pub local_column: String,
103    /// Name of the foreign table (e.g., "users").
104    pub foreign_table: String,
105    /// Name of the foreign column that the FK points to (e.g., "id").
106    pub foreign_column: String,
107}
108
109impl From<ColumnDef> for CandidColumnDef {
110    fn from(def: ColumnDef) -> Self {
111        Self {
112            table: None,
113            name: def.name.to_string(),
114            data_type: CandidDataTypeKind::from(def.data_type),
115            nullable: def.nullable,
116            primary_key: def.primary_key,
117            foreign_key: def.foreign_key.map(CandidForeignKeyDef::from),
118        }
119    }
120}
121
122impl From<ForeignKeyDef> for CandidForeignKeyDef {
123    fn from(def: ForeignKeyDef) -> Self {
124        Self {
125            local_column: def.local_column.to_string(),
126            foreign_table: def.foreign_table.to_string(),
127            foreign_column: def.foreign_column.to_string(),
128        }
129    }
130}
131
132#[cfg(test)]
133mod test {
134
135    use super::*;
136    use crate::dbms::types::DataTypeKind;
137
138    #[test]
139    fn test_should_create_column_def() {
140        let column = ColumnDef {
141            name: "id",
142            data_type: DataTypeKind::Uint32,
143            nullable: false,
144            primary_key: true,
145            foreign_key: None,
146        };
147
148        assert_eq!(column.name, "id");
149        assert_eq!(column.data_type, DataTypeKind::Uint32);
150        assert!(!column.nullable);
151        assert!(column.primary_key);
152        assert!(column.foreign_key.is_none());
153    }
154
155    #[test]
156    fn test_should_create_column_def_with_foreign_key() {
157        let fk = ForeignKeyDef {
158            local_column: "user_id",
159            foreign_table: "users",
160            foreign_column: "id",
161        };
162
163        let column = ColumnDef {
164            name: "user_id",
165            data_type: DataTypeKind::Uint32,
166            nullable: false,
167            primary_key: false,
168            foreign_key: Some(fk),
169        };
170
171        assert_eq!(column.name, "user_id");
172        assert!(column.foreign_key.is_some());
173        let fk_def = column.foreign_key.unwrap();
174        assert_eq!(fk_def.local_column, "user_id");
175        assert_eq!(fk_def.foreign_table, "users");
176        assert_eq!(fk_def.foreign_column, "id");
177    }
178
179    #[test]
180    #[allow(clippy::clone_on_copy)]
181    fn test_should_clone_column_def() {
182        let column = ColumnDef {
183            name: "email",
184            data_type: DataTypeKind::Text,
185            nullable: true,
186            primary_key: false,
187            foreign_key: None,
188        };
189
190        let cloned = column.clone();
191        assert_eq!(column, cloned);
192    }
193
194    #[test]
195    fn test_should_compare_column_defs() {
196        let column1 = ColumnDef {
197            name: "id",
198            data_type: DataTypeKind::Uint32,
199            nullable: false,
200            primary_key: true,
201            foreign_key: None,
202        };
203
204        let column2 = ColumnDef {
205            name: "id",
206            data_type: DataTypeKind::Uint32,
207            nullable: false,
208            primary_key: true,
209            foreign_key: None,
210        };
211
212        let column3 = ColumnDef {
213            name: "name",
214            data_type: DataTypeKind::Text,
215            nullable: true,
216            primary_key: false,
217            foreign_key: None,
218        };
219
220        assert_eq!(column1, column2);
221        assert_ne!(column1, column3);
222    }
223
224    #[test]
225    fn test_should_create_foreign_key_def() {
226        let fk = ForeignKeyDef {
227            local_column: "post_id",
228            foreign_table: "posts",
229            foreign_column: "id",
230        };
231
232        assert_eq!(fk.local_column, "post_id");
233        assert_eq!(fk.foreign_table, "posts");
234        assert_eq!(fk.foreign_column, "id");
235    }
236
237    #[test]
238    #[allow(clippy::clone_on_copy)]
239    fn test_should_clone_foreign_key_def() {
240        let fk = ForeignKeyDef {
241            local_column: "author_id",
242            foreign_table: "authors",
243            foreign_column: "id",
244        };
245
246        let cloned = fk.clone();
247        assert_eq!(fk, cloned);
248    }
249
250    #[test]
251    fn test_should_compare_foreign_key_defs() {
252        let fk1 = ForeignKeyDef {
253            local_column: "user_id",
254            foreign_table: "users",
255            foreign_column: "id",
256        };
257
258        let fk2 = ForeignKeyDef {
259            local_column: "user_id",
260            foreign_table: "users",
261            foreign_column: "id",
262        };
263
264        let fk3 = ForeignKeyDef {
265            local_column: "category_id",
266            foreign_table: "categories",
267            foreign_column: "id",
268        };
269
270        assert_eq!(fk1, fk2);
271        assert_ne!(fk1, fk3);
272    }
273
274    #[test]
275    fn test_should_create_candid_column_def_with_table() {
276        let col = CandidColumnDef {
277            table: Some("users".to_string()),
278            name: "id".to_string(),
279            data_type: CandidDataTypeKind::Uint32,
280            nullable: false,
281            primary_key: true,
282            foreign_key: None,
283        };
284        assert_eq!(col.table, Some("users".to_string()));
285    }
286
287    #[test]
288    fn test_should_convert_column_def_to_candid_with_none_table() {
289        let col = ColumnDef {
290            name: "id",
291            data_type: DataTypeKind::Uint32,
292            nullable: false,
293            primary_key: true,
294            foreign_key: None,
295        };
296        let candid_col = CandidColumnDef::from(col);
297        assert_eq!(candid_col.table, None);
298        assert_eq!(candid_col.name, "id");
299    }
300
301    #[test]
302    fn test_should_convert_custom_data_type_kind_to_candid() {
303        let kind = DataTypeKind::Custom("role");
304        let candid_kind = CandidDataTypeKind::from(kind);
305        assert_eq!(candid_kind, CandidDataTypeKind::Custom("role".to_string()));
306    }
307
308    #[test]
309    fn test_should_convert_builtin_data_type_kind_to_candid() {
310        let kind = DataTypeKind::Text;
311        let candid_kind = CandidDataTypeKind::from(kind);
312        assert_eq!(candid_kind, CandidDataTypeKind::Text);
313    }
314
315    #[test]
316    fn test_should_create_candid_column_def_with_custom_type() {
317        let col = ColumnDef {
318            name: "role",
319            data_type: DataTypeKind::Custom("role"),
320            nullable: false,
321            primary_key: false,
322            foreign_key: None,
323        };
324        let candid_col = CandidColumnDef::from(col);
325        assert_eq!(
326            candid_col.data_type,
327            CandidDataTypeKind::Custom("role".to_string())
328        );
329    }
330}