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