1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
use std::collections::hash_map::Entry;

use ahash::AHashMap;

use crate::core::config::Value;
use crate::core::parser::segments::base::ErasedSegment;
use crate::core::rules::base::{Erased, ErasedRule, LintResult, Rule, RuleGroups};
use crate::core::rules::context::RuleContext;
use crate::core::rules::crawlers::{Crawler, SegmentSeekerCrawler};
use crate::dialects::{SyntaxKind, SyntaxSet};

#[derive(Debug, Default, Clone)]
pub struct RuleAL08;

impl Rule for RuleAL08 {
    fn load_from_config(&self, _config: &AHashMap<String, Value>) -> Result<ErasedRule, String> {
        Ok(RuleAL08.erased())
    }

    fn name(&self) -> &'static str {
        "layout.cte_newline"
    }

    fn description(&self) -> &'static str {
        "Column aliases should be unique within each clause."
    }

    fn long_description(&self) -> &'static str {
        r#"
**Anti-pattern**

In this example, alias o is used for the orders table, and c is used for customers table.

```sql
SELECT
    COUNT(o.customer_id) as order_amount,
    c.name
FROM orders as o
JOIN customers as c on o.id = c.user_id
```

**Best practice**

Avoid aliases.

```sql
SELECT
    COUNT(orders.customer_id) as order_amount,
    customers.name
FROM orders
JOIN customers on orders.id = customers.user_id

-- Self-join will not raise issue

SELECT
    table1.a,
    table_alias.b,
FROM
    table1
    LEFT JOIN table1 AS table_alias ON
        table1.foreign_key = table_alias.foreign_key
```
"#
    }

    fn groups(&self) -> &'static [RuleGroups] {
        &[RuleGroups::All, RuleGroups::Core, RuleGroups::Aliasing]
    }

    fn eval(&self, context: RuleContext) -> Vec<LintResult> {
        let mut used_aliases = AHashMap::new();
        let mut violations = Vec::new();

        for clause_element in
            context.segment.children(const { &SyntaxSet::new(&[SyntaxKind::SelectClauseElement]) })
        {
            let mut column_alias = None;

            if let Some(alias_expression) =
                clause_element.child(const { &SyntaxSet::new(&[SyntaxKind::AliasExpression]) })
            {
                for it in alias_expression.segments() {
                    if !it.is_code() || it.get_raw_upper().unwrap() == "AS" {
                        continue;
                    }

                    column_alias = it.clone().into();
                    break;
                }
            } else if let Some(column_reference) =
                clause_element.child(const { &SyntaxSet::new(&[SyntaxKind::ColumnReference]) })
            {
                column_alias = column_reference.segments().last().cloned();
            }

            let Some(column_alias) = column_alias else { continue };

            let key = column_alias.get_raw_upper().unwrap().replace(['\"', '\'', '`'], "");

            match used_aliases.entry(key) {
                Entry::Occupied(entry) => {
                    let previous: &ErasedSegment = entry.get();

                    let alias = column_alias.raw();
                    let line_no = previous.get_position_marker().unwrap().source_position().0;

                    violations.push(LintResult::new(
                        column_alias.clone().into(),
                        vec![],
                        None,
                        format!("Reuse of column alias '{alias}' from line {line_no}.").into(),
                        None,
                    ))
                }
                Entry::Vacant(entry) => _ = entry.insert(clause_element),
            };
        }

        violations
    }

    fn crawl_behaviour(&self) -> Crawler {
        SegmentSeekerCrawler::new(const { SyntaxSet::new(&[SyntaxKind::SelectClause]) }).into()
    }
}