sqruff_lib/rules/ambiguous/
am01.rs1use 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}