Skip to main content

nodedb_sql/engine_rules/
timeseries.rs

1//! Engine rules for timeseries collections.
2//!
3//! Timeseries is append-only. INSERT, UPDATE, and UPSERT are not supported.
4//! Data enters via INGEST (mapped to `TimeseriesIngest`).
5//! Scans route to `TimeseriesScan` for time-range-aware execution.
6
7use crate::engine_rules::*;
8use crate::error::{Result, SqlError};
9use crate::types::*;
10
11pub struct TimeseriesRules;
12
13impl EngineRules for TimeseriesRules {
14    fn plan_insert(&self, p: InsertParams) -> Result<Vec<SqlPlan>> {
15        // Timeseries INSERT routes to TimeseriesIngest — append-only semantics.
16        Ok(vec![SqlPlan::TimeseriesIngest {
17            collection: p.collection,
18            rows: p.rows,
19        }])
20    }
21
22    fn plan_upsert(&self, _p: UpsertParams) -> Result<Vec<SqlPlan>> {
23        Err(SqlError::Unsupported {
24            detail: "UPSERT is not supported on timeseries collections (append-only)".into(),
25        })
26    }
27
28    fn plan_scan(&self, p: ScanParams) -> Result<SqlPlan> {
29        // Timeseries scans use TimeseriesScan for time-range-aware execution.
30        let time_range = default_time_range();
31        Ok(SqlPlan::TimeseriesScan {
32            collection: p.collection,
33            time_range,
34            bucket_interval_ms: 0,
35            group_by: Vec::new(),
36            aggregates: Vec::new(),
37            filters: p.filters,
38            projection: p.projection,
39            gap_fill: String::new(),
40            limit: p.limit.unwrap_or(10000),
41            tiered: false,
42        })
43    }
44
45    fn plan_point_get(&self, _p: PointGetParams) -> Result<SqlPlan> {
46        Err(SqlError::Unsupported {
47            detail: "point lookups are not supported on timeseries collections; \
48                     use SELECT with a time range filter instead"
49                .into(),
50        })
51    }
52
53    fn plan_update(&self, _p: UpdateParams) -> Result<Vec<SqlPlan>> {
54        Err(SqlError::Unsupported {
55            detail: "UPDATE is not supported on timeseries collections; \
56                     timeseries data is append-only"
57                .into(),
58        })
59    }
60
61    fn plan_delete(&self, p: DeleteParams) -> Result<Vec<SqlPlan>> {
62        // Timeseries supports range-based deletion (e.g. retention).
63        Ok(vec![SqlPlan::Delete {
64            collection: p.collection,
65            engine: EngineType::Timeseries,
66            filters: p.filters,
67            target_keys: p.target_keys,
68        }])
69    }
70
71    fn plan_aggregate(&self, p: AggregateParams) -> Result<SqlPlan> {
72        Ok(SqlPlan::TimeseriesScan {
73            collection: p.collection,
74            time_range: default_time_range(),
75            bucket_interval_ms: p.bucket_interval_ms.unwrap_or(0),
76            group_by: p.group_columns,
77            aggregates: p.aggregates,
78            filters: p.filters,
79            projection: Vec::new(),
80            gap_fill: String::new(),
81            limit: p.limit,
82            tiered: p.has_auto_tier,
83        })
84    }
85}
86
87/// Default time range bounds for the SqlPlan IR.
88///
89/// Actual time range extraction from filter predicates happens during
90/// PhysicalPlan conversion (Origin: `value::extract_time_range`).
91/// At the SqlPlan level, we pass unbounded defaults.
92fn default_time_range() -> (i64, i64) {
93    (i64::MIN, i64::MAX)
94}