oxirs_tsdb/query/
range.rs1use crate::error::TsdbResult;
6use crate::series::DataPoint;
7use crate::storage::TimeChunk;
8use chrono::{DateTime, Utc};
9
10#[derive(Debug, Clone, Copy)]
12pub struct TimeRange {
13 pub start: DateTime<Utc>,
15 pub end: DateTime<Utc>,
17}
18
19impl TimeRange {
20 pub fn new(start: DateTime<Utc>, end: DateTime<Utc>) -> Self {
22 Self { start, end }
23 }
24
25 pub fn contains(&self, timestamp: DateTime<Utc>) -> bool {
27 timestamp >= self.start && timestamp < self.end
28 }
29
30 pub fn overlaps_chunk(&self, chunk: &TimeChunk) -> bool {
32 self.start < chunk.end_time && self.end > chunk.start_time
34 }
35
36 pub fn duration(&self) -> chrono::Duration {
38 self.end - self.start
39 }
40}
41
42#[derive(Debug)]
44pub struct RangeQuery {
45 pub series_id: u64,
47 pub time_range: TimeRange,
49 pub limit: Option<usize>,
51 pub ascending: bool,
53}
54
55impl RangeQuery {
56 pub fn new(series_id: u64, start: DateTime<Utc>, end: DateTime<Utc>) -> Self {
58 Self {
59 series_id,
60 time_range: TimeRange::new(start, end),
61 limit: None,
62 ascending: true,
63 }
64 }
65
66 pub fn with_limit(mut self, limit: usize) -> Self {
68 self.limit = Some(limit);
69 self
70 }
71
72 pub fn with_order(mut self, ascending: bool) -> Self {
74 self.ascending = ascending;
75 self
76 }
77
78 pub fn execute(&self, chunks: &[TimeChunk]) -> TsdbResult<Vec<DataPoint>> {
80 let mut results = Vec::new();
81
82 let relevant_chunks: Vec<&TimeChunk> = chunks
84 .iter()
85 .filter(|c| c.series_id == self.series_id && self.time_range.overlaps_chunk(c))
86 .collect();
87
88 for chunk in relevant_chunks {
90 let chunk_results = chunk.query_range(self.time_range.start, self.time_range.end)?;
91 results.extend(chunk_results);
92
93 if let Some(limit) = self.limit {
95 if results.len() >= limit {
96 results.truncate(limit);
97 break;
98 }
99 }
100 }
101
102 if self.ascending {
104 results.sort_by(|a, b| a.timestamp.cmp(&b.timestamp));
105 } else {
106 results.sort_by(|a, b| b.timestamp.cmp(&a.timestamp));
107 }
108
109 if let Some(limit) = self.limit {
111 results.truncate(limit);
112 }
113
114 Ok(results)
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121 use chrono::Duration;
122
123 fn create_test_chunk(series_id: u64, start: DateTime<Utc>, count: usize) -> TimeChunk {
124 let mut points = Vec::new();
125 for i in 0..count {
126 points.push(DataPoint {
127 timestamp: start + Duration::seconds(i as i64),
128 value: i as f64,
129 });
130 }
131 TimeChunk::new(series_id, start, Duration::hours(2), points).unwrap()
132 }
133
134 #[test]
135 fn test_time_range_contains() {
136 let now = Utc::now();
137 let range = TimeRange::new(now, now + Duration::hours(1));
138
139 assert!(range.contains(now));
140 assert!(range.contains(now + Duration::minutes(30)));
141 assert!(!range.contains(now - Duration::minutes(1)));
142 assert!(!range.contains(now + Duration::hours(1))); }
144
145 #[test]
146 fn test_range_query_basic() {
147 let now = Utc::now();
148 let chunk = create_test_chunk(1, now, 100);
149
150 let query = RangeQuery::new(1, now + Duration::seconds(10), now + Duration::seconds(20));
151 let results = query.execute(&[chunk]).unwrap();
152
153 assert_eq!(results.len(), 10);
154 assert!(results[0].timestamp >= now + Duration::seconds(10));
155 }
156
157 #[test]
158 fn test_range_query_with_limit() {
159 let now = Utc::now();
160 let chunk = create_test_chunk(1, now, 100);
161
162 let query = RangeQuery::new(1, now, now + Duration::seconds(100)).with_limit(5);
163 let results = query.execute(&[chunk]).unwrap();
164
165 assert_eq!(results.len(), 5);
166 }
167
168 #[test]
169 fn test_range_query_descending() {
170 let now = Utc::now();
171 let chunk = create_test_chunk(1, now, 100);
172
173 let query = RangeQuery::new(1, now, now + Duration::seconds(100)).with_order(false);
174 let results = query.execute(&[chunk]).unwrap();
175
176 for window in results.windows(2) {
178 assert!(window[0].timestamp >= window[1].timestamp);
179 }
180 }
181
182 #[test]
183 fn test_range_overlaps_chunk() {
184 let now = Utc::now();
185 let chunk = create_test_chunk(1, now, 100);
186
187 let range1 = TimeRange::new(now - Duration::hours(1), now + Duration::hours(3));
189 assert!(range1.overlaps_chunk(&chunk));
190
191 let range2 = TimeRange::new(now + Duration::seconds(50), now + Duration::hours(3));
193 assert!(range2.overlaps_chunk(&chunk));
194
195 let range3 = TimeRange::new(now - Duration::hours(2), now - Duration::hours(1));
197 assert!(!range3.overlaps_chunk(&chunk));
198 }
199}