Skip to main content

rigsql_rules/convention/
cv08.rs

1use rigsql_core::{Segment, SegmentType};
2
3use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
4use crate::violation::LintViolation;
5
6/// CV08: Use LEFT JOIN instead of RIGHT JOIN.
7///
8/// LEFT JOIN is more readable and intuitive. Any RIGHT JOIN can be rewritten as a LEFT JOIN.
9#[derive(Debug, Default)]
10pub struct RuleCV08;
11
12impl Rule for RuleCV08 {
13    fn code(&self) -> &'static str {
14        "CV08"
15    }
16    fn name(&self) -> &'static str {
17        "convention.left_join"
18    }
19    fn description(&self) -> &'static str {
20        "Use LEFT JOIN instead of RIGHT JOIN."
21    }
22    fn explanation(&self) -> &'static str {
23        "RIGHT JOIN can always be rewritten as LEFT JOIN by swapping the table order. \
24         LEFT JOIN is more intuitive because it reads left-to-right: the 'main' table \
25         is on the left, and the 'optional' table is on the right."
26    }
27    fn groups(&self) -> &[RuleGroup] {
28        &[RuleGroup::Convention]
29    }
30    fn is_fixable(&self) -> bool {
31        false
32    }
33
34    fn crawl_type(&self) -> CrawlType {
35        CrawlType::Segment(vec![SegmentType::JoinClause])
36    }
37
38    fn eval(&self, ctx: &RuleContext) -> Vec<LintViolation> {
39        let children = ctx.segment.children();
40
41        // Look for RIGHT keyword in the join clause
42        for child in children {
43            if let Segment::Token(t) = child {
44                if t.segment_type == SegmentType::Keyword
45                    && t.token.text.eq_ignore_ascii_case("RIGHT")
46                {
47                    return vec![LintViolation::with_msg_key(
48                        self.code(),
49                        "Use LEFT JOIN instead of RIGHT JOIN.",
50                        t.token.span,
51                        "rules.CV08.msg",
52                        vec![],
53                    )];
54                }
55            }
56        }
57
58        vec![]
59    }
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65    use crate::test_utils::lint_sql;
66
67    #[test]
68    fn test_cv08_flags_right_join() {
69        let violations = lint_sql("SELECT * FROM a RIGHT JOIN b ON a.id = b.id", RuleCV08);
70        assert_eq!(violations.len(), 1);
71    }
72
73    #[test]
74    fn test_cv08_accepts_left_join() {
75        let violations = lint_sql("SELECT * FROM a LEFT JOIN b ON a.id = b.id", RuleCV08);
76        assert_eq!(violations.len(), 0);
77    }
78}