1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
use crate::{sql::func, Error, Expr, ToSqlAst};
use sqlparser::ast::{visit_expressions_mut, Expr as SqlExpr};
use std::ops::ControlFlow;
/// Traits for generating SQL for DuckDB with Spatial Extension
pub trait ToDuckSQL {
/// Convert Expression to SQL for DuckDB with Spatial Extension
fn to_ducksql(&self) -> Result<String, Error>;
}
impl ToDuckSQL for Expr {
/// Converts this expression to DuckDB SQL.
/// WARNING: This is an experimental feature with limited tests subject to change!
///
/// # Examples
///
/// ```
/// use cql2::Expr;
/// use cql2::ToDuckSQL;
///
/// let expr = Expr::Bool(true);
/// assert_eq!(expr.to_ducksql().unwrap(), "true");
/// ```
/// ```
/// use cql2::Expr;
/// use cql2::ToDuckSQL;
///
/// let expr: Expr = "s_intersects(geom, POINT(0 0)) and foo >= 1 and bar='baz' and TIMESTAMP('2020-01-01 00:00:00Z') >= BoRk and DATE('2020-01-01') > b and q = 'hello World!') > b".parse().unwrap();
/// assert_eq!(expr.to_ducksql().unwrap(), "st_intersects(geom, st_geomfromtext('POINT(0 0)')) AND foo >= 1 AND bar = 'baz' AND CAST('2020-01-01 00:00:00Z' AS TIMESTAMP WITH TIME ZONE) >= \"BoRk\" AND CAST('2020-01-01' AS DATE) > b AND q = 'hello World!'");
/// ```
/// ```
/// use cql2::Expr;
/// use cql2::ToDuckSQL;
///
/// let expr: Expr = "t_overlaps(interval(a,'2020-01-01T00:00:00Z'),interval('2020-01-01T00:00:00Z','2020-02-01T00:00:00Z'))".parse().unwrap();
/// assert_eq!(expr.to_ducksql().unwrap(), "(a < CAST('2020-02-01T00:00:00Z' AS TIMESTAMP WITH TIME ZONE) AND CAST('2020-01-01T00:00:00Z' AS TIMESTAMP WITH TIME ZONE) < CAST('2020-01-01T00:00:00Z' AS TIMESTAMP WITH TIME ZONE) AND CAST('2020-01-01T00:00:00Z' AS TIMESTAMP WITH TIME ZONE) < CAST('2020-02-01T00:00:00Z' AS TIMESTAMP WITH TIME ZONE))");
/// ```
/// ```
/// use cql2::Expr;
/// use cql2::ToDuckSQL;
///
/// let expr: Expr = "t_overlaps(interval(a,b),interval('2020-01-01T00:00:00Z','2020-02-01T00:00:00Z'))".parse().unwrap();
/// assert_eq!(expr.to_ducksql().unwrap(), "(a < CAST('2020-02-01T00:00:00Z' AS TIMESTAMP WITH TIME ZONE) AND CAST('2020-01-01T00:00:00Z' AS TIMESTAMP WITH TIME ZONE) < b AND b < CAST('2020-02-01T00:00:00Z' AS TIMESTAMP WITH TIME ZONE))");
/// ```
fn to_ducksql(&self) -> Result<String, Error> {
let mut ast = self.to_sql_ast()?;
let _ = visit_expressions_mut(&mut ast, |expr| {
if let SqlExpr::BinaryOp { op, right, left } = expr {
match *op {
sqlparser::ast::BinaryOperator::AtArrow => {
*expr = func("list_has_all", vec![*left.clone(), *right.clone()]);
}
sqlparser::ast::BinaryOperator::ArrowAt => {
*expr = func("list_has_all", vec![*right.clone(), *left.clone()]);
}
sqlparser::ast::BinaryOperator::AtAt => {
*expr = func("list_has_any", vec![*left.clone(), *right.clone()]);
}
_ => {}
}
}
ControlFlow::<()>::Continue(())
});
Ok(ast.to_string())
}
}
#[cfg(test)]
mod tests {
use super::ToDuckSQL;
use crate::Expr;
#[test]
fn test_to_ducksql() {
let expr: Expr = "foo = 1".parse().unwrap();
assert_eq!(expr.to_ducksql().unwrap(), "foo = 1");
}
#[test]
fn test_array_ops() {
let expr: Expr = "a_contains(foo, bar)".parse().unwrap();
assert_eq!(expr.to_ducksql().unwrap(), "list_has_all(foo, bar)");
}
}