Skip to main content

wasm_dbms_api/dbms/table/
record.rs

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