Skip to main content

ic_dbms_api/dbms/table/
column_def.rs

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