/home/runner/work/feoxdb/feoxdb/src/storage/format.rs
Line | Count | Source |
1 | | use crate::constants::*; |
2 | | use crate::core::record::Record; |
3 | | use std::sync::atomic::Ordering; |
4 | | |
5 | | /// Trait for handling different record format versions |
6 | | pub trait RecordFormat: Send + Sync { |
7 | | /// Calculate the size of a record on disk (excluding value) |
8 | | fn record_header_size(&self, key_len: usize) -> usize; |
9 | | |
10 | | /// Calculate total size including value |
11 | | fn total_size(&self, key_len: usize, value_len: usize) -> usize; |
12 | | |
13 | | /// Serialize a record to bytes for disk storage |
14 | | fn serialize_record(&self, record: &Record, include_value: bool) -> Vec<u8>; |
15 | | |
16 | | /// Parse a record from disk bytes (returns key, value_len, timestamp, ttl_expiry) |
17 | | fn parse_record(&self, data: &[u8]) -> Option<(Vec<u8>, usize, u64, u64)>; |
18 | | |
19 | | /// Get the offset where value data starts in the serialized format |
20 | | fn value_offset(&self, key_len: usize) -> usize; |
21 | | } |
22 | | |
23 | | /// Version 1 format (no TTL support) |
24 | | pub struct FormatV1; |
25 | | |
26 | | impl RecordFormat for FormatV1 { |
27 | 0 | fn record_header_size(&self, key_len: usize) -> usize { |
28 | 0 | SECTOR_HEADER_SIZE + 2 + key_len + 8 + 8 // header + key_len(2) + key + value_len(8) + timestamp(8) |
29 | 0 | } |
30 | | |
31 | 0 | fn total_size(&self, key_len: usize, value_len: usize) -> usize { |
32 | 0 | self.record_header_size(key_len) + value_len |
33 | 0 | } |
34 | | |
35 | 0 | fn serialize_record(&self, record: &Record, include_value: bool) -> Vec<u8> { |
36 | 0 | let mut data = Vec::with_capacity(self.total_size(record.key.len(), record.value_len)); |
37 | | |
38 | | // Key length (2 bytes) |
39 | 0 | data.extend_from_slice(&(record.key.len() as u16).to_le_bytes()); |
40 | | |
41 | | // Key |
42 | 0 | data.extend_from_slice(&record.key); |
43 | | |
44 | | // Value length (8 bytes) |
45 | 0 | data.extend_from_slice(&(record.value_len as u64).to_le_bytes()); |
46 | | |
47 | | // Timestamp (8 bytes) |
48 | 0 | data.extend_from_slice(&record.timestamp.to_le_bytes()); |
49 | | |
50 | | // Value (if requested) |
51 | 0 | if include_value { |
52 | 0 | if let Some(value) = record.value.read().as_ref() { |
53 | 0 | data.extend_from_slice(value); |
54 | 0 | } |
55 | 0 | } |
56 | | |
57 | 0 | data |
58 | 0 | } |
59 | | |
60 | 0 | fn parse_record(&self, data: &[u8]) -> Option<(Vec<u8>, usize, u64, u64)> { |
61 | 0 | if data.len() < SECTOR_HEADER_SIZE + 2 { |
62 | 0 | return None; |
63 | 0 | } |
64 | | |
65 | 0 | let mut offset = SECTOR_HEADER_SIZE + 2; |
66 | 0 | let key_len = u16::from_le_bytes( |
67 | 0 | data[SECTOR_HEADER_SIZE..SECTOR_HEADER_SIZE + 2] |
68 | 0 | .try_into() |
69 | 0 | .ok()?, |
70 | | ) as usize; |
71 | | |
72 | 0 | if offset + key_len + 16 > data.len() { |
73 | 0 | return None; |
74 | 0 | } |
75 | | |
76 | 0 | let key = data[offset..offset + key_len].to_vec(); |
77 | 0 | offset += key_len; |
78 | | |
79 | 0 | let value_len = u64::from_le_bytes(data[offset..offset + 8].try_into().ok()?) as usize; |
80 | 0 | offset += 8; |
81 | | |
82 | 0 | let timestamp = u64::from_le_bytes(data[offset..offset + 8].try_into().ok()?); |
83 | | |
84 | 0 | Some((key, value_len, timestamp, 0)) // No TTL in v1 |
85 | 0 | } |
86 | | |
87 | 0 | fn value_offset(&self, key_len: usize) -> usize { |
88 | 0 | SECTOR_HEADER_SIZE + 2 + key_len + 8 + 8 |
89 | 0 | } |
90 | | } |
91 | | |
92 | | /// Version 2 format (with TTL support) |
93 | | pub struct FormatV2; |
94 | | |
95 | | impl RecordFormat for FormatV2 { |
96 | 43.1k | fn record_header_size(&self, key_len: usize) -> usize { |
97 | 43.1k | SECTOR_HEADER_SIZE + 2 + key_len + 8 + 8 + 8 // header + key_len(2) + key + value_len(8) + timestamp(8) + ttl(8) |
98 | 43.1k | } |
99 | | |
100 | 43.1k | fn total_size(&self, key_len: usize, value_len: usize) -> usize { |
101 | 43.1k | self.record_header_size(key_len) + value_len |
102 | 43.1k | } |
103 | | |
104 | 21.4k | fn serialize_record(&self, record: &Record, include_value: bool) -> Vec<u8> { |
105 | 21.4k | let mut data = Vec::with_capacity(self.total_size(record.key.len(), record.value_len)); |
106 | | |
107 | | // Key length (2 bytes) |
108 | 21.4k | data.extend_from_slice(&(record.key.len() as u16).to_le_bytes()); |
109 | | |
110 | | // Key |
111 | 21.4k | data.extend_from_slice(&record.key); |
112 | | |
113 | | // Value length (8 bytes) |
114 | 21.4k | data.extend_from_slice(&(record.value_len as u64).to_le_bytes()); |
115 | | |
116 | | // Timestamp (8 bytes) |
117 | 21.4k | data.extend_from_slice(&record.timestamp.to_le_bytes()); |
118 | | |
119 | | // TTL expiry (8 bytes) - always included in v2 |
120 | 21.4k | data.extend_from_slice(&record.ttl_expiry.load(Ordering::Acquire).to_le_bytes()); |
121 | | |
122 | | // Value (if requested) |
123 | 21.4k | if include_value { |
124 | 21.4k | if let Some(value) = record.value.read().as_ref() { |
125 | 21.4k | data.extend_from_slice(value); |
126 | 21.4k | }0 |
127 | 0 | } |
128 | | |
129 | 21.4k | data |
130 | 21.4k | } |
131 | | |
132 | 303 | fn parse_record(&self, data: &[u8]) -> Option<(Vec<u8>, usize, u64, u64)> { |
133 | 303 | if data.len() < SECTOR_HEADER_SIZE + 2 { |
134 | 0 | return None; |
135 | 303 | } |
136 | | |
137 | 303 | let mut offset = SECTOR_HEADER_SIZE + 2; |
138 | 303 | let key_len = u16::from_le_bytes( |
139 | 303 | data[SECTOR_HEADER_SIZE..SECTOR_HEADER_SIZE + 2] |
140 | 303 | .try_into() |
141 | 303 | .ok()?0 , |
142 | | ) as usize; |
143 | | |
144 | 303 | if offset + key_len + 24 > data.len() { |
145 | | // 24 = value_len(8) + timestamp(8) + ttl(8) |
146 | 0 | return None; |
147 | 303 | } |
148 | | |
149 | 303 | let key = data[offset..offset + key_len].to_vec(); |
150 | 303 | offset += key_len; |
151 | | |
152 | 303 | let value_len = u64::from_le_bytes(data[offset..offset + 8].try_into().ok()?0 ) as usize; |
153 | 303 | offset += 8; |
154 | | |
155 | 303 | let timestamp = u64::from_le_bytes(data[offset..offset + 8].try_into().ok()?0 ); |
156 | 303 | offset += 8; |
157 | | |
158 | 303 | let ttl_expiry = u64::from_le_bytes(data[offset..offset + 8].try_into().ok()?0 ); |
159 | | |
160 | 303 | Some((key, value_len, timestamp, ttl_expiry)) |
161 | 303 | } |
162 | | |
163 | 16 | fn value_offset(&self, key_len: usize) -> usize { |
164 | 16 | SECTOR_HEADER_SIZE + 2 + key_len + 8 + 8 + 8 |
165 | 16 | } |
166 | | } |
167 | | |
168 | | /// Factory function to get the appropriate format handler based on version |
169 | 68 | pub fn get_format(version: u32) -> Box<dyn RecordFormat> { |
170 | 68 | match version { |
171 | 0 | 1 => Box::new(FormatV1), |
172 | 68 | 2 => Box::new(FormatV2), |
173 | 0 | _ => Box::new(FormatV2), // Default to latest version |
174 | | } |
175 | 68 | } |