sqruff_lib/rules/aliasing/
al04.rs

1use std::fmt::Debug;
2
3use ahash::{AHashMap, AHashSet};
4use smol_str::SmolStr;
5use sqruff_lib_core::dialects::common::{AliasInfo, ColumnAliasInfo};
6use sqruff_lib_core::dialects::syntax::{SyntaxKind, SyntaxSet};
7use sqruff_lib_core::helpers::IndexSet;
8use sqruff_lib_core::parser::segments::object_reference::ObjectReferenceSegment;
9use sqruff_lib_core::utils::analysis::select::get_select_statement_info;
10
11use crate::core::config::Value;
12use crate::core::rules::base::{Erased, ErasedRule, LintResult, Rule, RuleGroups};
13use crate::core::rules::context::RuleContext;
14use crate::core::rules::crawlers::{Crawler, SegmentSeekerCrawler};
15
16type Handle<T> = fn(
17    Vec<AliasInfo>,
18    Vec<SmolStr>,
19    Vec<ObjectReferenceSegment>,
20    Vec<ColumnAliasInfo>,
21    Vec<SmolStr>,
22    &T,
23) -> Vec<LintResult>;
24
25#[derive(Debug, Clone)]
26pub struct RuleAL04<T = ()> {
27    pub(crate) lint_references_and_aliases: Handle<T>,
28    pub(crate) context: T,
29}
30
31impl Default for RuleAL04 {
32    fn default() -> Self {
33        RuleAL04 {
34            lint_references_and_aliases: Self::lint_references_and_aliases,
35            context: (),
36        }
37    }
38}
39
40impl<T: Clone + Debug + Send + Sync + 'static> Rule for RuleAL04<T> {
41    fn load_from_config(&self, _config: &AHashMap<String, Value>) -> Result<ErasedRule, String> {
42        Ok(RuleAL04::default().erased())
43    }
44
45    fn name(&self) -> &'static str {
46        "aliasing.unique.table"
47    }
48
49    fn description(&self) -> &'static str {
50        "Table aliases should be unique within each clause."
51    }
52
53    fn long_description(&self) -> &'static str {
54        r#"
55**Anti-pattern**
56
57In this example, the alias t is reused for two different tables:
58
59```sql
60SELECT
61    t.a,
62    t.b
63FROM foo AS t, bar AS t
64
65-- This can also happen when using schemas where the
66-- implicit alias is the table name:
67
68SELECT
69    a,
70    b
71FROM
72    2020.foo,
73    2021.foo
74```
75
76**Best practice**
77
78Make all tables have a unique alias.
79
80```sql
81SELECT
82    f.a,
83    b.b
84FROM foo AS f, bar AS b
85
86-- Also use explicit aliases when referencing two tables
87-- with the same name from two different schemas.
88
89SELECT
90    f1.a,
91    f2.b
92FROM
93    2020.foo AS f1,
94    2021.foo AS f2
95```
96"#
97    }
98
99    fn groups(&self) -> &'static [RuleGroups] {
100        &[RuleGroups::All, RuleGroups::Core, RuleGroups::Aliasing]
101    }
102
103    fn eval(&self, context: &RuleContext) -> Vec<LintResult> {
104        let Some(select_info) =
105            get_select_statement_info(&context.segment, context.dialect.into(), true)
106        else {
107            return Vec::new();
108        };
109
110        let _parent_select = context
111            .parent_stack
112            .iter()
113            .rev()
114            .find(|seg| seg.is_type(SyntaxKind::SelectStatement));
115
116        (self.lint_references_and_aliases)(
117            select_info.table_aliases,
118            select_info.standalone_aliases,
119            select_info.reference_buffer,
120            select_info.col_aliases,
121            select_info.using_cols,
122            &self.context,
123        )
124    }
125
126    fn crawl_behaviour(&self) -> Crawler {
127        SegmentSeekerCrawler::new(const { SyntaxSet::new(&[SyntaxKind::SelectStatement]) }).into()
128    }
129}
130
131impl RuleAL04 {
132    pub fn lint_references_and_aliases(
133        table_aliases: Vec<AliasInfo>,
134        _: Vec<SmolStr>,
135        _: Vec<ObjectReferenceSegment>,
136        _: Vec<ColumnAliasInfo>,
137        _: Vec<SmolStr>,
138        _: &(),
139    ) -> Vec<LintResult> {
140        let mut duplicates = IndexSet::default();
141        let mut seen: AHashSet<_> = AHashSet::new();
142
143        for alias in table_aliases.iter() {
144            if !seen.insert(&alias.ref_str) && !alias.ref_str.is_empty() {
145                duplicates.insert(alias);
146            }
147        }
148
149        duplicates
150            .into_iter()
151            .map(|alias| {
152                LintResult::new(
153                    alias.segment.clone(),
154                    Vec::new(),
155                    format!(
156                        "Duplicate table alias '{}'. Table aliases should be unique.",
157                        alias.ref_str
158                    )
159                    .into(),
160                    None,
161                )
162            })
163            .collect()
164    }
165}