Skip to main content

nodedb_sql/planner/
catalog_expr_fold.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Recursive catalog-dependent expression validation and folding.
4
5use nodedb_types::DatabaseId;
6
7use crate::catalog::SqlCatalog;
8use crate::functions::registry::FunctionRegistry;
9use crate::types::{SqlExpr, SqlValue};
10
11pub(super) fn eval_catalog_constant(
12    expr: &SqlExpr,
13    catalog: &dyn SqlCatalog,
14    functions: &FunctionRegistry,
15) -> crate::Result<SqlValue> {
16    if let SqlExpr::Cast {
17        expr: inner,
18        to_type,
19    } = expr
20        && to_type.eq_ignore_ascii_case("regclass")
21        && let SqlExpr::Literal(SqlValue::String(name)) = inner.as_ref()
22    {
23        let normalized = crate::catalog::normalize_regclass_name(name)
24            .ok_or_else(|| crate::SqlError::UnknownTable { name: name.clone() })?;
25        catalog
26            .resolve_regclass(DatabaseId::DEFAULT, 0, name)
27            .ok_or_else(|| crate::SqlError::UnknownTable {
28                name: normalized.clone(),
29            })?;
30        return Ok(SqlValue::String(normalized));
31    }
32    Ok(super::select::helpers::eval_constant_expr(expr, functions))
33}
34
35pub(super) fn validate_expr(
36    expr: &SqlExpr,
37    catalog: &dyn SqlCatalog,
38    database_id: DatabaseId,
39    tenant_id: u64,
40) -> crate::Result<()> {
41    match expr {
42        SqlExpr::Cast { expr, to_type }
43            if to_type.eq_ignore_ascii_case("regclass")
44                && matches!(expr.as_ref(), SqlExpr::Literal(SqlValue::String(_))) =>
45        {
46            let SqlExpr::Literal(SqlValue::String(name)) = expr.as_ref() else {
47                unreachable!();
48            };
49            if catalog
50                .resolve_regclass(database_id, tenant_id, name)
51                .is_none()
52            {
53                return Err(crate::SqlError::UnknownTable { name: name.clone() });
54            }
55            Ok(())
56        }
57        SqlExpr::Cast { expr, .. }
58        | SqlExpr::IsNull { expr, .. }
59        | SqlExpr::UnaryOp { expr, .. } => validate_expr(expr, catalog, database_id, tenant_id),
60        SqlExpr::BinaryOp { left, right, .. } => {
61            validate_expr(left, catalog, database_id, tenant_id)?;
62            validate_expr(right, catalog, database_id, tenant_id)
63        }
64        SqlExpr::InList { expr, list, .. } => {
65            validate_expr(expr, catalog, database_id, tenant_id)?;
66            for item in list {
67                validate_expr(item, catalog, database_id, tenant_id)?;
68            }
69            Ok(())
70        }
71        SqlExpr::ArrayLiteral(items) | SqlExpr::Function { args: items, .. } => {
72            for item in items {
73                validate_expr(item, catalog, database_id, tenant_id)?;
74            }
75            Ok(())
76        }
77        SqlExpr::Between {
78            expr, low, high, ..
79        } => {
80            validate_expr(expr, catalog, database_id, tenant_id)?;
81            validate_expr(low, catalog, database_id, tenant_id)?;
82            validate_expr(high, catalog, database_id, tenant_id)
83        }
84        SqlExpr::Like { expr, pattern, .. } => {
85            validate_expr(expr, catalog, database_id, tenant_id)?;
86            validate_expr(pattern, catalog, database_id, tenant_id)
87        }
88        SqlExpr::Case {
89            operand,
90            when_then,
91            else_expr,
92        } => {
93            if let Some(operand) = operand {
94                validate_expr(operand, catalog, database_id, tenant_id)?;
95            }
96            for (when, then) in when_then {
97                validate_expr(when, catalog, database_id, tenant_id)?;
98                validate_expr(then, catalog, database_id, tenant_id)?;
99            }
100            if let Some(expr) = else_expr {
101                validate_expr(expr, catalog, database_id, tenant_id)?;
102            }
103            Ok(())
104        }
105        _ => Ok(()),
106    }
107}
108
109/// Recursively fold catalog-dependent expressions to constants.
110pub(super) fn fold_expr(
111    expr: SqlExpr,
112    catalog: &dyn SqlCatalog,
113    database_id: DatabaseId,
114    tenant_id: u64,
115) -> SqlExpr {
116    match expr {
117        SqlExpr::Cast {
118            expr: inner_expr,
119            to_type,
120        } => {
121            let upper = to_type.to_ascii_uppercase();
122            if upper == "REGCLASS" {
123                if let SqlExpr::Literal(SqlValue::String(ref name)) = *inner_expr
124                    && let Some(oid) = catalog.resolve_regclass(database_id, tenant_id, name)
125                {
126                    return SqlExpr::Literal(SqlValue::Int(oid));
127                }
128            } else if upper == "REGTYPE"
129                && let SqlExpr::Literal(SqlValue::String(ref name)) = *inner_expr
130                && let Some(oid) = catalog.resolve_regtype(name)
131            {
132                return SqlExpr::Literal(SqlValue::Int(oid));
133            }
134            SqlExpr::Cast {
135                expr: Box::new(fold_expr(*inner_expr, catalog, database_id, tenant_id)),
136                to_type,
137            }
138        }
139        SqlExpr::BinaryOp { left, op, right } => SqlExpr::BinaryOp {
140            left: Box::new(fold_expr(*left, catalog, database_id, tenant_id)),
141            op,
142            right: Box::new(fold_expr(*right, catalog, database_id, tenant_id)),
143        },
144        SqlExpr::InList {
145            expr,
146            list,
147            negated,
148        } => SqlExpr::InList {
149            expr: Box::new(fold_expr(*expr, catalog, database_id, tenant_id)),
150            list: list
151                .into_iter()
152                .map(|e| fold_expr(e, catalog, database_id, tenant_id))
153                .collect(),
154            negated,
155        },
156        SqlExpr::IsNull { expr, negated } => SqlExpr::IsNull {
157            expr: Box::new(fold_expr(*expr, catalog, database_id, tenant_id)),
158            negated,
159        },
160        SqlExpr::UnaryOp { op, expr } => SqlExpr::UnaryOp {
161            op,
162            expr: Box::new(fold_expr(*expr, catalog, database_id, tenant_id)),
163        },
164        SqlExpr::ArrayLiteral(elems) => SqlExpr::ArrayLiteral(
165            elems
166                .into_iter()
167                .map(|e| fold_expr(e, catalog, database_id, tenant_id))
168                .collect(),
169        ),
170        SqlExpr::Function {
171            name,
172            args,
173            distinct,
174        } => SqlExpr::Function {
175            name,
176            args: args
177                .into_iter()
178                .map(|arg| fold_expr(arg, catalog, database_id, tenant_id))
179                .collect(),
180            distinct,
181        },
182        SqlExpr::Between {
183            expr,
184            low,
185            high,
186            negated,
187        } => SqlExpr::Between {
188            expr: Box::new(fold_expr(*expr, catalog, database_id, tenant_id)),
189            low: Box::new(fold_expr(*low, catalog, database_id, tenant_id)),
190            high: Box::new(fold_expr(*high, catalog, database_id, tenant_id)),
191            negated,
192        },
193        SqlExpr::Like {
194            expr,
195            pattern,
196            negated,
197            case_insensitive,
198        } => SqlExpr::Like {
199            expr: Box::new(fold_expr(*expr, catalog, database_id, tenant_id)),
200            pattern: Box::new(fold_expr(*pattern, catalog, database_id, tenant_id)),
201            negated,
202            case_insensitive,
203        },
204        SqlExpr::Case {
205            operand,
206            when_then,
207            else_expr,
208        } => SqlExpr::Case {
209            operand: operand.map(|e| Box::new(fold_expr(*e, catalog, database_id, tenant_id))),
210            when_then: when_then
211                .into_iter()
212                .map(|(when, then)| {
213                    (
214                        fold_expr(when, catalog, database_id, tenant_id),
215                        fold_expr(then, catalog, database_id, tenant_id),
216                    )
217                })
218                .collect(),
219            else_expr: else_expr.map(|e| Box::new(fold_expr(*e, catalog, database_id, tenant_id))),
220        },
221        leaf => leaf,
222    }
223}