Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions src/dialect/mysql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,10 @@ impl Dialect for MySqlDialect {
fn supports_group_by_with_modifier(&self) -> bool {
true
}

fn supports_left_associative_joins_without_parens(&self) -> bool {
false
}
}

/// `LOCK TABLES`
Expand Down
4 changes: 4 additions & 0 deletions src/dialect/redshift.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,10 @@ impl Dialect for RedshiftSqlDialect {
true
}

fn supports_left_associative_joins_without_parens(&self) -> bool {
false
}

fn supports_select_exclude(&self) -> bool {
true
}
Expand Down
344 changes: 344 additions & 0 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10672,6 +10672,10 @@ fn one_statement_parses_to(sql: &str, canonical: &str) -> Statement {
all_dialects().one_statement_parses_to(sql, canonical)
}

fn non_left_associative_dialects() -> TestedDialects {
all_dialects_where(|d| !d.supports_left_associative_joins_without_parens())
}
Comment on lines +10675 to +10677
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we inline this into the function that uses it?


fn verified_stmt(query: &str) -> Statement {
all_dialects().verified_stmt(query)
}
Expand Down Expand Up @@ -17460,6 +17464,346 @@ fn join_precedence() {
);
}

#[test]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this test fail without the changes in this PR? if not it looks like we can skip it entirely, it looks similar to https://github.com/SatoriCyber/datafusion-sqlparser-rs/blob/00e1d917d439ff40e0c6a10829c491b0735b8818/tests/sqlparser_common.rs#L17445-L17465

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, it fails without this PR.
These are different cases which behave differently. Natural join behaves different than the other joins because it can't have a condition after it.

fn test_nested_join_without_parentheses() {
let query = "SELECT DISTINCT p.product_id FROM orders AS o INNER JOIN customers AS c INNER JOIN products AS p ON p.customer_id = c.customer_id ON c.order_id = o.order_id";
assert_eq!(
only(
non_left_associative_dialects()
.verified_only_select_with_canonical(query, "SELECT DISTINCT p.product_id FROM orders AS o INNER JOIN (customers AS c INNER JOIN products AS p ON p.customer_id = c.customer_id) ON c.order_id = o.order_id")
.from
)
.joins,
vec![Join {
relation: TableFactor::NestedJoin {
table_with_joins: Box::new(TableWithJoins {
relation: TableFactor::Table {
name: ObjectName::from(vec![Ident::new("customers".to_string())]),
alias: table_alias(true, "c"),
args: None,
with_hints: vec![],
version: None,
partitions: vec![],
with_ordinality: false,
json_path: None,
sample: None,
index_hints: vec![],
},
joins: vec![Join {
relation: TableFactor::Table {
name: ObjectName::from(vec![Ident::new("products".to_string())]),
alias: table_alias(true, "p"),
args: None,
with_hints: vec![],
version: None,
partitions: vec![],
with_ordinality: false,
json_path: None,
sample: None,
index_hints: vec![],
},
global: false,
join_operator: JoinOperator::Inner(JoinConstraint::On(Expr::BinaryOp {
left: Box::new(Expr::CompoundIdentifier(vec![
Ident::new("p".to_string()),
Ident::new("customer_id".to_string())
])),
op: BinaryOperator::Eq,
right: Box::new(Expr::CompoundIdentifier(vec![
Ident::new("c".to_string()),
Ident::new("customer_id".to_string())
])),
})),
}]
}),
alias: None
},
global: false,
join_operator: JoinOperator::Inner(JoinConstraint::On(Expr::BinaryOp {
left: Box::new(Expr::CompoundIdentifier(vec![
Ident::new("c".to_string()),
Ident::new("order_id".to_string())
])),
op: BinaryOperator::Eq,
right: Box::new(Expr::CompoundIdentifier(vec![
Ident::new("o".to_string()),
Ident::new("order_id".to_string())
])),
}))
}],
);

let query = "SELECT DISTINCT p.product_id FROM orders AS o JOIN customers AS c JOIN products AS p ON p.customer_id = c.customer_id ON c.order_id = o.order_id";
assert_eq!(
only(
non_left_associative_dialects()
.verified_only_select_with_canonical(query, "SELECT DISTINCT p.product_id FROM orders AS o JOIN (customers AS c JOIN products AS p ON p.customer_id = c.customer_id) ON c.order_id = o.order_id")
.from
)
.joins,
vec![Join {
relation: TableFactor::NestedJoin {
table_with_joins: Box::new(TableWithJoins {
relation: TableFactor::Table {
name: ObjectName::from(vec![Ident::new("customers".to_string())]),
alias: table_alias(true, "c"),
args: None,
with_hints: vec![],
version: None,
partitions: vec![],
with_ordinality: false,
json_path: None,
sample: None,
index_hints: vec![],
},
joins: vec![Join {
relation: TableFactor::Table {
name: ObjectName::from(vec![Ident::new("products".to_string())]),
alias: table_alias(true, "p"),
args: None,
with_hints: vec![],
version: None,
partitions: vec![],
with_ordinality: false,
json_path: None,
sample: None,
index_hints: vec![],
},
global: false,
join_operator: JoinOperator::Join(JoinConstraint::On(Expr::BinaryOp {
left: Box::new(Expr::CompoundIdentifier(vec![
Ident::new("p".to_string()),
Ident::new("customer_id".to_string())
])),
op: BinaryOperator::Eq,
right: Box::new(Expr::CompoundIdentifier(vec![
Ident::new("c".to_string()),
Ident::new("customer_id".to_string())
])),
})),
}]
}),
alias: None
},
global: false,
join_operator: JoinOperator::Join(JoinConstraint::On(Expr::BinaryOp {
left: Box::new(Expr::CompoundIdentifier(vec![
Ident::new("c".to_string()),
Ident::new("order_id".to_string())
])),
op: BinaryOperator::Eq,
right: Box::new(Expr::CompoundIdentifier(vec![
Ident::new("o".to_string()),
Ident::new("order_id".to_string())
])),
}))
}],
);

let query = "SELECT DISTINCT p.product_id FROM orders AS o LEFT JOIN customers AS c LEFT JOIN products AS p ON p.customer_id = c.customer_id ON c.order_id = o.order_id";
assert_eq!(
only(
non_left_associative_dialects()
.verified_only_select_with_canonical(query, "SELECT DISTINCT p.product_id FROM orders AS o LEFT JOIN (customers AS c LEFT JOIN products AS p ON p.customer_id = c.customer_id) ON c.order_id = o.order_id")
.from
)
.joins,
vec![Join {
relation: TableFactor::NestedJoin {
table_with_joins: Box::new(TableWithJoins {
relation: TableFactor::Table {
name: ObjectName::from(vec![Ident::new("customers".to_string())]),
alias: table_alias(true, "c"),
args: None,
with_hints: vec![],
version: None,
partitions: vec![],
with_ordinality: false,
json_path: None,
sample: None,
index_hints: vec![],
},
joins: vec![Join {
relation: TableFactor::Table {
name: ObjectName::from(vec![Ident::new("products".to_string())]),
alias: table_alias(true, "p"),
args: None,
with_hints: vec![],
version: None,
partitions: vec![],
with_ordinality: false,
json_path: None,
sample: None,
index_hints: vec![],
},
global: false,
join_operator: JoinOperator::Left(JoinConstraint::On(Expr::BinaryOp {
left: Box::new(Expr::CompoundIdentifier(vec![
Ident::new("p".to_string()),
Ident::new("customer_id".to_string())
])),
op: BinaryOperator::Eq,
right: Box::new(Expr::CompoundIdentifier(vec![
Ident::new("c".to_string()),
Ident::new("customer_id".to_string())
])),
})),
}]
}),
alias: None
},
global: false,
join_operator: JoinOperator::Left(JoinConstraint::On(Expr::BinaryOp {
left: Box::new(Expr::CompoundIdentifier(vec![
Ident::new("c".to_string()),
Ident::new("order_id".to_string())
])),
op: BinaryOperator::Eq,
right: Box::new(Expr::CompoundIdentifier(vec![
Ident::new("o".to_string()),
Ident::new("order_id".to_string())
])),
}))
}],
);

let query = "SELECT DISTINCT p.product_id FROM orders AS o RIGHT JOIN customers AS c RIGHT JOIN products AS p ON p.customer_id = c.customer_id ON c.order_id = o.order_id";
assert_eq!(
only(
non_left_associative_dialects()
.verified_only_select_with_canonical(query, "SELECT DISTINCT p.product_id FROM orders AS o RIGHT JOIN (customers AS c RIGHT JOIN products AS p ON p.customer_id = c.customer_id) ON c.order_id = o.order_id")
.from
)
.joins,
vec![Join {
relation: TableFactor::NestedJoin {
table_with_joins: Box::new(TableWithJoins {
relation: TableFactor::Table {
name: ObjectName::from(vec![Ident::new("customers".to_string())]),
alias: table_alias(true, "c"),
args: None,
with_hints: vec![],
version: None,
partitions: vec![],
with_ordinality: false,
json_path: None,
sample: None,
index_hints: vec![],
},
joins: vec![Join {
relation: TableFactor::Table {
name: ObjectName::from(vec![Ident::new("products".to_string())]),
alias: table_alias(true, "p"),
args: None,
with_hints: vec![],
version: None,
partitions: vec![],
with_ordinality: false,
json_path: None,
sample: None,
index_hints: vec![],
},
global: false,
join_operator: JoinOperator::Right(JoinConstraint::On(Expr::BinaryOp {
left: Box::new(Expr::CompoundIdentifier(vec![
Ident::new("p".to_string()),
Ident::new("customer_id".to_string())
])),
op: BinaryOperator::Eq,
right: Box::new(Expr::CompoundIdentifier(vec![
Ident::new("c".to_string()),
Ident::new("customer_id".to_string())
])),
})),
}]
}),
alias: None
},
global: false,
join_operator: JoinOperator::Right(JoinConstraint::On(Expr::BinaryOp {
left: Box::new(Expr::CompoundIdentifier(vec![
Ident::new("c".to_string()),
Ident::new("order_id".to_string())
])),
op: BinaryOperator::Eq,
right: Box::new(Expr::CompoundIdentifier(vec![
Ident::new("o".to_string()),
Ident::new("order_id".to_string())
])),
}))
}],
);

let query = "SELECT DISTINCT p.product_id FROM orders AS o FULL JOIN customers AS c FULL JOIN products AS p ON p.customer_id = c.customer_id ON c.order_id = o.order_id";
assert_eq!(
only(
non_left_associative_dialects()
.verified_only_select_with_canonical(query, "SELECT DISTINCT p.product_id FROM orders AS o FULL JOIN (customers AS c FULL JOIN products AS p ON p.customer_id = c.customer_id) ON c.order_id = o.order_id")
.from
)
.joins,
vec![Join {
relation: TableFactor::NestedJoin {
table_with_joins: Box::new(TableWithJoins {
relation: TableFactor::Table {
name: ObjectName::from(vec![Ident::new("customers".to_string())]),
alias: table_alias(true, "c"),
args: None,
with_hints: vec![],
version: None,
partitions: vec![],
with_ordinality: false,
json_path: None,
sample: None,
index_hints: vec![],
},
joins: vec![Join {
relation: TableFactor::Table {
name: ObjectName::from(vec![Ident::new("products".to_string())]),
alias: table_alias(true, "p"),
args: None,
with_hints: vec![],
version: None,
partitions: vec![],
with_ordinality: false,
json_path: None,
sample: None,
index_hints: vec![],
},
global: false,
join_operator: JoinOperator::FullOuter(JoinConstraint::On(
Expr::BinaryOp {
left: Box::new(Expr::CompoundIdentifier(vec![
Ident::new("p".to_string()),
Ident::new("customer_id".to_string())
])),
op: BinaryOperator::Eq,
right: Box::new(Expr::CompoundIdentifier(vec![
Ident::new("c".to_string()),
Ident::new("customer_id".to_string())
])),
}
)),
}]
}),
alias: None
},
global: false,
join_operator: JoinOperator::FullOuter(JoinConstraint::On(Expr::BinaryOp {
left: Box::new(Expr::CompoundIdentifier(vec![
Ident::new("c".to_string()),
Ident::new("order_id".to_string())
])),
op: BinaryOperator::Eq,
right: Box::new(Expr::CompoundIdentifier(vec![
Ident::new("o".to_string()),
Ident::new("order_id".to_string())
])),
}))
}],
);
}

#[test]
fn parse_create_procedure_with_language() {
let sql = r#"CREATE PROCEDURE test_proc LANGUAGE sql AS BEGIN SELECT 1; END"#;
Expand Down
Loading