Merge pull request #25 from nickolay/master

Support "searched" CASE expressions
This commit is contained in:
Andy Grove 2018-10-16 20:03:34 -06:00 committed by GitHub
commit 70a3ae93c8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 90 additions and 1 deletions

View file

@ -11,7 +11,7 @@ impl Dialect for GenericSqlDialect {
STORED, CSV, PARQUET, LOCATION, WITH, WITHOUT, HEADER, ROW, // SQL types
CHAR, CHARACTER, VARYING, LARGE, OBJECT, VARCHAR, CLOB, BINARY, VARBINARY, BLOB, FLOAT,
REAL, DOUBLE, PRECISION, INT, INTEGER, SMALLINT, BIGINT, NUMERIC, DECIMAL, DEC,
BOOLEAN, DATE, TIME, TIMESTAMP,
BOOLEAN, DATE, TIME, TIMESTAMP, CASE, WHEN, THEN, ELSE, END,
];
}

View file

@ -14,6 +14,7 @@ impl Dialect for PostgreSqlDialect {
DOUBLE, PRECISION, INT, INTEGER, SMALLINT, BIGINT, NUMERIC, DECIMAL, DEC, BOOLEAN,
DATE, TIME, TIMESTAMP, VALUES, DEFAULT, ZONE, REGCLASS, TEXT, BYTEA, TRUE, FALSE, COPY,
STDIN, PRIMARY, KEY, UNIQUE, UUID, ADD, CONSTRAINT, FOREIGN, REFERENCES,
CASE, WHEN, THEN, ELSE, END,
];
}

View file

@ -62,6 +62,13 @@ pub enum ASTNode {
SQLValue(Value),
/// Scalar function call e.g. `LEFT(foo, 5)`
SQLFunction { id: String, args: Vec<ASTNode> },
/// CASE [<operand>] WHEN <condition> THEN <result> ... [ELSE <result>] END
SQLCase {
// TODO: support optional operand for "simple case"
conditions: Vec<ASTNode>,
results: Vec<ASTNode>,
else_result: Option<Box<ASTNode>>,
},
/// SELECT
SQLSelect {
/// projection expressions
@ -160,6 +167,19 @@ impl ToString for ASTNode {
.collect::<Vec<String>>()
.join(", ")
),
ASTNode::SQLCase { conditions, results, else_result } => {
let mut s = format!(
"CASE {}",
conditions.iter().zip(results)
.map(|(c, r)| format!("WHEN {} THEN {}", c.to_string(), r.to_string()))
.collect::<Vec<String>>()
.join(" ")
);
if let Some(else_result) = else_result {
s += &format!(" ELSE {}", else_result.to_string())
}
s + " END"
},
ASTNode::SQLSelect {
projection,
relation,

View file

@ -109,6 +109,9 @@ impl Parser {
"NULL" => {
self.prev_token();
self.parse_sql_value()
},
"CASE" => {
self.parse_case_expression()
}
_ => return parser_err!(format!("No prefix parser for keyword {}", k)),
},
@ -196,6 +199,40 @@ impl Parser {
}
}
pub fn parse_case_expression(&mut self) -> Result<ASTNode, ParserError> {
if self.parse_keywords(vec!["WHEN"]) {
let mut conditions = vec![];
let mut results = vec![];
let mut else_result = None;
loop {
conditions.push(self.parse_expr(0)?);
self.consume_token(&Token::Keyword("THEN".to_string()))?;
results.push(self.parse_expr(0)?);
if self.parse_keywords(vec!["ELSE"]) {
else_result = Some(Box::new(self.parse_expr(0)?));
if self.parse_keywords(vec!["END"]) {
break
} else {
return parser_err!("Expecting END after a CASE..ELSE");
}
}
if self.parse_keywords(vec!["END"]) {
break
}
self.consume_token(&Token::Keyword("WHEN".to_string()))?;
}
Ok(ASTNode::SQLCase {
conditions,
results,
else_result
})
} else {
// TODO: implement "simple" case
// https://jakewheat.github.io/sql-overview/sql-2011-foundation-grammar.html#simple-case
parser_err!("Simple case not implemented")
}
}
/// Parse a SQL CAST function e.g. `CAST(expr AS FLOAT)`
pub fn parse_cast_expression(&mut self) -> Result<ASTNode, ParserError> {
self.consume_token(&Token::LParen)?;

View file

@ -396,6 +396,37 @@ fn parse_parens() {
, ast);
}
#[test]
fn parse_case_expression() {
let sql = "SELECT CASE WHEN bar IS NULL THEN 'null' WHEN bar = 0 THEN '=0' WHEN bar >= 0 THEN '>=0' ELSE '<0' END FROM foo";
let ast = parse_sql(&sql);
assert_eq!(sql, ast.to_string());
use self::ASTNode::*;
use self::SQLOperator::*;
match ast {
ASTNode::SQLSelect { projection, .. } => {
assert_eq!(1, projection.len());
assert_eq!(
SQLCase {
conditions: vec![
SQLIsNull(Box::new(SQLIdentifier("bar".to_string()))),
SQLBinaryExpr { left: Box::new(SQLIdentifier("bar".to_string())),
op: Eq, right: Box::new(SQLValue(Value::Long(0))) },
SQLBinaryExpr { left: Box::new(SQLIdentifier("bar".to_string())),
op: GtEq, right: Box::new(SQLValue(Value::Long(0))) }
],
results: vec![SQLValue(Value::SingleQuotedString("null".to_string())),
SQLValue(Value::SingleQuotedString("=0".to_string())),
SQLValue(Value::SingleQuotedString(">=0".to_string()))],
else_result: Some(Box::new(SQLValue(Value::SingleQuotedString("<0".to_string()))))
},
projection[0]
);
}
_ => assert!(false),
}
}
fn parse_sql(sql: &str) -> ASTNode {
let dialect = GenericSqlDialect {};
let mut tokenizer = Tokenizer::new(&dialect,&sql, );