sqruff_lib/rules/ambiguous/
am06.rs

1use ahash::AHashMap;
2use sqruff_lib_core::dialects::syntax::{SyntaxKind, SyntaxSet};
3
4use crate::core::config::Value;
5use crate::core::rules::base::{CloneRule, ErasedRule, LintResult, Rule, RuleGroups};
6use crate::core::rules::context::RuleContext;
7use crate::core::rules::crawlers::{Crawler, SegmentSeekerCrawler};
8use crate::utils::functional::context::FunctionalContext;
9
10#[derive(Clone, Copy)]
11struct PriorGroupByOrderByConvention(GroupByAndOrderByConvention);
12
13#[derive(Debug, Clone)]
14pub struct RuleAM06 {
15    group_by_and_order_by_style: GroupByAndOrderByConvention,
16}
17
18impl Default for RuleAM06 {
19    fn default() -> Self {
20        Self {
21            group_by_and_order_by_style: GroupByAndOrderByConvention::Consistent,
22        }
23    }
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, strum_macros::EnumString)]
27#[strum(serialize_all = "lowercase")]
28enum GroupByAndOrderByConvention {
29    Consistent,
30    Explicit,
31    Implicit,
32}
33
34impl Rule for RuleAM06 {
35    fn load_from_config(&self, config: &AHashMap<String, Value>) -> Result<ErasedRule, String> {
36        Ok(RuleAM06 {
37            group_by_and_order_by_style: config["group_by_and_order_by_style"]
38                .as_string()
39                .unwrap()
40                .parse()
41                .unwrap(),
42        }
43        .erased())
44    }
45
46    fn name(&self) -> &'static str {
47        "ambiguous.column_references"
48    }
49
50    fn description(&self) -> &'static str {
51        "Inconsistent column references in 'GROUP BY/ORDER BY' clauses."
52    }
53
54    fn long_description(&self) -> &'static str {
55        r#"
56**Anti-pattern**
57
58In this example, the ORRDER BY clause mixes explicit and implicit order by column references.
59
60```sql
61SELECT
62    a, b
63FROM foo
64ORDER BY a, b DESC
65```
66
67**Best practice**
68
69If any columns in the ORDER BY clause specify ASC or DESC, they should all do so.
70
71```sql
72SELECT
73    a, b
74FROM foo
75ORDER BY a ASC, b DESC
76```
77"#
78    }
79    fn groups(&self) -> &'static [RuleGroups] {
80        &[RuleGroups::All, RuleGroups::Core, RuleGroups::Ambiguous]
81    }
82
83    fn eval(&self, context: &RuleContext) -> Vec<LintResult> {
84        let skip = FunctionalContext::new(context)
85            .parent_stack()
86            .any(Some(|it| {
87                let ignore_types = [
88                    SyntaxKind::WithingroupClause,
89                    SyntaxKind::WindowSpecification,
90                    SyntaxKind::AggregateOrderByClause,
91                ];
92                ignore_types.iter().any(|&ty| it.is_type(ty))
93            }));
94
95        if skip {
96            return Vec::new();
97        }
98
99        // Initialize the map
100        let mut column_reference_category_map = AHashMap::new();
101        column_reference_category_map.insert(
102            SyntaxKind::ColumnReference,
103            GroupByAndOrderByConvention::Explicit,
104        );
105        column_reference_category_map.insert(
106            SyntaxKind::Expression,
107            GroupByAndOrderByConvention::Explicit,
108        );
109        column_reference_category_map.insert(
110            SyntaxKind::NumericLiteral,
111            GroupByAndOrderByConvention::Implicit,
112        );
113
114        let mut column_reference_category_set: Vec<_> = context
115            .segment
116            .segments()
117            .iter()
118            .filter_map(|segment| column_reference_category_map.get(&segment.get_type()))
119            .collect();
120        column_reference_category_set.dedup();
121
122        if column_reference_category_set.is_empty() {
123            return Vec::new();
124        }
125
126        if self.group_by_and_order_by_style == GroupByAndOrderByConvention::Consistent {
127            if column_reference_category_set.len() > 1 {
128                return vec![LintResult::new(
129                    context.segment.clone().into(),
130                    Vec::new(),
131                    None,
132                    None,
133                )];
134            } else {
135                let current_group_by_order_by_convention =
136                    column_reference_category_set.pop().copied().unwrap();
137
138                if let Some(PriorGroupByOrderByConvention(prior_group_by_order_by_convention)) =
139                    context.try_get::<PriorGroupByOrderByConvention>()
140                {
141                    if prior_group_by_order_by_convention != current_group_by_order_by_convention {
142                        return vec![LintResult::new(
143                            context.segment.clone().into(),
144                            Vec::new(),
145                            None,
146                            None,
147                        )];
148                    }
149                }
150
151                context.set(PriorGroupByOrderByConvention(
152                    current_group_by_order_by_convention,
153                ));
154            }
155        } else if column_reference_category_set
156            .into_iter()
157            .any(|category| *category != self.group_by_and_order_by_style)
158        {
159            return vec![LintResult::new(
160                context.segment.clone().into(),
161                Vec::new(),
162                None,
163                None,
164            )];
165        }
166
167        vec![]
168    }
169
170    fn crawl_behaviour(&self) -> Crawler {
171        SegmentSeekerCrawler::new(
172            const {
173                SyntaxSet::new(&[
174                    SyntaxKind::GroupbyClause,
175                    SyntaxKind::OrderbyClause,
176                    SyntaxKind::GroupingExpressionList,
177                ])
178            },
179        )
180        .into()
181    }
182}