Skip to main content

nodedb_sql/engine_rules/
spatial.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Engine rules for spatial columnar collections.
4
5use crate::engine_rules::*;
6use crate::error::{Result, SqlError};
7use crate::types::*;
8
9pub struct SpatialRules;
10
11impl EngineRules for SpatialRules {
12    fn plan_insert(&self, p: InsertParams) -> Result<Vec<SqlPlan>> {
13        Ok(vec![SqlPlan::Insert(InsertPlan {
14            collection: p.collection,
15            engine: EngineType::Spatial,
16            route: WriteRoute::ColumnarFamily,
17            rows: p.rows,
18            volatile_defaults: p.volatile_defaults,
19            if_absent: p.if_absent,
20            column_schema: p.column_schema,
21            primary_key: p.primary_key,
22        })])
23    }
24
25    /// Spatial extends columnar and inherits the same upsert semantics:
26    /// duplicate PK tombstones the prior row; the new row (or merged form
27    /// when `on_conflict_updates` is non-empty) is appended.
28    fn plan_insert_select(&self, p: InsertSelectParams) -> Result<Vec<SqlPlan>> {
29        Err(SqlError::Unsupported {
30            detail: format!(
31                "INSERT ... SELECT into spatial collection '{}' is not supported; \
32                 the target must be a document collection",
33                p.collection
34            ),
35        })
36    }
37
38    fn plan_upsert(&self, p: UpsertParams) -> Result<Vec<SqlPlan>> {
39        Ok(vec![SqlPlan::Upsert(UpsertPlan {
40            collection: p.collection,
41            engine: EngineType::Spatial,
42            route: WriteRoute::ColumnarFamily,
43            rows: p.rows,
44            volatile_defaults: p.volatile_defaults,
45            on_conflict_updates: p.on_conflict_updates,
46            column_schema: p.column_schema,
47            primary_key: p.primary_key,
48        })])
49    }
50
51    fn plan_scan(&self, p: ScanParams) -> Result<SqlPlan> {
52        if p.temporal.is_temporal() {
53            return Err(SqlError::Unsupported {
54                detail: format!(
55                    "FOR SYSTEM_TIME / FOR VALID_TIME is not supported on spatial collection '{}'",
56                    p.collection
57                ),
58            });
59        }
60        // Plain scan on spatial collection — no spatial predicate involved.
61        // Spatial predicates (ST_DWithin, ST_Contains, etc.) are detected
62        // in select.rs and routed to SpatialScan directly, bypassing this.
63        Ok(SqlPlan::Scan {
64            collection: p.collection,
65            alias: p.alias,
66            engine: EngineType::Spatial,
67            filters: p.filters,
68            projection: p.projection,
69            sort_keys: p.sort_keys,
70            limit: p.limit,
71            offset: p.offset,
72            distinct: p.distinct,
73            window_functions: p.window_functions,
74            temporal: p.temporal,
75        })
76    }
77
78    fn plan_point_get(&self, p: PointGetParams) -> Result<SqlPlan> {
79        Ok(SqlPlan::PointGet {
80            collection: p.collection,
81            alias: p.alias,
82            engine: EngineType::Spatial,
83            key_column: p.key_column,
84            key_value: p.key_value,
85            projection: p.projection,
86        })
87    }
88
89    fn plan_update(&self, p: UpdateParams) -> Result<Vec<SqlPlan>> {
90        Ok(vec![SqlPlan::Update {
91            collection: p.collection,
92            engine: EngineType::Spatial,
93            assignments: p.assignments,
94            filters: p.filters,
95            target_keys: p.target_keys,
96            returning: p.returning,
97        }])
98    }
99
100    /// `UPDATE ... FROM` writes through the document join-update, which a
101    /// spatial collection's columnar storage does not take.
102    fn plan_update_from(&self, p: UpdateFromParams) -> Result<Vec<SqlPlan>> {
103        Err(SqlError::Unsupported {
104            detail: format!(
105                "UPDATE ... FROM is not supported on spatial collection '{}'; \
106                 the target must be a document collection",
107                p.collection
108            ),
109        })
110    }
111
112    fn plan_delete(&self, p: DeleteParams) -> Result<Vec<SqlPlan>> {
113        Ok(vec![SqlPlan::Delete {
114            collection: p.collection,
115            engine: EngineType::Spatial,
116            filters: p.filters,
117            target_keys: p.target_keys,
118        }])
119    }
120
121    fn plan_truncate(&self, p: TruncateParams) -> Result<Vec<SqlPlan>> {
122        Ok(vec![SqlPlan::Truncate {
123            collection: p.collection,
124            engine: EngineType::Spatial,
125            restart_identity: p.restart_identity,
126        }])
127    }
128
129    fn plan_aggregate(&self, p: AggregateParams) -> Result<SqlPlan> {
130        let base_scan = SqlPlan::Scan {
131            collection: p.collection,
132            alias: p.alias,
133            engine: EngineType::Spatial,
134            filters: p.filters,
135            projection: Vec::new(),
136            sort_keys: Vec::new(),
137            limit: None,
138            offset: 0,
139            distinct: false,
140            window_functions: Vec::new(),
141            temporal: crate::temporal::TemporalScope::default(),
142        };
143        Ok(SqlPlan::Aggregate {
144            input: Box::new(base_scan),
145            group_by: p.group_by,
146            group_by_aliases: Vec::new(),
147            output_order: Vec::new(),
148            aggregates: p.aggregates,
149            having: p.having,
150            limit: p.limit,
151            grouping_sets: p.grouping_sets,
152            sort_keys: Vec::new(),
153        })
154    }
155
156    fn plan_merge(&self, p: MergeParams) -> Result<Vec<SqlPlan>> {
157        Err(SqlError::Unsupported {
158            detail: format!(
159                "MERGE is not supported on spatial collection '{}'",
160                p.collection
161            ),
162        })
163    }
164
165    /// Every column is declared at creation and encoded per column.
166    fn accepts_undeclared_columns(&self) -> bool {
167        false
168    }
169}