Skip to main content

nodedb_lite/
sequence.rs

1//! Local sequence support for NodeDB-Lite.
2//!
3//! Stores sequence definitions via the StorageEngine.
4//! Provides nextval/currval/setval for local use. Definitions sync
5//! from Origin via CRDT; counter state is ephemeral (in-memory only,
6//! reset to `start_value` on restart).
7
8use std::collections::HashMap;
9use std::sync::Mutex;
10use std::sync::atomic::{AtomicI64, Ordering};
11
12use serde::{Deserialize, Serialize};
13
14use crate::error::LiteError;
15
16/// Lite-side sequence definition (synced from Origin via CRDT).
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct LiteSequenceDef {
19    pub name: String,
20    pub start_value: i64,
21    pub increment: i64,
22    pub min_value: i64,
23    pub max_value: i64,
24    pub cycle: bool,
25}
26
27/// Per-sequence atomic counter.
28struct LiteSeqHandle {
29    counter: AtomicI64,
30    called: std::sync::atomic::AtomicBool,
31    def: LiteSequenceDef,
32}
33
34/// Local sequence registry for NodeDB-Lite.
35///
36/// Definitions arrive via CRDT sync from Origin. State (current_value)
37/// is purely local — each Lite instance has its own counter within
38/// an allocated range.
39pub struct LiteSequenceRegistry {
40    sequences: Mutex<HashMap<String, LiteSeqHandle>>,
41}
42
43impl LiteSequenceRegistry {
44    pub fn new() -> Self {
45        Self {
46            sequences: Mutex::new(HashMap::new()),
47        }
48    }
49
50    /// Register a sequence (from CRDT sync or local creation).
51    pub fn register(&self, def: LiteSequenceDef) {
52        let initial = def.start_value - def.increment;
53        let mut map = self.sequences.lock().unwrap_or_else(|p| p.into_inner());
54        map.insert(
55            def.name.clone(),
56            LiteSeqHandle {
57                counter: AtomicI64::new(initial),
58                called: std::sync::atomic::AtomicBool::new(false),
59                def,
60            },
61        );
62    }
63
64    /// Get next value (lock-free on the hot path).
65    pub fn nextval(&self, name: &str) -> Result<i64, LiteError> {
66        let map = self.sequences.lock().unwrap_or_else(|p| p.into_inner());
67        let handle = map
68            .get(name)
69            .ok_or_else(|| LiteError::Query(format!("sequence \"{name}\" does not exist")))?;
70
71        let inc = handle.def.increment;
72        let prev = handle.counter.fetch_add(inc, Ordering::Relaxed);
73        let new_val = prev + inc;
74        handle.called.store(true, Ordering::Relaxed);
75
76        if inc > 0 && new_val > handle.def.max_value {
77            if handle.def.cycle {
78                handle
79                    .counter
80                    .store(handle.def.min_value, Ordering::Relaxed);
81                return Ok(handle.def.min_value);
82            }
83            handle.counter.store(prev, Ordering::Relaxed);
84            return Err(LiteError::Query(format!("sequence \"{name}\" exhausted")));
85        }
86        if inc < 0 && new_val < handle.def.min_value {
87            if handle.def.cycle {
88                handle
89                    .counter
90                    .store(handle.def.max_value, Ordering::Relaxed);
91                return Ok(handle.def.max_value);
92            }
93            handle.counter.store(prev, Ordering::Relaxed);
94            return Err(LiteError::Query(format!("sequence \"{name}\" exhausted")));
95        }
96
97        Ok(new_val)
98    }
99
100    /// Get current value (last returned by nextval).
101    pub fn currval(&self, name: &str) -> Result<i64, LiteError> {
102        let map = self.sequences.lock().unwrap_or_else(|p| p.into_inner());
103        let handle = map
104            .get(name)
105            .ok_or_else(|| LiteError::Query(format!("sequence \"{name}\" does not exist")))?;
106        if !handle.called.load(Ordering::Relaxed) {
107            return Err(LiteError::Query(format!(
108                "currval of \"{name}\" not yet defined"
109            )));
110        }
111        Ok(handle.counter.load(Ordering::Relaxed))
112    }
113
114    /// Set the counter to a specific value.
115    pub fn setval(&self, name: &str, value: i64) -> Result<i64, LiteError> {
116        let map = self.sequences.lock().unwrap_or_else(|p| p.into_inner());
117        let handle = map
118            .get(name)
119            .ok_or_else(|| LiteError::Query(format!("sequence \"{name}\" does not exist")))?;
120        if value < handle.def.min_value || value > handle.def.max_value {
121            return Err(LiteError::Query(format!(
122                "setval: {value} outside [{}, {}]",
123                handle.def.min_value, handle.def.max_value
124            )));
125        }
126        handle.counter.store(value, Ordering::Relaxed);
127        handle.called.store(true, Ordering::Relaxed);
128        Ok(value)
129    }
130
131    /// Restart every sequence named `<collection>_<field>_seq` so its next
132    /// value is its start value. Backs `TRUNCATE ... RESTART IDENTITY`.
133    /// Returns the names restarted.
134    pub fn restart_collection_sequences(&self, collection: &str) -> Vec<String> {
135        let prefix = format!("{collection}_");
136        let map = self.sequences.lock().unwrap_or_else(|p| p.into_inner());
137        let mut restarted = Vec::new();
138        for (name, handle) in map.iter() {
139            if !(name.starts_with(&prefix) && name.ends_with("_seq")) {
140                continue;
141            }
142            handle.counter.store(
143                handle.def.start_value - handle.def.increment,
144                Ordering::Relaxed,
145            );
146            handle.called.store(false, Ordering::Relaxed);
147            restarted.push(name.clone());
148        }
149        restarted
150    }
151
152    /// List all sequence names.
153    pub fn names(&self) -> Vec<String> {
154        let map = self.sequences.lock().unwrap_or_else(|p| p.into_inner());
155        map.keys().cloned().collect()
156    }
157
158    /// Remove a sequence by name.
159    pub fn remove(&self, name: &str) -> bool {
160        let mut map = self.sequences.lock().unwrap_or_else(|p| p.into_inner());
161        map.remove(name).is_some()
162    }
163}
164
165impl Default for LiteSequenceRegistry {
166    fn default() -> Self {
167        Self::new()
168    }
169}
170
171#[cfg(test)]
172mod tests {
173    use super::*;
174
175    fn make_def(name: &str) -> LiteSequenceDef {
176        LiteSequenceDef {
177            name: name.to_string(),
178            start_value: 1,
179            increment: 1,
180            min_value: 1,
181            max_value: i64::MAX,
182            cycle: false,
183        }
184    }
185
186    #[test]
187    fn basic_nextval() {
188        let reg = LiteSequenceRegistry::new();
189        reg.register(make_def("s1"));
190        assert_eq!(reg.nextval("s1").unwrap(), 1);
191        assert_eq!(reg.nextval("s1").unwrap(), 2);
192        assert_eq!(reg.currval("s1").unwrap(), 2);
193    }
194
195    #[test]
196    fn setval_and_nextval() {
197        let reg = LiteSequenceRegistry::new();
198        reg.register(make_def("s1"));
199        reg.setval("s1", 50).unwrap();
200        assert_eq!(reg.nextval("s1").unwrap(), 51);
201    }
202
203    #[test]
204    fn restart_collection_sequences_makes_start_the_next_value() {
205        let reg = LiteSequenceRegistry::new();
206        reg.register(make_def("orders_id_seq"));
207        reg.register(make_def("orders_line_seq"));
208        reg.register(make_def("orders_archive_id_seq"));
209        reg.register(make_def("users_id_seq"));
210        for _ in 0..3 {
211            reg.nextval("orders_id_seq").unwrap();
212            reg.nextval("orders_line_seq").unwrap();
213            reg.nextval("orders_archive_id_seq").unwrap();
214            reg.nextval("users_id_seq").unwrap();
215        }
216        let mut restarted = reg.restart_collection_sequences("orders");
217        restarted.sort();
218        assert_eq!(
219            restarted,
220            vec![
221                "orders_archive_id_seq".to_string(),
222                "orders_id_seq".to_string(),
223                "orders_line_seq".to_string()
224            ]
225        );
226        assert!(
227            reg.currval("orders_id_seq").is_err(),
228            "restart clears currval"
229        );
230        assert_eq!(reg.nextval("orders_id_seq").unwrap(), 1);
231        assert_eq!(reg.nextval("orders_line_seq").unwrap(), 1);
232        assert_eq!(reg.nextval("users_id_seq").unwrap(), 4);
233    }
234
235    #[test]
236    fn not_found() {
237        let reg = LiteSequenceRegistry::new();
238        assert!(reg.nextval("nope").is_err());
239    }
240}