Skip to main content

rigsql_rules/ambiguous/
am08.rs

1use rigsql_core::SegmentType;
2
3use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
4use crate::violation::LintViolation;
5
6/// AM08: Implicit cross join (comma in FROM clause).
7///
8/// Using commas to join tables in the FROM clause is an implicit cross join,
9/// which should be replaced with explicit CROSS JOIN syntax.
10#[derive(Debug, Default)]
11pub struct RuleAM08;
12
13impl Rule for RuleAM08 {
14    fn code(&self) -> &'static str {
15        "AM08"
16    }
17    fn name(&self) -> &'static str {
18        "ambiguous.join_condition"
19    }
20    fn description(&self) -> &'static str {
21        "Implicit cross join in FROM clause."
22    }
23    fn explanation(&self) -> &'static str {
24        "Using commas to separate tables in a FROM clause creates an implicit cross join \
25         (Cartesian product). Use explicit CROSS JOIN syntax instead, or use appropriate \
26         JOIN ... ON clauses if you intend a different join type."
27    }
28    fn groups(&self) -> &[RuleGroup] {
29        &[RuleGroup::Ambiguous]
30    }
31    fn is_fixable(&self) -> bool {
32        false
33    }
34
35    fn crawl_type(&self) -> CrawlType {
36        CrawlType::Segment(vec![SegmentType::FromClause])
37    }
38
39    fn eval(&self, ctx: &RuleContext) -> Vec<LintViolation> {
40        let children = ctx.segment.children();
41
42        children
43            .iter()
44            .filter(|c| c.segment_type() == SegmentType::Comma)
45            .map(|comma| {
46                LintViolation::with_msg_key(
47                    self.code(),
48                    "Implicit cross join via comma in FROM clause. Use explicit JOIN.",
49                    comma.span(),
50                    "rules.AM08.msg",
51                    vec![],
52                )
53            })
54            .collect()
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61    use crate::test_utils::lint_sql;
62
63    #[test]
64    fn test_am08_flags_comma_join() {
65        let violations = lint_sql("SELECT a FROM t, u", RuleAM08);
66        assert_eq!(violations.len(), 1);
67    }
68
69    #[test]
70    fn test_am08_accepts_explicit_join() {
71        let violations = lint_sql("SELECT a FROM t INNER JOIN u ON t.id = u.id", RuleAM08);
72        assert_eq!(violations.len(), 0);
73    }
74
75    #[test]
76    fn test_am08_flags_multiple_commas() {
77        let violations = lint_sql("SELECT a FROM t, u, v", RuleAM08);
78        assert_eq!(violations.len(), 2);
79    }
80}