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