1use sqlparser::ast::Expr;
6
7use crate::error::{Result, SqlError};
8use crate::resolver::ColumnScope;
9use crate::resolver::expr::functions::convert_function_depth;
10use crate::types::*;
11
12use super::{builtins, identifier, literals, operators, predicates};
13
14const MAX_CONVERT_DEPTH: usize = 128;
17
18pub fn convert_expr(expr: &Expr, scope: &ColumnScope<'_>) -> Result<SqlExpr> {
20 convert_expr_depth(expr, &mut 0, scope)
21}
22
23pub(in crate::resolver::expr) fn convert_expr_depth(
26 expr: &Expr,
27 depth: &mut usize,
28 scope: &ColumnScope<'_>,
29) -> Result<SqlExpr> {
30 *depth += 1;
31 if *depth > MAX_CONVERT_DEPTH {
32 return Err(SqlError::Unsupported {
33 detail: format!("expression nesting depth exceeds maximum of {MAX_CONVERT_DEPTH}"),
34 });
35 }
36 let result = convert_expr_inner(expr, depth, scope);
37 *depth -= 1;
38 result
39}
40
41fn convert_expr_inner(expr: &Expr, depth: &mut usize, scope: &ColumnScope<'_>) -> Result<SqlExpr> {
42 match expr {
43 Expr::Identifier(ident) => identifier::convert_identifier(ident, scope),
44 Expr::CompoundIdentifier(parts) if parts.len() >= 2 => {
45 identifier::convert_compound_identifier(parts, scope)
46 }
47 Expr::Value(val) => literals::convert_value_expr(val),
48 Expr::BinaryOp { left, op, right } => {
49 operators::convert_binary(left, op, right, depth, scope)
50 }
51 Expr::UnaryOp { op, expr } => operators::convert_unary(op, expr, depth, scope),
52 Expr::Function(func) => convert_function_depth(func, depth, scope),
53 Expr::Nested(inner) => convert_expr_depth(inner, depth, scope),
54 Expr::IsNull(inner) => predicates::convert_is_null(inner, false, depth, scope),
55 Expr::IsNotNull(inner) => predicates::convert_is_null(inner, true, depth, scope),
56 Expr::InList {
57 expr,
58 list,
59 negated,
60 } => predicates::convert_in_list(expr, list, *negated, depth, scope),
61 Expr::Between {
62 expr,
63 low,
64 high,
65 negated,
66 } => predicates::convert_between(expr, low, high, *negated, depth, scope),
67 Expr::Like {
68 expr,
69 pattern,
70 negated,
71 ..
72 } => predicates::convert_like(expr, pattern, *negated, false, depth, scope),
73 Expr::ILike {
74 expr,
75 pattern,
76 negated,
77 ..
78 } => predicates::convert_like(expr, pattern, *negated, true, depth, scope),
79 Expr::Case {
80 operand,
81 conditions,
82 else_result,
83 ..
84 } => predicates::convert_case(
85 operand.as_deref(),
86 conditions,
87 else_result.as_deref(),
88 depth,
89 scope,
90 ),
91 Expr::TypedString(ts) => literals::convert_typed_string(ts),
92 Expr::Cast {
93 expr, data_type, ..
94 } => literals::convert_cast(expr, data_type, depth, scope),
95 Expr::Array(array) => literals::convert_array(array, depth, scope),
96 Expr::Wildcard(_) => literals::convert_wildcard(),
97 Expr::Trim { expr, .. } => builtins::convert_trim(expr, depth, scope),
98 Expr::Ceil { expr, .. } => builtins::convert_ceil(expr, depth, scope),
99 Expr::Floor { expr, .. } => builtins::convert_floor(expr, depth, scope),
100 Expr::Substring {
101 expr,
102 substring_from,
103 substring_for,
104 ..
105 } => builtins::convert_substring(
106 expr,
107 substring_from.as_deref(),
108 substring_for.as_deref(),
109 depth,
110 scope,
111 ),
112 Expr::Interval(interval) => literals::convert_interval(interval),
113 Expr::AnyOp {
114 left,
115 compare_op,
116 right,
117 ..
118 } => operators::convert_any_op(left, compare_op, right, depth, scope),
119 _ => Err(SqlError::Unsupported {
120 detail: format!("expression: {expr}"),
121 }),
122 }
123}
124
125#[cfg(test)]
126pub(super) mod tests {
127 use sqlparser::ast::{Expr, SelectItem, Statement};
128
129 use super::convert_expr;
130 use crate::error::SqlError;
131 use crate::parser::statement::parse_sql;
132 use crate::resolver::ColumnScope;
133 use crate::types::*;
134
135 pub(in crate::resolver::expr::convert) fn first_select_expr(sql: &str) -> Expr {
137 let stmts = parse_sql(sql).expect("parse failed");
138 let Statement::Query(q) = &stmts[0] else {
139 panic!("expected query");
140 };
141 let sqlparser::ast::SetExpr::Select(sel) = q.body.as_ref() else {
142 panic!("expected select body");
143 };
144 match &sel.projection[0] {
145 SelectItem::UnnamedExpr(e) => e.clone(),
146 SelectItem::ExprWithAlias { expr, .. } => expr.clone(),
147 other => panic!("unexpected projection item: {other:?}"),
148 }
149 }
150
151 pub(in crate::resolver::expr::convert) fn where_sql_expr(sql: &str) -> SqlExpr {
154 let stmts = parse_sql(sql).expect("parse failed");
155 let Statement::Query(q) = &stmts[0] else {
156 panic!("expected query");
157 };
158 let sqlparser::ast::SetExpr::Select(sel) = q.body.as_ref() else {
159 panic!("expected select body");
160 };
161 let raw = sel.selection.as_ref().expect("expected WHERE clause");
162 convert_expr(raw, &ColumnScope::Unchecked).expect("convert_expr failed")
163 }
164
165 pub(in crate::resolver::expr::convert) fn select_expr_lowered(sql: &str) -> SqlExpr {
167 let stmts = parse_sql(sql).expect("parse failed");
168 let Statement::Query(q) = &stmts[0] else {
169 panic!("expected query");
170 };
171 let sqlparser::ast::SetExpr::Select(sel) = q.body.as_ref() else {
172 panic!("expected select body");
173 };
174 let raw = &sel.projection[0];
175 let raw_expr = match raw {
176 SelectItem::UnnamedExpr(e) => e,
177 SelectItem::ExprWithAlias { expr, .. } => expr,
178 other => panic!("unexpected projection: {other:?}"),
179 };
180 convert_expr(raw_expr, &ColumnScope::Unchecked).expect("convert_expr failed")
181 }
182
183 #[test]
184 fn ts_rank_cd_is_unsupported() {
185 use crate::parser::statement::parse_sql;
186 let sql = "SELECT ts_rank_cd(body, to_tsquery('rust')) FROM t";
187 let stmts = parse_sql(sql).expect("parse ok");
188 let Statement::Query(q) = &stmts[0] else {
189 panic!("expected query");
190 };
191 let sqlparser::ast::SetExpr::Select(sel) = q.body.as_ref() else {
192 panic!("expected select body");
193 };
194 let raw = match &sel.projection[0] {
195 SelectItem::UnnamedExpr(e) => e,
196 SelectItem::ExprWithAlias { expr, .. } => expr,
197 other => panic!("unexpected projection: {other:?}"),
198 };
199 let err = convert_expr(raw, &ColumnScope::Unchecked).unwrap_err();
200 assert!(
201 matches!(err, SqlError::Unsupported { .. }),
202 "ts_rank_cd should be Unsupported, got {err:?}"
203 );
204 let msg = format!("{err}");
205 assert!(
206 msg.contains("ts_rank_cd"),
207 "error should mention ts_rank_cd: {msg}"
208 );
209 }
210
211 #[test]
212 fn to_tsquery_lowers_to_pg_to_tsquery() {
213 let expr = select_expr_lowered("SELECT to_tsquery('rust & lang') FROM t");
214 match expr {
215 SqlExpr::Function { ref name, .. } => {
216 assert_eq!(name, "pg_to_tsquery");
217 }
218 other => panic!("expected pg_to_tsquery Function, got {other:?}"),
219 }
220 }
221
222 #[test]
223 fn plainto_tsquery_lowers_correctly() {
224 let expr = select_expr_lowered("SELECT plainto_tsquery('rust lang') FROM t");
225 match expr {
226 SqlExpr::Function { ref name, .. } => {
227 assert_eq!(name, "pg_plainto_tsquery");
228 }
229 other => panic!("expected pg_plainto_tsquery, got {other:?}"),
230 }
231 }
232
233 #[test]
234 fn ts_rank_lowers_to_pg_ts_rank() {
235 let expr = select_expr_lowered("SELECT ts_rank(body, to_tsquery('rust')) FROM t");
236 match expr {
237 SqlExpr::Function { ref name, .. } => {
238 assert_eq!(name, "pg_ts_rank");
239 }
240 other => panic!("expected pg_ts_rank, got {other:?}"),
241 }
242 }
243}