1use crate::document::{Document, Field};
7use crate::sync::VectorClock;
8use crate::{DocumentID, FieldPath};
9use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11
12#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
17pub struct Delta {
18 pub document_id: DocumentID,
20
21 pub fields: HashMap<FieldPath, Field>,
23
24 pub version: VectorClock,
26}
27
28impl Delta {
29 pub fn new(
31 document_id: DocumentID,
32 fields: HashMap<FieldPath, Field>,
33 version: VectorClock,
34 ) -> Self {
35 Self {
36 document_id,
37 fields,
38 version,
39 }
40 }
41
42 pub fn empty(document_id: DocumentID, version: VectorClock) -> Self {
44 Self {
45 document_id,
46 fields: HashMap::new(),
47 version,
48 }
49 }
50
51 pub fn is_empty(&self) -> bool {
53 self.fields.is_empty()
54 }
55
56 pub fn len(&self) -> usize {
58 self.fields.len()
59 }
60}
61
62pub fn compute_delta(old: &Document, new: &Document) -> Delta {
77 let mut changed_fields = HashMap::new();
78
79 for (field_path, new_field) in &new.fields {
81 match old.fields.get(field_path) {
82 Some(old_field) => {
83 if old_field != new_field {
85 changed_fields.insert(field_path.clone(), new_field.clone());
86 }
87 }
88 None => {
89 changed_fields.insert(field_path.clone(), new_field.clone());
91 }
92 }
93 }
94
95 Delta::new(new.id.clone(), changed_fields, new.version.clone())
99}
100
101pub fn apply_delta(doc: &mut Document, delta: &Delta) {
113 assert_eq!(doc.id, delta.document_id, "Delta document ID mismatch");
115
116 for (field_path, delta_field) in &delta.fields {
118 match doc.fields.get(field_path) {
119 Some(local_field) => {
120 match delta_field.timestamp.cmp(&local_field.timestamp) {
122 std::cmp::Ordering::Greater => {
123 doc.fields.insert(field_path.clone(), delta_field.clone());
124 }
125 std::cmp::Ordering::Equal => {
126 if delta_field.timestamp.client_id > local_field.timestamp.client_id {
128 doc.fields.insert(field_path.clone(), delta_field.clone());
129 }
130 }
131 std::cmp::Ordering::Less => {} }
133 }
135 None => {
136 doc.fields.insert(field_path.clone(), delta_field.clone());
138 }
139 }
140 }
141
142 doc.version.merge(&delta.version);
144}
145
146pub fn merge_deltas(delta1: &Delta, delta2: &Delta) -> Delta {
153 assert_eq!(
154 delta1.document_id, delta2.document_id,
155 "Cannot merge deltas for different documents"
156 );
157
158 let mut merged_fields = delta1.fields.clone();
159
160 for (field_path, field2) in &delta2.fields {
162 match merged_fields.get(field_path) {
163 Some(field1) => {
164 match field2.timestamp.cmp(&field1.timestamp) {
166 std::cmp::Ordering::Greater => {
167 merged_fields.insert(field_path.clone(), field2.clone());
168 }
169 std::cmp::Ordering::Equal => {
170 if field2.timestamp.client_id > field1.timestamp.client_id {
172 merged_fields.insert(field_path.clone(), field2.clone());
173 }
174 }
175 std::cmp::Ordering::Less => {} }
177 }
178 None => {
179 merged_fields.insert(field_path.clone(), field2.clone());
181 }
182 }
183 }
184
185 let mut merged_version = delta1.version.clone();
187 merged_version.merge(&delta2.version);
188
189 Delta::new(delta1.document_id.clone(), merged_fields, merged_version)
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195 use crate::sync::Timestamp;
196 use serde_json::json;
197
198 #[test]
199 fn test_empty_delta() {
200 let doc = Document::new("doc1".to_string());
201 let delta = compute_delta(&doc, &doc);
202
203 assert!(delta.is_empty());
204 assert_eq!(delta.len(), 0);
205 }
206
207 #[test]
208 fn test_compute_delta_new_field() {
209 let old = Document::new("doc1".to_string());
210 let mut new = old.clone();
211
212 new.set_field(
213 "title".to_string(),
214 json!("Hello World"),
215 1,
216 "client1".to_string(),
217 );
218
219 let delta = compute_delta(&old, &new);
220
221 assert_eq!(delta.len(), 1);
222 assert!(delta.fields.contains_key("title"));
223 assert_eq!(delta.fields["title"].value, json!("Hello World"));
224 }
225
226 #[test]
227 fn test_compute_delta_modified_field() {
228 let mut old = Document::new("doc1".to_string());
229 old.set_field(
230 "title".to_string(),
231 json!("Old Title"),
232 1,
233 "client1".to_string(),
234 );
235
236 let mut new = old.clone();
237 new.set_field(
238 "title".to_string(),
239 json!("New Title"),
240 2,
241 "client1".to_string(),
242 );
243
244 let delta = compute_delta(&old, &new);
245
246 assert_eq!(delta.len(), 1);
247 assert_eq!(delta.fields["title"].value, json!("New Title"));
248 assert_eq!(delta.fields["title"].timestamp.clock, 2);
249 }
250
251 #[test]
252 fn test_compute_delta_multiple_changes() {
253 let old = Document::new("doc1".to_string());
254 let mut new = old.clone();
255
256 new.set_field(
257 "title".to_string(),
258 json!("Title"),
259 1,
260 "client1".to_string(),
261 );
262 new.set_field("body".to_string(), json!("Body"), 2, "client1".to_string());
263 new.set_field(
264 "author".to_string(),
265 json!("Alice"),
266 3,
267 "client1".to_string(),
268 );
269
270 let delta = compute_delta(&old, &new);
271
272 assert_eq!(delta.len(), 3);
273 assert!(delta.fields.contains_key("title"));
274 assert!(delta.fields.contains_key("body"));
275 assert!(delta.fields.contains_key("author"));
276 }
277
278 #[test]
279 fn test_apply_delta_new_field() {
280 let mut doc = Document::new("doc1".to_string());
281
282 let mut delta_fields = HashMap::new();
283 delta_fields.insert(
284 "title".to_string(),
285 Field {
286 value: json!("Hello"),
287 timestamp: Timestamp::new(1, "client1".to_string()),
288 },
289 );
290
291 let delta = Delta::new("doc1".to_string(), delta_fields, VectorClock::new());
292
293 apply_delta(&mut doc, &delta);
294
295 assert!(doc.fields.contains_key("title"));
296 assert_eq!(doc.fields["title"].value, json!("Hello"));
297 }
298
299 #[test]
300 fn test_apply_delta_lww_merge() {
301 let mut doc = Document::new("doc1".to_string());
302 doc.set_field("title".to_string(), json!("Old"), 1, "client1".to_string());
303
304 let mut delta_fields = HashMap::new();
306 delta_fields.insert(
307 "title".to_string(),
308 Field {
309 value: json!("New"),
310 timestamp: Timestamp::new(2, "client1".to_string()),
311 },
312 );
313
314 let delta = Delta::new("doc1".to_string(), delta_fields, VectorClock::new());
315
316 apply_delta(&mut doc, &delta);
317
318 assert_eq!(doc.fields["title"].value, json!("New"));
319 assert_eq!(doc.fields["title"].timestamp.clock, 2);
320 }
321
322 #[test]
323 fn test_apply_delta_keeps_local_if_newer() {
324 let mut doc = Document::new("doc1".to_string());
325 doc.set_field("title".to_string(), json!("New"), 2, "client1".to_string());
326
327 let mut delta_fields = HashMap::new();
329 delta_fields.insert(
330 "title".to_string(),
331 Field {
332 value: json!("Old"),
333 timestamp: Timestamp::new(1, "client1".to_string()),
334 },
335 );
336
337 let delta = Delta::new("doc1".to_string(), delta_fields, VectorClock::new());
338
339 apply_delta(&mut doc, &delta);
340
341 assert_eq!(doc.fields["title"].value, json!("New"));
343 assert_eq!(doc.fields["title"].timestamp.clock, 2);
344 }
345
346 #[test]
347 fn test_merge_deltas_non_overlapping() {
348 let mut fields1 = HashMap::new();
349 fields1.insert(
350 "title".to_string(),
351 Field {
352 value: json!("Title"),
353 timestamp: Timestamp::new(1, "client1".to_string()),
354 },
355 );
356
357 let mut fields2 = HashMap::new();
358 fields2.insert(
359 "body".to_string(),
360 Field {
361 value: json!("Body"),
362 timestamp: Timestamp::new(2, "client1".to_string()),
363 },
364 );
365
366 let delta1 = Delta::new("doc1".to_string(), fields1, VectorClock::new());
367 let delta2 = Delta::new("doc1".to_string(), fields2, VectorClock::new());
368
369 let merged = merge_deltas(&delta1, &delta2);
370
371 assert_eq!(merged.len(), 2);
372 assert!(merged.fields.contains_key("title"));
373 assert!(merged.fields.contains_key("body"));
374 }
375
376 #[test]
377 fn test_merge_deltas_overlapping_field() {
378 let mut fields1 = HashMap::new();
379 fields1.insert(
380 "title".to_string(),
381 Field {
382 value: json!("Old"),
383 timestamp: Timestamp::new(1, "client1".to_string()),
384 },
385 );
386
387 let mut fields2 = HashMap::new();
388 fields2.insert(
389 "title".to_string(),
390 Field {
391 value: json!("New"),
392 timestamp: Timestamp::new(2, "client1".to_string()),
393 },
394 );
395
396 let delta1 = Delta::new("doc1".to_string(), fields1, VectorClock::new());
397 let delta2 = Delta::new("doc1".to_string(), fields2, VectorClock::new());
398
399 let merged = merge_deltas(&delta1, &delta2);
400
401 assert_eq!(merged.len(), 1);
402 assert_eq!(merged.fields["title"].value, json!("New"));
403 assert_eq!(merged.fields["title"].timestamp.clock, 2);
404 }
405
406 #[test]
407 fn test_delta_roundtrip() {
408 let old = Document::new("doc1".to_string());
410 let mut new = old.clone();
411
412 new.set_field(
413 "title".to_string(),
414 json!("Hello"),
415 1,
416 "client1".to_string(),
417 );
418 new.set_field("body".to_string(), json!("World"), 2, "client1".to_string());
419
420 let delta = compute_delta(&old, &new);
421 let mut reconstructed = old.clone();
422 apply_delta(&mut reconstructed, &delta);
423
424 assert_eq!(reconstructed.fields["title"], new.fields["title"]);
426 assert_eq!(reconstructed.fields["body"], new.fields["body"]);
427 }
428}