1use crate::sql::error::SqlError;
2use chrono::{DateTime, Utc};
3use scouter_types::{
4 CustomMetricRecord, GenAIEvalRecord, GenAIEvalTaskResult, GenAIEvalWorkflowResult,
5 IntoServerRecord, PsiRecord, RecordType, ServerRecords, SpcRecord,
6};
7use sqlx::postgres::PgRow;
8use std::str::FromStr;
9use uuid::Uuid;
10pub fn pg_rows_to_server_records<T>(
12 rows: &[PgRow],
13 _record_type: &RecordType,
14) -> Result<ServerRecords, SqlError>
15where
16 T: for<'r> sqlx::FromRow<'r, PgRow> + IntoServerRecord + Send + Unpin,
17{
18 let mut records = Vec::with_capacity(rows.len());
19
20 for row in rows {
21 let record: T = sqlx::FromRow::from_row(row)?;
22 records.push(record.into_server_record());
23 }
24
25 Ok(ServerRecords::new(records))
26}
27
28pub fn parse_pg_rows(
30 rows: &[sqlx::postgres::PgRow],
31 record_type: &RecordType,
32) -> Result<ServerRecords, SqlError> {
33 match record_type {
34 RecordType::Spc => pg_rows_to_server_records::<SpcRecord>(rows, record_type),
35 RecordType::Psi => {
36 crate::sql::utils::pg_rows_to_server_records::<PsiRecord>(rows, record_type)
37 }
38 RecordType::Custom => {
39 crate::sql::utils::pg_rows_to_server_records::<CustomMetricRecord>(rows, record_type)
40 }
41 RecordType::GenAIEval => {
42 crate::sql::utils::pg_rows_to_server_records::<GenAIEvalRecord>(rows, record_type)
43 }
44 RecordType::GenAITask => {
45 crate::sql::utils::pg_rows_to_server_records::<GenAIEvalTaskResult>(rows, record_type)
46 }
47 RecordType::GenAIWorkflow => crate::sql::utils::pg_rows_to_server_records::<
48 GenAIEvalWorkflowResult,
49 >(rows, record_type),
50 _ => Err(SqlError::InvalidRecordTypeError(record_type.to_string())),
51 }
52}
53
54#[derive(Debug)]
55pub struct QueryTimestamps {
56 pub archived_range: Option<(DateTime<Utc>, DateTime<Utc>)>,
58
59 pub archived_minutes: Option<i32>,
61
62 pub active_range: Option<(DateTime<Utc>, DateTime<Utc>)>,
64}
65
66pub fn split_custom_interval(
92 start_datetime: DateTime<Utc>,
93 end_datetime: DateTime<Utc>,
94 retention_period: &i32,
95) -> Result<QueryTimestamps, SqlError> {
96 if start_datetime >= end_datetime {
97 return Err(SqlError::InvalidDateRangeError);
98 }
99
100 let retention_date = Utc::now() - chrono::Duration::days(*retention_period as i64);
101 let mut timestamps = QueryTimestamps {
102 archived_range: None,
103 archived_minutes: None,
104 active_range: None,
105 };
106
107 if start_datetime < retention_date {
109 let archive_end = if end_datetime <= retention_date {
110 end_datetime
111 } else {
112 retention_date
113 };
114 timestamps.archived_range = Some((start_datetime, archive_end));
115 timestamps.archived_minutes = Some(
116 archive_end
117 .signed_duration_since(start_datetime)
118 .num_minutes() as i32,
119 );
120 }
121
122 if end_datetime > retention_date {
124 let active_begin = if start_datetime < retention_date {
125 retention_date
126 } else {
127 start_datetime
128 };
129 timestamps.active_range = Some((active_begin, end_datetime));
130 }
131
132 Ok(timestamps)
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138 use chrono::Duration;
139
140 #[test]
141 fn test_split_custom_interval() {
142 let now = Utc::now();
143 let retention_period = &30; let result = split_custom_interval(
147 now - Duration::days(60),
148 now - Duration::days(40),
149 retention_period,
150 )
151 .unwrap();
152 assert!(result.archived_range.is_some());
153 assert!(result.active_range.is_none());
154
155 let result = split_custom_interval(
157 now - Duration::days(20),
158 now - Duration::days(1),
159 retention_period,
160 )
161 .unwrap();
162 assert!(result.archived_range.is_none());
163 assert!(result.active_range.is_some());
164
165 let result = split_custom_interval(
167 now - Duration::days(60),
168 now - Duration::days(1),
169 retention_period,
170 )
171 .unwrap();
172 assert!(result.archived_range.is_some());
173 assert!(result.active_range.is_some());
174
175 let result = split_custom_interval(
177 now - Duration::days(1),
178 now - Duration::days(2),
179 retention_period,
180 );
181 assert!(result.is_err());
182 }
183}
184
185#[derive(Debug, Clone, PartialEq, Eq, Hash)]
186pub struct EntityBytea(pub [u8; 16]);
187
188impl EntityBytea {
189 pub fn from_uuid(uid_str: &str) -> Result<Self, SqlError> {
190 let uuid = Uuid::from_str(uid_str)?;
191 Ok(Self(*uuid.as_bytes()))
192 }
193
194 pub fn as_bytes(&self) -> &[u8; 16] {
195 &self.0
196 }
197}