sqruff_lib/rules/ambiguous/
am01.rs1use ahash::AHashMap;
2use sqruff_lib_core::dialects::syntax::{SyntaxKind, SyntaxSet};
3use sqruff_lib_core::parser::segments::ErasedSegment;
4
5use crate::core::config::Value;
6use crate::core::rules::context::RuleContext;
7use crate::core::rules::crawlers::{Crawler, SegmentSeekerCrawler};
8use crate::core::rules::{Erased as _, ErasedRule, LintResult, Rule, RuleGroups};
9use crate::utils::functional::context::FunctionalContext;
10
11#[derive(Debug, Clone, Default)]
12pub struct RuleAM01;
13
14impl Rule for RuleAM01 {
15 fn load_from_config(&self, _config: &AHashMap<String, Value>) -> Result<ErasedRule, String> {
16 Ok(RuleAM01 {}.erased())
17 }
18
19 fn name(&self) -> &'static str {
20 "ambiguous.distinct"
21 }
22
23 fn description(&self) -> &'static str {
24 "Ambiguous use of 'DISTINCT' in a 'SELECT' statement with 'GROUP BY'."
25 }
26
27 fn long_description(&self) -> &'static str {
28 r#"
29**Anti-pattern**
30
31`DISTINCT` and `GROUP BY` are conflicting.
32
33```sql
34SELECT DISTINCT
35 a
36FROM foo
37GROUP BY a
38```
39
40**Best practice**
41
42Remove `DISTINCT` or `GROUP BY`. In our case, removing `GROUP BY` is better.
43
44
45```sql
46SELECT DISTINCT
47 a
48FROM foo
49```
50"#
51 }
52
53 fn groups(&self) -> &'static [RuleGroups] {
54 &[RuleGroups::All, RuleGroups::Core, RuleGroups::Ambiguous]
55 }
56
57 fn eval(&self, context: &RuleContext) -> Vec<LintResult> {
58 let segment = FunctionalContext::new(context).segment();
59
60 if !segment
61 .children_where(|it| it.is_type(SyntaxKind::GroupbyClause))
62 .is_empty()
63 {
64 let distinct = segment
65 .children_where(|it| it.is_type(SyntaxKind::SelectClause))
66 .children_where(|it| it.is_type(SyntaxKind::SelectClauseModifier))
67 .children_where(|it| it.is_type(SyntaxKind::Keyword))
68 .filter(|it: &ErasedSegment| it.is_keyword("DISTINCT"));
69
70 if !distinct.is_empty() {
71 return vec![LintResult::new(
72 distinct[0].clone().into(),
73 Vec::new(),
74 None,
75 None,
76 )];
77 }
78 }
79
80 Vec::new()
81 }
82
83 fn crawl_behaviour(&self) -> Crawler {
84 SegmentSeekerCrawler::new(const { SyntaxSet::new(&[SyntaxKind::SelectStatement]) }).into()
85 }
86}