Skip to main content

rigsql_rules/aliasing/
al06.rs

1use rigsql_core::{Segment, SegmentType};
2
3use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
4use crate::violation::LintViolation;
5
6/// AL06: Subqueries in FROM clause should have an alias.
7///
8/// A subquery used as a table source must be given an explicit alias
9/// so that its columns can be referenced unambiguously.
10#[derive(Debug, Default)]
11pub struct RuleAL06;
12
13impl Rule for RuleAL06 {
14    fn code(&self) -> &'static str {
15        "AL06"
16    }
17    fn name(&self) -> &'static str {
18        "aliasing.subquery"
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        // A bare Subquery (not wrapped in AliasExpression) lacks an alias
57        if st == SegmentType::Subquery {
58            violations.push(LintViolation::new(
59                code,
60                "Subquery in FROM/JOIN clause should have an alias.",
61                child.span(),
62            ));
63            continue;
64        }
65
66        // AliasExpression wraps aliased subqueries — skip (already aliased)
67        if st == SegmentType::AliasExpression {
68            continue;
69        }
70
71        // Recurse into other nodes (e.g., JoinClause inside FromClause)
72        check_subqueries_have_alias(child, violations, code);
73    }
74}