rigsql_rules/rigsql/
rg05.rs1use rigsql_core::{Segment, SegmentType};
2
3use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
4use crate::violation::LintViolation;
5
6#[derive(Debug, Default)]
11pub struct RuleRG05;
12
13impl Rule for RuleRG05 {
14 fn code(&self) -> &'static str {
15 "RG05"
16 }
17 fn name(&self) -> &'static str {
18 "rigsql.subquery_alias"
19 }
20 fn description(&self) -> &'static str {
21 "Subqueries in FROM clause should have an alias."
22 }
23 fn explanation(&self) -> &'static str {
24 "When a subquery is used as a table source in a FROM or JOIN clause, \
25 it must be given an explicit alias. Without an alias, columns from the \
26 subquery cannot be referenced clearly in the outer query."
27 }
28 fn groups(&self) -> &[RuleGroup] {
29 &[RuleGroup::Aliasing]
30 }
31 fn is_fixable(&self) -> bool {
32 false
33 }
34
35 fn crawl_type(&self) -> CrawlType {
36 CrawlType::Segment(vec![SegmentType::FromClause, SegmentType::JoinClause])
37 }
38
39 fn eval(&self, ctx: &RuleContext) -> Vec<LintViolation> {
40 let mut violations = Vec::new();
41 check_subqueries_have_alias(ctx.segment, &mut violations, self.code());
42 violations
43 }
44}
45
46fn check_subqueries_have_alias(
47 segment: &Segment,
48 violations: &mut Vec<LintViolation>,
49 code: &'static str,
50) {
51 let children = segment.children();
52
53 for child in children {
54 let st = child.segment_type();
55
56 if st == SegmentType::Subquery {
58 violations.push(LintViolation::with_msg_key(
59 code,
60 "Subquery in FROM/JOIN clause should have an alias.",
61 child.span(),
62 "rules.RG05.msg",
63 vec![],
64 ));
65 continue;
66 }
67
68 if st == SegmentType::AliasExpression {
70 continue;
71 }
72
73 check_subqueries_have_alias(child, violations, code);
75 }
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81 use crate::test_utils::lint_sql;
82
83 #[test]
84 fn test_rg05_flags_bare_subquery() {
85 let violations = lint_sql("SELECT * FROM (SELECT 1)", RuleRG05);
86 assert_eq!(violations.len(), 1);
87 }
88
89 #[test]
90 fn test_rg05_accepts_aliased_subquery() {
91 let violations = lint_sql("SELECT * FROM (SELECT 1) AS sub", RuleRG05);
92 assert_eq!(violations.len(), 0);
93 }
94}