nodedb_sql/engine_rules/
continuous_aggregate.rs1use crate::engine_rules::*;
12use crate::error::{Result, SqlError};
13use crate::types::*;
14
15pub struct ContinuousAggregateRules;
16
17fn read_only(op: &str, name: &str) -> SqlError {
19 SqlError::WrongObjectType {
20 detail: format!("continuous aggregate '{name}' is read-only; {op} is not allowed"),
21 }
22}
23
24fn refuse_temporal(collection: &str, temporal: &crate::temporal::TemporalScope) -> Result<()> {
25 if temporal.is_temporal() {
26 return Err(SqlError::Unsupported {
27 detail: format!(
28 "FOR SYSTEM_TIME / FOR VALID_TIME is not supported on continuous \
29 aggregate '{collection}'"
30 ),
31 });
32 }
33 Ok(())
34}
35
36impl EngineRules for ContinuousAggregateRules {
37 fn plan_insert(&self, p: InsertParams) -> Result<Vec<SqlPlan>> {
38 Err(read_only("INSERT", &p.collection))
39 }
40
41 fn plan_insert_select(&self, p: InsertSelectParams) -> Result<Vec<SqlPlan>> {
42 Err(read_only("INSERT ... SELECT", &p.collection))
43 }
44
45 fn plan_upsert(&self, p: UpsertParams) -> Result<Vec<SqlPlan>> {
46 Err(read_only("UPSERT", &p.collection))
47 }
48
49 fn plan_scan(&self, p: ScanParams) -> Result<SqlPlan> {
50 refuse_temporal(&p.collection, &p.temporal)?;
51 Ok(SqlPlan::Scan {
52 collection: p.collection,
53 alias: p.alias,
54 engine: EngineType::ContinuousAggregate,
55 filters: p.filters,
56 projection: p.projection,
57 sort_keys: p.sort_keys,
58 limit: p.limit,
59 offset: p.offset,
60 distinct: p.distinct,
61 window_functions: p.window_functions,
62 temporal: p.temporal,
63 })
64 }
65
66 fn plan_point_get(&self, p: PointGetParams) -> Result<SqlPlan> {
69 Err(SqlError::Unsupported {
70 detail: format!(
71 "continuous aggregate '{}' has no primary key to look up",
72 p.collection
73 ),
74 })
75 }
76
77 fn plan_update(&self, p: UpdateParams) -> Result<Vec<SqlPlan>> {
78 Err(read_only("UPDATE", &p.collection))
79 }
80
81 fn plan_update_from(&self, p: UpdateFromParams) -> Result<Vec<SqlPlan>> {
82 Err(read_only("UPDATE ... FROM", &p.collection))
83 }
84
85 fn plan_delete(&self, p: DeleteParams) -> Result<Vec<SqlPlan>> {
86 Err(read_only("DELETE", &p.collection))
87 }
88
89 fn plan_truncate(&self, p: TruncateParams) -> Result<Vec<SqlPlan>> {
90 Err(read_only("TRUNCATE", &p.collection))
91 }
92
93 fn plan_aggregate(&self, p: AggregateParams) -> Result<SqlPlan> {
94 refuse_temporal(&p.collection, &p.temporal)?;
95 let base_scan = SqlPlan::Scan {
96 collection: p.collection,
97 alias: p.alias,
98 engine: EngineType::ContinuousAggregate,
99 filters: p.filters,
100 projection: Vec::new(),
101 sort_keys: Vec::new(),
102 limit: None,
103 offset: 0,
104 distinct: false,
105 window_functions: Vec::new(),
106 temporal: p.temporal,
107 };
108 Ok(SqlPlan::Aggregate {
109 input: Box::new(base_scan),
110 group_by: p.group_by,
111 group_by_aliases: Vec::new(),
112 output_order: Vec::new(),
113 aggregates: p.aggregates,
114 having: p.having,
115 limit: p.limit,
116 grouping_sets: p.grouping_sets,
117 sort_keys: Vec::new(),
118 })
119 }
120
121 fn plan_merge(&self, p: MergeParams) -> Result<Vec<SqlPlan>> {
122 Err(read_only("MERGE", &p.collection))
123 }
124
125 fn accepts_undeclared_columns(&self) -> bool {
127 false
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134
135 #[test]
136 fn every_write_is_refused_as_read_only() {
137 let r = ContinuousAggregateRules;
138 let err = r
139 .plan_insert(InsertParams {
140 collection: "cpu_1m".into(),
141 columns: vec![],
142 rows: vec![],
143 volatile_defaults: false,
144 if_absent: false,
145 column_schema: vec![],
146 primary_key: None,
147 })
148 .unwrap_err();
149 assert!(
150 matches!(&err, SqlError::WrongObjectType { detail } if detail.contains("read-only")),
151 "{err:?}"
152 );
153 assert!(matches!(
154 r.plan_truncate(TruncateParams {
155 collection: "cpu_1m".into(),
156 restart_identity: false,
157 })
158 .unwrap_err(),
159 SqlError::WrongObjectType { .. }
160 ));
161 }
162}