Coverage Report

Created: 2026-01-04 13:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}