Skip to main content

nodedb_sql/engine_rules/
array.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Engine rules for the ND sparse array engine.
4//!
5//! Array operations live behind a dedicated DDL/DML surface
6//! (`CREATE ARRAY`, `INSERT INTO ARRAY`, `DELETE FROM ARRAY`,
7//! `DROP ARRAY`). The standard SQL DML pathways are unsupported by
8//! design — the planner refuses them with a hint pointing to the
9//! correct surface (use ARRAY_SLICE / ARRAY_AGG for table-valued reads).
10
11use crate::engine_rules::*;
12use crate::error::{Result, SqlError};
13use crate::types::*;
14
15pub struct ArrayRules;
16
17impl EngineRules for ArrayRules {
18    fn plan_insert(&self, _p: InsertParams) -> Result<Vec<SqlPlan>> {
19        Err(unsupported(
20            "INSERT",
21            "use INSERT INTO ARRAY <name> COORDS (...) VALUES (...)",
22        ))
23    }
24
25    fn plan_insert_select(&self, _p: InsertSelectParams) -> Result<Vec<SqlPlan>> {
26        Err(unsupported(
27            "INSERT ... SELECT",
28            "use INSERT INTO ARRAY for array engine mutations",
29        ))
30    }
31
32    fn plan_upsert(&self, _p: UpsertParams) -> Result<Vec<SqlPlan>> {
33        Err(unsupported("UPSERT", "arrays do not support UPSERT"))
34    }
35
36    fn plan_scan(&self, _p: ScanParams) -> Result<SqlPlan> {
37        Err(unsupported(
38            "SELECT",
39            "use ARRAY_SLICE or ARRAY_AGG for table-valued array reads",
40        ))
41    }
42
43    fn plan_point_get(&self, _p: PointGetParams) -> Result<SqlPlan> {
44        Err(unsupported(
45            "point lookup",
46            "arrays have no primary key; use ARRAY_SLICE for coord-range reads",
47        ))
48    }
49
50    fn plan_update(&self, _p: UpdateParams) -> Result<Vec<SqlPlan>> {
51        Err(unsupported(
52            "UPDATE",
53            "arrays are write-by-coord; re-INSERT to overwrite",
54        ))
55    }
56
57    fn plan_update_from(&self, _p: UpdateFromParams) -> Result<Vec<SqlPlan>> {
58        Err(unsupported(
59            "UPDATE ... FROM",
60            "arrays are write-by-coord; re-INSERT to overwrite",
61        ))
62    }
63
64    fn plan_delete(&self, _p: DeleteParams) -> Result<Vec<SqlPlan>> {
65        Err(unsupported(
66            "DELETE",
67            "use DELETE FROM ARRAY <name> WHERE COORDS IN ((...), ...)",
68        ))
69    }
70
71    fn plan_truncate(&self, _p: TruncateParams) -> Result<Vec<SqlPlan>> {
72        Err(unsupported(
73            "TRUNCATE",
74            "use DROP ARRAY <name> to remove the array, or \
75             DELETE FROM ARRAY <name> WHERE COORDS IN (...) to remove cells",
76        ))
77    }
78
79    fn plan_aggregate(&self, _p: AggregateParams) -> Result<SqlPlan> {
80        Err(unsupported(
81            "GROUP BY",
82            "use ARRAY_AGG for table-valued array aggregates",
83        ))
84    }
85
86    fn plan_merge(&self, _p: MergeParams) -> Result<Vec<SqlPlan>> {
87        Err(unsupported(
88            "MERGE",
89            "use INSERT INTO ARRAY / DELETE FROM ARRAY for array engine mutations",
90        ))
91    }
92
93    /// Columns are the array's declared dims and attrs.
94    fn accepts_undeclared_columns(&self) -> bool {
95        false
96    }
97}
98
99fn unsupported(op: &str, hint: &str) -> SqlError {
100    SqlError::Unsupported {
101        detail: format!("operation {op} not supported on array engine; {hint}"),
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108
109    fn ip() -> InsertParams {
110        InsertParams {
111            collection: "g".into(),
112            columns: vec![],
113            rows: vec![],
114            volatile_defaults: false,
115            if_absent: false,
116            column_schema: vec![],
117            primary_key: None,
118        }
119    }
120
121    #[test]
122    fn every_arm_is_unsupported() {
123        let r = ArrayRules;
124        assert!(matches!(
125            r.plan_insert(ip()).unwrap_err(),
126            SqlError::Unsupported { .. }
127        ));
128        assert!(matches!(
129            r.plan_upsert(UpsertParams {
130                collection: "g".into(),
131                columns: vec![],
132                rows: vec![],
133                volatile_defaults: false,
134                on_conflict_updates: vec![],
135                column_schema: vec![],
136                primary_key: None,
137            })
138            .unwrap_err(),
139            SqlError::Unsupported { .. }
140        ));
141    }
142}