aranet_store/
queries.rs

1//! Query builders for readings and history.
2
3use time::OffsetDateTime;
4
5/// Query builder for readings.
6#[derive(Debug, Default, Clone)]
7pub struct ReadingQuery {
8    /// Filter by device ID.
9    pub device_id: Option<String>,
10    /// Filter readings after this time.
11    pub since: Option<OffsetDateTime>,
12    /// Filter readings before this time.
13    pub until: Option<OffsetDateTime>,
14    /// Maximum number of results.
15    pub limit: Option<u32>,
16    /// Offset for pagination.
17    pub offset: Option<u32>,
18    /// Order by captured_at descending (newest first).
19    pub newest_first: bool,
20}
21
22impl ReadingQuery {
23    /// Create a new reading query.
24    pub fn new() -> Self {
25        Self {
26            newest_first: true,
27            ..Default::default()
28        }
29    }
30
31    /// Filter by device ID.
32    pub fn device(mut self, device_id: &str) -> Self {
33        self.device_id = Some(device_id.to_string());
34        self
35    }
36
37    /// Filter readings after this time.
38    pub fn since(mut self, time: OffsetDateTime) -> Self {
39        self.since = Some(time);
40        self
41    }
42
43    /// Filter readings before this time.
44    pub fn until(mut self, time: OffsetDateTime) -> Self {
45        self.until = Some(time);
46        self
47    }
48
49    /// Limit the number of results.
50    pub fn limit(mut self, limit: u32) -> Self {
51        self.limit = Some(limit);
52        self
53    }
54
55    /// Offset for pagination.
56    pub fn offset(mut self, offset: u32) -> Self {
57        self.offset = Some(offset);
58        self
59    }
60
61    /// Order by oldest first.
62    pub fn oldest_first(mut self) -> Self {
63        self.newest_first = false;
64        self
65    }
66
67    /// Build the SQL WHERE clause and parameters.
68    pub(crate) fn build_where(&self) -> (String, Vec<Box<dyn rusqlite::ToSql>>) {
69        let mut conditions = Vec::new();
70        let mut params: Vec<Box<dyn rusqlite::ToSql>> = Vec::new();
71
72        if let Some(ref device_id) = self.device_id {
73            conditions.push("device_id = ?");
74            params.push(Box::new(device_id.clone()));
75        }
76
77        if let Some(since) = self.since {
78            conditions.push("captured_at >= ?");
79            params.push(Box::new(since.unix_timestamp()));
80        }
81
82        if let Some(until) = self.until {
83            conditions.push("captured_at <= ?");
84            params.push(Box::new(until.unix_timestamp()));
85        }
86
87        let where_clause = if conditions.is_empty() {
88            String::new()
89        } else {
90            format!("WHERE {}", conditions.join(" AND "))
91        };
92
93        (where_clause, params)
94    }
95
96    /// Build the full SQL query.
97    pub(crate) fn build_sql(&self) -> String {
98        let (where_clause, _) = self.build_where();
99        let order = if self.newest_first { "DESC" } else { "ASC" };
100
101        let mut sql = format!(
102            "SELECT id, device_id, captured_at, co2, temperature, pressure, humidity, \
103             battery, status, radon, radiation_rate, radiation_total \
104             FROM readings {} ORDER BY captured_at {}",
105            where_clause, order
106        );
107
108        if let Some(limit) = self.limit {
109            sql.push_str(&format!(" LIMIT {}", limit));
110        }
111
112        if let Some(offset) = self.offset {
113            sql.push_str(&format!(" OFFSET {}", offset));
114        }
115
116        sql
117    }
118}
119
120/// Query builder for history records.
121#[derive(Debug, Default, Clone)]
122pub struct HistoryQuery {
123    /// Filter by device ID.
124    pub device_id: Option<String>,
125    /// Filter records after this time.
126    pub since: Option<OffsetDateTime>,
127    /// Filter records before this time.
128    pub until: Option<OffsetDateTime>,
129    /// Maximum number of results.
130    pub limit: Option<u32>,
131    /// Offset for pagination.
132    pub offset: Option<u32>,
133    /// Order by timestamp descending (newest first).
134    pub newest_first: bool,
135}
136
137impl HistoryQuery {
138    /// Create a new history query.
139    pub fn new() -> Self {
140        Self {
141            newest_first: true,
142            ..Default::default()
143        }
144    }
145
146    /// Filter by device ID.
147    pub fn device(mut self, device_id: &str) -> Self {
148        self.device_id = Some(device_id.to_string());
149        self
150    }
151
152    /// Filter records after this time.
153    pub fn since(mut self, time: OffsetDateTime) -> Self {
154        self.since = Some(time);
155        self
156    }
157
158    /// Filter records before this time.
159    pub fn until(mut self, time: OffsetDateTime) -> Self {
160        self.until = Some(time);
161        self
162    }
163
164    /// Limit the number of results.
165    pub fn limit(mut self, limit: u32) -> Self {
166        self.limit = Some(limit);
167        self
168    }
169
170    /// Offset for pagination.
171    pub fn offset(mut self, offset: u32) -> Self {
172        self.offset = Some(offset);
173        self
174    }
175
176    /// Order by oldest first.
177    pub fn oldest_first(mut self) -> Self {
178        self.newest_first = false;
179        self
180    }
181}