Merge pull request #87 from nickolay/pr/join-refactor

Refactor parse_joins
This commit is contained in:
Nikhil Benesch 2019-06-03 00:55:23 -04:00 committed by GitHub
commit 85ba953ea1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 94 additions and 94 deletions

View file

@ -271,14 +271,14 @@ impl ToString for Join {
} }
fn suffix(constraint: &JoinConstraint) -> String { fn suffix(constraint: &JoinConstraint) -> String {
match constraint { match constraint {
JoinConstraint::On(expr) => format!("ON {}", expr.to_string()), JoinConstraint::On(expr) => format!(" ON {}", expr.to_string()),
JoinConstraint::Using(attrs) => format!("USING({})", attrs.join(", ")), JoinConstraint::Using(attrs) => format!(" USING({})", attrs.join(", ")),
_ => "".to_string(), _ => "".to_string(),
} }
} }
match &self.join_operator { match &self.join_operator {
JoinOperator::Inner(constraint) => format!( JoinOperator::Inner(constraint) => format!(
" {}JOIN {} {}", " {}JOIN {}{}",
prefix(constraint), prefix(constraint),
self.relation.to_string(), self.relation.to_string(),
suffix(constraint) suffix(constraint)
@ -286,19 +286,19 @@ impl ToString for Join {
JoinOperator::Cross => format!(" CROSS JOIN {}", self.relation.to_string()), JoinOperator::Cross => format!(" CROSS JOIN {}", self.relation.to_string()),
JoinOperator::Implicit => format!(", {}", self.relation.to_string()), JoinOperator::Implicit => format!(", {}", self.relation.to_string()),
JoinOperator::LeftOuter(constraint) => format!( JoinOperator::LeftOuter(constraint) => format!(
" {}LEFT JOIN {} {}", " {}LEFT JOIN {}{}",
prefix(constraint), prefix(constraint),
self.relation.to_string(), self.relation.to_string(),
suffix(constraint) suffix(constraint)
), ),
JoinOperator::RightOuter(constraint) => format!( JoinOperator::RightOuter(constraint) => format!(
" {}RIGHT JOIN {} {}", " {}RIGHT JOIN {}{}",
prefix(constraint), prefix(constraint),
self.relation.to_string(), self.relation.to_string(),
suffix(constraint) suffix(constraint)
), ),
JoinOperator::FullOuter(constraint) => format!( JoinOperator::FullOuter(constraint) => format!(
" {}FULL JOIN {} {}", " {}FULL JOIN {}{}",
prefix(constraint), prefix(constraint),
self.relation.to_string(), self.relation.to_string(),
suffix(constraint) suffix(constraint)

View file

@ -702,14 +702,10 @@ impl Parser {
/// Consume the next token if it matches the expected token, otherwise return false /// Consume the next token if it matches the expected token, otherwise return false
#[must_use] #[must_use]
pub fn consume_token(&mut self, expected: &Token) -> bool { pub fn consume_token(&mut self, expected: &Token) -> bool {
match self.peek_token() { match &self.peek_token() {
Some(ref t) => { Some(t) if *t == *expected => {
if *t == *expected { self.next_token();
self.next_token(); true
true
} else {
false
}
} }
_ => false, _ => false,
} }
@ -1503,90 +1499,62 @@ impl Parser {
fn parse_joins(&mut self) -> Result<Vec<Join>, ParserError> { fn parse_joins(&mut self) -> Result<Vec<Join>, ParserError> {
let mut joins = vec![]; let mut joins = vec![];
loop { loop {
let natural = match &self.peek_token() { let join = match &self.peek_token() {
Some(Token::Comma) => { Some(Token::Comma) => {
self.next_token(); self.next_token();
let relation = self.parse_table_factor()?; Join {
let join = Join { relation: self.parse_table_factor()?,
relation,
join_operator: JoinOperator::Implicit, join_operator: JoinOperator::Implicit,
}; }
joins.push(join);
continue;
} }
Some(Token::SQLWord(kw)) if kw.keyword == "CROSS" => { Some(Token::SQLWord(kw)) if kw.keyword == "CROSS" => {
self.next_token(); self.next_token();
self.expect_keyword("JOIN")?; self.expect_keyword("JOIN")?;
let relation = self.parse_table_factor()?; Join {
let join = Join { relation: self.parse_table_factor()?,
relation,
join_operator: JoinOperator::Cross, join_operator: JoinOperator::Cross,
}
}
_ => {
let natural = self.parse_keyword("NATURAL");
let peek_keyword = if let Some(Token::SQLWord(kw)) = self.peek_token() {
kw.keyword
} else {
String::default()
}; };
joins.push(join);
continue;
}
Some(Token::SQLWord(kw)) if kw.keyword == "NATURAL" => {
self.next_token();
true
}
Some(_) => false,
None => return Ok(joins),
};
let join = match &self.peek_token() { let join_operator_type = match peek_keyword.as_ref() {
Some(Token::SQLWord(kw)) if kw.keyword == "INNER" => { "INNER" | "JOIN" => {
self.next_token(); let _ = self.parse_keyword("INNER");
self.expect_keyword("JOIN")?; self.expect_keyword("JOIN")?;
JoinOperator::Inner
}
kw @ "LEFT" | kw @ "RIGHT" | kw @ "FULL" => {
let _ = self.next_token();
let _ = self.parse_keyword("OUTER");
self.expect_keyword("JOIN")?;
match kw {
"LEFT" => JoinOperator::LeftOuter,
"RIGHT" => JoinOperator::RightOuter,
"FULL" => JoinOperator::FullOuter,
_ => unreachable!(),
}
}
_ if natural => {
return self.expected("a join type after NATURAL", self.peek_token());
}
_ => break,
};
let relation = self.parse_table_factor()?;
let join_constraint = self.parse_join_constraint(natural)?;
Join { Join {
relation: self.parse_table_factor()?, relation,
join_operator: JoinOperator::Inner(self.parse_join_constraint(natural)?), join_operator: join_operator_type(join_constraint),
} }
} }
Some(Token::SQLWord(kw)) if kw.keyword == "JOIN" => {
self.next_token();
Join {
relation: self.parse_table_factor()?,
join_operator: JoinOperator::Inner(self.parse_join_constraint(natural)?),
}
}
Some(Token::SQLWord(kw)) if kw.keyword == "LEFT" => {
self.next_token();
let _ = self.parse_keyword("OUTER");
self.expect_keyword("JOIN")?;
Join {
relation: self.parse_table_factor()?,
join_operator: JoinOperator::LeftOuter(
self.parse_join_constraint(natural)?,
),
}
}
Some(Token::SQLWord(kw)) if kw.keyword == "RIGHT" => {
self.next_token();
let _ = self.parse_keyword("OUTER");
self.expect_keyword("JOIN")?;
Join {
relation: self.parse_table_factor()?,
join_operator: JoinOperator::RightOuter(
self.parse_join_constraint(natural)?,
),
}
}
Some(Token::SQLWord(kw)) if kw.keyword == "FULL" => {
self.next_token();
let _ = self.parse_keyword("OUTER");
self.expect_keyword("JOIN")?;
Join {
relation: self.parse_table_factor()?,
join_operator: JoinOperator::FullOuter(
self.parse_join_constraint(natural)?,
),
}
}
_ => break,
}; };
joins.push(join); joins.push(join);
} }
Ok(joins) Ok(joins)
} }
@ -1611,10 +1579,9 @@ impl Parser {
let mut expr_list: Vec<ASTNode> = vec![]; let mut expr_list: Vec<ASTNode> = vec![];
loop { loop {
expr_list.push(self.parse_expr()?); expr_list.push(self.parse_expr()?);
match self.peek_token() { if !self.consume_token(&Token::Comma) {
Some(Token::Comma) => self.next_token(), break;
_ => break, }
};
} }
Ok(expr_list) Ok(expr_list)
} }
@ -1649,10 +1616,9 @@ impl Parser {
} }
} }
match self.peek_token() { if !self.consume_token(&Token::Comma) {
Some(Token::Comma) => self.next_token(), break;
_ => break, }
};
} }
Ok(projections) Ok(projections)
} }
@ -1672,10 +1638,7 @@ impl Parser {
}; };
expr_list.push(SQLOrderByExpr { expr, asc }); expr_list.push(SQLOrderByExpr { expr, asc });
if !self.consume_token(&Token::Comma) {
if let Some(Token::Comma) = self.peek_token() {
self.next_token();
} else {
break; break;
} }
} }

View file

@ -1218,6 +1218,43 @@ fn parse_joins_using() {
); );
} }
#[test]
fn parse_natural_join() {
fn natural_join(f: impl Fn(JoinConstraint) -> JoinOperator) -> Join {
Join {
relation: TableFactor::Table {
name: SQLObjectName(vec!["t2".to_string()]),
alias: None,
args: vec![],
with_hints: vec![],
},
join_operator: f(JoinConstraint::Natural),
}
}
assert_eq!(
verified_only_select("SELECT * FROM t1 NATURAL JOIN t2").joins,
vec![natural_join(JoinOperator::Inner)]
);
assert_eq!(
verified_only_select("SELECT * FROM t1 NATURAL LEFT JOIN t2").joins,
vec![natural_join(JoinOperator::LeftOuter)]
);
assert_eq!(
verified_only_select("SELECT * FROM t1 NATURAL RIGHT JOIN t2").joins,
vec![natural_join(JoinOperator::RightOuter)]
);
assert_eq!(
verified_only_select("SELECT * FROM t1 NATURAL FULL JOIN t2").joins,
vec![natural_join(JoinOperator::FullOuter)]
);
let sql = "SELECT * FROM t1 natural";
assert_eq!(
ParserError::ParserError("Expected a join type after NATURAL, found: EOF".to_string()),
parse_sql_statements(sql).unwrap_err(),
);
}
#[test] #[test]
fn parse_complex_join() { fn parse_complex_join() {
let sql = "SELECT c1, c2 FROM t1, t4 JOIN t2 ON t2.c = t1.c LEFT JOIN t3 USING(q, c) WHERE t4.c = t1.c"; let sql = "SELECT c1, c2 FROM t1, t4 JOIN t2 ON t2.c = t1.c LEFT JOIN t3 USING(q, c) WHERE t4.c = t1.c";