sqruff_lib/rules/ambiguous/
am01.rs

1use ahash::AHashMap;
2use sqruff_lib_core::dialects::syntax::{SyntaxKind, SyntaxSet};
3use sqruff_lib_core::parser::segments::base::ErasedSegment;
4
5use crate::core::config::Value;
6use crate::core::rules::base::{CloneRule, ErasedRule, LintResult, Rule, RuleGroups};
7use crate::core::rules::context::RuleContext;
8use crate::core::rules::crawlers::{Crawler, SegmentSeekerCrawler};
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(Some(|it| it.is_type(SyntaxKind::GroupbyClause)))
62            .is_empty()
63        {
64            let distinct = segment
65                .children(Some(|it| it.is_type(SyntaxKind::SelectClause)))
66                .children(Some(|it| it.is_type(SyntaxKind::SelectClauseModifier)))
67                .children(Some(|it| it.is_type(SyntaxKind::Keyword)))
68                .select(
69                    Some(|it: &ErasedSegment| it.is_keyword("DISTINCT")),
70                    None,
71                    None,
72                    None,
73                );
74
75            if !distinct.is_empty() {
76                return vec![LintResult::new(
77                    distinct[0].clone().into(),
78                    Vec::new(),
79                    None,
80                    None,
81                )];
82            }
83        }
84
85        Vec::new()
86    }
87
88    fn crawl_behaviour(&self) -> Crawler {
89        SegmentSeekerCrawler::new(const { SyntaxSet::new(&[SyntaxKind::SelectStatement]) }).into()
90    }
91}