diff --git a/src/ast/query.rs b/src/ast/query.rs index 4f3d79cdf..41661ec47 100644 --- a/src/ast/query.rs +++ b/src/ast/query.rs @@ -559,6 +559,34 @@ impl fmt::Display for Join { suffix(constraint) ), JoinOperator::CrossJoin => write!(f, " CROSS JOIN {}", self.relation), + JoinOperator::LeftSemi(constraint) => write!( + f, + " {}LEFT SEMI JOIN {}{}", + prefix(constraint), + self.relation, + suffix(constraint) + ), + JoinOperator::RightSemi(constraint) => write!( + f, + " {}RIGHT SEMI JOIN {}{}", + prefix(constraint), + self.relation, + suffix(constraint) + ), + JoinOperator::LeftAnti(constraint) => write!( + f, + " {}LEFT ANTI JOIN {}{}", + prefix(constraint), + self.relation, + suffix(constraint) + ), + JoinOperator::RightAnti(constraint) => write!( + f, + " {}RIGHT ANTI JOIN {}{}", + prefix(constraint), + self.relation, + suffix(constraint) + ), JoinOperator::CrossApply => write!(f, " CROSS APPLY {}", self.relation), JoinOperator::OuterApply => write!(f, " OUTER APPLY {}", self.relation), } @@ -573,6 +601,14 @@ pub enum JoinOperator { RightOuter(JoinConstraint), FullOuter(JoinConstraint), CrossJoin, + /// LEFT SEMI (non-standard) + LeftSemi(JoinConstraint), + /// RIGHT SEMI (non-standard) + RightSemi(JoinConstraint), + /// LEFT ANTI (non-standard) + LeftAnti(JoinConstraint), + /// RIGHT ANTI (non-standard) + RightAnti(JoinConstraint), /// CROSS APPLY (non-standard) CrossApply, /// OUTER APPLY (non-standard) diff --git a/src/keywords.rs b/src/keywords.rs index 47dd21e21..cfd58d5a2 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -76,6 +76,7 @@ define_keywords!( ALTER, ANALYZE, AND, + ANTI, ANY, APPLY, ARCHIVE, @@ -486,6 +487,7 @@ define_keywords!( SEARCH, SECOND, SELECT, + SEMI, SENSITIVE, SEQUENCE, SEQUENCEFILE, diff --git a/src/parser.rs b/src/parser.rs index f91223c2f..67fb62941 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -4746,16 +4746,57 @@ impl<'a> Parser<'a> { self.expect_keyword(Keyword::JOIN)?; JoinOperator::Inner } - kw @ Keyword::LEFT | kw @ Keyword::RIGHT | kw @ Keyword::FULL => { + kw @ Keyword::LEFT | kw @ Keyword::RIGHT => { + let _ = self.next_token(); + let join_type = self.parse_one_of_keywords(&[ + Keyword::OUTER, + Keyword::SEMI, + Keyword::ANTI, + Keyword::JOIN, + ]); + match join_type { + Some(Keyword::OUTER) => { + self.expect_keyword(Keyword::JOIN)?; + match kw { + Keyword::LEFT => JoinOperator::LeftOuter, + Keyword::RIGHT => JoinOperator::RightOuter, + _ => unreachable!(), + } + } + Some(Keyword::SEMI) => { + self.expect_keyword(Keyword::JOIN)?; + match kw { + Keyword::LEFT => JoinOperator::LeftSemi, + Keyword::RIGHT => JoinOperator::RightSemi, + _ => unreachable!(), + } + } + Some(Keyword::ANTI) => { + self.expect_keyword(Keyword::JOIN)?; + match kw { + Keyword::LEFT => JoinOperator::LeftAnti, + Keyword::RIGHT => JoinOperator::RightAnti, + _ => unreachable!(), + } + } + Some(Keyword::JOIN) => match kw { + Keyword::LEFT => JoinOperator::LeftOuter, + Keyword::RIGHT => JoinOperator::RightOuter, + _ => unreachable!(), + }, + _ => { + return Err(ParserError::ParserError(format!( + "expected OUTER, SEMI, ANTI or JOIN after {:?}", + kw + ))) + } + } + } + Keyword::FULL => { let _ = self.next_token(); let _ = self.parse_keyword(Keyword::OUTER); self.expect_keyword(Keyword::JOIN)?; - match kw { - Keyword::LEFT => JoinOperator::LeftOuter, - Keyword::RIGHT => JoinOperator::RightOuter, - Keyword::FULL => JoinOperator::FullOuter, - _ => unreachable!(), - } + JoinOperator::FullOuter } Keyword::OUTER => { return self.expected("LEFT, RIGHT, or FULL", self.peek_token()); diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 83a7a4ca6..3f207da66 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -3686,6 +3686,22 @@ fn parse_joins_on() { only(&verified_only_select("SELECT * FROM t1 RIGHT JOIN t2 ON c1 = c2").from).joins, vec![join_with_constraint("t2", None, JoinOperator::RightOuter)] ); + assert_eq!( + only(&verified_only_select("SELECT * FROM t1 LEFT SEMI JOIN t2 ON c1 = c2").from).joins, + vec![join_with_constraint("t2", None, JoinOperator::LeftSemi)] + ); + assert_eq!( + only(&verified_only_select("SELECT * FROM t1 RIGHT SEMI JOIN t2 ON c1 = c2").from).joins, + vec![join_with_constraint("t2", None, JoinOperator::RightSemi)] + ); + assert_eq!( + only(&verified_only_select("SELECT * FROM t1 LEFT ANTI JOIN t2 ON c1 = c2").from).joins, + vec![join_with_constraint("t2", None, JoinOperator::LeftAnti)] + ); + assert_eq!( + only(&verified_only_select("SELECT * FROM t1 RIGHT ANTI JOIN t2 ON c1 = c2").from).joins, + vec![join_with_constraint("t2", None, JoinOperator::RightAnti)] + ); assert_eq!( only(&verified_only_select("SELECT * FROM t1 FULL JOIN t2 ON c1 = c2").from).joins, vec![join_with_constraint("t2", None, JoinOperator::FullOuter)] @@ -3735,6 +3751,22 @@ fn parse_joins_using() { only(&verified_only_select("SELECT * FROM t1 RIGHT JOIN t2 USING(c1)").from).joins, vec![join_with_constraint("t2", None, JoinOperator::RightOuter)] ); + assert_eq!( + only(&verified_only_select("SELECT * FROM t1 LEFT SEMI JOIN t2 USING(c1)").from).joins, + vec![join_with_constraint("t2", None, JoinOperator::LeftSemi)] + ); + assert_eq!( + only(&verified_only_select("SELECT * FROM t1 RIGHT SEMI JOIN t2 USING(c1)").from).joins, + vec![join_with_constraint("t2", None, JoinOperator::RightSemi)] + ); + assert_eq!( + only(&verified_only_select("SELECT * FROM t1 LEFT ANTI JOIN t2 USING(c1)").from).joins, + vec![join_with_constraint("t2", None, JoinOperator::LeftAnti)] + ); + assert_eq!( + only(&verified_only_select("SELECT * FROM t1 RIGHT ANTI JOIN t2 USING(c1)").from).joins, + vec![join_with_constraint("t2", None, JoinOperator::RightAnti)] + ); assert_eq!( only(&verified_only_select("SELECT * FROM t1 FULL JOIN t2 USING(c1)").from).joins, vec![join_with_constraint("t2", None, JoinOperator::FullOuter)]