Skip to main content

nodedb_types/timeseries/
continuous_agg.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Continuous aggregate definition types.
4//!
5//! Shared between Origin and Lite. Origin uses these for SQL DDL parsing
6//! and the continuous aggregate manager. Lite uses them for the embedded
7//! continuous aggregate engine and its DDL handler.
8
9use serde::{Deserialize, Serialize};
10
11/// Definition of a continuous aggregate.
12///
13/// Map-encoded so fields (e.g. `database_id`) can be added with
14/// `#[msgpack(default)]` and older persisted definitions still decode
15/// without a migration.
16///
17/// `Default` gives an empty definition whose `incarnation` is `Hlc::ZERO`.
18/// No CREATE stamps that incarnation, and Origin never installs persisted
19/// state into, or from, a definition that carries it.
20#[derive(
21    Debug,
22    Clone,
23    Default,
24    PartialEq,
25    Serialize,
26    Deserialize,
27    zerompk::ToMessagePack,
28    zerompk::FromMessagePack,
29)]
30#[msgpack(map)]
31pub struct ContinuousAggregateDef {
32    /// Owning database. Scopes the Data-Plane manager's per-name maps and
33    /// catalog keys so an aggregate named identically in two databases
34    /// never collides or materializes against the wrong database's storage.
35    ///
36    /// `#[msgpack(default)]`: definitions persisted before database scoping
37    /// decode with `0` (`DatabaseId::DEFAULT`) — the database those legacy
38    /// rows lived in — so no migration is required.
39    #[msgpack(default)]
40    pub database_id: u64,
41    /// Name of this aggregate (e.g., "metrics_1m").
42    pub name: String,
43    /// Source collection or aggregate to read from.
44    pub source: String,
45    /// Time bucket interval string (e.g., "1m", "1h", "1d").
46    pub bucket_interval: String,
47    /// Bucket interval in milliseconds (computed from bucket_interval).
48    pub bucket_interval_ms: i64,
49    /// Columns to GROUP BY (tag/symbol columns).
50    pub group_by: Vec<String>,
51    /// Aggregate expressions to compute.
52    pub aggregates: Vec<AggregateExpr>,
53    /// When to refresh.
54    pub refresh_policy: RefreshPolicy,
55    /// Retention period in milliseconds (0 = infinite, independent of source).
56    pub retention_period_ms: u64,
57    /// Whether this aggregate is currently stale (schema change invalidation).
58    pub stale: bool,
59    /// Identity of the CREATE that made this definition. A re-created
60    /// aggregate of the same name never loads the persisted buckets of a
61    /// dropped one.
62    #[msgpack(default)]
63    #[serde(default)]
64    pub incarnation: crate::Hlc,
65}
66
67/// An aggregate expression: function + source column → result column.
68#[derive(
69    Debug,
70    Clone,
71    PartialEq,
72    Serialize,
73    Deserialize,
74    zerompk::ToMessagePack,
75    zerompk::FromMessagePack,
76)]
77pub struct AggregateExpr {
78    /// Aggregate function.
79    pub function: AggFunction,
80    /// Source column name (e.g., "cpu"). "*" for COUNT.
81    pub source_column: String,
82    /// Output column name (e.g., "cpu_avg"). Auto-generated if empty.
83    pub output_column: String,
84}
85
86/// Supported aggregate functions.
87#[derive(
88    Debug,
89    Clone,
90    PartialEq,
91    Serialize,
92    Deserialize,
93    zerompk::ToMessagePack,
94    zerompk::FromMessagePack,
95)]
96#[serde(rename_all = "snake_case")]
97#[non_exhaustive]
98pub enum AggFunction {
99    #[serde(rename = "sum")]
100    Sum,
101    #[serde(rename = "count")]
102    Count,
103    #[serde(rename = "min")]
104    Min,
105    #[serde(rename = "max")]
106    Max,
107    #[serde(rename = "avg")]
108    Avg,
109    #[serde(rename = "first")]
110    First,
111    #[serde(rename = "last")]
112    Last,
113    /// Approximate count distinct via HyperLogLog.
114    #[serde(rename = "count_distinct")]
115    CountDistinct,
116    /// Approximate percentile via TDigest. Inner value is the quantile (0.0–1.0).
117    #[serde(rename = "percentile")]
118    Percentile(f64),
119    /// Approximate top-K heavy hitters via SpaceSaving. Inner value is K.
120    #[serde(rename = "topk")]
121    TopK(usize),
122}
123
124impl AggFunction {
125    pub fn as_str(&self) -> &'static str {
126        match self {
127            Self::Sum => "sum",
128            Self::Count => "count",
129            Self::Min => "min",
130            Self::Max => "max",
131            Self::Avg => "avg",
132            Self::First => "first",
133            Self::Last => "last",
134            Self::CountDistinct => "count_distinct",
135            Self::Percentile(_) => "percentile",
136            Self::TopK(_) => "topk",
137        }
138    }
139
140    /// Whether this function requires sketch state in PartialAggregate.
141    pub fn uses_sketch(&self) -> bool {
142        matches!(
143            self,
144            Self::CountDistinct | Self::Percentile(_) | Self::TopK(_)
145        )
146    }
147}
148
149/// When to refresh the aggregate.
150#[derive(
151    Debug,
152    Clone,
153    Default,
154    PartialEq,
155    Eq,
156    Serialize,
157    Deserialize,
158    zerompk::ToMessagePack,
159    zerompk::FromMessagePack,
160)]
161#[serde(rename_all = "snake_case")]
162#[non_exhaustive]
163pub enum RefreshPolicy {
164    /// Refresh on every memtable flush. Lowest latency.
165    #[default]
166    #[serde(rename = "on_flush")]
167    OnFlush,
168    /// Refresh when a partition is sealed. Lower CPU cost.
169    #[serde(rename = "on_seal")]
170    OnSeal,
171    /// Refresh every N milliseconds.
172    #[serde(rename = "periodic")]
173    Periodic(u64),
174    /// Only refresh via explicit REFRESH command.
175    #[serde(rename = "manual")]
176    Manual,
177}