sqruff_lib/rules/aliasing/
al08.rs

1use std::collections::hash_map::Entry;
2
3use ahash::AHashMap;
4use sqruff_lib_core::dialects::syntax::{SyntaxKind, SyntaxSet};
5use sqruff_lib_core::parser::segments::base::ErasedSegment;
6
7use crate::core::config::Value;
8use crate::core::rules::base::{Erased, ErasedRule, LintResult, Rule, RuleGroups};
9use crate::core::rules::context::RuleContext;
10use crate::core::rules::crawlers::{Crawler, SegmentSeekerCrawler};
11
12#[derive(Debug, Default, Clone)]
13pub struct RuleAL08;
14
15impl Rule for RuleAL08 {
16    fn load_from_config(&self, _config: &AHashMap<String, Value>) -> Result<ErasedRule, String> {
17        Ok(RuleAL08.erased())
18    }
19
20    fn name(&self) -> &'static str {
21        "layout.cte_newline"
22    }
23
24    fn description(&self) -> &'static str {
25        "Column aliases should be unique within each clause."
26    }
27
28    fn long_description(&self) -> &'static str {
29        r#"
30**Anti-pattern**
31
32In this example, alias o is used for the orders table, and c is used for customers table.
33
34```sql
35SELECT
36    COUNT(o.customer_id) as order_amount,
37    c.name
38FROM orders as o
39JOIN customers as c on o.id = c.user_id
40```
41
42**Best practice**
43
44Avoid aliases.
45
46```sql
47SELECT
48    COUNT(orders.customer_id) as order_amount,
49    customers.name
50FROM orders
51JOIN customers on orders.id = customers.user_id
52
53-- Self-join will not raise issue
54
55SELECT
56    table1.a,
57    table_alias.b,
58FROM
59    table1
60    LEFT JOIN table1 AS table_alias ON
61        table1.foreign_key = table_alias.foreign_key
62```
63"#
64    }
65
66    fn groups(&self) -> &'static [RuleGroups] {
67        &[RuleGroups::All, RuleGroups::Core, RuleGroups::Aliasing]
68    }
69
70    fn eval(&self, context: &RuleContext) -> Vec<LintResult> {
71        let mut used_aliases = AHashMap::new();
72        let mut violations = Vec::new();
73
74        for clause_element in context
75            .segment
76            .children(const { &SyntaxSet::new(&[SyntaxKind::SelectClauseElement]) })
77        {
78            let mut column_alias = None;
79
80            if let Some(alias_expression) =
81                clause_element.child(const { &SyntaxSet::new(&[SyntaxKind::AliasExpression]) })
82            {
83                for it in alias_expression.segments() {
84                    if !it.is_code() || it.raw().eq_ignore_ascii_case("AS") {
85                        continue;
86                    }
87
88                    column_alias = it.clone().into();
89                    break;
90                }
91            } else if let Some(column_reference) =
92                clause_element.child(const { &SyntaxSet::new(&[SyntaxKind::ColumnReference]) })
93            {
94                column_alias = column_reference.segments().last().cloned();
95            }
96
97            let Some(column_alias) = column_alias else {
98                continue;
99            };
100
101            let key = column_alias
102                .raw()
103                .to_uppercase()
104                .replace(['\"', '\'', '`'], "");
105
106            match used_aliases.entry(key) {
107                Entry::Occupied(entry) => {
108                    let previous: &ErasedSegment = entry.get();
109
110                    let alias = column_alias.raw();
111                    let line_no = previous.get_position_marker().unwrap().source_position().0;
112
113                    violations.push(LintResult::new(
114                        column_alias.clone().into(),
115                        vec![],
116                        format!("Reuse of column alias '{alias}' from line {line_no}.").into(),
117                        None,
118                    ))
119                }
120                Entry::Vacant(entry) => _ = entry.insert(clause_element.clone()),
121            };
122        }
123
124        violations
125    }
126
127    fn crawl_behaviour(&self) -> Crawler {
128        SegmentSeekerCrawler::new(const { SyntaxSet::new(&[SyntaxKind::SelectClause]) }).into()
129    }
130}