absurder_sql/storage/
optimistic_updates.rs

1/// Optimistic Updates Module
2/// 
3/// Provides optimistic UI update capabilities for multi-tab coordination.
4/// Allows UI to show pending writes immediately before they're confirmed by the leader.
5/// 
6/// Key Features:
7/// - Track pending writes in-memory
8/// - Merge pending writes with confirmed data in query results
9/// - Clear pending writes after leader confirmation
10/// - Rollback support for failed writes
11
12use serde::{Deserialize, Serialize};
13use std::collections::HashMap;
14
15/// Represents a pending optimistic write
16#[derive(Clone, Debug, Serialize, Deserialize)]
17pub struct OptimisticWrite {
18    /// Unique ID for this write
19    pub id: String,
20    /// The SQL statement
21    pub sql: String,
22    /// Timestamp when added
23    pub timestamp: f64,
24    /// Status of the write
25    pub status: OptimisticWriteStatus,
26}
27
28/// Status of an optimistic write
29#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
30pub enum OptimisticWriteStatus {
31    /// Pending execution by leader
32    Pending,
33    /// Confirmed by leader
34    Confirmed,
35    /// Failed (conflict or error)
36    Failed,
37}
38
39/// Optimistic updates manager
40pub struct OptimisticUpdatesManager {
41    /// Whether optimistic mode is enabled
42    enabled: bool,
43    /// Pending writes keyed by ID
44    pending_writes: HashMap<String, OptimisticWrite>,
45}
46
47impl OptimisticUpdatesManager {
48    /// Create a new optimistic updates manager
49    pub fn new() -> Self {
50        Self {
51            enabled: false,
52            pending_writes: HashMap::new(),
53        }
54    }
55
56    /// Enable or disable optimistic mode
57    pub fn set_enabled(&mut self, enabled: bool) {
58        self.enabled = enabled;
59        if !enabled {
60            // Clear all pending writes when disabled
61            self.pending_writes.clear();
62        }
63    }
64
65    /// Check if optimistic mode is enabled
66    pub fn is_enabled(&self) -> bool {
67        self.enabled
68    }
69
70    /// Track a new optimistic write
71    pub fn track_write(&mut self, sql: String) -> String {
72        if !self.enabled {
73            return String::new();
74        }
75
76        // Generate unique ID
77        let id = Self::generate_id();
78        
79        #[cfg(target_arch = "wasm32")]
80        let timestamp = js_sys::Date::now();
81        
82        #[cfg(not(target_arch = "wasm32"))]
83        let timestamp = std::time::SystemTime::now()
84            .duration_since(std::time::UNIX_EPOCH)
85            .unwrap()
86            .as_secs_f64() * 1000.0;
87
88        let write = OptimisticWrite {
89            id: id.clone(),
90            sql,
91            timestamp,
92            status: OptimisticWriteStatus::Pending,
93        };
94
95        self.pending_writes.insert(id.clone(), write);
96        
97        #[cfg(target_arch = "wasm32")]
98        web_sys::console::log_1(&format!("Tracked optimistic write: {}", id).into());
99
100        id
101    }
102
103    /// Mark a write as confirmed
104    pub fn confirm_write(&mut self, id: &str) {
105        if let Some(write) = self.pending_writes.get_mut(id) {
106            write.status = OptimisticWriteStatus::Confirmed;
107            
108            #[cfg(target_arch = "wasm32")]
109            web_sys::console::log_1(&format!("Confirmed optimistic write: {}", id).into());
110        }
111    }
112
113    /// Mark a write as failed
114    pub fn fail_write(&mut self, id: &str) {
115        if let Some(write) = self.pending_writes.get_mut(id) {
116            write.status = OptimisticWriteStatus::Failed;
117            
118            #[cfg(target_arch = "wasm32")]
119            web_sys::console::log_1(&format!("Failed optimistic write: {}", id).into());
120        }
121    }
122
123    /// Remove a write from tracking
124    pub fn remove_write(&mut self, id: &str) {
125        self.pending_writes.remove(id);
126    }
127
128    /// Clear all pending writes
129    pub fn clear_all(&mut self) {
130        #[cfg(target_arch = "wasm32")]
131        {
132            let count = self.pending_writes.len();
133            web_sys::console::log_1(&format!("Cleared {} optimistic writes", count).into());
134        }
135        
136        self.pending_writes.clear();
137    }
138
139    /// Get count of pending writes
140    pub fn get_pending_count(&self) -> usize {
141        self.pending_writes
142            .values()
143            .filter(|w| w.status == OptimisticWriteStatus::Pending)
144            .count()
145    }
146
147    /// Get all pending writes
148    pub fn get_pending_writes(&self) -> Vec<&OptimisticWrite> {
149        self.pending_writes
150            .values()
151            .filter(|w| w.status == OptimisticWriteStatus::Pending)
152            .collect()
153    }
154
155    /// Generate a unique ID for a write
156    fn generate_id() -> String {
157        #[cfg(target_arch = "wasm32")]
158        {
159            let timestamp = js_sys::Date::now();
160            let random = js_sys::Math::random();
161            format!("opt_{}_{}", timestamp as u64, (random * 1000000.0) as u64)
162        }
163        
164        #[cfg(not(target_arch = "wasm32"))]
165        {
166            use std::time::SystemTime;
167            let timestamp = SystemTime::now()
168                .duration_since(std::time::UNIX_EPOCH)
169                .unwrap()
170                .as_nanos();
171            // Use thread_rng for additional randomness to prevent collisions
172            use std::sync::atomic::{AtomicU64, Ordering};
173            static COUNTER: AtomicU64 = AtomicU64::new(0);
174            let counter = COUNTER.fetch_add(1, Ordering::SeqCst);
175            format!("opt_{}_{}", timestamp, counter)
176        }
177    }
178}
179
180impl Default for OptimisticUpdatesManager {
181    fn default() -> Self {
182        Self::new()
183    }
184}
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189
190    #[test]
191    fn test_enable_disable() {
192        let mut manager = OptimisticUpdatesManager::new();
193        assert!(!manager.is_enabled());
194        
195        manager.set_enabled(true);
196        assert!(manager.is_enabled());
197        
198        manager.set_enabled(false);
199        assert!(!manager.is_enabled());
200    }
201
202    #[test]
203    fn test_track_write() {
204        let mut manager = OptimisticUpdatesManager::new();
205        manager.set_enabled(true);
206        
207        let id = manager.track_write("INSERT INTO test VALUES (1)".to_string());
208        assert!(!id.is_empty());
209        assert_eq!(manager.get_pending_count(), 1);
210    }
211
212    #[test]
213    fn test_clear_all() {
214        let mut manager = OptimisticUpdatesManager::new();
215        manager.set_enabled(true);
216        
217        manager.track_write("INSERT INTO test VALUES (1)".to_string());
218        manager.track_write("INSERT INTO test VALUES (2)".to_string());
219        assert_eq!(manager.get_pending_count(), 2);
220        
221        manager.clear_all();
222        assert_eq!(manager.get_pending_count(), 0);
223    }
224}