Skip to main content

rigsql_rules/structure/
st05.rs

1use rigsql_core::SegmentType;
2
3use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
4use crate::violation::LintViolation;
5
6/// ST05: Derived tables (subqueries in FROM) should be CTEs.
7///
8/// Subqueries in the FROM clause are harder to read than CTEs.
9#[derive(Debug, Default)]
10pub struct RuleST05;
11
12impl Rule for RuleST05 {
13    fn code(&self) -> &'static str {
14        "ST05"
15    }
16    fn name(&self) -> &'static str {
17        "structure.subquery"
18    }
19    fn description(&self) -> &'static str {
20        "Derived tables should use CTEs instead."
21    }
22    fn explanation(&self) -> &'static str {
23        "Subqueries in the FROM clause (derived tables) reduce readability compared \
24         to Common Table Expressions (CTEs). Consider refactoring derived tables \
25         into CTEs defined in a WITH clause."
26    }
27    fn groups(&self) -> &[RuleGroup] {
28        &[RuleGroup::Structure]
29    }
30    fn is_fixable(&self) -> bool {
31        false
32    }
33
34    fn crawl_type(&self) -> CrawlType {
35        CrawlType::Segment(vec![SegmentType::FromClause])
36    }
37
38    fn eval(&self, ctx: &RuleContext) -> Vec<LintViolation> {
39        let mut violations = Vec::new();
40
41        ctx.segment.walk(&mut |seg| {
42            if seg.segment_type() == SegmentType::Subquery {
43                violations.push(LintViolation::with_msg_key(
44                    self.code(),
45                    "Use a CTE instead of a derived table (subquery in FROM).",
46                    seg.span(),
47                    "rules.ST05.msg",
48                    vec![],
49                ));
50            }
51        });
52
53        violations
54    }
55}
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60    use crate::test_utils::lint_sql;
61
62    #[test]
63    fn test_st05_flags_subquery_in_from() {
64        let violations = lint_sql("SELECT * FROM (SELECT id FROM t) AS sub;", RuleST05);
65        assert_eq!(violations.len(), 1);
66        assert!(violations[0].message.contains("CTE"));
67    }
68
69    #[test]
70    fn test_st05_accepts_simple_from() {
71        let violations = lint_sql("SELECT * FROM t;", RuleST05);
72        assert_eq!(violations.len(), 0);
73    }
74}