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