1use std::collections::HashMap;
9
10use serde::{Deserialize, Serialize};
11
12use crate::edit_error::EditError;
13use crate::functor::FInstance;
14use crate::value::Value;
15
16#[derive(Clone, Debug, Serialize, Deserialize)]
21pub enum TableEdit {
22 Identity,
24
25 InsertRow {
27 table: String,
29 row: HashMap<String, Value>,
31 },
32
33 DeleteRow {
35 table: String,
37 key: Value,
39 key_column: String,
41 },
42
43 UpdateCell {
45 table: String,
47 key: Value,
49 key_column: String,
51 column: String,
53 value: Value,
55 },
56
57 Sequence(Vec<Self>),
59}
60
61impl TableEdit {
62 #[must_use]
64 pub const fn identity() -> Self {
65 Self::Identity
66 }
67
68 #[must_use]
72 pub fn compose(self, other: Self) -> Self {
73 let mut steps = Vec::new();
74 flatten_into(&mut steps, self);
75 flatten_into(&mut steps, other);
76 match steps.len() {
77 0 => Self::Identity,
78 1 => steps.into_iter().next().unwrap_or(Self::Identity),
79 _ => Self::Sequence(steps),
80 }
81 }
82
83 #[must_use]
85 pub fn is_identity(&self) -> bool {
86 match self {
87 Self::Identity => true,
88 Self::Sequence(steps) => steps.iter().all(Self::is_identity),
89 _ => false,
90 }
91 }
92
93 pub fn apply(&self, instance: &mut FInstance) -> Result<(), EditError> {
99 match self {
100 Self::Identity => Ok(()),
101
102 Self::InsertRow { table, row } => {
103 instance
104 .tables
105 .entry(table.clone())
106 .or_default()
107 .push(row.clone());
108 Ok(())
109 }
110
111 Self::DeleteRow {
112 table,
113 key,
114 key_column,
115 } => {
116 let rows = instance
117 .tables
118 .get_mut(table.as_str())
119 .ok_or_else(|| EditError::TableNotFound(table.clone()))?;
120 let before = rows.len();
121 rows.retain(|row| row.get(key_column.as_str()) != Some(key));
122 if rows.len() == before {
123 return Err(EditError::RowNotFound {
124 table: table.clone(),
125 key: format!("{key:?}"),
126 });
127 }
128 Ok(())
129 }
130
131 Self::UpdateCell {
132 table,
133 key,
134 key_column,
135 column,
136 value,
137 } => {
138 let rows = instance
139 .tables
140 .get_mut(table.as_str())
141 .ok_or_else(|| EditError::TableNotFound(table.clone()))?;
142 let row = rows
143 .iter_mut()
144 .find(|r| r.get(key_column.as_str()) == Some(key))
145 .ok_or_else(|| EditError::RowNotFound {
146 table: table.clone(),
147 key: format!("{key:?}"),
148 })?;
149 row.insert(column.clone(), value.clone());
150 Ok(())
151 }
152
153 Self::Sequence(steps) => {
154 for step in steps {
155 step.apply(instance)?;
156 }
157 Ok(())
158 }
159 }
160 }
161}
162
163fn flatten_into(out: &mut Vec<TableEdit>, edit: TableEdit) {
165 match edit {
166 TableEdit::Identity => {}
167 TableEdit::Sequence(steps) => {
168 for step in steps {
169 flatten_into(out, step);
170 }
171 }
172 other => out.push(other),
173 }
174}
175
176#[cfg(test)]
177#[allow(clippy::unwrap_used)]
178mod tests {
179 use std::collections::HashMap;
180
181 use crate::functor::FInstance;
182 use crate::value::Value;
183
184 use super::TableEdit;
185
186 fn sample_instance() -> FInstance {
187 let mut row1 = HashMap::new();
188 row1.insert("id".into(), Value::Int(1));
189 row1.insert("name".into(), Value::Str("alice".into()));
190
191 let mut row2 = HashMap::new();
192 row2.insert("id".into(), Value::Int(2));
193 row2.insert("name".into(), Value::Str("bob".into()));
194
195 FInstance::new().with_table("users", vec![row1, row2])
196 }
197
198 #[test]
199 fn identity_is_noop() {
200 let mut inst = sample_instance();
201 TableEdit::identity().apply(&mut inst).unwrap();
202 assert_eq!(inst.row_count("users"), 2);
203 }
204
205 #[test]
206 fn insert_row() {
207 let mut inst = sample_instance();
208 let mut row = HashMap::new();
209 row.insert("id".into(), Value::Int(3));
210 row.insert("name".into(), Value::Str("charlie".into()));
211
212 let edit = TableEdit::InsertRow {
213 table: "users".into(),
214 row,
215 };
216 edit.apply(&mut inst).unwrap();
217 assert_eq!(inst.row_count("users"), 3);
218 }
219
220 #[test]
221 fn delete_row() {
222 let mut inst = sample_instance();
223 let edit = TableEdit::DeleteRow {
224 table: "users".into(),
225 key: Value::Int(1),
226 key_column: "id".into(),
227 };
228 edit.apply(&mut inst).unwrap();
229 assert_eq!(inst.row_count("users"), 1);
230 }
231
232 #[test]
233 fn update_cell() {
234 let mut inst = sample_instance();
235 let edit = TableEdit::UpdateCell {
236 table: "users".into(),
237 key: Value::Int(1),
238 key_column: "id".into(),
239 column: "name".into(),
240 value: Value::Str("alicia".into()),
241 };
242 edit.apply(&mut inst).unwrap();
243 let rows = &inst.tables["users"];
244 let row = rows
245 .iter()
246 .find(|r| r.get("id") == Some(&Value::Int(1)))
247 .unwrap();
248 assert_eq!(row.get("name"), Some(&Value::Str("alicia".into())));
249 }
250
251 #[test]
252 fn insert_then_delete_is_identity() {
253 let mut inst = sample_instance();
254 let original_count = inst.row_count("users");
255
256 let mut row = HashMap::new();
257 row.insert("id".into(), Value::Int(99));
258 row.insert("name".into(), Value::Str("temp".into()));
259
260 let edit = TableEdit::InsertRow {
261 table: "users".into(),
262 row,
263 }
264 .compose(TableEdit::DeleteRow {
265 table: "users".into(),
266 key: Value::Int(99),
267 key_column: "id".into(),
268 });
269 edit.apply(&mut inst).unwrap();
270 assert_eq!(inst.row_count("users"), original_count);
271 }
272
273 #[test]
274 fn delete_from_nonexistent_table_fails() {
275 let mut inst = sample_instance();
276 let edit = TableEdit::DeleteRow {
277 table: "nonexistent".into(),
278 key: Value::Int(1),
279 key_column: "id".into(),
280 };
281 assert!(edit.apply(&mut inst).is_err());
282 }
283
284 #[test]
285 fn monoid_identity_law() {
286 let mut inst1 = sample_instance();
287 let mut inst2 = sample_instance();
288
289 let mut row = HashMap::new();
290 row.insert("id".into(), Value::Int(5));
291 row.insert("name".into(), Value::Str("eve".into()));
292
293 let edit = TableEdit::InsertRow {
294 table: "users".into(),
295 row: row.clone(),
296 };
297
298 TableEdit::identity()
299 .compose(edit.clone())
300 .apply(&mut inst1)
301 .unwrap();
302 edit.apply(&mut inst2).unwrap();
303
304 assert_eq!(inst1.row_count("users"), inst2.row_count("users"));
305 }
306}