nodedb_sql/resolver/expr/convert/
operators.rs1use sqlparser::ast::{BinaryOperator, Expr, UnaryOperator, Value};
6
7use crate::error::{Result, SqlError};
8use crate::resolver::ColumnScope;
9use crate::resolver::expr::binary_ops::{convert_binary_op, convert_unary_op};
10use crate::types::*;
11
12use super::entry::convert_expr_depth;
13
14pub(super) fn convert_binary(
15 left: &Expr,
16 op: &BinaryOperator,
17 right: &Expr,
18 depth: &mut usize,
19 scope: &ColumnScope<'_>,
20) -> Result<SqlExpr> {
21 let json_fn: Option<&str> = match op {
25 BinaryOperator::Arrow => Some("pg_json_get"),
26 BinaryOperator::LongArrow => Some("pg_json_get_text"),
27 BinaryOperator::HashArrow => Some("pg_json_path_get"),
28 BinaryOperator::HashLongArrow => Some("pg_json_path_get_text"),
29 BinaryOperator::AtArrow => Some("pg_json_contains"),
30 BinaryOperator::ArrowAt => Some("pg_json_contained_by"),
31 BinaryOperator::Question => Some("pg_json_has_key"),
32 BinaryOperator::QuestionAnd => Some("pg_json_has_all_keys"),
33 BinaryOperator::QuestionPipe => Some("pg_json_has_any_key"),
34 _ => None,
35 };
36 if let Some(name) = json_fn {
37 return Ok(SqlExpr::Function {
38 name: name.into(),
39 args: vec![
40 convert_expr_depth(left, depth, scope)?,
41 convert_expr_depth(right, depth, scope)?,
42 ],
43 distinct: false,
44 });
45 }
46 if matches!(op, BinaryOperator::AtAt) {
48 let col_expr = convert_expr_depth(left, depth, scope)?;
49 let query_expr = convert_expr_depth(right, depth, scope)?;
50 return Ok(crate::functions::fts_ops::pg_fts_funcs::lower_pg_fts_match(
51 col_expr, query_expr,
52 ));
53 }
54 Ok(SqlExpr::BinaryOp {
55 left: Box::new(convert_expr_depth(left, depth, scope)?),
56 op: convert_binary_op(op)?,
57 right: Box::new(convert_expr_depth(right, depth, scope)?),
58 })
59}
60
61pub(super) fn convert_unary(
69 op: &UnaryOperator,
70 inner: &Expr,
71 depth: &mut usize,
72 scope: &ColumnScope<'_>,
73) -> Result<SqlExpr> {
74 if matches!(op, UnaryOperator::Minus)
75 && let Expr::Value(v) = inner
76 && let Value::Number(n, _) = &v.value
77 {
78 return match format!("-{n}").parse::<i64>() {
79 Ok(i) => Ok(SqlExpr::Literal(SqlValue::Int(i))),
80 Err(_) => Ok(SqlExpr::UnaryOp {
81 op: UnaryOp::Neg,
82 expr: Box::new(convert_expr_depth(inner, depth, scope)?),
83 }),
84 };
85 }
86 Ok(SqlExpr::UnaryOp {
87 op: convert_unary_op(op)?,
88 expr: Box::new(convert_expr_depth(inner, depth, scope)?),
89 })
90}
91
92pub(super) fn convert_any_op(
97 left: &Expr,
98 compare_op: &BinaryOperator,
99 right: &Expr,
100 depth: &mut usize,
101 scope: &ColumnScope<'_>,
102) -> Result<SqlExpr> {
103 if !matches!(compare_op, BinaryOperator::Eq) {
106 return Err(SqlError::Unsupported {
107 detail: "ANY operator with non-equality comparison is not supported".into(),
108 });
109 }
110 let left_expr = convert_expr_depth(left, depth, scope)?;
111 let right_expr = convert_expr_depth(right, depth, scope)?;
112 let list = match right_expr {
115 SqlExpr::ArrayLiteral(elems) => elems,
116 other => vec![other],
117 };
118 Ok(SqlExpr::InList {
119 expr: Box::new(left_expr),
120 list,
121 negated: false,
122 })
123}
124
125#[cfg(test)]
126mod tests {
127 use crate::resolver::expr::convert::entry::tests::{select_expr_lowered, where_sql_expr};
128 use crate::types::*;
129
130 #[test]
135 fn double_quoted_col_eq_single_quoted_literal() {
136 let expr = where_sql_expr(r#"SELECT * FROM t WHERE "col" = 'literal'"#);
137 match expr {
138 SqlExpr::BinaryOp { left, right, .. } => {
139 assert!(
140 matches!(*left, SqlExpr::Column { ref name, .. } if name == "col"),
141 "left should be Column(col), got {left:?}"
142 );
143 assert!(
144 matches!(*right, SqlExpr::Literal(SqlValue::String(ref s)) if s == "literal"),
145 "right should be Literal(String(\"literal\")), got {right:?}"
146 );
147 }
148 other => panic!("expected BinaryOp, got {other:?}"),
149 }
150 }
151
152 #[test]
155 fn double_quoted_col_eq_double_quoted_col() {
156 let expr = where_sql_expr(r#"SELECT * FROM t WHERE "colA" = "colB""#);
157 match expr {
158 SqlExpr::BinaryOp { left, right, .. } => {
159 assert!(
160 matches!(*left, SqlExpr::Column { ref name, .. } if name == "colA"),
161 "left should be Column(colA), got {left:?}"
162 );
163 assert!(
164 matches!(*right, SqlExpr::Column { ref name, .. } if name == "colB"),
165 "right should be Column(colB), got {right:?}"
166 );
167 }
168 other => panic!("expected BinaryOp, got {other:?}"),
169 }
170 }
171
172 fn assert_json_fn(sql: &str, expected_fn: &str) {
175 let expr = select_expr_lowered(sql);
176 match expr {
177 SqlExpr::Function { name, args, .. } => {
178 assert_eq!(name, expected_fn, "wrong function name");
179 assert_eq!(args.len(), 2, "expected 2 args");
180 }
181 other => panic!("expected Function, got {other:?}"),
182 }
183 }
184
185 #[test]
186 fn arrow_lowers_to_pg_json_get() {
187 assert_json_fn("SELECT data->'key' FROM t", "pg_json_get");
188 }
189
190 #[test]
191 fn long_arrow_lowers_to_pg_json_get_text() {
192 assert_json_fn("SELECT data->>'key' FROM t", "pg_json_get_text");
193 }
194
195 #[test]
196 fn hash_arrow_lowers_to_pg_json_path_get() {
197 assert_json_fn("SELECT data#>'{a,b}' FROM t", "pg_json_path_get");
198 }
199
200 #[test]
201 fn hash_long_arrow_lowers_to_pg_json_path_get_text() {
202 assert_json_fn("SELECT data#>>'{a,b}' FROM t", "pg_json_path_get_text");
203 }
204
205 #[test]
206 fn at_arrow_lowers_to_pg_json_contains() {
207 assert_json_fn("SELECT data @> '{\"a\":1}' FROM t", "pg_json_contains");
208 }
209
210 #[test]
211 fn arrow_at_lowers_to_pg_json_contained_by() {
212 assert_json_fn("SELECT '{\"a\":1}' <@ data FROM t", "pg_json_contained_by");
213 }
214
215 #[test]
216 fn question_lowers_to_pg_json_has_key() {
217 assert_json_fn("SELECT data ? 'key' FROM t", "pg_json_has_key");
218 }
219
220 #[test]
221 fn question_and_lowers_to_pg_json_has_all_keys() {
222 assert_json_fn(
223 "SELECT data ?& ARRAY['a','b'] FROM t",
224 "pg_json_has_all_keys",
225 );
226 }
227
228 #[test]
229 fn question_pipe_lowers_to_pg_json_has_any_key() {
230 assert_json_fn(
231 "SELECT data ?| ARRAY['a','b'] FROM t",
232 "pg_json_has_any_key",
233 );
234 }
235
236 #[test]
237 fn chained_arrow_lowers_nested() {
238 let expr = select_expr_lowered("SELECT data->'a'->'b' FROM t");
240 match expr {
241 SqlExpr::Function { name, ref args, .. } => {
242 assert_eq!(name, "pg_json_get", "outer fn should be pg_json_get");
243 match &args[0] {
244 SqlExpr::Function {
245 name: inner_name, ..
246 } => {
247 assert_eq!(inner_name, "pg_json_get", "inner fn should be pg_json_get");
248 }
249 other => panic!("expected inner pg_json_get, got {other:?}"),
250 }
251 }
252 other => panic!("expected outer pg_json_get, got {other:?}"),
253 }
254 }
255
256 fn where_fn(sql: &str) -> SqlExpr {
259 where_sql_expr(sql)
260 }
261
262 #[test]
263 fn at_at_lowers_to_pg_fts_match() {
264 let expr = where_fn("SELECT * FROM t WHERE body @@ to_tsquery('rust & lang')");
266 match expr {
267 SqlExpr::Function {
268 ref name, ref args, ..
269 } => {
270 assert_eq!(
271 name, "pg_fts_match",
272 "operator @@ should lower to pg_fts_match"
273 );
274 assert_eq!(args.len(), 2, "expected 2 args");
275 match &args[1] {
276 SqlExpr::Function { name: inner, .. } => {
277 assert_eq!(inner, "pg_to_tsquery");
278 }
279 other => panic!("expected pg_to_tsquery as right arg, got {other:?}"),
280 }
281 }
282 other => panic!("expected pg_fts_match Function, got {other:?}"),
283 }
284 }
285
286 #[test]
287 fn at_at_with_plainto_tsquery() {
288 let expr = where_fn("SELECT * FROM t WHERE body @@ plainto_tsquery('rust lang')");
290 match expr {
291 SqlExpr::Function {
292 ref name, ref args, ..
293 } => {
294 assert_eq!(name, "pg_fts_match");
295 match &args[1] {
296 SqlExpr::Function { name: inner, .. } => {
297 assert_eq!(inner, "pg_plainto_tsquery");
298 }
299 other => panic!("expected pg_plainto_tsquery, got {other:?}"),
300 }
301 }
302 other => panic!("expected pg_fts_match, got {other:?}"),
303 }
304 }
305}