sqruff_lib/rules/ambiguous/
am01.rs

1use 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}