Development Documentation (main branch) - For stable release docs, see docs.rs/eidetica

eidetica/crdt/doc/
list.rs

1//! List positioning system and List type for CRDT documents.
2//!
3//! This module provides both the Position type for stable list ordering
4//! and the List type itself for ordered collections in CRDT documents.
5
6use std::{cmp::Ordering, collections::BTreeMap};
7
8use uuid::Uuid;
9
10// Import Value from the value module
11use super::value::Value;
12use crate::crdt::{CRDTError, traits::Data};
13
14/// Represents a position in a CRDT list using rational numbers.
15///
16/// This type provides a stable ordering mechanism for list elements that allows
17/// insertion between any two existing elements without requiring renumbering.
18/// Each position consists of:
19/// - A rational number (numerator/denominator) for ordering
20/// - A unique UUID for deterministic tie-breaking
21///
22/// # Examples
23///
24/// ```
25/// use eidetica::crdt::doc::list::Position;
26///
27/// let pos1 = Position::new(10, 1);
28/// let pos2 = Position::new(20, 1);
29/// let between = Position::between(&pos1, &pos2);
30///
31/// assert!(pos1 < between);
32/// assert!(between < pos2);
33/// ```
34#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
35pub struct Position {
36    /// Numerator of the rational number
37    pub numerator: i64,
38    /// Denominator of the rational number (always positive)
39    pub denominator: u64,
40    /// Unique identifier for deterministic ordering
41    pub unique_id: Uuid,
42}
43
44impl Position {
45    /// Creates a new position with the specified rational number.
46    ///
47    /// # Arguments
48    /// * `numerator` - The numerator of the rational number
49    /// * `denominator` - The denominator of the rational number (must be > 0)
50    ///
51    /// # Examples
52    ///
53    /// ```
54    /// use eidetica::crdt::doc::list::Position;
55    ///
56    /// let pos = Position::new(3, 2); // Represents 3/2 = 1.5
57    /// ```
58    pub fn new(numerator: i64, denominator: u64) -> Self {
59        assert!(denominator > 0, "Denominator must be positive");
60        let mut pos = Self {
61            numerator,
62            denominator,
63            unique_id: Uuid::new_v4(),
64        };
65        pos.reduce();
66        pos
67    }
68
69    /// Creates a position at the beginning of the sequence.
70    ///
71    /// # Examples
72    ///
73    /// ```
74    /// use eidetica::crdt::doc::list::Position;
75    ///
76    /// let beginning = Position::beginning();
77    /// let after = Position::new(1, 1);
78    /// assert!(beginning < after);
79    /// ```
80    pub fn beginning() -> Self {
81        Self::new(0, 1)
82    }
83
84    /// Creates a position at the end of the sequence.
85    ///
86    /// # Examples
87    ///
88    /// ```
89    /// use eidetica::crdt::doc::list::Position;
90    ///
91    /// let end = Position::end();
92    /// let before = Position::new(1000, 1);
93    /// assert!(before < end);
94    /// ```
95    pub fn end() -> Self {
96        Self::new(i64::MAX, 1)
97    }
98
99    /// Creates a position between two existing positions.
100    ///
101    /// This method finds the rational number that falls between the two given positions
102    /// and creates a new position with that value.
103    ///
104    /// # Arguments
105    /// * `left` - The left (smaller) position
106    /// * `right` - The right (larger) position
107    ///
108    /// # Examples
109    ///
110    /// ```
111    /// use eidetica::crdt::doc::list::Position;
112    ///
113    /// let pos1 = Position::new(1, 1);
114    /// let pos2 = Position::new(3, 1);
115    /// let between = Position::between(&pos1, &pos2);
116    ///
117    /// assert!(pos1 < between);
118    /// assert!(between < pos2);
119    /// ```
120    pub fn between(left: &Position, right: &Position) -> Self {
121        // Convert to common denominator for easier calculation
122        let left_num = left.numerator as i128 * right.denominator as i128;
123        let right_num = right.numerator as i128 * left.denominator as i128;
124        let common_denom = left.denominator as i128 * right.denominator as i128;
125
126        // Find the midpoint
127        let mid_num = (left_num + right_num) / 2;
128
129        // If the midpoint is the same as one of the endpoints, we need to increase precision
130        if mid_num == left_num || mid_num == right_num {
131            // Double the denominator to increase precision
132            let new_denom = common_denom * 2;
133            let new_mid_num = (left_num * 2 + right_num * 2) / 2;
134
135            Self::new(new_mid_num as i64, new_denom as u64)
136        } else {
137            Self::new(mid_num as i64, common_denom as u64)
138        }
139    }
140
141    /// Reduces the fraction to its simplest form.
142    fn reduce(&mut self) {
143        let gcd = gcd(self.numerator.unsigned_abs(), self.denominator);
144        self.numerator /= gcd as i64;
145        self.denominator /= gcd;
146    }
147
148    /// Returns the rational value as a floating point number.
149    ///
150    /// Note: This is primarily for debugging and should not be used for ordering.
151    pub fn as_f64(&self) -> f64 {
152        self.numerator as f64 / self.denominator as f64
153    }
154}
155
156impl PartialOrd for Position {
157    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
158        Some(self.cmp(other))
159    }
160}
161
162impl Ord for Position {
163    fn cmp(&self, other: &Self) -> Ordering {
164        // Compare rational numbers: a/b vs c/d -> a*d vs c*b
165        let left = self.numerator as i128 * other.denominator as i128;
166        let right = other.numerator as i128 * self.denominator as i128;
167
168        match left.cmp(&right) {
169            Ordering::Equal => {
170                // If rational numbers are equal, use UUID for deterministic ordering
171                self.unique_id.cmp(&other.unique_id)
172            }
173            ordering => ordering,
174        }
175    }
176}
177
178/// Calculates the greatest common divisor of two numbers.
179fn gcd(a: u64, b: u64) -> u64 {
180    if b == 0 { a } else { gcd(b, a % b) }
181}
182
183#[cfg(test)]
184mod tests {
185    use super::*;
186
187    #[test]
188    fn test_position_creation() {
189        let pos = Position::new(3, 2);
190        assert_eq!(pos.numerator, 3);
191        assert_eq!(pos.denominator, 2);
192    }
193
194    #[test]
195    fn test_position_reduction() {
196        let pos = Position::new(6, 4);
197        // Should be reduced to 3/2
198        assert_eq!(pos.numerator, 3);
199        assert_eq!(pos.denominator, 2);
200    }
201
202    #[test]
203    fn test_position_ordering() {
204        let pos1 = Position::new(1, 2); // 0.5
205        let pos2 = Position::new(3, 4); // 0.75
206        let pos3 = Position::new(1, 1); // 1.0
207
208        assert!(pos1 < pos2);
209        assert!(pos2 < pos3);
210        assert!(pos1 < pos3);
211    }
212
213    #[test]
214    fn test_position_between() {
215        let pos1 = Position::new(1, 1);
216        let pos2 = Position::new(3, 1);
217        let between = Position::between(&pos1, &pos2);
218
219        assert!(pos1 < between);
220        assert!(between < pos2);
221    }
222
223    #[test]
224    fn test_position_beginning_end() {
225        let beginning = Position::beginning();
226        let end = Position::end();
227        let middle = Position::new(100, 1);
228
229        assert!(beginning < middle);
230        assert!(middle < end);
231    }
232
233    #[test]
234    fn test_position_uuid_ordering() {
235        let pos1 = Position::new(1, 1);
236        let pos2 = Position::new(1, 1);
237
238        // Same rational number, but different UUIDs should provide deterministic ordering
239        assert_ne!(pos1.cmp(&pos2), Ordering::Equal);
240    }
241}
242
243/// Ordered list with stable positioning for CRDT operations.
244///
245/// `List` maintains a stable ordering of elements using [`Position`] keys
246/// in a `BTreeMap`. This design enables concurrent insertions without requiring
247/// coordination between distributed replicas.
248///
249/// # Key Features
250///
251/// - **Stable ordering**: Elements maintain their relative positions even with concurrent modifications
252/// - **Insertion anywhere**: Can insert between any two existing elements
253/// - **CRDT semantics**: Proper merge behavior for distributed systems
254/// - **Efficient access**: O(log n) access by position, O(1) by index for small lists
255///
256/// # Usage Patterns
257///
258/// ```
259/// # use eidetica::crdt::doc::List;
260/// # use eidetica::crdt::doc::list::Position;
261/// let mut list = List::new();
262///
263/// // Simple append operations
264/// list.push("first");  // Returns index 0
265/// list.push("second"); // Returns index 1
266///
267/// // Insert between existing elements using index
268/// list.insert(1, "between").unwrap();
269///
270/// // Access by traditional index
271/// assert_eq!(list.get(0).unwrap().as_text(), Some("first"));
272///
273/// // Advanced users can use Position for precise control
274/// let pos1 = Position::new(1, 1);  // 1.0
275/// let pos2 = Position::new(2, 1);  // 2.0
276/// let middle = Position::between(&pos1, &pos2);
277/// list.insert_at_position(middle, "advanced");
278/// ```
279///
280/// # Concurrent Operations
281///
282/// When two replicas insert at the same logical position, the rational number
283/// system ensures a consistent final order:
284///
285/// ```text
286/// Replica A: ["item1", "item3"] -> inserts "item2" between them
287/// Replica B: ["item1", "item3"] -> inserts "item4" between them
288///
289/// After merge: ["item1", "item2", "item4", "item3"]
290/// (order determined by Position comparison)
291/// ```
292#[derive(Debug, Clone, PartialEq, Eq)]
293pub struct List {
294    /// Internal storage using BTreeMap for ordered access
295    items: BTreeMap<Position, Value>,
296}
297
298impl List {
299    /// Creates a new empty list
300    pub fn new() -> Self {
301        Self {
302            items: BTreeMap::new(),
303        }
304    }
305
306    /// Returns the number of items in the list (excluding tombstones)
307    pub fn len(&self) -> usize {
308        self.items
309            .values()
310            .filter(|v| !matches!(v, Value::Deleted))
311            .count()
312    }
313
314    /// Returns the total number of items including tombstones
315    pub fn total_len(&self) -> usize {
316        self.items.len()
317    }
318
319    /// Returns true if the list is empty
320    pub fn is_empty(&self) -> bool {
321        self.items.is_empty()
322    }
323
324    /// Pushes a value to the end of the list
325    /// Returns the index of the newly added element
326    pub fn push(&mut self, value: impl Into<Value>) -> usize {
327        let value = value.into();
328        let position = if let Some((last_pos, _)) = self.items.last_key_value() {
329            // Create a position after the last element
330            Position::new(last_pos.numerator.saturating_add(1), 1)
331        } else {
332            // First element
333            Position::beginning()
334        };
335
336        self.items.insert(position, value);
337        // Return the index (count of non-tombstone elements - 1)
338        self.len() - 1
339    }
340
341    /// Inserts a value at a specific index
342    pub fn insert(&mut self, index: usize, value: impl Into<Value>) -> Result<(), CRDTError> {
343        let len = self.len();
344        if index > len {
345            return Err(CRDTError::ListIndexOutOfBounds { index, len });
346        }
347
348        let position = if index == 0 {
349            // Insert at beginning
350            if let Some((first_pos, _)) = self.items.first_key_value() {
351                Position::new(first_pos.numerator - 1, first_pos.denominator)
352            } else {
353                Position::beginning()
354            }
355        } else if index == len {
356            // Insert at end (same as push)
357            if let Some((last_pos, _)) = self.items.last_key_value() {
358                Position::new(last_pos.numerator + 1, last_pos.denominator)
359            } else {
360                Position::beginning()
361            }
362        } else {
363            // Insert between two existing positions
364            let positions: Vec<_> = self.items.keys().collect();
365            let left_pos = positions[index - 1];
366            let right_pos = positions[index];
367            Position::between(left_pos, right_pos)
368        };
369
370        self.items.insert(position, value.into());
371        Ok(())
372    }
373
374    /// Gets a value by index (0-based), filtering out tombstones
375    pub fn get(&self, index: usize) -> Option<&Value> {
376        self.items
377            .values()
378            .filter(|v| !matches!(v, Value::Deleted))
379            .nth(index)
380    }
381
382    /// Gets a mutable reference to a value by index (0-based), filtering out tombstones
383    pub fn get_mut(&mut self, index: usize) -> Option<&mut Value> {
384        // Find the position of the nth non-tombstone element
385        let mut current_index = 0;
386        let mut target_position = None;
387
388        for (pos, value) in &self.items {
389            if !matches!(value, Value::Deleted) {
390                if current_index == index {
391                    target_position = Some(pos.clone());
392                    break;
393                }
394                current_index += 1;
395            }
396        }
397
398        if let Some(pos) = target_position {
399            self.items.get_mut(&pos)
400        } else {
401            None
402        }
403    }
404
405    /// Inserts a value at a specific position (advanced API)
406    pub fn insert_at_position(&mut self, position: Position, value: impl Into<Value>) {
407        self.items.insert(position, value.into());
408    }
409
410    /// Gets a value by position
411    pub fn get_by_position(&self, position: &Position) -> Option<&Value> {
412        self.items.get(position)
413    }
414
415    /// Gets a mutable reference to a value by position
416    pub fn get_by_position_mut(&mut self, position: &Position) -> Option<&mut Value> {
417        self.items.get_mut(position)
418    }
419
420    /// Sets a value at a specific index, returns the old value if present
421    /// Only considers non-tombstone elements for indexing
422    pub fn set(&mut self, index: usize, value: impl Into<Value>) -> Option<Value> {
423        let value = value.into();
424        // Find the position of the nth non-tombstone element
425        let mut current_index = 0;
426        let mut target_position = None;
427
428        for (pos, val) in &self.items {
429            if !matches!(val, Value::Deleted) {
430                if current_index == index {
431                    target_position = Some(pos.clone());
432                    break;
433                }
434                current_index += 1;
435            }
436        }
437
438        if let Some(pos) = target_position {
439            self.items.insert(pos, value)
440        } else {
441            None
442        }
443    }
444
445    /// Removes a value by index (tombstones it for CRDT semantics)
446    /// Only considers non-tombstone elements for indexing
447    pub fn remove(&mut self, index: usize) -> Option<Value> {
448        // Find the position of the nth non-tombstone element
449        let mut current_index = 0;
450        let mut target_position = None;
451
452        for (pos, val) in &self.items {
453            if !matches!(val, Value::Deleted) {
454                if current_index == index {
455                    target_position = Some(pos.clone());
456                    break;
457                }
458                current_index += 1;
459            }
460        }
461
462        if let Some(pos) = target_position {
463            let old_value = self.items.get(&pos).cloned();
464            self.items.insert(pos, Value::Deleted);
465            old_value
466        } else {
467            None
468        }
469    }
470
471    /// Removes a value by position
472    pub fn remove_by_position(&mut self, position: &Position) -> Option<Value> {
473        self.items.remove(position)
474    }
475
476    /// Returns an iterator over the values in order (excluding tombstones)
477    pub fn iter(&self) -> impl Iterator<Item = &Value> {
478        self.items.values().filter(|v| !matches!(v, Value::Deleted))
479    }
480
481    /// Returns an iterator over all values including tombstones
482    pub fn iter_all(&self) -> impl Iterator<Item = &Value> {
483        self.items.values()
484    }
485
486    /// Returns an iterator over position-value pairs in order
487    pub fn iter_with_positions(&self) -> impl Iterator<Item = (&Position, &Value)> {
488        self.items.iter()
489    }
490
491    /// Returns a mutable iterator over the values in order
492    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Value> {
493        self.items.values_mut()
494    }
495
496    /// Merges another List into this one (CRDT merge operation)
497    pub fn merge(&mut self, other: &List) {
498        for (position, value) in &other.items {
499            match self.items.get_mut(position) {
500                Some(existing_value) => {
501                    // If both lists have the same position, merge the values
502                    existing_value.merge(value);
503                }
504                None => {
505                    // Position doesn't exist, add it
506                    self.items.insert(position.clone(), value.clone());
507                }
508            }
509        }
510    }
511
512    /// Clears all items from the list
513    pub fn clear(&mut self) {
514        self.items.clear();
515    }
516
517    /// Converts to a Vec of values (loses position information)
518    pub fn to_vec(&self) -> Vec<Value> {
519        self.items.values().cloned().collect()
520    }
521}
522
523impl Default for List {
524    fn default() -> Self {
525        Self::new()
526    }
527}
528
529// Custom serde implementation to handle Position keys in JSON
530impl serde::Serialize for List {
531    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
532    where
533        S: serde::Serializer,
534    {
535        use serde::ser::SerializeSeq;
536
537        // Serialize as an array of [position, value] pairs
538        let mut seq = serializer.serialize_seq(Some(self.items.len()))?;
539        for (position, value) in &self.items {
540            seq.serialize_element(&(position, value))?;
541        }
542        seq.end()
543    }
544}
545
546impl<'de> serde::Deserialize<'de> for List {
547    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
548    where
549        D: serde::Deserializer<'de>,
550    {
551        use std::fmt;
552
553        use serde::de::{SeqAccess, Visitor};
554
555        struct ListVisitor;
556
557        impl<'de> Visitor<'de> for ListVisitor {
558            type Value = List;
559
560            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
561                formatter.write_str("a sequence of [position, value] pairs")
562            }
563
564            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
565            where
566                A: SeqAccess<'de>,
567            {
568                let mut items = BTreeMap::new();
569
570                while let Some((position, value)) = seq.next_element::<(Position, Value)>()? {
571                    items.insert(position, value);
572                }
573
574                Ok(List { items })
575            }
576        }
577
578        deserializer.deserialize_seq(ListVisitor)
579    }
580}
581
582impl FromIterator<Value> for List {
583    fn from_iter<T: IntoIterator<Item = Value>>(iter: T) -> Self {
584        let mut list = List::new();
585        for value in iter {
586            list.push(value);
587        }
588        list
589    }
590}
591
592// Data trait implementations
593impl Data for List {}