Skip to main content

icydb_core/patch/
merge.rs

1use crate::{
2    patch::{ListPatch, MapPatch, SetPatch},
3    traits::{Atomic, UpdateView},
4};
5use std::{
6    collections::{
7        BTreeMap, BTreeSet, HashMap, HashSet, btree_map::Entry as BTreeMapEntry,
8        hash_map::Entry as HashMapEntry,
9    },
10    hash::{BuildHasher, Hash},
11};
12use thiserror::Error as ThisError;
13
14///
15/// MergePatchError
16///
17/// Structured failures for user-driven patch application.
18///
19
20#[derive(Clone, Debug, Eq, PartialEq, ThisError)]
21pub enum MergePatchError {
22    #[error("invalid patch shape: expected {expected}, found {actual}")]
23    InvalidShape {
24        expected: &'static str,
25        actual: &'static str,
26    },
27
28    #[error("invalid patch cardinality: expected {expected}, found {actual}")]
29    CardinalityViolation { expected: usize, actual: usize },
30
31    #[error("patch merge failed at {path}: {source}")]
32    Context {
33        path: String,
34        #[source]
35        source: Box<Self>,
36    },
37}
38
39impl MergePatchError {
40    /// Prepend a field segment to the merge error path.
41    #[must_use]
42    pub fn with_field(self, field: impl AsRef<str>) -> Self {
43        self.with_path_segment(field.as_ref())
44    }
45
46    /// Prepend an index segment to the merge error path.
47    #[must_use]
48    pub fn with_index(self, index: usize) -> Self {
49        self.with_path_segment(format!("[{index}]"))
50    }
51
52    /// Return the full contextual path, if available.
53    #[must_use]
54    pub const fn path(&self) -> Option<&str> {
55        match self {
56            Self::Context { path, .. } => Some(path.as_str()),
57            _ => None,
58        }
59    }
60
61    /// Return the innermost, non-context merge error variant.
62    #[must_use]
63    pub fn leaf(&self) -> &Self {
64        match self {
65            Self::Context { source, .. } => source.leaf(),
66            _ => self,
67        }
68    }
69
70    #[must_use]
71    fn with_path_segment(self, segment: impl Into<String>) -> Self {
72        let segment = segment.into();
73        match self {
74            Self::Context { path, source } => Self::Context {
75                path: Self::join_segments(segment.as_str(), path.as_str()),
76                source,
77            },
78            source => Self::Context {
79                path: segment,
80                source: Box::new(source),
81            },
82        }
83    }
84
85    #[must_use]
86    fn join_segments(prefix: &str, suffix: &str) -> String {
87        if suffix.starts_with('[') {
88            format!("{prefix}{suffix}")
89        } else {
90            format!("{prefix}.{suffix}")
91        }
92    }
93}
94
95/// Apply full-replacement semantics for atomic update payloads.
96pub fn merge_atomic<T>(value: &mut T, patch: T) -> Result<(), MergePatchError>
97where
98    T: Atomic + UpdateView<UpdateViewType = T>,
99{
100    *value = patch;
101
102    Ok(())
103}
104
105/// Apply optional update payloads with create-on-update semantics.
106pub fn merge_option<T>(
107    value: &mut Option<T>,
108    patch: Option<T::UpdateViewType>,
109) -> Result<(), MergePatchError>
110where
111    T: UpdateView + Default,
112{
113    match patch {
114        None => {
115            // Explicit delete
116            *value = None;
117        }
118        Some(inner_patch) => {
119            if let Some(inner_value) = value.as_mut() {
120                inner_value
121                    .merge(inner_patch)
122                    .map_err(|err| err.with_field("value"))?;
123            } else {
124                let mut new_value = T::default();
125                new_value
126                    .merge(inner_patch)
127                    .map_err(|err| err.with_field("value"))?;
128                *value = Some(new_value);
129            }
130        }
131    }
132
133    Ok(())
134}
135
136/// Apply ordered list patch operations in sequence.
137pub fn merge_vec<T>(
138    values: &mut Vec<T>,
139    patches: Vec<ListPatch<T::UpdateViewType>>,
140) -> Result<(), MergePatchError>
141where
142    T: UpdateView + Default,
143{
144    for patch in patches {
145        match patch {
146            ListPatch::Update { index, patch } => {
147                if let Some(elem) = values.get_mut(index) {
148                    elem.merge(patch).map_err(|err| err.with_index(index))?;
149                }
150            }
151
152            ListPatch::Insert { index, value } => {
153                let idx = index.min(values.len());
154                let mut elem = T::default();
155                elem.merge(value).map_err(|err| err.with_index(idx))?;
156                values.insert(idx, elem);
157            }
158
159            ListPatch::Push { value } => {
160                let idx = values.len();
161                let mut elem = T::default();
162                elem.merge(value).map_err(|err| err.with_index(idx))?;
163                values.push(elem);
164            }
165
166            ListPatch::Overwrite {
167                values: next_values,
168            } => {
169                values.clear();
170                values.reserve(next_values.len());
171
172                for (index, value) in next_values.into_iter().enumerate() {
173                    let mut elem = T::default();
174                    elem.merge(value).map_err(|err| err.with_index(index))?;
175                    values.push(elem);
176                }
177            }
178
179            ListPatch::Remove { index } => {
180                if index < values.len() {
181                    values.remove(index);
182                }
183            }
184
185            ListPatch::Clear => values.clear(),
186        }
187    }
188
189    Ok(())
190}
191
192/// Apply set patch operations for hash sets.
193pub fn merge_hash_set<T, S>(
194    values: &mut HashSet<T, S>,
195    patches: Vec<SetPatch<T::UpdateViewType>>,
196) -> Result<(), MergePatchError>
197where
198    T: UpdateView + Clone + Default + Eq + Hash,
199    S: BuildHasher + Default,
200{
201    for patch in patches {
202        match patch {
203            SetPatch::Insert(value) => {
204                let mut elem = T::default();
205                elem.merge(value).map_err(|err| err.with_field("insert"))?;
206                values.insert(elem);
207            }
208
209            SetPatch::Remove(value) => {
210                let mut elem = T::default();
211                elem.merge(value).map_err(|err| err.with_field("remove"))?;
212                values.remove(&elem);
213            }
214
215            SetPatch::Overwrite {
216                values: next_values,
217            } => {
218                values.clear();
219
220                for (index, value) in next_values.into_iter().enumerate() {
221                    let mut elem = T::default();
222                    elem.merge(value)
223                        .map_err(|err| err.with_field("overwrite").with_index(index))?;
224                    values.insert(elem);
225                }
226            }
227
228            SetPatch::Clear => values.clear(),
229        }
230    }
231
232    Ok(())
233}
234
235/// Internal representation used to normalize map patches before application.
236enum MapPatchOp<K, V> {
237    Insert { key: K, value: V },
238    Remove { key: K },
239    Replace { key: K, value: V },
240    Clear,
241}
242
243/// Apply map patch operations for hash maps.
244#[expect(clippy::too_many_lines)]
245pub fn merge_hash_map<K, V, S>(
246    values: &mut HashMap<K, V, S>,
247    patches: Vec<MapPatch<K::UpdateViewType, V::UpdateViewType>>,
248) -> Result<(), MergePatchError>
249where
250    K: UpdateView + Clone + Default + Eq + Hash,
251    V: UpdateView + Default,
252    S: BuildHasher + Default,
253{
254    // Phase 1: decode patch payload into concrete keys.
255    let mut ops = Vec::with_capacity(patches.len());
256    for patch in patches {
257        match patch {
258            MapPatch::Insert { key, value } => {
259                let mut key_value = K::default();
260                key_value
261                    .merge(key)
262                    .map_err(|err| err.with_field("insert").with_field("key"))?;
263                ops.push(MapPatchOp::Insert {
264                    key: key_value,
265                    value,
266                });
267            }
268            MapPatch::Remove { key } => {
269                let mut key_value = K::default();
270                key_value
271                    .merge(key)
272                    .map_err(|err| err.with_field("remove").with_field("key"))?;
273                ops.push(MapPatchOp::Remove { key: key_value });
274            }
275            MapPatch::Replace { key, value } => {
276                let mut key_value = K::default();
277                key_value
278                    .merge(key)
279                    .map_err(|err| err.with_field("replace").with_field("key"))?;
280                ops.push(MapPatchOp::Replace {
281                    key: key_value,
282                    value,
283                });
284            }
285            MapPatch::Clear => ops.push(MapPatchOp::Clear),
286        }
287    }
288
289    // Phase 2: reject ambiguous patch batches to keep semantics deterministic.
290    let mut saw_clear = false;
291    let mut touched = HashSet::with_capacity(ops.len());
292    for op in &ops {
293        match op {
294            MapPatchOp::Clear => {
295                if saw_clear {
296                    return Err(MergePatchError::InvalidShape {
297                        expected: "at most one Clear operation per map patch batch",
298                        actual: "duplicate Clear operations",
299                    });
300                }
301                saw_clear = true;
302                if ops.len() != 1 {
303                    return Err(MergePatchError::CardinalityViolation {
304                        expected: 1,
305                        actual: ops.len(),
306                    });
307                }
308            }
309            MapPatchOp::Insert { key, .. }
310            | MapPatchOp::Remove { key }
311            | MapPatchOp::Replace { key, .. } => {
312                if saw_clear {
313                    return Err(MergePatchError::InvalidShape {
314                        expected: "Clear must be the only operation in a map patch batch",
315                        actual: "Clear combined with key operation",
316                    });
317                }
318                if !touched.insert(key.clone()) {
319                    return Err(MergePatchError::InvalidShape {
320                        expected: "unique key operations per map patch batch",
321                        actual: "duplicate key operation",
322                    });
323                }
324            }
325        }
326    }
327
328    if saw_clear {
329        values.clear();
330        return Ok(());
331    }
332
333    // Phase 3: apply deterministic map operations.
334    for op in ops {
335        match op {
336            MapPatchOp::Insert { key, value } => match values.entry(key) {
337                HashMapEntry::Occupied(mut slot) => {
338                    slot.get_mut()
339                        .merge(value)
340                        .map_err(|err| err.with_field("insert").with_field("value"))?;
341                }
342                HashMapEntry::Vacant(slot) => {
343                    let mut value_value = V::default();
344                    value_value
345                        .merge(value)
346                        .map_err(|err| err.with_field("insert").with_field("value"))?;
347                    slot.insert(value_value);
348                }
349            },
350
351            MapPatchOp::Remove { key } => {
352                // Align with list/set semantics: removing a missing entry is a no-op.
353                values.remove(&key);
354            }
355
356            MapPatchOp::Replace { key, value } => match values.entry(key) {
357                HashMapEntry::Occupied(mut slot) => {
358                    slot.get_mut()
359                        .merge(value)
360                        .map_err(|err| err.with_field("replace").with_field("value"))?;
361                }
362                // Align with list/set semantics: replacing a missing entry is a no-op.
363                HashMapEntry::Vacant(_) => {}
364            },
365
366            MapPatchOp::Clear => {
367                return Err(MergePatchError::InvalidShape {
368                    expected: "Clear to be handled before apply phase",
369                    actual: "Clear reached apply phase",
370                });
371            }
372        }
373    }
374
375    Ok(())
376}
377
378/// Apply set patch operations for ordered sets.
379pub fn merge_btree_set<T>(
380    values: &mut BTreeSet<T>,
381    patches: Vec<SetPatch<T::UpdateViewType>>,
382) -> Result<(), MergePatchError>
383where
384    T: UpdateView + Clone + Default + Ord,
385{
386    for patch in patches {
387        match patch {
388            SetPatch::Insert(value) => {
389                let mut elem = T::default();
390                elem.merge(value).map_err(|err| err.with_field("insert"))?;
391                values.insert(elem);
392            }
393            SetPatch::Remove(value) => {
394                let mut elem = T::default();
395                elem.merge(value).map_err(|err| err.with_field("remove"))?;
396                values.remove(&elem);
397            }
398            SetPatch::Overwrite {
399                values: next_values,
400            } => {
401                values.clear();
402
403                for (index, value) in next_values.into_iter().enumerate() {
404                    let mut elem = T::default();
405                    elem.merge(value)
406                        .map_err(|err| err.with_field("overwrite").with_index(index))?;
407                    values.insert(elem);
408                }
409            }
410            SetPatch::Clear => values.clear(),
411        }
412    }
413
414    Ok(())
415}
416
417/// Apply map patch operations for ordered maps.
418#[expect(clippy::too_many_lines)]
419pub fn merge_btree_map<K, V>(
420    values: &mut BTreeMap<K, V>,
421    patches: Vec<MapPatch<K::UpdateViewType, V::UpdateViewType>>,
422) -> Result<(), MergePatchError>
423where
424    K: UpdateView + Clone + Default + Ord,
425    V: UpdateView + Default,
426{
427    // Phase 1: decode patch payload into concrete keys.
428    let mut ops = Vec::with_capacity(patches.len());
429    for patch in patches {
430        match patch {
431            MapPatch::Insert { key, value } => {
432                let mut key_value = K::default();
433                key_value
434                    .merge(key)
435                    .map_err(|err| err.with_field("insert").with_field("key"))?;
436                ops.push(MapPatchOp::Insert {
437                    key: key_value,
438                    value,
439                });
440            }
441            MapPatch::Remove { key } => {
442                let mut key_value = K::default();
443                key_value
444                    .merge(key)
445                    .map_err(|err| err.with_field("remove").with_field("key"))?;
446                ops.push(MapPatchOp::Remove { key: key_value });
447            }
448            MapPatch::Replace { key, value } => {
449                let mut key_value = K::default();
450                key_value
451                    .merge(key)
452                    .map_err(|err| err.with_field("replace").with_field("key"))?;
453                ops.push(MapPatchOp::Replace {
454                    key: key_value,
455                    value,
456                });
457            }
458            MapPatch::Clear => ops.push(MapPatchOp::Clear),
459        }
460    }
461
462    // Phase 2: reject ambiguous patch batches to keep semantics deterministic.
463    let mut saw_clear = false;
464    let mut touched = BTreeSet::new();
465    for op in &ops {
466        match op {
467            MapPatchOp::Clear => {
468                if saw_clear {
469                    return Err(MergePatchError::InvalidShape {
470                        expected: "at most one Clear operation per map patch batch",
471                        actual: "duplicate Clear operations",
472                    });
473                }
474                saw_clear = true;
475                if ops.len() != 1 {
476                    return Err(MergePatchError::CardinalityViolation {
477                        expected: 1,
478                        actual: ops.len(),
479                    });
480                }
481            }
482            MapPatchOp::Insert { key, .. }
483            | MapPatchOp::Remove { key }
484            | MapPatchOp::Replace { key, .. } => {
485                if saw_clear {
486                    return Err(MergePatchError::InvalidShape {
487                        expected: "Clear must be the only operation in a map patch batch",
488                        actual: "Clear combined with key operation",
489                    });
490                }
491                if !touched.insert(key.clone()) {
492                    return Err(MergePatchError::InvalidShape {
493                        expected: "unique key operations per map patch batch",
494                        actual: "duplicate key operation",
495                    });
496                }
497            }
498        }
499    }
500    if saw_clear {
501        values.clear();
502        return Ok(());
503    }
504
505    // Phase 3: apply deterministic map operations.
506    for op in ops {
507        match op {
508            MapPatchOp::Insert { key, value } => match values.entry(key) {
509                BTreeMapEntry::Occupied(mut slot) => {
510                    slot.get_mut()
511                        .merge(value)
512                        .map_err(|err| err.with_field("insert").with_field("value"))?;
513                }
514                BTreeMapEntry::Vacant(slot) => {
515                    let mut value_value = V::default();
516                    value_value
517                        .merge(value)
518                        .map_err(|err| err.with_field("insert").with_field("value"))?;
519                    slot.insert(value_value);
520                }
521            },
522            MapPatchOp::Remove { key } => {
523                // Align with list/set semantics: removing a missing entry is a no-op.
524                values.remove(&key);
525            }
526            MapPatchOp::Replace { key, value } => match values.entry(key) {
527                BTreeMapEntry::Occupied(mut slot) => {
528                    slot.get_mut()
529                        .merge(value)
530                        .map_err(|err| err.with_field("replace").with_field("value"))?;
531                }
532                // Align with list/set semantics: replacing a missing entry is a no-op.
533                BTreeMapEntry::Vacant(_) => {}
534            },
535            MapPatchOp::Clear => {
536                return Err(MergePatchError::InvalidShape {
537                    expected: "Clear to be handled before apply phase",
538                    actual: "Clear reached apply phase",
539                });
540            }
541        }
542    }
543
544    Ok(())
545}