1use std::collections::{BTreeSet, HashMap};
12use std::ops::Bound;
13use std::sync::Arc;
14
15use parking_lot::RwLock;
16
17use chronix_core::{SegmentId, Timestamp};
18
19#[derive(Debug, Clone, Eq)]
21pub struct TimeIndexEntry {
22 pub segment_id: SegmentId,
24 pub min_ts: Timestamp,
26 pub max_ts: Timestamp,
28}
29
30impl PartialEq for TimeIndexEntry {
31 fn eq(&self, other: &Self) -> bool {
32 self.min_ts == other.min_ts && self.segment_id == other.segment_id
33 }
34}
35
36impl PartialOrd for TimeIndexEntry {
37 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
38 Some(self.cmp(other))
39 }
40}
41
42impl Ord for TimeIndexEntry {
43 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
44 self.min_ts
45 .cmp(&other.min_ts)
46 .then_with(|| self.segment_id.cmp(&other.segment_id))
47 }
48}
49
50impl TimeIndexEntry {
51 #[inline]
53 #[must_use]
54 pub fn overlaps(&self, start: Timestamp, end: Timestamp) -> bool {
55 self.min_ts <= end && self.max_ts >= start
56 }
57}
58
59#[derive(Debug, Clone)]
71pub struct TimeIndex {
72 entries: Arc<RwLock<BTreeSet<TimeIndexEntry>>>,
73 segment_meta: Arc<RwLock<HashMap<SegmentId, (Timestamp, Timestamp)>>>,
75}
76
77impl TimeIndex {
78 #[must_use]
80 pub fn new() -> Self {
81 Self {
82 entries: Arc::new(RwLock::new(BTreeSet::new())),
83 segment_meta: Arc::new(RwLock::new(HashMap::new())),
84 }
85 }
86
87 #[must_use]
98 pub fn segments_for_range(&self, start_ts: Timestamp, end_ts: Timestamp) -> Vec<SegmentId> {
99 let entries = self.entries.read();
100 if entries.is_empty() {
101 return Vec::new();
102 }
103 let upper = TimeIndexEntry {
106 segment_id: SegmentId(u64::MAX),
107 min_ts: end_ts,
108 max_ts: Timestamp::MAX,
109 };
110 entries
111 .range((Bound::Unbounded, Bound::Included(&upper)))
112 .filter(|e| e.max_ts >= start_ts)
113 .map(|e| e.segment_id)
114 .collect()
115 }
116
117 pub fn add_segment(&self, entry: TimeIndexEntry) {
122 let seg_id = entry.segment_id;
123 let meta_val = (entry.min_ts, entry.max_ts);
124 let mut entries = self.entries.write();
125 let mut meta = self.segment_meta.write();
126 entries.insert(entry);
127 meta.insert(seg_id, meta_val);
128 }
129
130 #[must_use]
137 pub fn remove_segment(&self, segment_id: SegmentId) -> bool {
138 let mut entries = self.entries.write();
139 let mut meta = self.segment_meta.write();
140 if let Some(&(min_ts, max_ts)) = meta.get(&segment_id) {
141 let key = TimeIndexEntry {
142 segment_id,
143 min_ts,
144 max_ts,
145 };
146 let removed = entries.remove(&key);
147 if removed {
148 meta.remove(&segment_id);
149 }
150 removed
151 } else {
152 false
153 }
154 }
155
156 #[must_use]
158 pub fn len(&self) -> usize {
159 self.entries.read().len()
160 }
161
162 #[must_use]
164 pub fn is_empty(&self) -> bool {
165 self.entries.read().is_empty()
166 }
167
168 #[must_use]
170 pub fn all_entries(&self) -> Vec<TimeIndexEntry> {
171 self.entries.read().iter().cloned().collect()
172 }
173}
174
175impl Default for TimeIndex {
176 fn default() -> Self {
177 Self::new()
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184
185 fn entry(id: u64, min: i64, max: i64) -> TimeIndexEntry {
186 TimeIndexEntry {
187 segment_id: SegmentId(id),
188 min_ts: min,
189 max_ts: max,
190 }
191 }
192
193 #[test]
194 fn empty_index_returns_nothing() {
195 let idx = TimeIndex::new();
196 let result = idx.segments_for_range(0, 1000);
197 assert!(result.is_empty());
198 }
199
200 #[test]
201 fn single_segment_match() {
202 let idx = TimeIndex::new();
203 idx.add_segment(entry(1, 100, 200));
204
205 assert_eq!(idx.segments_for_range(100, 200), vec![SegmentId(1)]);
207 assert_eq!(idx.segments_for_range(150, 300), vec![SegmentId(1)]);
209 assert!(idx.segments_for_range(0, 99).is_empty());
211 assert!(idx.segments_for_range(201, 300).is_empty());
213 assert_eq!(idx.segments_for_range(200, 300), vec![SegmentId(1)]);
215 assert_eq!(idx.segments_for_range(0, 100), vec![SegmentId(1)]);
216 }
217
218 #[test]
219 fn multiple_segments_pruning() {
220 let idx = TimeIndex::new();
221 idx.add_segment(entry(1, 100, 200));
223 idx.add_segment(entry(2, 300, 400));
224 idx.add_segment(entry(3, 500, 600));
225 idx.add_segment(entry(4, 700, 800));
226
227 let result = idx.segments_for_range(250, 550);
229 assert_eq!(result, vec![SegmentId(2), SegmentId(3)]);
230
231 let result = idx.segments_for_range(0, 1000);
233 assert_eq!(
234 result,
235 vec![SegmentId(1), SegmentId(2), SegmentId(3), SegmentId(4)]
236 );
237
238 assert!(idx.segments_for_range(900, 1000).is_empty());
240 }
241
242 #[test]
243 fn overlapping_segments() {
244 let idx = TimeIndex::new();
245 idx.add_segment(entry(1, 100, 300));
247 idx.add_segment(entry(2, 200, 400));
248 idx.add_segment(entry(3, 350, 500));
249
250 let result = idx.segments_for_range(250, 350);
251 assert_eq!(result, vec![SegmentId(1), SegmentId(2), SegmentId(3)]);
252 }
253
254 #[test]
255 fn add_maintains_sort_order() {
256 let idx = TimeIndex::new();
257 idx.add_segment(entry(3, 500, 600));
258 idx.add_segment(entry(1, 100, 200));
259 idx.add_segment(entry(2, 300, 400));
260
261 let entries = idx.all_entries();
262 assert_eq!(entries[0].min_ts, 100);
263 assert_eq!(entries[1].min_ts, 300);
264 assert_eq!(entries[2].min_ts, 500);
265 }
266
267 #[test]
268 fn remove_segment() {
269 let idx = TimeIndex::new();
270 idx.add_segment(entry(1, 100, 200));
271 idx.add_segment(entry(2, 300, 400));
272
273 assert!(idx.remove_segment(SegmentId(1)));
274 assert_eq!(idx.len(), 1);
275 assert!(!idx.remove_segment(SegmentId(99)));
276 }
277
278 #[test]
279 fn hundred_segments_pruning() {
280 let idx = TimeIndex::new();
281
282 for i in 0u64..100 {
284 #[allow(clippy::cast_possible_wrap)]
285 let start = (i as i64) * 1000;
286 idx.add_segment(entry(i, start, start + 500));
287 }
288 assert_eq!(idx.len(), 100);
289
290 let result = idx.segments_for_range(5000, 7500);
293 assert_eq!(result, vec![SegmentId(5), SegmentId(6), SegmentId(7)]);
294
295 let result = idx.segments_for_range(50250, 50250);
297 assert_eq!(result, vec![SegmentId(50)]);
298 }
299
300 #[test]
301 fn entry_overlaps() {
302 let e = entry(1, 100, 200);
303 assert!(e.overlaps(100, 200)); assert!(e.overlaps(50, 150)); assert!(e.overlaps(150, 250)); assert!(e.overlaps(120, 180)); assert!(e.overlaps(50, 250)); assert!(!e.overlaps(0, 99)); assert!(!e.overlaps(201, 300)); }
311}