1use sqlparser::ast::{self, Expr, Value};
6
7use crate::error::{Result, SqlError};
8use crate::parser::normalize::{SCHEMA_QUALIFIED_MSG, normalize_ident};
9use crate::types::*;
10
11use super::binary_ops::{convert_binary_op, convert_unary_op};
12use super::functions::convert_function_depth;
13use super::value::{convert_value, parse_interval_to_micros};
14
15const MAX_CONVERT_DEPTH: usize = 128;
18
19fn is_zero_arg_keyword_function(name: &str) -> bool {
23 matches!(
24 name,
25 "current_timestamp"
26 | "current_date"
27 | "current_time"
28 | "localtime"
29 | "localtimestamp"
30 | "current_user"
31 | "current_role"
32 | "current_schema"
33 | "session_user"
34 | "user"
35 | "version"
36 )
37}
38
39pub fn convert_expr(expr: &Expr) -> Result<SqlExpr> {
41 convert_expr_depth(expr, &mut 0)
42}
43
44pub(super) fn convert_expr_depth(expr: &Expr, depth: &mut usize) -> Result<SqlExpr> {
47 *depth += 1;
48 if *depth > MAX_CONVERT_DEPTH {
49 return Err(SqlError::Unsupported {
50 detail: format!("expression nesting depth exceeds maximum of {MAX_CONVERT_DEPTH}"),
51 });
52 }
53 let result = convert_expr_inner(expr, depth);
54 *depth -= 1;
55 result
56}
57
58fn convert_expr_inner(expr: &Expr, depth: &mut usize) -> Result<SqlExpr> {
59 match expr {
60 Expr::Identifier(ident) => {
61 let name = normalize_ident(ident);
62 if is_zero_arg_keyword_function(&name) {
67 return Ok(SqlExpr::Function {
68 name,
69 args: vec![],
70 distinct: false,
71 });
72 }
73 Ok(SqlExpr::Column { table: None, name })
74 }
75 Expr::CompoundIdentifier(parts) if parts.len() >= 3 => {
76 let qualified: String = parts
77 .iter()
78 .map(normalize_ident)
79 .collect::<Vec<_>>()
80 .join(".");
81 Err(SqlError::Unsupported {
82 detail: format!(
83 "schema-qualified column reference '{qualified}': {SCHEMA_QUALIFIED_MSG}"
84 ),
85 })
86 }
87 Expr::CompoundIdentifier(parts) if parts.len() == 2 => Ok(SqlExpr::Column {
88 table: Some(normalize_ident(&parts[0])),
89 name: normalize_ident(&parts[1]),
90 }),
91 Expr::Value(val) => Ok(SqlExpr::Literal(convert_value(&val.value)?)),
92 Expr::BinaryOp { left, op, right } => {
93 use ast::BinaryOperator;
97 let json_fn: Option<&str> = match op {
98 BinaryOperator::Arrow => Some("pg_json_get"),
99 BinaryOperator::LongArrow => Some("pg_json_get_text"),
100 BinaryOperator::HashArrow => Some("pg_json_path_get"),
101 BinaryOperator::HashLongArrow => Some("pg_json_path_get_text"),
102 BinaryOperator::AtArrow => Some("pg_json_contains"),
103 BinaryOperator::ArrowAt => Some("pg_json_contained_by"),
104 BinaryOperator::Question => Some("pg_json_has_key"),
105 BinaryOperator::QuestionAnd => Some("pg_json_has_all_keys"),
106 BinaryOperator::QuestionPipe => Some("pg_json_has_any_key"),
107 _ => None,
108 };
109 if let Some(name) = json_fn {
110 return Ok(SqlExpr::Function {
111 name: name.into(),
112 args: vec![
113 convert_expr_depth(left, depth)?,
114 convert_expr_depth(right, depth)?,
115 ],
116 distinct: false,
117 });
118 }
119 if matches!(op, BinaryOperator::AtAt) {
121 let col_expr = convert_expr_depth(left, depth)?;
122 let query_expr = convert_expr_depth(right, depth)?;
123 return Ok(crate::functions::fts_ops::pg_fts_funcs::lower_pg_fts_match(
124 col_expr, query_expr,
125 ));
126 }
127 Ok(SqlExpr::BinaryOp {
128 left: Box::new(convert_expr_depth(left, depth)?),
129 op: convert_binary_op(op)?,
130 right: Box::new(convert_expr_depth(right, depth)?),
131 })
132 }
133 Expr::UnaryOp { op, expr } => Ok(SqlExpr::UnaryOp {
134 op: convert_unary_op(op)?,
135 expr: Box::new(convert_expr_depth(expr, depth)?),
136 }),
137 Expr::Function(func) => convert_function_depth(func, depth),
138 Expr::Nested(inner) => convert_expr_depth(inner, depth),
139 Expr::IsNull(inner) => Ok(SqlExpr::IsNull {
140 expr: Box::new(convert_expr_depth(inner, depth)?),
141 negated: false,
142 }),
143 Expr::IsNotNull(inner) => Ok(SqlExpr::IsNull {
144 expr: Box::new(convert_expr_depth(inner, depth)?),
145 negated: true,
146 }),
147 Expr::InList {
148 expr,
149 list,
150 negated,
151 } => Ok(SqlExpr::InList {
152 expr: Box::new(convert_expr_depth(expr, depth)?),
153 list: list
154 .iter()
155 .map(|e| convert_expr_depth(e, depth))
156 .collect::<Result<_>>()?,
157 negated: *negated,
158 }),
159 Expr::Between {
160 expr,
161 low,
162 high,
163 negated,
164 } => Ok(SqlExpr::Between {
165 expr: Box::new(convert_expr_depth(expr, depth)?),
166 low: Box::new(convert_expr_depth(low, depth)?),
167 high: Box::new(convert_expr_depth(high, depth)?),
168 negated: *negated,
169 }),
170 Expr::Like {
171 expr,
172 pattern,
173 negated,
174 ..
175 } => Ok(SqlExpr::Like {
176 expr: Box::new(convert_expr_depth(expr, depth)?),
177 pattern: Box::new(convert_expr_depth(pattern, depth)?),
178 negated: *negated,
179 case_insensitive: false,
180 }),
181 Expr::ILike {
182 expr,
183 pattern,
184 negated,
185 ..
186 } => Ok(SqlExpr::Like {
187 expr: Box::new(convert_expr_depth(expr, depth)?),
188 pattern: Box::new(convert_expr_depth(pattern, depth)?),
189 negated: *negated,
190 case_insensitive: true,
191 }),
192 Expr::Case {
193 operand,
194 conditions,
195 else_result,
196 ..
197 } => {
198 let when_then = conditions
199 .iter()
200 .map(|cw| {
201 Ok((
202 convert_expr_depth(&cw.condition, depth)?,
203 convert_expr_depth(&cw.result, depth)?,
204 ))
205 })
206 .collect::<Result<Vec<_>>>()?;
207 Ok(SqlExpr::Case {
208 operand: operand
209 .as_ref()
210 .map(|e| convert_expr_depth(e, depth).map(Box::new))
211 .transpose()?,
212 when_then,
213 else_expr: else_result
214 .as_ref()
215 .map(|e| convert_expr_depth(e, depth).map(Box::new))
216 .transpose()?,
217 })
218 }
219 Expr::TypedString(ts) => {
220 let type_str = format!("{}", ts.data_type).to_ascii_uppercase();
222 let raw = match &ts.value.value {
223 Value::SingleQuotedString(s) => s.clone(),
224 other => {
225 return Err(SqlError::Unsupported {
226 detail: format!("typed string value: {other}"),
227 });
228 }
229 };
230 match type_str.as_str() {
231 "TIMESTAMP" => {
232 let dt =
233 nodedb_types::NdbDateTime::parse(&raw).ok_or_else(|| SqlError::Parse {
234 detail: format!("cannot parse TIMESTAMP literal: '{raw}'"),
235 })?;
236 return Ok(SqlExpr::Literal(SqlValue::Timestamp(dt)));
237 }
238 "TIMESTAMPTZ" | "TIMESTAMP WITH TIME ZONE" => {
239 let dt =
240 nodedb_types::NdbDateTime::parse(&raw).ok_or_else(|| SqlError::Parse {
241 detail: format!("cannot parse TIMESTAMPTZ literal: '{raw}'"),
242 })?;
243 return Ok(SqlExpr::Literal(SqlValue::Timestamptz(dt)));
244 }
245 _ => {}
246 }
247 Ok(SqlExpr::Literal(SqlValue::String(raw)))
249 }
250 Expr::Cast {
251 expr, data_type, ..
252 } => {
253 let type_str = format!("{data_type}").to_ascii_lowercase();
258 if type_str == "tsvector" || type_str == "tsquery" {
259 return convert_expr_depth(expr, depth);
260 }
261 let upper = type_str.to_uppercase();
264 if (upper == "TIMESTAMP"
265 || upper == "TIMESTAMPTZ"
266 || upper == "TIMESTAMP WITH TIME ZONE")
267 && let Expr::Value(v) = expr.as_ref()
268 && let Value::SingleQuotedString(s) = &v.value
269 {
270 let dt = nodedb_types::NdbDateTime::parse(s).ok_or_else(|| SqlError::Parse {
271 detail: format!("cannot parse timestamp cast: '{s}'"),
272 })?;
273 return Ok(SqlExpr::Literal(if upper == "TIMESTAMP" {
274 SqlValue::Timestamp(dt)
275 } else {
276 SqlValue::Timestamptz(dt)
277 }));
278 }
279 Ok(SqlExpr::Cast {
280 expr: Box::new(convert_expr_depth(expr, depth)?),
281 to_type: format!("{data_type}"),
282 })
283 }
284 Expr::Array(ast::Array { elem, .. }) => {
285 let elems = elem
286 .iter()
287 .map(|e| convert_expr_depth(e, depth))
288 .collect::<Result<_>>()?;
289 Ok(SqlExpr::ArrayLiteral(elems))
290 }
291 Expr::Wildcard(_) => Ok(SqlExpr::Wildcard),
292 Expr::Trim { expr, .. } => Ok(SqlExpr::Function {
294 name: "trim".into(),
295 args: vec![convert_expr_depth(expr, depth)?],
296 distinct: false,
297 }),
298 Expr::Ceil { expr, .. } => Ok(SqlExpr::Function {
300 name: "ceil".into(),
301 args: vec![convert_expr_depth(expr, depth)?],
302 distinct: false,
303 }),
304 Expr::Floor { expr, .. } => Ok(SqlExpr::Function {
305 name: "floor".into(),
306 args: vec![convert_expr_depth(expr, depth)?],
307 distinct: false,
308 }),
309 Expr::Substring {
311 expr,
312 substring_from,
313 substring_for,
314 ..
315 } => {
316 let mut args = vec![convert_expr_depth(expr, depth)?];
317 if let Some(from) = substring_from {
318 args.push(convert_expr_depth(from, depth)?);
319 }
320 if let Some(len) = substring_for {
321 args.push(convert_expr_depth(len, depth)?);
322 }
323 Ok(SqlExpr::Function {
324 name: "substring".into(),
325 args,
326 distinct: false,
327 })
328 }
329 Expr::Interval(interval) => {
330 let interval_str = match interval.value.as_ref() {
333 Expr::Value(v) => match &v.value {
334 Value::SingleQuotedString(s) => s.clone(),
335 Value::Number(n, _) => {
336 if let Some(ref field) = interval.leading_field {
338 format!("{n} {field}")
339 } else {
340 n.clone()
341 }
342 }
343 _ => {
344 return Err(SqlError::Unsupported {
345 detail: format!("INTERVAL value: {}", interval.value),
346 });
347 }
348 },
349 _ => {
350 return Err(SqlError::Unsupported {
351 detail: format!("INTERVAL expression: {}", interval.value),
352 });
353 }
354 };
355
356 let full_str = if interval_str.chars().all(|c| c.is_ascii_digit())
358 && let Some(ref field) = interval.leading_field
359 {
360 format!("{interval_str} {field}")
361 } else {
362 interval_str
363 };
364
365 let micros = parse_interval_to_micros(&full_str).ok_or_else(|| SqlError::Parse {
366 detail: format!("cannot parse INTERVAL '{full_str}'"),
367 })?;
368
369 Ok(SqlExpr::Literal(SqlValue::Int(micros)))
370 }
371 Expr::AnyOp {
376 left,
377 compare_op,
378 right,
379 ..
380 } => {
381 use ast::BinaryOperator;
384 if !matches!(compare_op, BinaryOperator::Eq) {
385 return Err(SqlError::Unsupported {
386 detail: "ANY operator with non-equality comparison is not supported".into(),
387 });
388 }
389 let left_expr = convert_expr_depth(left, depth)?;
390 let right_expr = convert_expr_depth(right, depth)?;
391 let list = match right_expr {
394 SqlExpr::ArrayLiteral(elems) => elems,
395 other => vec![other],
396 };
397 Ok(SqlExpr::InList {
398 expr: Box::new(left_expr),
399 list,
400 negated: false,
401 })
402 }
403 _ => Err(SqlError::Unsupported {
404 detail: format!("expression: {expr}"),
405 }),
406 }
407}