diff --git a/crates/lib-core/src/dialects/syntax.rs b/crates/lib-core/src/dialects/syntax.rs index 4d9c39cc1..cd073bd2c 100644 --- a/crates/lib-core/src/dialects/syntax.rs +++ b/crates/lib-core/src/dialects/syntax.rs @@ -309,6 +309,7 @@ pub enum SyntaxKind { ChangesClause, FromAtExpression, FromBeforeExpression, + MatchConditionClause, SnowflakeKeywordExpression, SemiStructuredExpression, SelectExcludeClause, diff --git a/crates/lib-dialects/src/snowflake.rs b/crates/lib-dialects/src/snowflake.rs index ffd9de5e1..cfd2bc93a 100644 --- a/crates/lib-dialects/src/snowflake.rs +++ b/crates/lib-dialects/src/snowflake.rs @@ -8,6 +8,7 @@ use sqruff_lib_core::parser::grammar::anyof::{ }; use sqruff_lib_core::parser::grammar::base::{Nothing, Ref}; use sqruff_lib_core::parser::grammar::delimited::Delimited; +use sqruff_lib_core::parser::grammar::conditional::Conditional; use sqruff_lib_core::parser::grammar::sequence::{Bracketed, Sequence}; use sqruff_lib_core::parser::lexer::Matcher; use sqruff_lib_core::parser::matchable::{Matchable, MatchableTrait}; @@ -1630,6 +1631,19 @@ pub fn dialect() -> Dialect { .to_matchable() .into(), ), + ( + "MatchConditionSegment".into(), + NodeMatcher::new( + SyntaxKind::MatchConditionClause, + Sequence::new(vec_of_erased![ + Ref::keyword("MATCH_CONDITION"), + Bracketed::new(vec_of_erased![Ref::new("ExpressionSegment")]) + ]) + .to_matchable(), + ) + .to_matchable() + .into(), + ), ( "FromPivotExpressionSegment".into(), NodeMatcher::new( @@ -7654,6 +7668,76 @@ pub fn dialect() -> Dialect { .into(), )]); + snowflake_dialect.add([( + "JoinTypeKeywordsGrammar".into(), + one_of(vec_of_erased![ + Ref::keyword("CROSS"), + Ref::keyword("INNER"), + Sequence::new(vec_of_erased![ + one_of(vec_of_erased![ + Ref::keyword("FULL"), + Ref::keyword("LEFT"), + Ref::keyword("RIGHT"), + ]), + Ref::keyword("OUTER").optional(), + ]), + Ref::keyword("ASOF"), + ]) + .config(|this| this.optional()) + .to_matchable() + .into(), + )]); + + snowflake_dialect.replace_grammar( + "JoinClauseSegment", + NodeMatcher::new( + SyntaxKind::JoinClause, + one_of(vec_of_erased![ + Sequence::new(vec_of_erased![ + Ref::new("JoinTypeKeywordsGrammar").optional(), + Ref::new("JoinKeywordsGrammar"), + MetaSegment::indent(), + Ref::new("FromExpressionElementSegment"), + AnyNumberOf::new(vec_of_erased![Ref::new("NestedJoinGrammar")]), + MetaSegment::dedent(), + Ref::new("MatchConditionSegment").optional(), + Sequence::new(vec_of_erased![ + Conditional::new(MetaSegment::indent()).indented_using_on(), + one_of(vec_of_erased![ + Ref::new("JoinOnConditionSegment"), + Sequence::new(vec_of_erased![ + Ref::keyword("USING"), + MetaSegment::indent(), + Bracketed::new(vec_of_erased![Delimited::new(vec_of_erased![ + Ref::new("SingleIdentifierGrammar") + ])]) + .config(|this| this.parse_mode = ParseMode::Greedy), + MetaSegment::dedent(), + ]) + ]), + Conditional::new(MetaSegment::dedent()).indented_using_on(), + ]) + .config(|this| this.optional()) + ]), + Sequence::new(vec_of_erased![ + Ref::new("NaturalJoinKeywordsGrammar"), + Ref::new("JoinKeywordsGrammar"), + MetaSegment::indent(), + Ref::new("FromExpressionElementSegment"), + MetaSegment::dedent(), + ]), + Sequence::new(vec_of_erased![ + Ref::new("ExtendedNaturalJoinKeywordsGrammar"), + MetaSegment::indent(), + Ref::new("FromExpressionElementSegment"), + MetaSegment::dedent(), + ]) + ]) + .to_matchable(), + ) + .to_matchable(), + ); + snowflake_dialect.expand(); snowflake_dialect } diff --git a/crates/lib-dialects/src/snowflake_keywords.rs b/crates/lib-dialects/src/snowflake_keywords.rs index 335c29f45..d7c30ca13 100644 --- a/crates/lib-dialects/src/snowflake_keywords.rs +++ b/crates/lib-dialects/src/snowflake_keywords.rs @@ -45,6 +45,7 @@ LIKE LOCALTIME LOCALTIMESTAMP MATCH_RECOGNIZE +MATCH_CONDITION MINUS NATURAL NOT @@ -102,6 +103,7 @@ APPEND_ONLY APPLY ARRAY ASC +ASOF AT ATTACH AUTHORIZATION diff --git a/crates/lib-dialects/test/fixtures/dialects/snowflake/asof_join.sql b/crates/lib-dialects/test/fixtures/dialects/snowflake/asof_join.sql new file mode 100644 index 000000000..862f46fc0 --- /dev/null +++ b/crates/lib-dialects/test/fixtures/dialects/snowflake/asof_join.sql @@ -0,0 +1,7 @@ +select + a.id as id_d, + b.id as id_b +from schema.table_a as a +asof join + schema.table_b as b + match_condition(a.event_ts >= b.event_ts) on a.id = b.id diff --git a/crates/lib-dialects/test/fixtures/dialects/snowflake/asof_join.yml b/crates/lib-dialects/test/fixtures/dialects/snowflake/asof_join.yml new file mode 100644 index 000000000..0ce5e68db --- /dev/null +++ b/crates/lib-dialects/test/fixtures/dialects/snowflake/asof_join.yml @@ -0,0 +1,77 @@ +file: +- statement: + - select_statement: + - select_clause: + - keyword: select + - select_clause_element: + - column_reference: + - naked_identifier: a + - dot: . + - naked_identifier: id + - alias_expression: + - keyword: as + - naked_identifier: id_d + - comma: ',' + - select_clause_element: + - column_reference: + - naked_identifier: b + - dot: . + - naked_identifier: id + - alias_expression: + - keyword: as + - naked_identifier: id_b + - from_clause: + - keyword: from + - from_expression: + - from_expression_element: + - table_expression: + - table_reference: + - naked_identifier: schema + - dot: . + - naked_identifier: table_a + - alias_expression: + - keyword: as + - naked_identifier: a + - join_clause: + - join_clause: + - keyword: asof + - keyword: join + - from_expression_element: + - table_expression: + - table_reference: + - naked_identifier: schema + - dot: . + - naked_identifier: table_b + - alias_expression: + - keyword: as + - naked_identifier: b + - match_condition_clause: + - keyword: match_condition + - bracketed: + - start_bracket: ( + - expression: + - column_reference: + - naked_identifier: a + - dot: . + - naked_identifier: event_ts + - comparison_operator: + - raw_comparison_operator: '>' + - raw_comparison_operator: = + - column_reference: + - naked_identifier: b + - dot: . + - naked_identifier: event_ts + - end_bracket: ) + - join_on_condition: + - keyword: on + - expression: + - column_reference: + - naked_identifier: a + - dot: . + - naked_identifier: id + - comparison_operator: + - raw_comparison_operator: = + - column_reference: + - naked_identifier: b + - dot: . + - naked_identifier: id diff --git a/crates/lib/test/fixtures/rules/std_rule_cases/RF01.yml b/crates/lib/test/fixtures/rules/std_rule_cases/RF01.yml index 13101c9ac..26758ebb6 100644 --- a/crates/lib/test/fixtures/rules/std_rule_cases/RF01.yml +++ b/crates/lib/test/fixtures/rules/std_rule_cases/RF01.yml @@ -316,3 +316,16 @@ test_pass_postgres_select_with_alias_quoted: configs: core: dialect: postgres + +test_pass_snowflake_asof_join: + pass_str: | + select + a.id as id_d, + b.id as id_b + from schema.table_a as a + asof join + schema.table_b as b + match_condition(a.event_ts >= b.event_ts) on a.id = b.id + configs: + core: + dialect: snowflake