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::new(
44                    self.code(),
45                    "Use a CTE instead of a derived table (subquery in FROM).",
46                    seg.span(),
47                ));
48            }
49        });
50
51        violations
52    }
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58    use crate::test_utils::lint_sql;
59
60    #[test]
61    fn test_st05_flags_subquery_in_from() {
62        let violations = lint_sql("SELECT * FROM (SELECT id FROM t) AS sub;", RuleST05);
63        assert_eq!(violations.len(), 1);
64        assert!(violations[0].message.contains("CTE"));
65    }
66
67    #[test]
68    fn test_st05_accepts_simple_from() {
69        let violations = lint_sql("SELECT * FROM t;", RuleST05);
70        assert_eq!(violations.len(), 0);
71    }
72}