1use serde::{Deserialize, Serialize};
2
3use crate::dbms::types::DataTypeKind;
4use crate::dbms::value::Value;
5
6pub type DefaultValueFn = fn() -> Value;
12
13#[derive(Clone, Copy, Debug)]
15pub struct ColumnDef {
16 pub name: &'static str,
18 pub data_type: DataTypeKind,
20 pub auto_increment: bool,
23 pub nullable: bool,
25 pub primary_key: bool,
27 pub unique: bool,
29 pub foreign_key: Option<ForeignKeyDef>,
31 pub default: Option<DefaultValueFn>,
39 pub renamed_from: &'static [&'static str],
45}
46
47impl PartialEq for ColumnDef {
48 fn eq(&self, other: &Self) -> bool {
49 self.name == other.name
50 && self.data_type == other.data_type
51 && self.auto_increment == other.auto_increment
52 && self.nullable == other.nullable
53 && self.primary_key == other.primary_key
54 && self.unique == other.unique
55 && self.foreign_key == other.foreign_key
56 && self.default.map(|f| f as usize) == other.default.map(|f| f as usize)
57 && self.renamed_from == other.renamed_from
58 }
59}
60
61impl Eq for ColumnDef {}
62
63#[derive(Clone, Copy, Debug, PartialEq, Eq)]
65pub struct ForeignKeyDef {
66 pub local_column: &'static str,
68 pub foreign_table: &'static str,
70 pub foreign_column: &'static str,
72}
73
74#[derive(Clone, Copy, Debug, PartialEq, Eq)]
78pub struct IndexDef(pub &'static [&'static str]);
79
80impl IndexDef {
81 pub fn columns(&self) -> &'static [&'static str] {
83 self.0
84 }
85}
86
87#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
92#[cfg_attr(feature = "candid", derive(candid::CandidType))]
93pub enum CandidDataTypeKind {
94 Blob,
95 Boolean,
96 Date,
97 DateTime,
98 Decimal,
99 Int8,
100 Int16,
101 Int32,
102 Int64,
103 Json,
104 Text,
105 Uint8,
106 Uint16,
107 Uint32,
108 Uint64,
109 Uuid,
110 Custom(String),
111}
112
113impl From<DataTypeKind> for CandidDataTypeKind {
114 fn from(kind: DataTypeKind) -> Self {
115 match kind {
116 DataTypeKind::Blob => Self::Blob,
117 DataTypeKind::Boolean => Self::Boolean,
118 DataTypeKind::Date => Self::Date,
119 DataTypeKind::DateTime => Self::DateTime,
120 DataTypeKind::Decimal => Self::Decimal,
121 DataTypeKind::Int8 => Self::Int8,
122 DataTypeKind::Int16 => Self::Int16,
123 DataTypeKind::Int32 => Self::Int32,
124 DataTypeKind::Int64 => Self::Int64,
125 DataTypeKind::Json => Self::Json,
126 DataTypeKind::Text => Self::Text,
127 DataTypeKind::Uint8 => Self::Uint8,
128 DataTypeKind::Uint16 => Self::Uint16,
129 DataTypeKind::Uint32 => Self::Uint32,
130 DataTypeKind::Uint64 => Self::Uint64,
131 DataTypeKind::Uuid => Self::Uuid,
132 DataTypeKind::Custom { tag, .. } => Self::Custom(tag.to_string()),
133 }
134 }
135}
136
137#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
142#[cfg_attr(feature = "candid", derive(candid::CandidType))]
143pub struct JoinColumnDef {
144 pub table: Option<String>,
146 pub name: String,
148 pub data_type: CandidDataTypeKind,
150 pub nullable: bool,
152 pub primary_key: bool,
154 pub foreign_key: Option<CandidForeignKeyDef>,
156}
157
158#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
163#[cfg_attr(feature = "candid", derive(candid::CandidType))]
164pub struct CandidForeignKeyDef {
165 pub local_column: String,
167 pub foreign_table: String,
169 pub foreign_column: String,
171}
172
173impl From<ColumnDef> for JoinColumnDef {
174 fn from(def: ColumnDef) -> Self {
175 Self {
176 table: None,
177 name: def.name.to_string(),
178 data_type: CandidDataTypeKind::from(def.data_type),
179 nullable: def.nullable,
180 primary_key: def.primary_key,
181 foreign_key: def.foreign_key.map(CandidForeignKeyDef::from),
182 }
183 }
184}
185
186impl From<ForeignKeyDef> for CandidForeignKeyDef {
187 fn from(def: ForeignKeyDef) -> Self {
188 Self {
189 local_column: def.local_column.to_string(),
190 foreign_table: def.foreign_table.to_string(),
191 foreign_column: def.foreign_column.to_string(),
192 }
193 }
194}
195
196#[cfg(test)]
197mod test {
198
199 use super::*;
200 use crate::dbms::types::DataTypeKind;
201
202 #[test]
203 fn test_should_create_column_def() {
204 let column = ColumnDef {
205 name: "id",
206 data_type: DataTypeKind::Uint32,
207 auto_increment: false,
208 nullable: false,
209 primary_key: true,
210 unique: false,
211 foreign_key: None,
212 default: None,
213 renamed_from: &[],
214 };
215
216 assert_eq!(column.name, "id");
217 assert_eq!(column.data_type, DataTypeKind::Uint32);
218 assert!(!column.auto_increment);
219 assert!(!column.nullable);
220 assert!(column.primary_key);
221 assert!(!column.unique);
222 assert!(column.foreign_key.is_none());
223 }
224
225 #[test]
226 fn test_should_create_column_def_with_foreign_key() {
227 let fk = ForeignKeyDef {
228 local_column: "user_id",
229 foreign_table: "users",
230 foreign_column: "id",
231 };
232
233 let column = ColumnDef {
234 name: "user_id",
235 data_type: DataTypeKind::Uint32,
236 auto_increment: false,
237 nullable: false,
238 primary_key: false,
239 unique: false,
240 foreign_key: Some(fk),
241 default: None,
242 renamed_from: &[],
243 };
244
245 assert_eq!(column.name, "user_id");
246 assert!(column.foreign_key.is_some());
247 let fk_def = column.foreign_key.unwrap();
248 assert_eq!(fk_def.local_column, "user_id");
249 assert_eq!(fk_def.foreign_table, "users");
250 assert_eq!(fk_def.foreign_column, "id");
251 }
252
253 #[test]
254 #[allow(clippy::clone_on_copy)]
255 fn test_should_clone_column_def() {
256 let column = ColumnDef {
257 name: "email",
258 data_type: DataTypeKind::Text,
259 auto_increment: false,
260 nullable: true,
261 primary_key: false,
262 unique: true,
263 foreign_key: None,
264 default: None,
265 renamed_from: &[],
266 };
267
268 let cloned = column.clone();
269 assert_eq!(column, cloned);
270 }
271
272 #[test]
273 fn test_should_compare_column_defs() {
274 let column1 = ColumnDef {
275 name: "id",
276 data_type: DataTypeKind::Uint32,
277 auto_increment: false,
278 nullable: false,
279 primary_key: true,
280 unique: false,
281 foreign_key: None,
282 default: None,
283 renamed_from: &[],
284 };
285
286 let column2 = ColumnDef {
287 name: "id",
288 data_type: DataTypeKind::Uint32,
289 auto_increment: false,
290 nullable: false,
291 primary_key: true,
292 unique: false,
293 foreign_key: None,
294 default: None,
295 renamed_from: &[],
296 };
297
298 let column3 = ColumnDef {
299 name: "name",
300 data_type: DataTypeKind::Text,
301 auto_increment: false,
302 nullable: true,
303 primary_key: false,
304 unique: true,
305 foreign_key: None,
306 default: None,
307 renamed_from: &[],
308 };
309
310 assert_eq!(column1, column2);
311 assert_ne!(column1, column3);
312 }
313
314 #[test]
315 fn test_should_create_foreign_key_def() {
316 let fk = ForeignKeyDef {
317 local_column: "post_id",
318 foreign_table: "posts",
319 foreign_column: "id",
320 };
321
322 assert_eq!(fk.local_column, "post_id");
323 assert_eq!(fk.foreign_table, "posts");
324 assert_eq!(fk.foreign_column, "id");
325 }
326
327 #[test]
328 #[allow(clippy::clone_on_copy)]
329 fn test_should_clone_foreign_key_def() {
330 let fk = ForeignKeyDef {
331 local_column: "author_id",
332 foreign_table: "authors",
333 foreign_column: "id",
334 };
335
336 let cloned = fk.clone();
337 assert_eq!(fk, cloned);
338 }
339
340 #[test]
341 fn test_should_compare_foreign_key_defs() {
342 let fk1 = ForeignKeyDef {
343 local_column: "user_id",
344 foreign_table: "users",
345 foreign_column: "id",
346 };
347
348 let fk2 = ForeignKeyDef {
349 local_column: "user_id",
350 foreign_table: "users",
351 foreign_column: "id",
352 };
353
354 let fk3 = ForeignKeyDef {
355 local_column: "category_id",
356 foreign_table: "categories",
357 foreign_column: "id",
358 };
359
360 assert_eq!(fk1, fk2);
361 assert_ne!(fk1, fk3);
362 }
363
364 #[test]
365 fn test_should_create_candid_column_def_with_table() {
366 let col = JoinColumnDef {
367 table: Some("users".to_string()),
368 name: "id".to_string(),
369 data_type: CandidDataTypeKind::Uint32,
370 nullable: false,
371 primary_key: true,
372 foreign_key: None,
373 };
374 assert_eq!(col.table, Some("users".to_string()));
375 }
376
377 #[test]
378 fn test_should_convert_column_def_to_candid_with_none_table() {
379 let col = ColumnDef {
380 name: "id",
381 data_type: DataTypeKind::Uint32,
382 auto_increment: false,
383 nullable: false,
384 primary_key: true,
385 unique: false,
386 foreign_key: None,
387 default: None,
388 renamed_from: &[],
389 };
390 let candid_col = JoinColumnDef::from(col);
391 assert_eq!(candid_col.table, None);
392 assert_eq!(candid_col.name, "id");
393 }
394
395 #[test]
396 fn test_should_convert_custom_data_type_kind_to_candid() {
397 use crate::dbms::table::WireSize;
398 let kind = DataTypeKind::Custom {
399 tag: "role",
400 wire_size: WireSize::Fixed(1),
401 };
402 let candid_kind = CandidDataTypeKind::from(kind);
403 assert_eq!(candid_kind, CandidDataTypeKind::Custom("role".to_string()));
404 }
405
406 #[test]
407 fn test_should_convert_builtin_data_type_kind_to_candid() {
408 let kind = DataTypeKind::Text;
409 let candid_kind = CandidDataTypeKind::from(kind);
410 assert_eq!(candid_kind, CandidDataTypeKind::Text);
411 }
412
413 #[test]
414 fn test_should_create_candid_column_def_with_custom_type() {
415 use crate::dbms::table::WireSize;
416 let col = ColumnDef {
417 name: "role",
418 data_type: DataTypeKind::Custom {
419 tag: "role",
420 wire_size: WireSize::Fixed(1),
421 },
422 auto_increment: false,
423 nullable: false,
424 primary_key: false,
425 unique: false,
426 foreign_key: None,
427 default: None,
428 renamed_from: &[],
429 };
430 let candid_col = JoinColumnDef::from(col);
431 assert_eq!(
432 candid_col.data_type,
433 CandidDataTypeKind::Custom("role".to_string())
434 );
435 }
436}