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