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