sqruff_lib/rules/convention/
cv08.rs1use 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}