Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement ToSql trait which displays AST as SQL text format #554

Merged
merged 49 commits into from Jun 6, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
e4560d7
add expr_decoder
bearney74 May 15, 2022
0750bd0
add decoder tests for various expr enums
bearney74 May 16, 2022
ad77579
remove comments
bearney74 May 16, 2022
fd3c7fa
Merge branch 'gluesql:main' into ExprDecode
bearney74 May 16, 2022
73b3019
add Aggregate tests
bearney74 May 16, 2022
d46bbee
Merge branch 'gluesql:main' into ExprDecode
bearney74 May 18, 2022
95dbb3b
Merge branch 'gluesql:main' into ExprDecode
bearney74 May 19, 2022
586ec11
Merge branch 'gluesql:main' into ExprDecode
bearney74 May 22, 2022
36a5b6b
Merge branch 'gluesql:main' into ExprDecode
bearney74 May 25, 2022
e4f87e0
Merge branch 'gluesql:main' into ExprDecode
bearney74 May 26, 2022
8ab5f94
add description column for show indexes command
bearney74 May 26, 2022
cde046c
coverage failed
bearney74 May 26, 2022
273847f
use strum screaming camel case
bearney74 May 26, 2022
19589e9
uppercase CASE, AS and EXTRACT
bearney74 May 26, 2022
fc4d7df
remove necessary Display from enum
bearney74 May 26, 2022
6709b29
Merge branch 'gluesql:main' into ExprDecode
bearney74 May 26, 2022
890e58c
Int to INT
bearney74 May 26, 2022
9e6c413
Merge branch 'ExprDecode' of github.com:earney/gluesql into ExprDecode
bearney74 May 26, 2022
ddba6ef
Int to INT
bearney74 May 26, 2022
f14f6df
Merge branch 'main' into ExprDecode
bearney74 May 28, 2022
768db2b
fix merge issues
bearney74 May 28, 2022
b74ad34
Merge branch 'gluesql:main' into ExprDecode
bearney74 May 29, 2022
dffcf30
Merge branch 'gluesql:main' into ExprDecode
bearney74 May 30, 2022
54ecbe6
add stub statements for expression decoder
bearney74 May 30, 2022
ac0d99a
remove _ => statement
bearney74 May 30, 2022
bc956f4
Merge branch 'gluesql:main' into ExprDecode
bearney74 May 31, 2022
549529a
Merge branch 'gluesql:main' into ExprDecode
bearney74 May 31, 2022
2ca46f0
merge hexstring code
bearney74 May 31, 2022
555abc3
use join function
bearney74 Jun 1, 2022
32cc07f
expr_decode to ToSql
bearney74 Jun 1, 2022
49e8aec
implement ToSql
bearney74 Jun 2, 2022
3b25e6f
move unary and binary operators to use ToSql
bearney74 Jun 2, 2022
4229c93
Merge branch 'gluesql:main' into ExprDecode
bearney74 Jun 2, 2022
58991f4
fmt
bearney74 Jun 2, 2022
056358e
fmt
bearney74 Jun 2, 2022
4bd52cb
fix payload.rs
bearney74 Jun 3, 2022
ed31677
add ToSql module to js
bearney74 Jun 3, 2022
84778f1
make fmt happy
bearney74 Jun 3, 2022
c6fe845
make changes requested by reviewers
bearney74 Jun 3, 2022
8c8bb91
Merge branch 'gluesql:main' into ExprDecode
bearney74 Jun 4, 2022
758509c
make changes requested by reviewers
bearney74 Jun 6, 2022
94e94c6
fix test
bearney74 Jun 6, 2022
dd5da90
make fmt happy
bearney74 Jun 6, 2022
adf2db1
revert show indexes portion of PR
bearney74 Jun 6, 2022
bcbfb7a
add unary/binary op test cases, make code adjustments
bearney74 Jun 6, 2022
382af28
fix module import
bearney74 Jun 6, 2022
6ee365b
Merge branch 'gluesql:main' into ExprDecode
bearney74 Jun 6, 2022
52bbf44
merge with main
bearney74 Jun 6, 2022
043896b
fix nested string; add nested test
bearney74 Jun 6, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
expr_decode to ToSql
  • Loading branch information
bearney74 committed Jun 1, 2022
commit 32cc07fc314520b3128e37877cbbdab1fd0c9466
305 changes: 304 additions & 1 deletion core/src/ast/expr.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
use {
super::{
Aggregate, AstLiteral, BinaryOperator, DataType, DateTimeField, Function, Query,
UnaryOperator,
UnaryOperator, CountArgExpr,
},
serde::{Deserialize, Serialize},
};

use crate::ast::ToSql;

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Expr {
Identifier(String),
Expand Down Expand Up @@ -61,3 +63,304 @@ pub enum Expr {
else_result: Option<Box<Expr>>,
},
}

impl ToSql for Expr {
fn to_sql(&self) -> String {
match self {
Expr::Identifier(s) => s.to_string(),
Expr::BinaryOp { left, op, right } => {
format!("{:} {:} {:}", &*left.to_sql(), op, &*right.to_sql())
}
Expr::CompoundIdentifier(idents) => idents.join("."),
Expr::IsNull(s) => format!("{:} IS NULL", s.to_sql()),
Expr::IsNotNull(s) => format!("{:} IS NOT NULL", s.to_sql()),
Expr::InList {
expr,
list,
negated,
} => {
let mut s: String = "".to_string();

for item in list {
if !s.is_empty() {
s += ",";
}
s += &item.to_sql();
}

match negated {
true => format!("{:} NOT IN ({:})", expr.to_sql(), s),
false => format!("{:} IN ({:})", expr.to_sql(), s),
}
}
Expr::Between {
expr,
negated,
low,
high,
} => match negated {
true => format!(
"{:} NOT BETWEEN {:} AND {:}",
&*expr.to_sql(),
&*low.to_sql(),
&*high.to_sql()
),

false => format!(
"{:} BETWEEN {:} AND {:}",
&*expr.to_sql(),
&*low.to_sql(),
&*high.to_sql()
),
},
Expr::UnaryOp { op, expr } => format!("{:}{:}", op, &*expr.to_sql()),
Expr::Cast { expr, data_type } => {
format!("CAST({:} AS {:})", &*expr.to_sql(), data_type)
}
Expr::Extract { field, expr } => {
format!("EXTRACT({:} FROM \"{:}\")", field, &*expr.to_sql())
}
Expr::Nested(expr) => format!("todo:Nested({:})", &*expr.to_sql()),
Expr::Literal(s) => match s {
AstLiteral::Boolean(b) => format!("{:}", b),
AstLiteral::Number(d) => format!("{:}", d),
AstLiteral::QuotedString(qs) => format!("\"{:}\"", qs),
AstLiteral::HexString(hs) => format!("\"{:}\"", hs),
AstLiteral::Null => "Null".to_string(),
AstLiteral::Interval { .. } => "Interval not implemented yet..".to_string(),
},
Expr::TypedString { data_type, value } => format!("{:}(\"{:}\")", data_type, value),
Expr::Case {
operand,
when_then,
else_result,
} => {
let mut str = match operand {
Some(s) => format!("CASE {:}", s.to_sql()),
None => "CASE ".to_string(),
};
for (_when, _then) in when_then {
str += format!("\nWHEN {:} THEN {:}", _when.to_sql(), _then.to_sql()).as_str();
}

match else_result {
Some(s) => str += format!("\nELSE {:}", s.to_sql()).as_str(),
None => str += "", // no operation?
};
str + "\nEND"
}
Expr::Aggregate(a) => match &**a {
Aggregate::Count(c) => match c {
CountArgExpr::Expr(e) => format!("Count({:})", e.to_sql()),
CountArgExpr::Wildcard => "Count(*)".to_string(),
},
Aggregate::Sum(e) => format!("Sum({:})", e.to_sql()),
Aggregate::Max(e) => format!("Max({:})", e.to_sql()),
Aggregate::Min(e) => format!("Min({:})", e.to_sql()),
Aggregate::Avg(e) => format!("Avg({:})", e.to_sql()),
},
Expr::Function(f) => {
format!("{:}(todo:args)", f)
}
// todo's... these require enum query..
Expr::InSubquery {
expr: _,
subquery: _,
negated: _,
} => "InSubquery(..)".to_string(),
Expr::Exists(_q) => "Exists(..)".to_string(),
Expr::Subquery(_q) => "Subquery(..)".to_string(),
}
}
}

#[cfg(test)]
mod tests {

use crate::ast::{
expr_decoder::decode, Aggregate, AstLiteral, BinaryOperator, CountArgExpr, DataType,
DateTimeField, Expr, Function, UnaryOperator,
};
use bigdecimal::BigDecimal;
use std::str::FromStr;

#[test]
fn basic_decoder() {
assert_eq!(
"id".to_string(),
decode(&Expr::Identifier("id".to_string()))
);

assert_eq!(
"id + num",
decode(&Expr::BinaryOp {
left: Box::new(Expr::Identifier("id".to_string())),
op: BinaryOperator::Plus,
right: Box::new(Expr::Identifier("num".to_string()))
})
);

assert_eq!(
"-id",
decode(&Expr::UnaryOp {
op: UnaryOperator::Minus,
expr: Box::new(Expr::Identifier("id".to_string()))
})
);

assert_eq!(
"id.name.first",
decode(&Expr::CompoundIdentifier(vec![
"id".to_string(),
"name".to_string(),
"first".to_string()
]))
);

let id_expr: Box<Expr> = Box::new(Expr::Identifier("id".to_string()));
assert_eq!("id IS NULL", decode(&Expr::IsNull(id_expr)));

let id_expr: Box<Expr> = Box::new(Expr::Identifier("id".to_string()));
assert_eq!("id IS NOT NULL", decode(&Expr::IsNotNull(id_expr)));

assert_eq!(
"CAST(1.0 AS INT)",
decode(&Expr::Cast {
expr: Box::new(Expr::Literal(AstLiteral::Number(
BigDecimal::from_str("1.0").unwrap()
))),
data_type: DataType::Int
})
);

assert_eq!(
r#"INT("1")"#,
decode(&Expr::TypedString {
data_type: DataType::Int,
value: "1".to_string()
})
);

assert_eq!(
r#"EXTRACT(MINUTE FROM "2022-05-05 01:02:03")"#,
decode(&Expr::Extract {
field: DateTimeField::Minute,
expr: Box::new(Expr::Identifier("2022-05-05 01:02:03".to_string()))
})
);

assert_eq!(
"id BETWEEN low AND high",
decode(&Expr::Between {
expr: Box::new(Expr::Identifier("id".to_string())),
negated: false,
low: Box::new(Expr::Identifier("low".to_string())),
high: Box::new(Expr::Identifier("high".to_string()))
})
);

assert_eq!(
"id NOT BETWEEN low AND high",
decode(&Expr::Between {
expr: Box::new(Expr::Identifier("id".to_string())),
negated: true,
low: Box::new(Expr::Identifier("low".to_string())),
high: Box::new(Expr::Identifier("high".to_string()))
})
);

assert_eq!(
r#"id IN ("a","b","c")"#,
decode(&Expr::InList {
expr: Box::new(Expr::Identifier("id".to_string())),
list: vec![
Expr::Literal(AstLiteral::QuotedString("a".to_string())),
Expr::Literal(AstLiteral::QuotedString("b".to_string())),
Expr::Literal(AstLiteral::QuotedString("c".to_string()))
],
negated: false
})
);

assert_eq!(
r#"id NOT IN ("a","b","c")"#,
decode(&Expr::InList {
expr: Box::new(Expr::Identifier("id".to_string())),
list: vec![
Expr::Literal(AstLiteral::QuotedString("a".to_string())),
Expr::Literal(AstLiteral::QuotedString("b".to_string())),
Expr::Literal(AstLiteral::QuotedString("c".to_string()))
],
negated: true
})
);

assert_eq!(
"CASE id\nWHEN 1 THEN \"a\"\nWHEN 2 THEN \"b\"\nELSE \"c\"\nEND",
decode(&Expr::Case {
operand: Some(Box::new(Expr::Identifier("id".to_string()))),
when_then: vec![
(
Expr::Literal(AstLiteral::Number(BigDecimal::from_str("1").unwrap())),
Expr::Literal(AstLiteral::QuotedString("a".to_string()))
),
(
Expr::Literal(AstLiteral::Number(BigDecimal::from_str("2").unwrap())),
Expr::Literal(AstLiteral::QuotedString("b".to_string()))
)
],
else_result: Some(Box::new(Expr::Literal(AstLiteral::QuotedString(
"c".to_string()
))))
})
);

// todo..
assert_eq!(
"SIGN(todo:args)",
decode(&Expr::Function(Box::new(Function::Sign(Expr::Literal(
AstLiteral::Number(BigDecimal::from_str("1.0").unwrap())
)))))
);

// aggregate max
assert_eq!(
"Max(id)",
decode(&Expr::Aggregate(Box::new(Aggregate::Max(
Expr::Identifier("id".to_string())
))))
);

//aggregate count
assert_eq!(
"Count(*)",
decode(&Expr::Aggregate(Box::new(Aggregate::Count(
CountArgExpr::Wildcard
))))
);

//aggregate min
assert_eq!(
"Min(id)",
decode(&Expr::Aggregate(Box::new(Aggregate::Min(
Expr::Identifier("id".to_string())
))))
);

//aggregate sum
assert_eq!(
"Sum(price)",
decode(&Expr::Aggregate(Box::new(Aggregate::Sum(
Expr::Identifier("price".to_string())
))))
);

//aggregate avg
assert_eq!(
"Avg(pay)",
decode(&Expr::Aggregate(Box::new(Aggregate::Avg(
Expr::Identifier("pay".to_string())
))))
);
}
}
4 changes: 4 additions & 0 deletions core/src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ObjectName(pub Vec<String>);

pub trait ToSql {
fn to_sql(&self) -> String;
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Statement {
ShowColumns {
Expand Down