sqruff_lib/rules/convention/
cv08.rs

1use ahash::{AHashMap, AHashSet};
2use smol_str::{SmolStr, StrExt};
3use sqruff_lib_core::dialects::syntax::{SyntaxKind, SyntaxSet};
4
5use crate::core::config::Value;
6use crate::core::rules::base::{Erased, ErasedRule, LintResult, Rule, RuleGroups};
7use crate::core::rules::context::RuleContext;
8use crate::core::rules::crawlers::{Crawler, SegmentSeekerCrawler};
9
10#[derive(Default, Clone, Debug)]
11pub struct RuleCV08;
12
13impl Rule for RuleCV08 {
14    fn load_from_config(&self, _config: &AHashMap<String, Value>) -> Result<ErasedRule, String> {
15        Ok(RuleCV08.erased())
16    }
17
18    fn name(&self) -> &'static str {
19        "convention.left_join"
20    }
21
22    fn description(&self) -> &'static str {
23        "Use LEFT JOIN instead of RIGHT JOIN."
24    }
25
26    fn long_description(&self) -> &'static str {
27        r#"
28**Anti-pattern**
29
30`RIGHT JOIN` is used.
31
32```sql
33SELECT
34    foo.col1,
35    bar.col2
36FROM foo
37RIGHT JOIN bar
38    ON foo.bar_id = bar.id;
39```
40
41**Best practice**
42
43Refactor and use ``LEFT JOIN`` instead.
44
45```sql
46SELECT
47    foo.col1,
48    bar.col2
49FROM bar
50LEFT JOIN foo
51   ON foo.bar_id = bar.id;
52```
53"#
54    }
55
56    fn groups(&self) -> &'static [RuleGroups] {
57        &[RuleGroups::All, RuleGroups::Convention]
58    }
59
60    fn eval(&self, context: &RuleContext) -> Vec<LintResult> {
61        assert!(context.segment.is_type(SyntaxKind::JoinClause));
62
63        let segments = context
64            .segment
65            .segments()
66            .iter()
67            .map(|segment| segment.raw().to_uppercase_smolstr())
68            .collect::<AHashSet<_>>();
69
70        let mut set = AHashSet::new();
71        set.insert(SmolStr::new_static("RIGHT"));
72        set.insert(SmolStr::new_static("JOIN"));
73
74        if set.is_subset(&segments) {
75            vec![LintResult::new(
76                Some(context.segment.segments()[0].clone()),
77                vec![],
78                None,
79                None,
80            )]
81        } else {
82            vec![]
83        }
84    }
85
86    fn crawl_behaviour(&self) -> Crawler {
87        SegmentSeekerCrawler::new(const { SyntaxSet::new(&[SyntaxKind::JoinClause]) }).into()
88    }
89}