diff --git a/src/dialect/generic.rs b/src/dialect/generic.rs index 694eb8f19b..9e6b2880be 100644 --- a/src/dialect/generic.rs +++ b/src/dialect/generic.rs @@ -173,6 +173,10 @@ impl Dialect for GenericDialect { true } + fn supports_comma_join_constraint(&self) -> bool { + true + } + fn supports_comment_on(&self) -> bool { true } diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index 512af328ed..e120b590b4 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -445,6 +445,14 @@ pub trait Dialect: Debug + Any { false } + /// Returns true if the dialect supports `ON`/`USING` after a comma-joined table, + /// e.g. `SELECT * FROM t, u ON t.a = u.a`. + /// + /// [SQLite](https://www.sqlite.org/syntax/join-clause.html) + fn supports_comma_join_constraint(&self) -> bool { + false + } + /// Returns true if the dialect supports CONNECT BY. fn supports_connect_by(&self) -> bool { false diff --git a/src/dialect/sqlite.rs b/src/dialect/sqlite.rs index f63e4cbe13..3ccf3547a0 100644 --- a/src/dialect/sqlite.rs +++ b/src/dialect/sqlite.rs @@ -146,4 +146,8 @@ impl Dialect for SQLiteDialect { fn supports_parens_around_table_factor(&self) -> bool { true } + + fn supports_comma_join_constraint(&self) -> bool { + true + } } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index db54296fce..8d32b9fb99 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -5086,13 +5086,37 @@ impl<'a> Parser<'a> { /// Parse a list of [TableWithJoins] fn parse_table_with_joins(&mut self) -> Result, ParserError> { - let trailing_commas = self.dialect.supports_from_trailing_commas(); + let dialect = self.dialect; + let trailing_commas = dialect.supports_from_trailing_commas(); + let mut tables: Vec = vec![]; self.parse_comma_separated_with_trailing_commas( - Parser::parse_table_and_joins, + |parser| { + let table = parser.parse_table_and_joins()?; + match tables.last_mut() { + Some(previous) + if dialect.supports_comma_join_constraint() + && table.joins.is_empty() + && parser + .peek_one_of_keywords(&[Keyword::ON, Keyword::USING]) + .is_some() => + { + let constraint = parser.parse_join_constraint(false)?; + previous.joins.push(Join { + relation: table.relation, + global: false, + join_operator: JoinOperator::Join(constraint), + }); + previous.joins.extend(parser.parse_joins()?); + } + _ => tables.push(table), + } + Ok(()) + }, trailing_commas, - |kw, parser| !self.dialect.is_table_factor(kw, parser), - ) + |kw, parser| !dialect.is_table_factor(kw, parser), + )?; + Ok(tables) } /// Parse the comma of a comma-separated syntax element. diff --git a/tests/sqlparser_sqlite.rs b/tests/sqlparser_sqlite.rs index 2e60c75efa..ccebfb8c18 100644 --- a/tests/sqlparser_sqlite.rs +++ b/tests/sqlparser_sqlite.rs @@ -1142,6 +1142,44 @@ fn parse_parenthesized_table_factor() { ); } +#[test] +fn parse_comma_join_constraint() { + let dialects = all_dialects_where(|d| d.supports_comma_join_constraint()); + for (sql, canonical) in [ + ( + "SELECT * FROM t, u ON t.a = u.a", + "SELECT * FROM t JOIN u ON t.a = u.a", + ), + ( + "SELECT * FROM t, u USING (a)", + "SELECT * FROM t JOIN u USING(a)", + ), + ( + "SELECT * FROM t, u ON t.a = u.a, v ON v.a = u.a", + "SELECT * FROM t JOIN u ON t.a = u.a JOIN v ON v.a = u.a", + ), + ( + "SELECT * FROM t, u ON t.a = u.a JOIN v ON v.a = u.a", + "SELECT * FROM t JOIN u ON t.a = u.a JOIN v ON v.a = u.a", + ), + ( + "UPDATE t SET a = 1 FROM u, v ON u.a = v.a", + "UPDATE t SET a = 1 FROM u JOIN v ON u.a = v.a", + ), + ] { + dialects.one_statement_parses_to(sql, canonical); + } + dialects.verified_stmt("SELECT * FROM t, u"); + + assert_eq!( + all_dialects_where(|d| !d.supports_comma_join_constraint()) + .parse_sql_statements("SELECT * FROM t, u ON t.a = u.a"), + Err(ParserError::ParserError( + "Expected: end of statement, found: ON".to_string() + )) + ); +} + fn sqlite() -> TestedDialects { TestedDialects::new(vec![Box::new(SQLiteDialect {})]) }