mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-07-07 17:04:59 +00:00
Fix join precedence for non-snowflake queries (#1905)
Some checks failed
license / Release Audit Tool (RAT) (push) Has been cancelled
Rust / codestyle (push) Has been cancelled
Rust / lint (push) Has been cancelled
Rust / benchmark-lint (push) Has been cancelled
Rust / compile (push) Has been cancelled
Rust / docs (push) Has been cancelled
Rust / compile-no-std (push) Has been cancelled
Rust / test (beta) (push) Has been cancelled
Rust / test (nightly) (push) Has been cancelled
Rust / test (stable) (push) Has been cancelled
Some checks failed
license / Release Audit Tool (RAT) (push) Has been cancelled
Rust / codestyle (push) Has been cancelled
Rust / lint (push) Has been cancelled
Rust / benchmark-lint (push) Has been cancelled
Rust / compile (push) Has been cancelled
Rust / docs (push) Has been cancelled
Rust / compile-no-std (push) Has been cancelled
Rust / test (beta) (push) Has been cancelled
Rust / test (nightly) (push) Has been cancelled
Rust / test (stable) (push) Has been cancelled
This commit is contained in:
parent
50c605a471
commit
3bc94234df
5 changed files with 64 additions and 1 deletions
|
@ -52,6 +52,10 @@ impl Dialect for GenericDialect {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn supports_left_associative_joins_without_parens(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
fn supports_connect_by(&self) -> bool {
|
fn supports_connect_by(&self) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
|
@ -278,6 +278,34 @@ pub trait Dialect: Debug + Any {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Indicates whether the dialect supports left-associative join parsing
|
||||||
|
/// by default when parentheses are omitted in nested joins.
|
||||||
|
///
|
||||||
|
/// Most dialects (like MySQL or Postgres) assume **left-associative** precedence,
|
||||||
|
/// so a query like:
|
||||||
|
///
|
||||||
|
/// ```sql
|
||||||
|
/// SELECT * FROM t1 NATURAL JOIN t5 INNER JOIN t0 ON ...
|
||||||
|
/// ```
|
||||||
|
/// is interpreted as:
|
||||||
|
/// ```sql
|
||||||
|
/// ((t1 NATURAL JOIN t5) INNER JOIN t0 ON ...)
|
||||||
|
/// ```
|
||||||
|
/// and internally represented as a **flat list** of joins.
|
||||||
|
///
|
||||||
|
/// In contrast, some dialects (e.g. **Snowflake**) assume **right-associative**
|
||||||
|
/// precedence and interpret the same query as:
|
||||||
|
/// ```sql
|
||||||
|
/// (t1 NATURAL JOIN (t5 INNER JOIN t0 ON ...))
|
||||||
|
/// ```
|
||||||
|
/// which results in a **nested join** structure in the AST.
|
||||||
|
///
|
||||||
|
/// If this method returns `false`, the parser must build nested join trees
|
||||||
|
/// even in the absence of parentheses to reflect the correct associativity
|
||||||
|
fn supports_left_associative_joins_without_parens(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true if the dialect supports the `(+)` syntax for OUTER JOIN.
|
/// Returns true if the dialect supports the `(+)` syntax for OUTER JOIN.
|
||||||
fn supports_outer_join_operator(&self) -> bool {
|
fn supports_outer_join_operator(&self) -> bool {
|
||||||
false
|
false
|
||||||
|
|
|
@ -283,6 +283,10 @@ impl Dialect for SnowflakeDialect {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn supports_left_associative_joins_without_parens(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
fn is_reserved_for_identifier(&self, kw: Keyword) -> bool {
|
fn is_reserved_for_identifier(&self, kw: Keyword) -> bool {
|
||||||
// Unreserve some keywords that Snowflake accepts as identifiers
|
// Unreserve some keywords that Snowflake accepts as identifiers
|
||||||
// See: https://docs.snowflake.com/en/sql-reference/reserved-keywords
|
// See: https://docs.snowflake.com/en/sql-reference/reserved-keywords
|
||||||
|
|
|
@ -12495,7 +12495,11 @@ impl<'a> Parser<'a> {
|
||||||
};
|
};
|
||||||
let mut relation = self.parse_table_factor()?;
|
let mut relation = self.parse_table_factor()?;
|
||||||
|
|
||||||
if self.peek_parens_less_nested_join() {
|
if !self
|
||||||
|
.dialect
|
||||||
|
.supports_left_associative_joins_without_parens()
|
||||||
|
&& self.peek_parens_less_nested_join()
|
||||||
|
{
|
||||||
let joins = self.parse_joins()?;
|
let joins = self.parse_joins()?;
|
||||||
relation = TableFactor::NestedJoin {
|
relation = TableFactor::NestedJoin {
|
||||||
table_with_joins: Box::new(TableWithJoins { relation, joins }),
|
table_with_joins: Box::new(TableWithJoins { relation, joins }),
|
||||||
|
|
|
@ -15359,6 +15359,29 @@ fn check_enforced() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn join_precedence() {
|
||||||
|
all_dialects_except(|d| !d.supports_left_associative_joins_without_parens())
|
||||||
|
.verified_query_with_canonical(
|
||||||
|
"SELECT *
|
||||||
|
FROM t1
|
||||||
|
NATURAL JOIN t5
|
||||||
|
INNER JOIN t0 ON (t0.v1 + t5.v0) > 0
|
||||||
|
WHERE t0.v1 = t1.v0",
|
||||||
|
// canonical string without parentheses
|
||||||
|
"SELECT * FROM t1 NATURAL JOIN t5 INNER JOIN t0 ON (t0.v1 + t5.v0) > 0 WHERE t0.v1 = t1.v0",
|
||||||
|
);
|
||||||
|
all_dialects_except(|d| d.supports_left_associative_joins_without_parens()).verified_query_with_canonical(
|
||||||
|
"SELECT *
|
||||||
|
FROM t1
|
||||||
|
NATURAL JOIN t5
|
||||||
|
INNER JOIN t0 ON (t0.v1 + t5.v0) > 0
|
||||||
|
WHERE t0.v1 = t1.v0",
|
||||||
|
// canonical string with parentheses
|
||||||
|
"SELECT * FROM t1 NATURAL JOIN (t5 INNER JOIN t0 ON (t0.v1 + t5.v0) > 0) WHERE t0.v1 = t1.v0",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_create_procedure_with_language() {
|
fn parse_create_procedure_with_language() {
|
||||||
let sql = r#"CREATE PROCEDURE test_proc LANGUAGE sql AS BEGIN SELECT 1; END"#;
|
let sql = r#"CREATE PROCEDURE test_proc LANGUAGE sql AS BEGIN SELECT 1; END"#;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue