sqruff_lib/rules/aliasing/
al08.rs1use 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}