pub mod aggregate;
pub mod planner;
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::{CanonicalColumnName, aggregation::Aggregate, unit::NullValue};
pub use aggregate::{
IntervalAggregateOutput, IntervalStats, apply_interval, build_resampling_plans, run_interval,
};
pub use planner::{AggregationSource, ResamplingPath, ResamplingPlan, ResamplingPlanner};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReportInterval {
pub bucket: IntervalBucket,
#[serde(default)]
pub strategy: RateStrategy,
#[serde(default)]
pub aggregation_override: Option<HashMap<CanonicalColumnName, Aggregate>>,
#[serde(default)]
pub empty_bucket: EmptyBucketPolicy,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum IntervalBucket {
Months(u32),
Weeks(u32),
Days(u32),
Hours(u32),
Fixed {
duration_ms: i64,
},
WholeWindow,
}
impl IntervalBucket {
pub fn parse(s: &str) -> Result<Self, String> {
let trimmed = s.trim().to_ascii_lowercase();
if matches!(trimmed.as_str(), "whole_window" | "whole") {
return Ok(Self::WholeWindow);
}
for (suffix, build) in [
(
"mo",
Box::new(|n: i64| Self::Months(n as u32)) as Box<dyn Fn(i64) -> Self>,
),
(
"ms",
Box::new(|n: i64| Self::Fixed { duration_ms: n }) as Box<_>,
),
("w", Box::new(|n: i64| Self::Weeks(n as u32)) as Box<_>),
("d", Box::new(|n: i64| Self::Days(n as u32)) as Box<_>),
("h", Box::new(|n: i64| Self::Hours(n as u32)) as Box<_>),
(
"m",
Box::new(|n: i64| Self::Fixed {
duration_ms: n * 60_000,
}) as Box<_>,
),
(
"s",
Box::new(|n: i64| Self::Fixed {
duration_ms: n * 1_000,
}) as Box<_>,
),
] {
if let Some(num_str) = trimmed.strip_suffix(suffix)
&& let Ok(n) = num_str.trim().parse::<i64>()
&& n > 0
{
return Ok(build(n));
}
}
Err(format!(
"unrecognized interval '{s}'. Use '1h', '1d', '1mo', 'whole_window', etc."
))
}
pub fn approximate_ms(&self) -> i64 {
const MS_PER_SECOND: i64 = 1_000;
const MS_PER_MINUTE: i64 = 60 * MS_PER_SECOND;
const MS_PER_HOUR: i64 = 60 * MS_PER_MINUTE;
const MS_PER_DAY: i64 = 24 * MS_PER_HOUR;
const MS_PER_WEEK: i64 = 7 * MS_PER_DAY;
const MS_PER_MONTH_AVG: i64 = 30 * MS_PER_DAY;
match self {
Self::Months(n) => i64::from(*n) * MS_PER_MONTH_AVG,
Self::Weeks(n) => i64::from(*n) * MS_PER_WEEK,
Self::Days(n) => i64::from(*n) * MS_PER_DAY,
Self::Hours(n) => i64::from(*n) * MS_PER_HOUR,
Self::Fixed { duration_ms } => *duration_ms,
Self::WholeWindow => i64::MAX,
}
}
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RateStrategy {
#[default]
Auto,
Upsample,
Native,
AggregateOrSparse,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum EmptyBucketPolicy {
#[default]
Null,
FromConfig,
Value(NullValue),
}