Skip to main content

kalamdb_commons/ids/
seq_id.rs

1//! SeqId - Sequence ID based on Snowflake ID for MVCC versioning
2//!
3//! This module provides a wrapper around Snowflake IDs for use as sequence identifiers
4//! in the MVCC architecture. Each SeqId represents a unique version of a row.
5
6use std::{
7    fmt,
8    time::{SystemTime, UNIX_EPOCH},
9};
10
11use serde::{
12    de::{self, Visitor},
13    Deserialize, Deserializer, Serialize, Serializer,
14};
15
16use crate::{ids::SnowflakeGenerator, StorageKey};
17
18/// Sequence ID for MVCC versioning
19///
20/// Internally uses Snowflake ID format (64 bits):
21/// - 41 bits: timestamp in milliseconds since custom epoch
22/// - 10 bits: machine/worker ID
23/// - 12 bits: sequence number
24///
25/// **MVCC Architecture**: Used as `_seq` column for version tracking
26/// Storage key format: `{user_id}:{_seq}` or just `{_seq}` for shared tables
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
28pub struct SeqId(i64);
29
30impl SeqId {
31    /// Custom epoch: 2024-01-01 00:00:00 UTC (same as SnowflakeGenerator)
32    pub const EPOCH: u64 = 1704067200000;
33
34    /// Create a new SeqId from a Snowflake ID
35    pub fn new(snowflake_id: i64) -> Self {
36        Self(snowflake_id)
37    }
38
39    /// Create SeqId from raw i64 value
40    pub fn from_i64(value: i64) -> Self {
41        Self(value)
42    }
43
44    /// Get the raw i64 value
45    pub fn as_i64(&self) -> i64 {
46        self.0
47    }
48
49    /// Extract timestamp in milliseconds since Unix epoch
50    ///
51    /// This is useful for debugging, logging, and time-based queries.
52    pub fn timestamp_millis(&self) -> u64 {
53        let id = self.0 as u64;
54        (id >> 22) + Self::EPOCH
55    }
56
57    /// Extract timestamp in whole seconds since Unix epoch
58    pub fn timestamp_seconds(&self) -> u64 {
59        self.timestamp_millis() / 1000
60    }
61
62    /// Compute how many whole seconds old this SeqId is relative to `now_millis`
63    pub fn age_seconds(&self, now_millis: u64) -> u64 {
64        let ts = self.timestamp_millis();
65        now_millis.saturating_sub(ts) / 1000
66    }
67
68    /// Extract timestamp as SystemTime
69    pub fn timestamp(&self) -> SystemTime {
70        let millis = self.timestamp_millis();
71        UNIX_EPOCH + std::time::Duration::from_millis(millis)
72    }
73
74    /// Extract worker ID (0-1023)
75    pub fn worker_id(&self) -> u16 {
76        let id = self.0 as u64;
77        ((id >> 12) & 0x3FF) as u16
78    }
79
80    /// Extract sequence number (0-4095)
81    pub fn sequence(&self) -> u16 {
82        let id = self.0 as u64;
83        (id & 0xFFF) as u16
84    }
85
86    // String conversion is provided by Display/ToString; no inherent method needed
87
88    /// Parse from string representation
89    pub fn from_string(s: &str) -> Result<Self, String> {
90        s.parse::<i64>()
91            .map(Self::new)
92            .map_err(|e| format!("Failed to parse SeqId: {}", e))
93    }
94
95    /// Convert to bytes (big-endian for consistent ordering in RocksDB)
96    pub fn to_bytes(&self) -> [u8; 8] {
97        self.0.to_be_bytes()
98    }
99
100    /// Parse from bytes (big-endian)
101    pub fn from_bytes(bytes: &[u8]) -> Result<Self, String> {
102        if bytes.len() != 8 {
103            return Err(format!("Invalid byte length: expected 8, got {}", bytes.len()));
104        }
105        let mut array = [0u8; 8];
106        array.copy_from_slice(bytes);
107        Ok(Self::new(i64::from_be_bytes(array)))
108    }
109
110    /// Return the maximum possible SeqId for the provided timestamp.
111    ///
112    /// This packs the timestamp together with the largest worker/sequence values
113    /// so the returned SeqId encompasses every Snowflake generated at or before
114    /// `timestamp_millis`.
115    pub fn max_id_for_timestamp(timestamp_millis: u64) -> Result<Self, String> {
116        // let normalized = timestamp_millis.max(Self::EPOCH);
117        let id = SnowflakeGenerator::max_id_for_timestamp(timestamp_millis)?;
118        Ok(Self::new(id))
119    }
120}
121
122impl fmt::Display for SeqId {
123    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124        write!(f, "{}", self.0)
125    }
126}
127
128impl Serialize for SeqId {
129    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
130    where
131        S: Serializer,
132    {
133        serializer.serialize_i64(self.0)
134    }
135}
136
137impl<'de> Deserialize<'de> for SeqId {
138    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
139    where
140        D: Deserializer<'de>,
141    {
142        struct SeqIdVisitor;
143
144        impl Visitor<'_> for SeqIdVisitor {
145            type Value = SeqId;
146
147            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
148                formatter.write_str("an i64 sequence ID or a decimal string")
149            }
150
151            fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
152            where
153                E: de::Error,
154            {
155                Ok(SeqId::new(value))
156            }
157
158            fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
159            where
160                E: de::Error,
161            {
162                let value = i64::try_from(value)
163                    .map_err(|_| E::custom(format!("sequence ID {} exceeds i64 range", value)))?;
164                Ok(SeqId::new(value))
165            }
166
167            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
168            where
169                E: de::Error,
170            {
171                SeqId::from_string(value).map_err(E::custom)
172            }
173
174            fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
175            where
176                E: de::Error,
177            {
178                self.visit_str(&value)
179            }
180        }
181
182        deserializer.deserialize_any(SeqIdVisitor)
183    }
184}
185
186impl From<i64> for SeqId {
187    fn from(value: i64) -> Self {
188        Self::new(value)
189    }
190}
191
192impl From<SeqId> for i64 {
193    fn from(seq_id: SeqId) -> Self {
194        seq_id.0
195    }
196}
197
198impl StorageKey for SeqId {
199    fn storage_key(&self) -> Vec<u8> {
200        self.to_bytes().to_vec()
201    }
202
203    fn from_storage_key(bytes: &[u8]) -> Result<Self, String> {
204        Self::from_bytes(bytes)
205    }
206}
207
208#[cfg(test)]
209mod tests {
210    use super::*;
211
212    #[test]
213    fn test_seq_id_creation() {
214        let seq_id = SeqId::new(123456789);
215        assert_eq!(seq_id.as_i64(), 123456789);
216    }
217
218    #[test]
219    fn test_seq_id_timestamp_extraction() {
220        // Create a SeqId with known timestamp component
221        let timestamp_offset = 1000u64; // 1000ms after epoch
222        let worker_id = 5u64;
223        let sequence = 42u64;
224
225        let id = (timestamp_offset << 22) | (worker_id << 12) | sequence;
226        let seq_id = SeqId::new(id as i64);
227
228        assert_eq!(seq_id.timestamp_millis(), SeqId::EPOCH + timestamp_offset);
229        assert_eq!(seq_id.worker_id(), 5);
230        assert_eq!(seq_id.sequence(), 42);
231    }
232
233    #[test]
234    fn test_seq_id_timestamp_seconds() {
235        let timestamp_offset = 5000u64; // 5 seconds after epoch
236        let id = (timestamp_offset << 22) as i64;
237        let seq_id = SeqId::new(id);
238
239        assert_eq!(seq_id.timestamp_seconds(), (SeqId::EPOCH + timestamp_offset) / 1000);
240    }
241
242    #[test]
243    fn test_seq_id_age_seconds() {
244        let timestamp_offset = 2000u64;
245        let id = (timestamp_offset << 22) as i64;
246        let seq_id = SeqId::new(id);
247        let now_millis = SeqId::EPOCH + timestamp_offset + 7000; // 7s later
248
249        assert_eq!(seq_id.age_seconds(now_millis), 7);
250    }
251
252    #[test]
253    fn test_seq_id_string_conversion() {
254        let seq_id = SeqId::new(987654321);
255        let s = seq_id.to_string();
256        assert_eq!(s, "987654321");
257
258        let parsed = SeqId::from_string(&s).unwrap();
259        assert_eq!(parsed, seq_id);
260    }
261
262    #[test]
263    fn test_seq_id_bytes_conversion() {
264        let seq_id = SeqId::new(123456789);
265        let bytes = seq_id.to_bytes();
266        let parsed = SeqId::from_bytes(&bytes).unwrap();
267        assert_eq!(parsed, seq_id);
268    }
269
270    #[test]
271    fn test_seq_id_max_id_for_timestamp() {
272        let ts = SeqId::EPOCH + 5000;
273        let seq_id = SeqId::max_id_for_timestamp(ts).expect("seq id");
274        assert!(seq_id.timestamp_millis() >= ts);
275        assert!(seq_id >= SeqId::new(0));
276    }
277
278    #[test]
279    fn test_seq_id_ordering() {
280        let seq1 = SeqId::new(100);
281        let seq2 = SeqId::new(200);
282        let seq3 = SeqId::new(300);
283
284        assert!(seq1 < seq2);
285        assert!(seq2 < seq3);
286        assert!(seq3 > seq1);
287    }
288
289    #[test]
290    fn test_seq_id_from_i64() {
291        let seq_id: SeqId = 42i64.into();
292        assert_eq!(seq_id.as_i64(), 42);
293
294        let value: i64 = seq_id.into();
295        assert_eq!(value, 42);
296    }
297}