Skip to main content

linkrs_core/metadata/
sequence.rs

1use serde::{Deserialize, Serialize};
2use std::sync::atomic::{AtomicI64, Ordering};
3
4use crate::StorageError;
5
6/// Sequence definition
7///
8/// An auto-incrementing counter object with configurable bounds and cycle behavior.
9/// The `current_value` field uses atomic operations for thread-safe access.
10#[derive(Debug, Serialize, Deserialize)]
11pub struct SequenceDef {
12    /// Sequence name
13    pub name: String,
14    /// Current value (skipped in serialization, managed via atomic)
15    #[serde(skip)]
16    current_value: AtomicI64,
17    /// Increment step
18    pub increment: i64,
19    /// Minimum value
20    pub min_value: i64,
21    /// Maximum value
22    pub max_value: i64,
23    /// Whether to cycle when hitting bounds
24    pub cycle: bool,
25    /// Initial value (used for persistence and reset)
26    pub start_value: i64,
27}
28
29impl Clone for SequenceDef {
30    fn clone(&self) -> Self {
31        Self {
32            name: self.name.clone(),
33            current_value: AtomicI64::new(self.current_value.load(Ordering::SeqCst)),
34            increment: self.increment,
35            min_value: self.min_value,
36            max_value: self.max_value,
37            cycle: self.cycle,
38            start_value: self.start_value,
39        }
40    }
41}
42
43impl SequenceDef {
44    pub fn new(
45        name: String,
46        start: i64,
47        increment: i64,
48        min_value: i64,
49        max_value: i64,
50        cycle: bool,
51    ) -> Self {
52        Self {
53            name,
54            current_value: AtomicI64::new(start),
55            increment,
56            min_value,
57            max_value,
58            cycle,
59            start_value: start,
60        }
61    }
62
63    /// Get the current value without incrementing
64    pub fn current_value(&self) -> i64 {
65        self.current_value.load(Ordering::SeqCst)
66    }
67
68    /// Get the next value with atomic increment.
69    ///
70    /// Uses a compare-exchange retry loop so concurrent callers never lose
71    /// increments. Returns `Err` if the value would exceed bounds and
72    /// `cycle` is false.
73    pub fn next_value(&self) -> Result<i64, StorageError> {
74        loop {
75            let current = self.current_value.load(Ordering::SeqCst);
76            let next = current + self.increment;
77
78            let new_value = if next > self.max_value {
79                if self.cycle {
80                    self.min_value
81                } else {
82                    return Err(StorageError::db_error(format!(
83                        "Sequence '{}' overflow: value {} would exceed max_value {}",
84                        self.name, next, self.max_value
85                    )));
86                }
87            } else if next < self.min_value {
88                if self.cycle {
89                    self.max_value
90                } else {
91                    return Err(StorageError::db_error(format!(
92                        "Sequence '{}' underflow: value {} would go below min_value {}",
93                        self.name, next, self.min_value
94                    )));
95                }
96            } else {
97                next
98            };
99
100            match self.current_value.compare_exchange(
101                current,
102                new_value,
103                Ordering::SeqCst,
104                Ordering::SeqCst,
105            ) {
106                Ok(_) => return Ok(new_value),
107                // Lost the race to another increment; retry with fresh state.
108                Err(_) => continue,
109            }
110        }
111    }
112
113    /// Reset to the initial start value
114    pub fn reset(&self) {
115        self.current_value.store(self.start_value, Ordering::SeqCst);
116    }
117
118    /// Set the current value directly
119    pub fn set_value(&self, value: i64) {
120        self.current_value.store(value, Ordering::SeqCst);
121    }
122}
123
124impl Default for SequenceDef {
125    fn default() -> Self {
126        Self::new(String::new(), 1, 1, i64::MIN, i64::MAX, false)
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133
134    #[test]
135    fn test_sequence_basic() {
136        let seq = SequenceDef::new("test".into(), 1, 1, 1, 100, false);
137        assert_eq!(seq.current_value(), 1);
138        assert_eq!(seq.next_value().unwrap(), 2);
139        assert_eq!(seq.next_value().unwrap(), 3);
140        assert_eq!(seq.current_value(), 3);
141    }
142
143    #[test]
144    fn test_sequence_increment() {
145        let seq = SequenceDef::new("test".into(), 10, 5, 1, 1000, false);
146        assert_eq!(seq.next_value().unwrap(), 15);
147        assert_eq!(seq.next_value().unwrap(), 20);
148    }
149
150    #[test]
151    fn test_sequence_negative_increment() {
152        let seq = SequenceDef::new("test".into(), 100, -10, 1, 1000, false);
153        assert_eq!(seq.next_value().unwrap(), 90);
154        assert_eq!(seq.next_value().unwrap(), 80);
155    }
156
157    #[test]
158    fn test_sequence_cycle() {
159        let seq = SequenceDef::new("test".into(), 1, 1, 1, 3, true);
160        assert_eq!(seq.next_value().unwrap(), 2);
161        assert_eq!(seq.next_value().unwrap(), 3);
162        assert_eq!(seq.next_value().unwrap(), 1); // cycles back to min
163    }
164
165    #[test]
166    fn test_sequence_overflow_no_cycle() {
167        let seq = SequenceDef::new("test".into(), 1, 1, 1, 3, false);
168        assert_eq!(seq.next_value().unwrap(), 2);
169        assert_eq!(seq.next_value().unwrap(), 3);
170        assert!(seq.next_value().is_err());
171    }
172
173    #[test]
174    fn test_sequence_underflow_no_cycle() {
175        let seq = SequenceDef::new("test".into(), 100, -50, 10, 200, false);
176        assert_eq!(seq.next_value().unwrap(), 50);
177        assert!(seq.next_value().is_err());
178    }
179
180    #[test]
181    fn test_sequence_reset() {
182        let seq = SequenceDef::new("test".into(), 1, 1, 1, 100, false);
183        seq.next_value().unwrap();
184        seq.next_value().unwrap();
185        assert_eq!(seq.current_value(), 3);
186        seq.reset();
187        assert_eq!(seq.current_value(), 1);
188    }
189
190    #[test]
191    fn test_sequence_set_value() {
192        let seq = SequenceDef::new("test".into(), 1, 1, 1, 100, false);
193        seq.set_value(50);
194        assert_eq!(seq.current_value(), 50);
195        assert_eq!(seq.next_value().unwrap(), 51);
196    }
197
198    #[test]
199    fn test_sequence_concurrent_next_value() {
200        use std::sync::Arc;
201        use std::thread;
202
203        let seq = Arc::new(SequenceDef::new("test".into(), 0, 1, 0, 10000, false));
204        let mut handles = vec![];
205
206        for _ in 0..10 {
207            let seq = Arc::clone(&seq);
208            handles.push(thread::spawn(move || {
209                for _ in 0..1000 {
210                    seq.next_value().unwrap();
211                }
212            }));
213        }
214
215        for h in handles {
216            h.join().unwrap();
217        }
218
219        assert_eq!(seq.current_value(), 10000);
220    }
221}