Skip to main content

ic_dbms_api/dbms/table/
record.rs

1use candid::CandidType;
2
3use crate::dbms::table::{ColumnDef, TableSchema};
4use crate::dbms::value::Value;
5use crate::prelude::{Filter, IcDbmsResult};
6
7pub type TableColumns = Vec<(ValuesSource, Vec<(ColumnDef, Value)>)>;
8
9/// Indicates the source of the column values.
10#[derive(Debug, Clone, PartialEq, Eq, Hash)]
11pub enum ValuesSource {
12    /// Column values belong to the current table.
13    This,
14    /// Column values belong to a foreign table.
15    Foreign { table: String, column: String },
16}
17
18/// This trait represents a record returned by a [`crate::dbms::query::Query`] for a table.
19pub trait TableRecord: CandidType + for<'de> candid::Deserialize<'de> {
20    /// The table schema associated with this record.
21    type Schema: TableSchema<Record = Self>;
22
23    /// Constructs [`TableRecord`] from a list of column values grouped by table.
24    fn from_values(values: TableColumns) -> Self;
25
26    /// Converts the record into a list of column [`Value`]s.
27    fn to_values(&self) -> Vec<(ColumnDef, Value)>;
28}
29
30/// This trait represents a record for inserting into a table.
31pub trait InsertRecord: Sized + Clone + CandidType {
32    /// The [`TableRecord`] type associated with this table schema.
33    type Record: TableRecord;
34    /// The table schema associated with this record.
35    type Schema: TableSchema<Record = Self::Record>;
36
37    /// Creates an insert record from a list of column [`Value`]s.
38    fn from_values(values: &[(ColumnDef, Value)]) -> IcDbmsResult<Self>;
39
40    /// Converts the record into a list of column [`Value`]s for insertion.
41    fn into_values(self) -> Vec<(ColumnDef, Value)>;
42
43    /// Converts the insert record into the corresponding table record.
44    fn into_record(self) -> Self::Schema;
45}
46
47/// This trait represents a record for updating a table.
48pub trait UpdateRecord: Sized + CandidType {
49    /// The [`TableRecord`] type associated with this table schema.
50    type Record: TableRecord;
51    /// The table schema associated with this record.
52    type Schema: TableSchema<Record = Self::Record>;
53
54    /// Creates an update record from a list of column [`Value`]s and an optional [`Filter`] for the where clause.
55    fn from_values(values: &[(ColumnDef, Value)], where_clause: Option<Filter>) -> Self;
56
57    /// Get the list of column [`Value`]s to be updated.
58    fn update_values(&self) -> Vec<(ColumnDef, Value)>;
59
60    /// Get the [`Filter`] condition for the update operation.
61    fn where_clause(&self) -> Option<Filter>;
62}
63
64#[cfg(test)]
65mod test {
66
67    use super::*;
68
69    #[test]
70    fn test_should_create_values_source_this() {
71        let source = ValuesSource::This;
72        assert_eq!(source, ValuesSource::This);
73    }
74
75    #[test]
76    fn test_should_create_values_source_foreign() {
77        let source = ValuesSource::Foreign {
78            table: "users".to_string(),
79            column: "id".to_string(),
80        };
81
82        if let ValuesSource::Foreign { table, column } = source {
83            assert_eq!(table, "users");
84            assert_eq!(column, "id");
85        } else {
86            panic!("expected ValuesSource::Foreign");
87        }
88    }
89
90    #[test]
91    fn test_should_clone_values_source() {
92        let source = ValuesSource::Foreign {
93            table: "posts".to_string(),
94            column: "author_id".to_string(),
95        };
96
97        let cloned = source.clone();
98        assert_eq!(source, cloned);
99    }
100
101    #[test]
102    fn test_should_compare_values_sources() {
103        let source1 = ValuesSource::This;
104        let source2 = ValuesSource::This;
105        let source3 = ValuesSource::Foreign {
106            table: "users".to_string(),
107            column: "id".to_string(),
108        };
109        let source4 = ValuesSource::Foreign {
110            table: "users".to_string(),
111            column: "id".to_string(),
112        };
113        let source5 = ValuesSource::Foreign {
114            table: "posts".to_string(),
115            column: "id".to_string(),
116        };
117
118        assert_eq!(source1, source2);
119        assert_eq!(source3, source4);
120        assert_ne!(source1, source3);
121        assert_ne!(source3, source5);
122    }
123
124    #[test]
125    fn test_should_hash_values_source() {
126        use std::collections::HashSet;
127
128        let mut set = HashSet::new();
129        set.insert(ValuesSource::This);
130        set.insert(ValuesSource::Foreign {
131            table: "users".to_string(),
132            column: "id".to_string(),
133        });
134
135        assert!(set.contains(&ValuesSource::This));
136        assert!(set.contains(&ValuesSource::Foreign {
137            table: "users".to_string(),
138            column: "id".to_string(),
139        }));
140        assert!(!set.contains(&ValuesSource::Foreign {
141            table: "posts".to_string(),
142            column: "id".to_string(),
143        }));
144    }
145
146    #[test]
147    fn test_should_debug_values_source() {
148        let source = ValuesSource::This;
149        let debug_str = format!("{:?}", source);
150        assert_eq!(debug_str, "This");
151
152        let foreign = ValuesSource::Foreign {
153            table: "users".to_string(),
154            column: "id".to_string(),
155        };
156        let foreign_debug = format!("{:?}", foreign);
157        assert!(foreign_debug.contains("Foreign"));
158        assert!(foreign_debug.contains("users"));
159        assert!(foreign_debug.contains("id"));
160    }
161}