Skip to main content

nodedb_sql/planner/
catalog_plan_validate.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Catalog-dependent expression validation across nested SQL plan shapes.
4
5use nodedb_types::DatabaseId;
6
7use crate::catalog::SqlCatalog;
8use crate::types::{FilterExpr, MergePlanAction, SqlPlan};
9
10use super::catalog_expr_fold::validate_expr;
11use super::catalog_plan_shapes::{
12    validate_aggregates, validate_projection, validate_sort_keys, validate_windows,
13};
14
15pub(super) fn validate_catalog_exprs(
16    plan: &SqlPlan,
17    catalog: &dyn SqlCatalog,
18    database_id: DatabaseId,
19    tenant_id: u64,
20) -> crate::Result<()> {
21    match plan {
22        SqlPlan::Scan {
23            filters,
24            projection,
25            sort_keys,
26            window_functions,
27            ..
28        }
29        | SqlPlan::DocumentIndexLookup {
30            filters,
31            projection,
32            sort_keys,
33            window_functions,
34            ..
35        } => {
36            validate_filters(filters, catalog, database_id, tenant_id)?;
37            validate_projection(projection, catalog, database_id, tenant_id)?;
38            validate_sort_keys(sort_keys, catalog, database_id, tenant_id)?;
39            validate_windows(window_functions, catalog, database_id, tenant_id)?;
40        }
41        SqlPlan::PointGet { projection, .. } | SqlPlan::RangeScan { projection, .. } => {
42            validate_projection(projection, catalog, database_id, tenant_id)?;
43        }
44        SqlPlan::Update {
45            assignments,
46            filters,
47            ..
48        } => {
49            for (_, expr) in assignments {
50                validate_expr(expr, catalog, database_id, tenant_id)?;
51            }
52            validate_filters(filters, catalog, database_id, tenant_id)?;
53        }
54        SqlPlan::Delete { filters, .. } => {
55            validate_filters(filters, catalog, database_id, tenant_id)?
56        }
57        SqlPlan::UpdateFrom {
58            source,
59            assignments,
60            target_filters,
61            ..
62        } => {
63            validate_catalog_exprs(source, catalog, database_id, tenant_id)?;
64            for (_, expr) in assignments {
65                validate_expr(expr, catalog, database_id, tenant_id)?;
66            }
67            validate_filters(target_filters, catalog, database_id, tenant_id)?;
68        }
69        SqlPlan::InsertSelect { source, .. } => {
70            validate_catalog_exprs(source, catalog, database_id, tenant_id)?
71        }
72        SqlPlan::Aggregate {
73            input,
74            group_by,
75            aggregates,
76            having,
77            sort_keys,
78            ..
79        } => {
80            validate_catalog_exprs(input, catalog, database_id, tenant_id)?;
81            for expr in group_by {
82                validate_expr(expr, catalog, database_id, tenant_id)?;
83            }
84            validate_aggregates(aggregates, catalog, database_id, tenant_id)?;
85            validate_filters(having, catalog, database_id, tenant_id)?;
86            validate_sort_keys(sort_keys, catalog, database_id, tenant_id)?;
87        }
88        SqlPlan::Union { inputs, .. } => {
89            for input in inputs {
90                validate_catalog_exprs(input, catalog, database_id, tenant_id)?;
91            }
92        }
93        SqlPlan::Intersect { left, right, .. } | SqlPlan::Except { left, right, .. } => {
94            validate_catalog_exprs(left, catalog, database_id, tenant_id)?;
95            validate_catalog_exprs(right, catalog, database_id, tenant_id)?;
96        }
97        SqlPlan::Cte { definitions, outer } => {
98            for (_, definition) in definitions {
99                validate_catalog_exprs(definition, catalog, database_id, tenant_id)?;
100            }
101            validate_catalog_exprs(outer, catalog, database_id, tenant_id)?;
102        }
103        SqlPlan::Join {
104            left,
105            right,
106            condition,
107            projection,
108            filters,
109            ..
110        } => {
111            validate_catalog_exprs(left, catalog, database_id, tenant_id)?;
112            validate_catalog_exprs(right, catalog, database_id, tenant_id)?;
113            if let Some(condition) = condition {
114                validate_expr(condition, catalog, database_id, tenant_id)?;
115            }
116            validate_projection(projection, catalog, database_id, tenant_id)?;
117            validate_filters(filters, catalog, database_id, tenant_id)?;
118        }
119        SqlPlan::LateralTopK {
120            outer,
121            inner_filters,
122            inner_order_by,
123            projection,
124            ..
125        } => {
126            validate_catalog_exprs(outer, catalog, database_id, tenant_id)?;
127            validate_filters(inner_filters, catalog, database_id, tenant_id)?;
128            validate_sort_keys(inner_order_by, catalog, database_id, tenant_id)?;
129            validate_projection(projection, catalog, database_id, tenant_id)?;
130        }
131        SqlPlan::LateralLoop {
132            outer,
133            inner,
134            projection,
135            ..
136        } => {
137            validate_catalog_exprs(outer, catalog, database_id, tenant_id)?;
138            validate_catalog_exprs(inner, catalog, database_id, tenant_id)?;
139            validate_projection(projection, catalog, database_id, tenant_id)?;
140        }
141        SqlPlan::Merge {
142            source, clauses, ..
143        } => {
144            validate_catalog_exprs(source, catalog, database_id, tenant_id)?;
145            for clause in clauses {
146                validate_filters(&clause.extra_predicate, catalog, database_id, tenant_id)?;
147                match &clause.action {
148                    MergePlanAction::Update { assignments } => {
149                        for (_, expr) in assignments {
150                            validate_expr(expr, catalog, database_id, tenant_id)?;
151                        }
152                    }
153                    MergePlanAction::Insert { values, .. } => {
154                        for expr in values {
155                            validate_expr(expr, catalog, database_id, tenant_id)?;
156                        }
157                    }
158                    MergePlanAction::Delete | MergePlanAction::DoNothing => {}
159                }
160            }
161        }
162        SqlPlan::VectorSearch {
163            filters,
164            projection,
165            ..
166        } => {
167            validate_filters(filters, catalog, database_id, tenant_id)?;
168            validate_projection(projection, catalog, database_id, tenant_id)?;
169        }
170        SqlPlan::MultiVectorSearch { projection, .. }
171        | SqlPlan::SparseSearch { projection, .. }
172        | SqlPlan::HybridSearch { projection, .. }
173        | SqlPlan::HybridSearchTriple { projection, .. } => {
174            validate_projection(projection, catalog, database_id, tenant_id)?;
175        }
176        SqlPlan::TextSearch {
177            filters,
178            projection,
179            ..
180        } => {
181            validate_filters(filters, catalog, database_id, tenant_id)?;
182            validate_projection(projection, catalog, database_id, tenant_id)?;
183        }
184        SqlPlan::SpatialScan {
185            attribute_filters,
186            projection,
187            ..
188        } => {
189            validate_filters(attribute_filters, catalog, database_id, tenant_id)?;
190            validate_projection(projection, catalog, database_id, tenant_id)?;
191        }
192        SqlPlan::RecursiveScan {
193            base_filters,
194            recursive_filters,
195            projection,
196            ..
197        } => {
198            validate_filters(base_filters, catalog, database_id, tenant_id)?;
199            validate_filters(recursive_filters, catalog, database_id, tenant_id)?;
200            validate_projection(projection, catalog, database_id, tenant_id)?;
201        }
202        _ => {}
203    }
204    Ok(())
205}
206
207fn validate_filters(
208    filters: &[crate::types::Filter],
209    catalog: &dyn SqlCatalog,
210    database_id: DatabaseId,
211    tenant_id: u64,
212) -> crate::Result<()> {
213    for filter in filters {
214        validate_filter_expr(&filter.expr, catalog, database_id, tenant_id)?;
215    }
216    Ok(())
217}
218
219fn validate_filter_expr(
220    expr: &FilterExpr,
221    catalog: &dyn SqlCatalog,
222    database_id: DatabaseId,
223    tenant_id: u64,
224) -> crate::Result<()> {
225    match expr {
226        FilterExpr::Expr(expr) => validate_expr(expr, catalog, database_id, tenant_id),
227        FilterExpr::And(children) | FilterExpr::Or(children) => {
228            validate_filters(children, catalog, database_id, tenant_id)
229        }
230        FilterExpr::Not(child) => {
231            validate_filter_expr(&child.expr, catalog, database_id, tenant_id)
232        }
233        _ => Ok(()),
234    }
235}