Skip to main content

ormdb_proto/
mutation.rs

1//! Mutation IR types for write operations.
2
3use crate::value::Value;
4use rkyv::{Archive, Deserialize, Serialize};
5use serde::{Deserialize as SerdeDeserialize, Serialize as SerdeSerialize};
6
7/// A mutation operation (insert, update, or delete).
8#[derive(Debug, Clone, PartialEq, Archive, Serialize, Deserialize, SerdeSerialize, SerdeDeserialize)]
9pub enum Mutation {
10    /// Insert a new entity.
11    Insert {
12        /// Entity type to insert into.
13        entity: String,
14        /// Field values for the new entity.
15        data: Vec<FieldValue>,
16    },
17    /// Update an existing entity.
18    Update {
19        /// Entity type to update.
20        entity: String,
21        /// ID of the entity to update.
22        id: [u8; 16],
23        /// Field values to update.
24        data: Vec<FieldValue>,
25    },
26    /// Delete an entity.
27    Delete {
28        /// Entity type to delete from.
29        entity: String,
30        /// ID of the entity to delete.
31        id: [u8; 16],
32    },
33    /// Upsert (insert or update) an entity.
34    Upsert {
35        /// Entity type to upsert.
36        entity: String,
37        /// ID of the entity (if updating).
38        id: Option<[u8; 16]>,
39        /// Field values for the entity.
40        data: Vec<FieldValue>,
41    },
42}
43
44/// A field name and value pair.
45#[derive(Debug, Clone, PartialEq, Archive, Serialize, Deserialize, SerdeSerialize, SerdeDeserialize)]
46pub struct FieldValue {
47    /// Field name.
48    pub field: String,
49    /// Field value.
50    pub value: Value,
51}
52
53impl FieldValue {
54    /// Create a new field-value pair.
55    pub fn new(field: impl Into<String>, value: impl Into<Value>) -> Self {
56        Self {
57            field: field.into(),
58            value: value.into(),
59        }
60    }
61}
62
63impl Mutation {
64    /// Create an insert mutation.
65    pub fn insert(entity: impl Into<String>, data: Vec<FieldValue>) -> Self {
66        Mutation::Insert {
67            entity: entity.into(),
68            data,
69        }
70    }
71
72    /// Create an update mutation.
73    pub fn update(entity: impl Into<String>, id: [u8; 16], data: Vec<FieldValue>) -> Self {
74        Mutation::Update {
75            entity: entity.into(),
76            id,
77            data,
78        }
79    }
80
81    /// Create a delete mutation.
82    pub fn delete(entity: impl Into<String>, id: [u8; 16]) -> Self {
83        Mutation::Delete {
84            entity: entity.into(),
85            id,
86        }
87    }
88
89    /// Create an upsert mutation.
90    pub fn upsert(entity: impl Into<String>, id: Option<[u8; 16]>, data: Vec<FieldValue>) -> Self {
91        Mutation::Upsert {
92            entity: entity.into(),
93            id,
94            data,
95        }
96    }
97
98    /// Get the entity type this mutation operates on.
99    pub fn entity(&self) -> &str {
100        match self {
101            Mutation::Insert { entity, .. } => entity,
102            Mutation::Update { entity, .. } => entity,
103            Mutation::Delete { entity, .. } => entity,
104            Mutation::Upsert { entity, .. } => entity,
105        }
106    }
107}
108
109/// A batch of mutations to execute atomically.
110#[derive(Debug, Clone, PartialEq, Archive, Serialize, Deserialize, SerdeSerialize, SerdeDeserialize)]
111pub struct MutationBatch {
112    /// Mutations to execute in order.
113    pub mutations: Vec<Mutation>,
114}
115
116impl MutationBatch {
117    /// Create a new empty batch.
118    pub fn new() -> Self {
119        Self { mutations: vec![] }
120    }
121
122    /// Create a batch from mutations.
123    pub fn from_mutations(mutations: Vec<Mutation>) -> Self {
124        Self { mutations }
125    }
126
127    /// Add a mutation to the batch.
128    pub fn push(&mut self, mutation: Mutation) {
129        self.mutations.push(mutation);
130    }
131
132    /// Check if the batch is empty.
133    pub fn is_empty(&self) -> bool {
134        self.mutations.is_empty()
135    }
136
137    /// Get the number of mutations in the batch.
138    pub fn len(&self) -> usize {
139        self.mutations.len()
140    }
141}
142
143impl Default for MutationBatch {
144    fn default() -> Self {
145        Self::new()
146    }
147}
148
149impl FromIterator<Mutation> for MutationBatch {
150    fn from_iter<T: IntoIterator<Item = Mutation>>(iter: T) -> Self {
151        Self {
152            mutations: iter.into_iter().collect(),
153        }
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160
161    #[test]
162    fn test_insert_mutation() {
163        let mutation = Mutation::insert(
164            "User",
165            vec![
166                FieldValue::new("name", "Alice"),
167                FieldValue::new("email", "alice@example.com"),
168                FieldValue::new("active", true),
169            ],
170        );
171
172        if let Mutation::Insert { entity, data } = &mutation {
173            assert_eq!(entity, "User");
174            assert_eq!(data.len(), 3);
175            assert_eq!(data[0].field, "name");
176        } else {
177            panic!("Expected Insert mutation");
178        }
179    }
180
181    #[test]
182    fn test_update_mutation() {
183        let id = [1u8; 16];
184        let mutation = Mutation::update(
185            "User",
186            id,
187            vec![FieldValue::new("name", "Bob"), FieldValue::new("age", 30i32)],
188        );
189
190        if let Mutation::Update {
191            entity,
192            id: update_id,
193            data,
194        } = &mutation
195        {
196            assert_eq!(entity, "User");
197            assert_eq!(*update_id, id);
198            assert_eq!(data.len(), 2);
199        } else {
200            panic!("Expected Update mutation");
201        }
202    }
203
204    #[test]
205    fn test_delete_mutation() {
206        let id = [2u8; 16];
207        let mutation = Mutation::delete("User", id);
208
209        if let Mutation::Delete {
210            entity,
211            id: delete_id,
212        } = &mutation
213        {
214            assert_eq!(entity, "User");
215            assert_eq!(*delete_id, id);
216        } else {
217            panic!("Expected Delete mutation");
218        }
219    }
220
221    #[test]
222    fn test_mutation_batch() {
223        let mut batch = MutationBatch::new();
224        assert!(batch.is_empty());
225
226        batch.push(Mutation::insert(
227            "User",
228            vec![FieldValue::new("name", "Test")],
229        ));
230        batch.push(Mutation::delete("Session", [0u8; 16]));
231
232        assert_eq!(batch.len(), 2);
233        assert!(!batch.is_empty());
234    }
235
236    #[test]
237    fn test_mutation_serialization_roundtrip() {
238        let mutations = vec![
239            Mutation::insert(
240                "Post",
241                vec![
242                    FieldValue::new("title", "Hello World"),
243                    FieldValue::new("published", false),
244                ],
245            ),
246            Mutation::update(
247                "Post",
248                [1u8; 16],
249                vec![FieldValue::new("published", true)],
250            ),
251            Mutation::delete("Comment", [2u8; 16]),
252        ];
253
254        let batch = MutationBatch::from_mutations(mutations);
255
256        let bytes = rkyv::to_bytes::<rkyv::rancor::Error>(&batch).unwrap();
257        let archived = rkyv::access::<ArchivedMutationBatch, rkyv::rancor::Error>(&bytes).unwrap();
258        let deserialized: MutationBatch =
259            rkyv::deserialize::<MutationBatch, rkyv::rancor::Error>(archived).unwrap();
260
261        assert_eq!(batch, deserialized);
262    }
263}