1use crate::sql::error::SqlError;
2use crate::sql::schema::llm_drift_record_from_row;
3use chrono::{DateTime, Utc};
4use scouter_types::{
5 CustomMetricServerRecord, LLMMetricRecord, PsiServerRecord, RecordType, ServerRecord,
6 ServerRecords, SpcServerRecord,
7};
8
9use sqlx::{postgres::PgRow, Row};
10fn spc_record_from_row(row: &PgRow) -> Result<SpcServerRecord, SqlError> {
12 Ok(SpcServerRecord {
13 created_at: row.try_get("created_at")?,
14 name: row.try_get("name")?,
15 space: row.try_get("space")?,
16 version: row.try_get("version")?,
17 feature: row.try_get("feature")?,
18 value: row.try_get("value")?,
19 })
20}
21
22fn psi_record_from_row(row: &PgRow) -> Result<PsiServerRecord, SqlError> {
24 let bin_id: i32 = row.try_get("bin_id")?;
25 let bin_count: i32 = row.try_get("bin_count")?;
26
27 Ok(PsiServerRecord {
28 created_at: row.try_get("created_at")?,
29 name: row.try_get("name")?,
30 space: row.try_get("space")?,
31 version: row.try_get("version")?,
32 feature: row.try_get("feature")?,
33 bin_id: bin_id as usize,
34 bin_count: bin_count as usize,
35 })
36}
37
38fn custom_record_from_row(row: &PgRow) -> Result<CustomMetricServerRecord, SqlError> {
40 Ok(CustomMetricServerRecord {
41 created_at: row.try_get("created_at")?,
42 name: row.try_get("name")?,
43 space: row.try_get("space")?,
44 version: row.try_get("version")?,
45 metric: row.try_get("metric")?,
46 value: row.try_get("value")?,
47 })
48}
49
50fn llm_drift_metric_from_row(row: &PgRow) -> Result<LLMMetricRecord, SqlError> {
51 Ok(LLMMetricRecord {
52 record_uid: row.try_get("record_uid")?,
53 created_at: row.try_get("created_at")?,
54 space: row.try_get("space")?,
55 name: row.try_get("name")?,
56 version: row.try_get("version")?,
57 metric: row.try_get("metric")?,
58 value: row.try_get("value")?,
59 })
60}
61
62pub fn pg_rows_to_server_records(
74 rows: &[PgRow],
75 record_type: &RecordType,
76) -> Result<ServerRecords, SqlError> {
77 let convert_fn = match record_type {
80 RecordType::Spc => |row| Ok(ServerRecord::Spc(spc_record_from_row(row)?)),
81 RecordType::Psi => |row| Ok(ServerRecord::Psi(psi_record_from_row(row)?)),
82 RecordType::Custom => |row| Ok(ServerRecord::Custom(custom_record_from_row(row)?)),
83 RecordType::LLMDrift => |row| Ok(ServerRecord::LLMDrift(llm_drift_record_from_row(row)?)),
84 RecordType::LLMMetric => |row| Ok(ServerRecord::LLMMetric(llm_drift_metric_from_row(row)?)),
85 _ => return Err(SqlError::InvalidRecordTypeError(record_type.to_string())),
86 };
87
88 let records: Result<Vec<ServerRecord>, SqlError> = rows.iter().map(convert_fn).collect();
90
91 records.map(ServerRecords::new)
93}
94
95#[derive(Debug)]
96pub struct QueryTimestamps {
97 pub archived_range: Option<(DateTime<Utc>, DateTime<Utc>)>,
99
100 pub archived_minutes: Option<i32>,
101
102 pub current_minutes: Option<i32>,
104}
105
106pub fn split_custom_interval(
130 begin_datetime: DateTime<Utc>,
131 end_datetime: DateTime<Utc>,
132 retention_period: &i32,
133) -> Result<QueryTimestamps, SqlError> {
134 if begin_datetime >= end_datetime {
135 return Err(SqlError::InvalidDateRangeError);
136 }
137
138 let retention_date = Utc::now() - chrono::Duration::days(*retention_period as i64);
139 let mut timestamps = QueryTimestamps {
140 archived_range: None,
141 current_minutes: None,
142 archived_minutes: None,
143 };
144
145 if begin_datetime < retention_date {
147 let archive_end = if end_datetime <= retention_date {
148 end_datetime
149 } else {
150 retention_date
151 };
152 timestamps.archived_range = Some((begin_datetime, archive_end));
153 }
154
155 if end_datetime > retention_date {
157 let current_begin = if begin_datetime < retention_date {
158 retention_date
159 } else {
160 begin_datetime
161 };
162 let minutes = end_datetime
163 .signed_duration_since(current_begin)
164 .num_minutes() as i32;
165 timestamps.current_minutes = Some(minutes);
166 }
167
168 if let Some((begin, end)) = timestamps.archived_range {
170 timestamps.archived_minutes = Some(end.signed_duration_since(begin).num_minutes() as i32);
171 }
172
173 Ok(timestamps)
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179 use chrono::Duration;
180
181 #[test]
182 fn test_split_custom_interval() {
183 let now = Utc::now();
184 let retention_period = &30; let result = split_custom_interval(
188 now - Duration::days(60),
189 now - Duration::days(40),
190 retention_period,
191 )
192 .unwrap();
193 assert!(result.archived_range.is_some());
194 assert!(result.current_minutes.is_none());
195
196 let result = split_custom_interval(
198 now - Duration::days(20),
199 now - Duration::days(1),
200 retention_period,
201 )
202 .unwrap();
203 assert!(result.archived_range.is_none());
204 assert!(result.current_minutes.is_some());
205
206 let result = split_custom_interval(
208 now - Duration::days(60),
209 now - Duration::days(1),
210 retention_period,
211 )
212 .unwrap();
213 assert!(result.archived_range.is_some());
214 assert!(result.current_minutes.is_some());
215
216 let result = split_custom_interval(
218 now - Duration::days(1),
219 now - Duration::days(2),
220 retention_period,
221 );
222 assert!(result.is_err());
223 }
224}