Skip to main content

dittolive_ditto/dql/
differ.rs

1use std::{collections::HashSet, fmt};
2
3use ffi_sdk::{self, ffi_utils::repr_c};
4
5use crate::dql::QueryResultItem;
6
7/// Describes the move of an item in a [`Diff`].
8#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
9pub struct DiffMove {
10    /// The index in the list of "old items" that the item has moved from.
11    pub from: usize,
12
13    /// The index in the list of "new items" that the item has moved to.
14    pub to: usize,
15}
16
17impl From<(usize, usize)> for DiffMove {
18    fn from((from, to): (usize, usize)) -> Self {
19        Self { from, to }
20    }
21}
22
23/// Represents a diff between two arrays.
24///
25/// Create a diff between arrays of [`QueryResultItem`]s using a [`Differ`].
26///
27/// Alternatively, you can directly create a [`Diff`] via [`Diff::empty`]:
28/// ```rust
29/// # use dittolive_ditto::dql::Diff;
30/// let mut diff = Diff::empty();
31/// diff.insertions.extend([1, 2, 3]);
32/// ```
33#[derive(Debug, Clone, Eq, PartialEq, Default)]
34#[non_exhaustive]
35pub struct Diff {
36    /// The set of indexes in the _new_ array at which new items have been inserted.
37    pub insertions: HashSet<usize>,
38
39    /// The set of indexes in the _old_ array at which old items have been deleted.
40    pub deletions: HashSet<usize>,
41
42    /// The set of indexes in the _new_ array at which items have been updated.
43    pub updates: HashSet<usize>,
44
45    /// A set of tuples each representing a move of an item `from` a particular
46    /// index in the _old_ array `to` a particular index in the _new_ array.
47    pub moves: HashSet<DiffMove>,
48}
49
50impl Diff {
51    /// Create a new empty [Diff] instance.
52    ///
53    /// This is equivalent to calling `Diff::default()`.
54    pub fn empty() -> Self {
55        Self::default()
56    }
57}
58
59/// Calculates diffs between arrays of [`QueryResultItem`]s.
60///
61/// Use a [`Differ`] with a
62/// [`StoreObserver`][crate::store::StoreObserver] to get the diff
63/// between subsequent query results delivered by the store observer.
64pub struct Differ {
65    raw: repr_c::Box<ffi_sdk::FfiDiffer>,
66}
67
68impl Differ {
69    /// Create a new [`Differ`] instance.
70    #[inline]
71    pub fn new() -> Self {
72        Self {
73            raw: ffi_sdk::dittoffi_differ_new(),
74        }
75    }
76
77    /// Calculate the diff of the provided items against the last set of items
78    /// that were passed to this differ.
79    ///
80    /// The returned [`Diff`] identifies changes from the old array of items
81    /// to the new array of items using indices into both arrays.
82    ///
83    /// Initially, the differ has no items, so the first call to this method
84    /// will always return a diff showing all items as insertions.
85    ///
86    /// The identity of items is determined by their `_id` field.
87    ///
88    /// - `items` The query result items to compare against the last array of items.
89    pub fn diff(&self, items: impl IntoIterator<Item = QueryResultItem>) -> Diff {
90        let raw_items: Vec<_> = items.into_iter().map(|item| item.raw.clone()).collect();
91        let diff_cbor = ffi_sdk::dittoffi_differ_diff(&self.raw, raw_items.as_slice().into());
92        let deserialized: serialization::SerializedDiff =
93            ::serde_cbor::from_slice(&diff_cbor.0).unwrap();
94        Diff {
95            insertions: deserialized.insertions.into_iter().collect(),
96            deletions: deserialized.deletions.into_iter().collect(),
97            updates: deserialized.updates.into_iter().collect(),
98            moves: deserialized
99                .moves
100                .into_iter()
101                .map(|m| DiffMove::from((m.from, m.to)))
102                .collect(),
103        }
104    }
105}
106
107impl fmt::Debug for Differ {
108    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109        f.debug_struct("Differ").finish()
110    }
111}
112
113impl Default for Differ {
114    #[inline]
115    fn default() -> Self {
116        Self::new()
117    }
118}
119
120/// Private module for serialization and deserialization of [Diff]
121mod serialization {
122    /// Model for serialized CBOR data for a [Diff].
123    #[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
124    pub(super) struct SerializedDiff {
125        pub(super) insertions: Vec<usize>,
126        pub(super) deletions: Vec<usize>,
127        pub(super) updates: Vec<usize>,
128        pub(super) moves: Vec<SerializedDiffMove>,
129    }
130
131    /// Model for serialized CBOR data for a [DiffMove] element of a [Diff].
132    #[derive(Debug, PartialEq, Eq)]
133    pub(super) struct SerializedDiffMove {
134        pub(super) from: usize,
135        pub(super) to: usize,
136    }
137
138    impl From<(usize, usize)> for SerializedDiffMove {
139        fn from((from, to): (usize, usize)) -> Self {
140            Self { from, to }
141        }
142    }
143
144    // A DiffMove gets deserialized from a 2-element array where the first element is the "from"
145    // index and the second element is the "to" index.
146    impl<'de> serde::Deserialize<'de> for SerializedDiffMove {
147        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
148        where
149            D: serde::de::Deserializer<'de>,
150        {
151            let arr: Vec<usize> = serde::Deserialize::deserialize(deserializer)?;
152            if arr.len() != 2 {
153                return Err(serde::de::Error::custom(
154                    "DiffMove must be a 2-element array",
155                ));
156            }
157            Ok(SerializedDiffMove {
158                from: arr[0],
159                to: arr[1],
160            })
161        }
162    }
163
164    impl serde::Serialize for SerializedDiffMove {
165        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
166        where
167            S: serde::Serializer,
168        {
169            use serde::ser::SerializeSeq;
170            let mut state = serializer.serialize_seq(Some(2))?;
171            state.serialize_element(&self.from)?;
172            state.serialize_element(&self.to)?;
173            state.end()
174        }
175    }
176
177    #[cfg(test)]
178    mod tests {
179        use super::*;
180
181        #[test]
182        fn diff_serialization_and_deserialization_works() {
183            let diff = SerializedDiff {
184                insertions: [0, 1, 2].into(),
185                deletions: [3, 4].into(),
186                updates: [5].into(),
187                moves: vec![(6, 7), (8, 9)].into_iter().map(Into::into).collect(),
188            };
189
190            let cbor_data = serde_cbor::to_vec(&diff).unwrap();
191
192            let deserialized_diff: SerializedDiff = serde_cbor::from_slice(&cbor_data).unwrap();
193
194            assert_eq!(diff.insertions, deserialized_diff.insertions);
195            assert_eq!(diff.deletions, deserialized_diff.deletions);
196            assert_eq!(diff.updates, deserialized_diff.updates);
197            assert_eq!(diff.moves, deserialized_diff.moves);
198        }
199    }
200}