use crate::sql::error::SqlError;
use chrono::{DateTime, Utc};
use scouter_types::{
CustomMetricRecord, EvalRecord, EvalTaskResult, GenAIEvalWorkflowResult, IntoServerRecord,
PsiRecord, RecordType, ServerRecords, SpcRecord,
};
use sqlx::postgres::PgRow;
use std::str::FromStr;
use uuid::Uuid;
pub fn pg_rows_to_server_records<T>(
rows: &[PgRow],
_record_type: &RecordType,
) -> Result<ServerRecords, SqlError>
where
T: for<'r> sqlx::FromRow<'r, PgRow> + IntoServerRecord + Send + Unpin,
{
let mut records = Vec::with_capacity(rows.len());
for row in rows {
let record: T = sqlx::FromRow::from_row(row)?;
records.push(record.into_server_record());
}
Ok(ServerRecords::new(records))
}
pub fn parse_pg_rows(
rows: &[sqlx::postgres::PgRow],
record_type: &RecordType,
) -> Result<ServerRecords, SqlError> {
match record_type {
RecordType::Spc => pg_rows_to_server_records::<SpcRecord>(rows, record_type),
RecordType::Psi => {
crate::sql::utils::pg_rows_to_server_records::<PsiRecord>(rows, record_type)
}
RecordType::Custom => {
crate::sql::utils::pg_rows_to_server_records::<CustomMetricRecord>(rows, record_type)
}
RecordType::GenAIEval => {
crate::sql::utils::pg_rows_to_server_records::<EvalRecord>(rows, record_type)
}
RecordType::GenAITask => {
crate::sql::utils::pg_rows_to_server_records::<EvalTaskResult>(rows, record_type)
}
RecordType::GenAIWorkflow => crate::sql::utils::pg_rows_to_server_records::<
GenAIEvalWorkflowResult,
>(rows, record_type),
_ => Err(SqlError::InvalidRecordTypeError(record_type.to_string())),
}
}
#[derive(Debug)]
pub struct QueryTimestamps {
pub archived_range: Option<(DateTime<Utc>, DateTime<Utc>)>,
pub archived_minutes: Option<i32>,
pub active_range: Option<(DateTime<Utc>, DateTime<Utc>)>,
}
pub fn split_custom_interval(
start_datetime: DateTime<Utc>,
end_datetime: DateTime<Utc>,
retention_period: &i32,
) -> Result<QueryTimestamps, SqlError> {
if start_datetime >= end_datetime {
return Err(SqlError::InvalidDateRangeError);
}
let retention_date = Utc::now() - chrono::Duration::days(*retention_period as i64);
let mut timestamps = QueryTimestamps {
archived_range: None,
archived_minutes: None,
active_range: None,
};
if start_datetime < retention_date {
let archive_end = if end_datetime <= retention_date {
end_datetime
} else {
retention_date
};
timestamps.archived_range = Some((start_datetime, archive_end));
timestamps.archived_minutes = Some(
archive_end
.signed_duration_since(start_datetime)
.num_minutes() as i32,
);
}
if end_datetime > retention_date {
let active_begin = if start_datetime < retention_date {
retention_date
} else {
start_datetime
};
timestamps.active_range = Some((active_begin, end_datetime));
}
Ok(timestamps)
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::Duration;
#[test]
fn test_split_custom_interval() {
let now = Utc::now();
let retention_period = &30;
let result = split_custom_interval(
now - Duration::days(60),
now - Duration::days(40),
retention_period,
)
.unwrap();
assert!(result.archived_range.is_some());
assert!(result.active_range.is_none());
let result = split_custom_interval(
now - Duration::days(20),
now - Duration::days(1),
retention_period,
)
.unwrap();
assert!(result.archived_range.is_none());
assert!(result.active_range.is_some());
let result = split_custom_interval(
now - Duration::days(60),
now - Duration::days(1),
retention_period,
)
.unwrap();
assert!(result.archived_range.is_some());
assert!(result.active_range.is_some());
let result = split_custom_interval(
now - Duration::days(1),
now - Duration::days(2),
retention_period,
);
assert!(result.is_err());
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct UuidBytea(pub [u8; 16]);
impl UuidBytea {
pub fn from_uuid(uid_str: &str) -> Result<Self, SqlError> {
let uuid = Uuid::from_str(uid_str)?;
Ok(Self(*uuid.as_bytes()))
}
pub fn as_bytes(&self) -> &[u8; 16] {
&self.0
}
}