diff --git a/crates/lib-dialects/src/ansi.rs b/crates/lib-dialects/src/ansi.rs index 6e9044029..c3913a637 100644 --- a/crates/lib-dialects/src/ansi.rs +++ b/crates/lib-dialects/src/ansi.rs @@ -3953,7 +3953,11 @@ pub fn raw_dialect() -> Dialect { ]; }) .to_matchable(), - Ref::new("ElseClauseSegment").optional().to_matchable(), + Ref::new("ElseClauseSegment") + .optional() + .reset_terminators() + .terminators(vec![Ref::keyword("END").to_matchable()]) + .to_matchable(), MetaSegment::dedent().to_matchable(), Ref::keyword("END").to_matchable(), ]) @@ -3971,7 +3975,11 @@ pub fn raw_dialect() -> Dialect { ]; }) .to_matchable(), - Ref::new("ElseClauseSegment").optional().to_matchable(), + Ref::new("ElseClauseSegment") + .optional() + .reset_terminators() + .terminators(vec![Ref::keyword("END").to_matchable()]) + .to_matchable(), MetaSegment::dedent().to_matchable(), Ref::keyword("END").to_matchable(), ]) diff --git a/crates/lib-dialects/test/fixtures/dialects/postgres/sqruff/case_else_concat.sql b/crates/lib-dialects/test/fixtures/dialects/postgres/sqruff/case_else_concat.sql new file mode 100644 index 000000000..6336718f8 --- /dev/null +++ b/crates/lib-dialects/test/fixtures/dialects/postgres/sqruff/case_else_concat.sql @@ -0,0 +1,6 @@ +select case + when a = 1 then 'one' + when a = 2 then 'two' + else 'other' || 's' + end as b +from test; diff --git a/crates/lib-dialects/test/fixtures/dialects/postgres/sqruff/case_else_concat.yml b/crates/lib-dialects/test/fixtures/dialects/postgres/sqruff/case_else_concat.yml new file mode 100644 index 000000000..58043d07e --- /dev/null +++ b/crates/lib-dialects/test/fixtures/dialects/postgres/sqruff/case_else_concat.yml @@ -0,0 +1,51 @@ +file: +- statement: + - select_statement: + - select_clause: + - keyword: select + - select_clause_element: + - expression: + - case_expression: + - keyword: case + - when_clause: + - keyword: when + - expression: + - column_reference: + - naked_identifier: a + - comparison_operator: + - raw_comparison_operator: = + - numeric_literal: '1' + - keyword: then + - expression: + - quoted_literal: '''one''' + - when_clause: + - keyword: when + - expression: + - column_reference: + - naked_identifier: a + - comparison_operator: + - raw_comparison_operator: = + - numeric_literal: '2' + - keyword: then + - expression: + - quoted_literal: '''two''' + - else_clause: + - keyword: else + - expression: + - quoted_literal: '''other''' + - binary_operator: + - pipe: '|' + - pipe: '|' + - quoted_literal: '''s''' + - keyword: end + - alias_expression: + - keyword: as + - naked_identifier: b + - from_clause: + - keyword: from + - from_expression: + - from_expression_element: + - table_expression: + - table_reference: + - naked_identifier: test +- statement_terminator: ; diff --git a/crates/lib/src/core/linter/core.rs b/crates/lib/src/core/linter/core.rs index b3a805ee7..94913201c 100644 --- a/crates/lib/src/core/linter/core.rs +++ b/crates/lib/src/core/linter/core.rs @@ -864,6 +864,19 @@ mod tests { use crate::core::config::FluffConfig; use crate::core::linter::core::Linter; + fn postgres_all_rules_linter() -> Linter { + let config = FluffConfig::from_source( + r#" +[sqruff] +dialect = postgres +rules = all +"#, + None, + ); + + Linter::new(config, None, None, true).unwrap() + } + fn normalise_paths(paths: Vec) -> Vec { paths .into_iter() @@ -1082,4 +1095,88 @@ mod tests { "Should not have LT01 false positives on template syntax" ); } + + #[test] + fn test_postgres_case_else_concat_does_not_raise_lt01_and_fixes_cleanly() { + let sql = r#"select case + when a = 1 then 'one' + when a = 2 then 'two' + else 'other' || 's' + end as b +from test; +"#; + let expected = r#"select + case + when a = 1 then 'one' + when a = 2 then 'two' + else 'other' || 's' + end as b +from test; +"#; + + let mut linter = postgres_all_rules_linter(); + let linted = linter.lint_string_wrapped(sql, false).unwrap(); + let violations = linted.violations(); + + assert!( + !violations.iter().any(|v| v.rule_code() == "LT01"), + "Expected no LT01 violations, got: {:?}", + violations + .iter() + .map(|v| (v.rule_code(), v.desc().to_string())) + .collect::>() + ); + assert!( + violations.iter().all(|v| v.rule_code() == "LT02"), + "Expected only LT02 violations, got: {:?}", + violations + .iter() + .map(|v| (v.rule_code(), v.desc().to_string())) + .collect::>() + ); + + let fixed = postgres_all_rules_linter() + .lint_string_wrapped(sql, true) + .unwrap() + .fix_string(); + + assert_eq!(fixed, expected); + } + + #[test] + fn test_postgres_case_else_binary_operator_spacing_still_triggers_lt01() { + let sql = r#"select case + when a = 1 then 'one' + else 1+2 + end as b +from test; +"#; + let expected = r#"select + case + when a = 1 then 'one' + else 1 + 2 + end as b +from test; +"#; + + let mut linter = postgres_all_rules_linter(); + let linted = linter.lint_string_wrapped(sql, false).unwrap(); + let violations = linted.violations(); + + assert!( + violations.iter().any(|v| v.rule_code() == "LT01"), + "Expected LT01 violations, got: {:?}", + violations + .iter() + .map(|v| (v.rule_code(), v.desc().to_string())) + .collect::>() + ); + + let fixed = postgres_all_rules_linter() + .lint_string_wrapped(sql, true) + .unwrap() + .fix_string(); + + assert_eq!(fixed, expected); + } } diff --git a/crates/lib/test/fixtures/rules/std_rule_cases/LT01-operators.yml b/crates/lib/test/fixtures/rules/std_rule_cases/LT01-operators.yml index 47167eec6..d968c8790 100644 --- a/crates/lib/test/fixtures/rules/std_rule_cases/LT01-operators.yml +++ b/crates/lib/test/fixtures/rules/std_rule_cases/LT01-operators.yml @@ -90,6 +90,35 @@ pass_tsql_assignment_operator: pass_concat_string: pass_str: SELECT 'barry' || 'pollard' +pass_postgres_case_else_concat_string: + pass_str: | + select case + when a = 1 then 'one' + when a = 2 then 'two' + else 'other' || 's' + end as b + from test; + configs: + core: + dialect: postgres + +fail_postgres_case_else_binary_operator_spacing: + fail_str: | + select case + when a = 1 then 'one' + else 1+2 + end as b + from test; + fix_str: | + select case + when a = 1 then 'one' + else 1 + 2 + end as b + from test; + configs: + core: + dialect: postgres + test_pass_placeholder_spacing: # Test for spacing issues around placeholders # https://github.com/sqlfluff/sqlfluff/issues/4253