sqruff_lib/rules/aliasing/
al06.rs

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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
use ahash::AHashMap;
use sqruff_lib_core::dialects::syntax::{SyntaxKind, SyntaxSet};
use sqruff_lib_core::parser::segments::base::ErasedSegment;

use crate::core::config::Value;
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::utils::functional::context::FunctionalContext;

#[derive(Debug, Clone, Default)]
pub struct RuleAL06 {
    min_alias_length: Option<usize>,
    max_alias_length: Option<usize>,
}

impl RuleAL06 {
    fn lint_aliases(&self, from_expression_elements: Vec<ErasedSegment>) -> Vec<LintResult> {
        let mut violation_buff = Vec::new();

        for from_expression_element in from_expression_elements {
            let table_ref = from_expression_element
                .child(const { &SyntaxSet::new(&[SyntaxKind::TableExpression]) })
                .and_then(|table_expression| {
                    table_expression.child(
                        const {
                            &SyntaxSet::new(&[
                                SyntaxKind::ObjectReference,
                                SyntaxKind::TableReference,
                            ])
                        },
                    )
                });

            let Some(_table_ref) = table_ref else {
                return Vec::new();
            };

            let Some(alias_exp_ref) = from_expression_element
                .child(const { &SyntaxSet::new(&[SyntaxKind::AliasExpression]) })
            else {
                return Vec::new();
            };

            if let Some(min_alias_length) = self.min_alias_length {
                if let Some(alias_identifier_ref) =
                    alias_exp_ref.child(const { &SyntaxSet::new(&[SyntaxKind::Identifier, SyntaxKind::NakedIdentifier]) })
                {
                    let alias_identifier = alias_identifier_ref.raw();
                    if alias_identifier.len() < min_alias_length {
                        violation_buff.push(LintResult::new(
                            Some(alias_identifier_ref),
                            Vec::new(),
                            None,
                            format!(
                                "Aliases should be at least '{:?}' character(s) long",
                                self.min_alias_length
                            )
                            .into(),
                            None,
                        ))
                    }
                }
            }

            if let Some(max_alias_length) = self.max_alias_length {
                if let Some(alias_identifier_ref) =
                    alias_exp_ref.child(const { &SyntaxSet::new(&[SyntaxKind::Identifier, SyntaxKind::NakedIdentifier]) })
                {
                    let alias_identifier = alias_identifier_ref.raw();

                    if alias_identifier.len() > max_alias_length {
                        violation_buff.push(LintResult::new(
                            Some(alias_identifier_ref),
                            Vec::new(),
                            None,
                            format!(
                                "Aliases should be no more than '{:?}' character(s) long.",
                                self.max_alias_length
                            )
                            .into(),
                            None,
                        ))
                    }
                }
            }
        }

        violation_buff
    }
}

impl Rule for RuleAL06 {
    fn load_from_config(&self, config: &AHashMap<String, Value>) -> Result<ErasedRule, String> {
        Ok(RuleAL06 {
            min_alias_length: config["min_alias_length"].as_int().map(|it| it as usize),
            max_alias_length: config["max_alias_length"].as_int().map(|it| it as usize),
        }
        .erased())
    }

    fn name(&self) -> &'static str {
        "aliasing.length"
    }

    fn description(&self) -> &'static str {
        "Identify aliases in from clause and join conditions"
    }

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

In this example, alias `o` is used for the orders table.

```sql
SELECT
    SUM(o.amount) as order_amount,
FROM orders as o
```

**Best practice**

Avoid aliases. Avoid short aliases when aliases are necessary.

See also: Rule_AL07.

```sql
SELECT
    SUM(orders.amount) as order_amount,
FROM orders

SELECT
    replacement_orders.amount,
    previous_orders.amount
FROM
    orders AS replacement_orders
JOIN
    orders AS previous_orders
    ON replacement_orders.id = previous_orders.replacement_id
```
"#
    }

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

    fn eval(&self, context: RuleContext) -> Vec<LintResult> {
        let children = FunctionalContext::new(context.clone())
            .segment()
            .children(None);
        let from_expression_elements = children.recursive_crawl(
            const { &SyntaxSet::new(&[SyntaxKind::FromExpressionElement]) },
            true,
        );
        self.lint_aliases(from_expression_elements.base)
    }

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