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
11use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13
14/// Represents a pending optimistic write
15#[derive(Clone, Debug, Serialize, Deserialize)]
16pub struct OptimisticWrite {
17    /// Unique ID for this write
18    pub id: String,
19    /// The SQL statement
20    pub sql: String,
21    /// Timestamp when added
22    pub timestamp: f64,
23    /// Status of the write
24    pub status: OptimisticWriteStatus,
25}
26
27/// Status of an optimistic write
28#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
29pub enum OptimisticWriteStatus {
30    /// Pending execution by leader
31    Pending,
32    /// Confirmed by leader
33    Confirmed,
34    /// Failed (conflict or error)
35    Failed,
36}
37
38/// Optimistic updates manager
39pub struct OptimisticUpdatesManager {
40    /// Whether optimistic mode is enabled
41    enabled: bool,
42    /// Pending writes keyed by ID
43    pending_writes: HashMap<String, OptimisticWrite>,
44}
45
46impl OptimisticUpdatesManager {
47    /// Create a new optimistic updates manager
48    pub fn new() -> Self {
49        Self {
50            enabled: false,
51            pending_writes: HashMap::new(),
52        }
53    }
54
55    /// Enable or disable optimistic mode
56    pub fn set_enabled(&mut self, enabled: bool) {
57        self.enabled = enabled;
58        if !enabled {
59            // Clear all pending writes when disabled
60            self.pending_writes.clear();
61        }
62    }
63
64    /// Check if optimistic mode is enabled
65    pub fn is_enabled(&self) -> bool {
66        self.enabled
67    }
68
69    /// Track a new optimistic write
70    pub fn track_write(&mut self, sql: String) -> String {
71        if !self.enabled {
72            return String::new();
73        }
74
75        // Generate unique ID
76        let id = Self::generate_id();
77
78        #[cfg(target_arch = "wasm32")]
79        let timestamp = js_sys::Date::now();
80
81        #[cfg(not(target_arch = "wasm32"))]
82        let timestamp = std::time::SystemTime::now()
83            .duration_since(std::time::UNIX_EPOCH)
84            .unwrap()
85            .as_secs_f64()
86            * 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}