Allow plain JOIN without turning it into INNER (#1692)

This commit is contained in:
Michael Victor Zink 2025-01-29 22:15:57 -08:00 committed by GitHub
parent 784605c913
commit 252fdbab82
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 42 additions and 16 deletions

View file

@ -2038,13 +2038,20 @@ impl fmt::Display for Join {
} }
match &self.join_operator { match &self.join_operator {
JoinOperator::Inner(constraint) => write!( JoinOperator::Join(constraint) => write!(
f, f,
" {}JOIN {}{}", " {}JOIN {}{}",
prefix(constraint), prefix(constraint),
self.relation, self.relation,
suffix(constraint) suffix(constraint)
), ),
JoinOperator::Inner(constraint) => write!(
f,
" {}INNER JOIN {}{}",
prefix(constraint),
self.relation,
suffix(constraint)
),
JoinOperator::LeftOuter(constraint) => write!( JoinOperator::LeftOuter(constraint) => write!(
f, f,
" {}LEFT JOIN {}{}", " {}LEFT JOIN {}{}",
@ -2128,6 +2135,7 @@ impl fmt::Display for Join {
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub enum JoinOperator { pub enum JoinOperator {
Join(JoinConstraint),
Inner(JoinConstraint), Inner(JoinConstraint),
LeftOuter(JoinConstraint), LeftOuter(JoinConstraint),
RightOuter(JoinConstraint), RightOuter(JoinConstraint),

View file

@ -2001,6 +2001,7 @@ impl Spanned for Join {
impl Spanned for JoinOperator { impl Spanned for JoinOperator {
fn span(&self) -> Span { fn span(&self) -> Span {
match self { match self {
JoinOperator::Join(join_constraint) => join_constraint.span(),
JoinOperator::Inner(join_constraint) => join_constraint.span(), JoinOperator::Inner(join_constraint) => join_constraint.span(),
JoinOperator::LeftOuter(join_constraint) => join_constraint.span(), JoinOperator::LeftOuter(join_constraint) => join_constraint.span(),
JoinOperator::RightOuter(join_constraint) => join_constraint.span(), JoinOperator::RightOuter(join_constraint) => join_constraint.span(),

View file

@ -11000,9 +11000,13 @@ impl<'a> Parser<'a> {
let join_operator_type = match peek_keyword { let join_operator_type = match peek_keyword {
Keyword::INNER | Keyword::JOIN => { Keyword::INNER | Keyword::JOIN => {
let _ = self.parse_keyword(Keyword::INNER); // [ INNER ] let inner = self.parse_keyword(Keyword::INNER); // [ INNER ]
self.expect_keyword_is(Keyword::JOIN)?; self.expect_keyword_is(Keyword::JOIN)?;
JoinOperator::Inner if inner {
JoinOperator::Inner
} else {
JoinOperator::Join
}
} }
kw @ Keyword::LEFT | kw @ Keyword::RIGHT => { kw @ Keyword::LEFT | kw @ Keyword::RIGHT => {
let _ = self.next_token(); // consume LEFT/RIGHT let _ = self.next_token(); // consume LEFT/RIGHT

View file

@ -403,7 +403,7 @@ pub fn join(relation: TableFactor) -> Join {
Join { Join {
relation, relation,
global: false, global: false,
join_operator: JoinOperator::Inner(JoinConstraint::Natural), join_operator: JoinOperator::Join(JoinConstraint::Natural),
} }
} }

View file

@ -1602,7 +1602,7 @@ fn parse_join_constraint_unnest_alias() {
with_ordinality: false, with_ordinality: false,
}, },
global: false, global: false,
join_operator: JoinOperator::Inner(JoinConstraint::On(Expr::BinaryOp { join_operator: JoinOperator::Join(JoinConstraint::On(Expr::BinaryOp {
left: Box::new(Expr::Identifier("c1".into())), left: Box::new(Expr::Identifier("c1".into())),
op: BinaryOperator::Eq, op: BinaryOperator::Eq,
right: Box::new(Expr::Identifier("c2".into())), right: Box::new(Expr::Identifier("c2".into())),

View file

@ -6457,7 +6457,7 @@ fn parse_implicit_join() {
joins: vec![Join { joins: vec![Join {
relation: table_from_name(ObjectName::from(vec!["t1b".into()])), relation: table_from_name(ObjectName::from(vec!["t1b".into()])),
global: false, global: false,
join_operator: JoinOperator::Inner(JoinConstraint::Natural), join_operator: JoinOperator::Join(JoinConstraint::Natural),
}], }],
}, },
TableWithJoins { TableWithJoins {
@ -6465,7 +6465,7 @@ fn parse_implicit_join() {
joins: vec![Join { joins: vec![Join {
relation: table_from_name(ObjectName::from(vec!["t2b".into()])), relation: table_from_name(ObjectName::from(vec!["t2b".into()])),
global: false, global: false,
join_operator: JoinOperator::Inner(JoinConstraint::Natural), join_operator: JoinOperator::Join(JoinConstraint::Natural),
}], }],
}, },
], ],
@ -6523,7 +6523,7 @@ fn parse_joins_on() {
"t2", "t2",
table_alias("foo"), table_alias("foo"),
false, false,
JoinOperator::Inner, JoinOperator::Join,
)] )]
); );
one_statement_parses_to( one_statement_parses_to(
@ -6533,7 +6533,7 @@ fn parse_joins_on() {
// Test parsing of different join operators // Test parsing of different join operators
assert_eq!( assert_eq!(
only(&verified_only_select("SELECT * FROM t1 JOIN t2 ON c1 = c2").from).joins, only(&verified_only_select("SELECT * FROM t1 JOIN t2 ON c1 = c2").from).joins,
vec![join_with_constraint("t2", None, false, JoinOperator::Inner)] vec![join_with_constraint("t2", None, false, JoinOperator::Join)]
); );
assert_eq!( assert_eq!(
only(&verified_only_select("SELECT * FROM t1 LEFT JOIN t2 ON c1 = c2").from).joins, only(&verified_only_select("SELECT * FROM t1 LEFT JOIN t2 ON c1 = c2").from).joins,
@ -6650,7 +6650,7 @@ fn parse_joins_using() {
vec![join_with_constraint( vec![join_with_constraint(
"t2", "t2",
table_alias("foo"), table_alias("foo"),
JoinOperator::Inner, JoinOperator::Join,
)] )]
); );
one_statement_parses_to( one_statement_parses_to(
@ -6660,6 +6660,10 @@ fn parse_joins_using() {
// Test parsing of different join operators // Test parsing of different join operators
assert_eq!( assert_eq!(
only(&verified_only_select("SELECT * FROM t1 JOIN t2 USING(c1)").from).joins, only(&verified_only_select("SELECT * FROM t1 JOIN t2 USING(c1)").from).joins,
vec![join_with_constraint("t2", None, JoinOperator::Join)]
);
assert_eq!(
only(&verified_only_select("SELECT * FROM t1 INNER JOIN t2 USING(c1)").from).joins,
vec![join_with_constraint("t2", None, JoinOperator::Inner)] vec![join_with_constraint("t2", None, JoinOperator::Inner)]
); );
assert_eq!( assert_eq!(
@ -6722,9 +6726,14 @@ fn parse_natural_join() {
} }
} }
// if not specified, inner join as default // unspecified join
assert_eq!( assert_eq!(
only(&verified_only_select("SELECT * FROM t1 NATURAL JOIN t2").from).joins, only(&verified_only_select("SELECT * FROM t1 NATURAL JOIN t2").from).joins,
vec![natural_join(JoinOperator::Join, None)]
);
// inner join explicitly
assert_eq!(
only(&verified_only_select("SELECT * FROM t1 NATURAL INNER JOIN t2").from).joins,
vec![natural_join(JoinOperator::Inner, None)] vec![natural_join(JoinOperator::Inner, None)]
); );
// left join explicitly // left join explicitly
@ -6748,7 +6757,7 @@ fn parse_natural_join() {
// natural join another table with alias // natural join another table with alias
assert_eq!( assert_eq!(
only(&verified_only_select("SELECT * FROM t1 NATURAL JOIN t2 AS t3").from).joins, only(&verified_only_select("SELECT * FROM t1 NATURAL JOIN t2 AS t3").from).joins,
vec![natural_join(JoinOperator::Inner, table_alias("t3"))] vec![natural_join(JoinOperator::Join, table_alias("t3"))]
); );
let sql = "SELECT * FROM t1 natural"; let sql = "SELECT * FROM t1 natural";
@ -6816,8 +6825,12 @@ fn parse_join_nesting() {
#[test] #[test]
fn parse_join_syntax_variants() { fn parse_join_syntax_variants() {
one_statement_parses_to( one_statement_parses_to(
"SELECT c1 FROM t1 INNER JOIN t2 USING(c1)",
"SELECT c1 FROM t1 JOIN t2 USING(c1)", "SELECT c1 FROM t1 JOIN t2 USING(c1)",
"SELECT c1 FROM t1 JOIN t2 USING(c1)",
);
one_statement_parses_to(
"SELECT c1 FROM t1 INNER JOIN t2 USING(c1)",
"SELECT c1 FROM t1 INNER JOIN t2 USING(c1)",
); );
one_statement_parses_to( one_statement_parses_to(
"SELECT c1 FROM t1 LEFT OUTER JOIN t2 USING(c1)", "SELECT c1 FROM t1 LEFT OUTER JOIN t2 USING(c1)",
@ -6981,7 +6994,7 @@ fn parse_derived_tables() {
joins: vec![Join { joins: vec![Join {
relation: table_from_name(ObjectName::from(vec!["t2".into()])), relation: table_from_name(ObjectName::from(vec!["t2".into()])),
global: false, global: false,
join_operator: JoinOperator::Inner(JoinConstraint::Natural), join_operator: JoinOperator::Join(JoinConstraint::Natural),
}], }],
}), }),
alias: None, alias: None,

View file

@ -2055,7 +2055,7 @@ fn parse_update_with_joins() {
index_hints: vec![], index_hints: vec![],
}, },
global: false, global: false,
join_operator: JoinOperator::Inner(JoinConstraint::On(Expr::BinaryOp { join_operator: JoinOperator::Join(JoinConstraint::On(Expr::BinaryOp {
left: Box::new(Expr::CompoundIdentifier(vec![ left: Box::new(Expr::CompoundIdentifier(vec![
Ident::new("o"), Ident::new("o"),
Ident::new("customer_id") Ident::new("customer_id")

View file

@ -4371,7 +4371,7 @@ fn parse_join_constraint_unnest_alias() {
with_ordinality: false, with_ordinality: false,
}, },
global: false, global: false,
join_operator: JoinOperator::Inner(JoinConstraint::On(Expr::BinaryOp { join_operator: JoinOperator::Join(JoinConstraint::On(Expr::BinaryOp {
left: Box::new(Expr::Identifier("c1".into())), left: Box::new(Expr::Identifier("c1".into())),
op: BinaryOperator::Eq, op: BinaryOperator::Eq,
right: Box::new(Expr::Identifier("c2".into())), right: Box::new(Expr::Identifier("c2".into())),