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