1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
//! LN information for cleaning operations.
//!
//! holds information about an LN entry found during
//! file processing, used for pending LNs and look-ahead caches.
use noxu_util::Lsn;
/// Information about an LN entry found during file processing.
///
/// Used for pending LNs that are locked and must be migrated later, or
/// cannot be migrated immediately during a split. Also used in a look-ahead
/// cache in FileProcessor.
#[derive(Debug, Clone, PartialEq)]
pub struct LnInfo {
/// The LSN of the LN entry in the log.
pub lsn: Lsn,
/// The database ID this LN belongs to.
pub db_id: i64,
/// The key for the LN.
pub key: Vec<u8>,
/// Whether the LN was found to be obsolete.
pub obsolete: bool,
/// Size of the LN entry in the log (in bytes).
pub log_size: i32,
/// Whether this is a deleted LN.
pub deleted: bool,
/// Expiration time in **hours since epoch** (packed-hours unit used
/// throughout the TTL subsystem and log format), or 0 if the record
/// never expires.
///
/// Must match the unit used by `ExpirationTracker::track` and
/// `ExpirationTracker::get_expired_bytes` (both expect hours).
/// JE: `LNInfo.expirationTime` is stored in ms because it calls
/// `TTL.expirationToSystemTime(packed_hours)` before storing; Noxu keeps
/// the packed-hours value directly so callers must pass hours to
/// `is_expired`. See CLN-10.
pub expiration_time: u64,
}
impl LnInfo {
/// Creates a new LN info record.
pub fn new(
lsn: Lsn,
db_id: i64,
key: Vec<u8>,
log_size: i32,
deleted: bool,
expiration_time: u64,
) -> Self {
Self {
lsn,
db_id,
key,
obsolete: false,
log_size,
deleted,
expiration_time,
}
}
/// Returns the LSN of this LN entry.
pub fn lsn(&self) -> Lsn {
self.lsn
}
/// Returns the database ID.
pub fn db_id(&self) -> i64 {
self.db_id
}
/// Returns a reference to the key.
pub fn key(&self) -> &[u8] {
&self.key
}
/// Returns the size of the entry in the log.
pub fn log_size(&self) -> i32 {
self.log_size
}
/// Returns whether this LN is deleted.
pub fn is_deleted(&self) -> bool {
self.deleted
}
/// Returns whether this LN is obsolete.
pub fn is_obsolete(&self) -> bool {
self.obsolete
}
/// Marks this LN as obsolete.
pub fn set_obsolete(&mut self, obsolete: bool) {
self.obsolete = obsolete;
}
/// Returns the expiration time in **hours since epoch** (0 = never).
pub fn expiration_time(&self) -> u64 {
self.expiration_time
}
/// Returns whether this LN has expired.
///
/// # Arguments
/// * `current_time_hours` — current time expressed as **hours since epoch**.
/// Must use the same packed-hours unit as `expiration_time`.
pub fn is_expired(&self, current_time_hours: u64) -> bool {
self.expiration_time > 0 && self.expiration_time <= current_time_hours
}
/// Estimates the memory size of this LN info.
///
/// Includes the key size and fixed overhead, but not the LN data itself
/// since it may not be resident.
pub fn memory_size(&self) -> usize {
// Base overhead: LSN (8) + db_id (8) + log_size (4) + flags (2) + expiration_time (8) + Vec overhead (24)
let base = 54;
// Key data
let key_size = self.key.len();
base + key_size
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_ln_info() {
let lsn = Lsn::new(1, 1000);
let key = vec![1, 2, 3, 4];
let info = LnInfo::new(lsn, 42, key, 128, false, 0);
assert_eq!(info.lsn(), lsn);
assert_eq!(info.db_id(), 42);
assert_eq!(info.key(), &[1, 2, 3, 4]);
assert_eq!(info.log_size(), 128);
assert!(!info.is_deleted());
assert!(!info.is_obsolete());
assert_eq!(info.expiration_time(), 0);
}
#[test]
fn test_deleted_ln() {
let lsn = Lsn::new(1, 1000);
let info = LnInfo::new(lsn, 42, vec![1, 2, 3], 64, true, 0);
assert!(info.is_deleted());
assert!(!info.is_obsolete());
}
#[test]
fn test_mark_obsolete() {
let lsn = Lsn::new(1, 1000);
let mut info = LnInfo::new(lsn, 42, vec![1, 2, 3], 64, false, 0);
assert!(!info.is_obsolete());
info.set_obsolete(true);
assert!(info.is_obsolete());
info.set_obsolete(false);
assert!(!info.is_obsolete());
}
#[test]
fn test_expiration() {
let lsn = Lsn::new(1, 1000);
let expiration_time = 1000000;
let info =
LnInfo::new(lsn, 42, vec![1, 2, 3], 64, false, expiration_time);
assert_eq!(info.expiration_time(), expiration_time);
assert!(!info.is_expired(500000)); // Before expiration
assert!(info.is_expired(1000000)); // At expiration
assert!(info.is_expired(1500000)); // After expiration
}
#[test]
fn test_no_expiration() {
let lsn = Lsn::new(1, 1000);
let info = LnInfo::new(lsn, 42, vec![1, 2, 3], 64, false, 0);
assert!(!info.is_expired(1000000)); // Never expires
}
#[test]
fn test_memory_size() {
let lsn = Lsn::new(1, 1000);
let small_key = vec![1, 2, 3];
let large_key = vec![0u8; 1000];
let info_small = LnInfo::new(lsn, 42, small_key.clone(), 64, false, 0);
let info_large = LnInfo::new(lsn, 42, large_key.clone(), 64, false, 0);
let size_small = info_small.memory_size();
let size_large = info_large.memory_size();
// Larger key should result in larger memory size
assert!(size_large > size_small);
assert_eq!(size_large - size_small, large_key.len() - small_key.len());
}
#[test]
fn test_clone() {
let lsn = Lsn::new(1, 1000);
let info = LnInfo::new(lsn, 42, vec![1, 2, 3, 4], 128, false, 5000);
let cloned = info.clone();
assert_eq!(cloned.lsn(), info.lsn());
assert_eq!(cloned.db_id(), info.db_id());
assert_eq!(cloned.key(), info.key());
assert_eq!(cloned.log_size(), info.log_size());
assert_eq!(cloned.is_deleted(), info.is_deleted());
assert_eq!(cloned.expiration_time(), info.expiration_time());
}
#[test]
fn test_large_database_id() {
let lsn = Lsn::new(1, 1000);
let info = LnInfo::new(lsn, i64::MAX, vec![1, 2, 3], 64, false, 0);
assert_eq!(info.db_id(), i64::MAX);
}
#[test]
fn test_empty_key() {
let lsn = Lsn::new(1, 1000);
let info = LnInfo::new(lsn, 42, vec![], 64, false, 0);
assert_eq!(info.key(), &[]);
assert!(info.memory_size() > 0); // Still has overhead
}
#[test]
fn test_accessors() {
let lsn = Lsn::new(5, 12345);
let key = vec![10, 20, 30];
let info = LnInfo::new(lsn, 99, key, 256, true, 88888);
assert_eq!(info.lsn(), Lsn::new(5, 12345));
assert_eq!(info.db_id(), 99);
assert_eq!(info.key(), &[10, 20, 30]);
assert_eq!(info.log_size(), 256);
assert!(info.is_deleted());
assert_eq!(info.expiration_time(), 88888);
}
}