rigsql_rules/structure/
st05.rs1use rigsql_core::SegmentType;
2
3use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
4use crate::violation::LintViolation;
5
6#[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}