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::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}