1use nodedb_types::DatabaseId;
6
7use crate::catalog::SqlCatalog;
8use crate::functions::registry::FunctionRegistry;
9use crate::planner::const_fold::FoldScope;
10use crate::types::{SqlExpr, SqlValue};
11
12pub(super) fn eval_catalog_constant(
13 expr: &SqlExpr,
14 catalog: &dyn SqlCatalog,
15 functions: &FunctionRegistry,
16) -> crate::Result<SqlValue> {
17 if let SqlExpr::Cast {
18 expr: inner,
19 to_type,
20 } = expr
21 && to_type.eq_ignore_ascii_case("regclass")
22 && let SqlExpr::Literal(SqlValue::String(name)) = inner.as_ref()
23 {
24 let normalized = crate::catalog::normalize_regclass_name(name)
25 .ok_or_else(|| crate::SqlError::UnknownTable { name: name.clone() })?;
26 catalog
27 .resolve_regclass(DatabaseId::DEFAULT, 0, name)
28 .ok_or_else(|| crate::SqlError::UnknownTable {
29 name: normalized.clone(),
30 })?;
31 return Ok(SqlValue::String(normalized));
32 }
33 if let Some(value) = eval_sequence_accessor(expr, catalog)? {
34 return Ok(value);
35 }
36 Ok(
40 crate::planner::const_fold::fold_constant_scoped(expr, functions, FoldScope::Once)?
41 .unwrap_or(SqlValue::Null),
42 )
43}
44
45pub(super) fn eval_sequence_accessor(
51 expr: &SqlExpr,
52 catalog: &dyn SqlCatalog,
53) -> crate::Result<Option<SqlValue>> {
54 let SqlExpr::Function { name, args, .. } = expr else {
55 return Ok(None);
56 };
57 let lowered = name.to_ascii_lowercase();
58 if !crate::functions::sequence_accessor::is_sequence_accessor(&lowered) {
59 return Ok(None);
60 }
61 let sequence = match args.first() {
62 Some(SqlExpr::Literal(SqlValue::String(name))) => name.as_str(),
63 _ => {
64 return Err(crate::SqlError::Arity {
65 detail: format!("{lowered} requires a literal sequence name"),
66 });
67 }
68 };
69 let value = match lowered.as_str() {
70 "nextval" => catalog.sequence_nextval(DatabaseId::DEFAULT, 0, sequence)?,
71 "currval" => catalog.sequence_currval(DatabaseId::DEFAULT, 0, sequence)?,
72 _ => {
73 let Some(SqlExpr::Literal(SqlValue::Int(target))) = args.get(1) else {
74 return Err(crate::SqlError::Arity {
75 detail: "setval requires a literal bigint second argument".into(),
76 });
77 };
78 catalog.sequence_setval(DatabaseId::DEFAULT, 0, sequence, *target)?
79 }
80 };
81 Ok(Some(SqlValue::Int(value)))
82}
83
84pub(super) fn validate_expr(
85 expr: &SqlExpr,
86 catalog: &dyn SqlCatalog,
87 database_id: DatabaseId,
88 tenant_id: u64,
89) -> crate::Result<()> {
90 match expr {
91 SqlExpr::Cast { expr, to_type }
92 if to_type.eq_ignore_ascii_case("regclass")
93 && matches!(expr.as_ref(), SqlExpr::Literal(SqlValue::String(_))) =>
94 {
95 let SqlExpr::Literal(SqlValue::String(name)) = expr.as_ref() else {
96 unreachable!();
97 };
98 if catalog
99 .resolve_regclass(database_id, tenant_id, name)
100 .is_none()
101 {
102 return Err(crate::SqlError::UnknownTable { name: name.clone() });
103 }
104 Ok(())
105 }
106 SqlExpr::Cast { expr, .. }
107 | SqlExpr::IsNull { expr, .. }
108 | SqlExpr::UnaryOp { expr, .. } => validate_expr(expr, catalog, database_id, tenant_id),
109 SqlExpr::BinaryOp { left, right, .. } => {
110 validate_expr(left, catalog, database_id, tenant_id)?;
111 validate_expr(right, catalog, database_id, tenant_id)
112 }
113 SqlExpr::InList { expr, list, .. } => {
114 validate_expr(expr, catalog, database_id, tenant_id)?;
115 for item in list {
116 validate_expr(item, catalog, database_id, tenant_id)?;
117 }
118 Ok(())
119 }
120 SqlExpr::ArrayLiteral(items) | SqlExpr::Function { args: items, .. } => {
121 for item in items {
122 validate_expr(item, catalog, database_id, tenant_id)?;
123 }
124 Ok(())
125 }
126 SqlExpr::Between {
127 expr, low, high, ..
128 } => {
129 validate_expr(expr, catalog, database_id, tenant_id)?;
130 validate_expr(low, catalog, database_id, tenant_id)?;
131 validate_expr(high, catalog, database_id, tenant_id)
132 }
133 SqlExpr::Like { expr, pattern, .. } => {
134 validate_expr(expr, catalog, database_id, tenant_id)?;
135 validate_expr(pattern, catalog, database_id, tenant_id)
136 }
137 SqlExpr::Case {
138 operand,
139 when_then,
140 else_expr,
141 } => {
142 if let Some(operand) = operand {
143 validate_expr(operand, catalog, database_id, tenant_id)?;
144 }
145 for (when, then) in when_then {
146 validate_expr(when, catalog, database_id, tenant_id)?;
147 validate_expr(then, catalog, database_id, tenant_id)?;
148 }
149 if let Some(expr) = else_expr {
150 validate_expr(expr, catalog, database_id, tenant_id)?;
151 }
152 Ok(())
153 }
154 _ => Ok(()),
155 }
156}
157
158pub(super) fn fold_expr(
160 expr: SqlExpr,
161 catalog: &dyn SqlCatalog,
162 database_id: DatabaseId,
163 tenant_id: u64,
164) -> SqlExpr {
165 match expr {
166 SqlExpr::Cast {
167 expr: inner_expr,
168 to_type,
169 } => {
170 let upper = to_type.to_ascii_uppercase();
171 if upper == "REGCLASS" {
172 if let SqlExpr::Literal(SqlValue::String(ref name)) = *inner_expr
173 && let Some(oid) = catalog.resolve_regclass(database_id, tenant_id, name)
174 {
175 return SqlExpr::Literal(SqlValue::Int(oid));
176 }
177 } else if upper == "REGTYPE"
178 && let SqlExpr::Literal(SqlValue::String(ref name)) = *inner_expr
179 && let Some(oid) = catalog.resolve_regtype(name)
180 {
181 return SqlExpr::Literal(SqlValue::Int(oid));
182 }
183 SqlExpr::Cast {
184 expr: Box::new(fold_expr(*inner_expr, catalog, database_id, tenant_id)),
185 to_type,
186 }
187 }
188 SqlExpr::BinaryOp { left, op, right } => SqlExpr::BinaryOp {
189 left: Box::new(fold_expr(*left, catalog, database_id, tenant_id)),
190 op,
191 right: Box::new(fold_expr(*right, catalog, database_id, tenant_id)),
192 },
193 SqlExpr::InList {
194 expr,
195 list,
196 negated,
197 } => SqlExpr::InList {
198 expr: Box::new(fold_expr(*expr, catalog, database_id, tenant_id)),
199 list: list
200 .into_iter()
201 .map(|e| fold_expr(e, catalog, database_id, tenant_id))
202 .collect(),
203 negated,
204 },
205 SqlExpr::IsNull { expr, negated } => SqlExpr::IsNull {
206 expr: Box::new(fold_expr(*expr, catalog, database_id, tenant_id)),
207 negated,
208 },
209 SqlExpr::UnaryOp { op, expr } => SqlExpr::UnaryOp {
210 op,
211 expr: Box::new(fold_expr(*expr, catalog, database_id, tenant_id)),
212 },
213 SqlExpr::ArrayLiteral(elems) => SqlExpr::ArrayLiteral(
214 elems
215 .into_iter()
216 .map(|e| fold_expr(e, catalog, database_id, tenant_id))
217 .collect(),
218 ),
219 SqlExpr::Function {
220 name,
221 args,
222 distinct,
223 } => SqlExpr::Function {
224 name,
225 args: args
226 .into_iter()
227 .map(|arg| fold_expr(arg, catalog, database_id, tenant_id))
228 .collect(),
229 distinct,
230 },
231 SqlExpr::Between {
232 expr,
233 low,
234 high,
235 negated,
236 } => SqlExpr::Between {
237 expr: Box::new(fold_expr(*expr, catalog, database_id, tenant_id)),
238 low: Box::new(fold_expr(*low, catalog, database_id, tenant_id)),
239 high: Box::new(fold_expr(*high, catalog, database_id, tenant_id)),
240 negated,
241 },
242 SqlExpr::Like {
243 expr,
244 pattern,
245 negated,
246 case_insensitive,
247 } => SqlExpr::Like {
248 expr: Box::new(fold_expr(*expr, catalog, database_id, tenant_id)),
249 pattern: Box::new(fold_expr(*pattern, catalog, database_id, tenant_id)),
250 negated,
251 case_insensitive,
252 },
253 SqlExpr::Case {
254 operand,
255 when_then,
256 else_expr,
257 } => SqlExpr::Case {
258 operand: operand.map(|e| Box::new(fold_expr(*e, catalog, database_id, tenant_id))),
259 when_then: when_then
260 .into_iter()
261 .map(|(when, then)| {
262 (
263 fold_expr(when, catalog, database_id, tenant_id),
264 fold_expr(then, catalog, database_id, tenant_id),
265 )
266 })
267 .collect(),
268 else_expr: else_expr.map(|e| Box::new(fold_expr(*e, catalog, database_id, tenant_id))),
269 },
270 leaf => leaf,
271 }
272}