rigsql_rules/rigsql/
rg04.rs1use rigsql_core::SegmentType;
2
3use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
4use crate::violation::LintViolation;
5
6#[derive(Debug, Default)]
11pub struct RuleRG04;
12
13impl Rule for RuleRG04 {
14 fn code(&self) -> &'static str {
15 "RG04"
16 }
17 fn name(&self) -> &'static str {
18 "rigsql.having_without_group_by"
19 }
20 fn description(&self) -> &'static str {
21 "Use of HAVING without GROUP BY."
22 }
23 fn explanation(&self) -> &'static str {
24 "HAVING is designed to filter grouped results. Using HAVING without GROUP BY \
25 treats the entire result set as a single group, which is almost always a mistake. \
26 Use WHERE for filtering ungrouped rows, or add the missing GROUP BY clause."
27 }
28 fn groups(&self) -> &[RuleGroup] {
29 &[RuleGroup::Convention]
30 }
31 fn is_fixable(&self) -> bool {
32 false
33 }
34
35 fn crawl_type(&self) -> CrawlType {
36 CrawlType::Segment(vec![SegmentType::SelectStatement])
37 }
38
39 fn eval(&self, ctx: &RuleContext) -> Vec<LintViolation> {
40 let children = ctx.segment.children();
41
42 let has_having = children
43 .iter()
44 .any(|c| c.segment_type() == SegmentType::HavingClause);
45 let has_group_by = children
46 .iter()
47 .any(|c| c.segment_type() == SegmentType::GroupByClause);
48
49 if has_having && !has_group_by {
50 let having_span = children
51 .iter()
52 .find(|c| c.segment_type() == SegmentType::HavingClause)
53 .map(|c| c.span())
54 .unwrap_or(ctx.segment.span());
55
56 return vec![LintViolation::with_msg_key(
57 self.code(),
58 "HAVING clause without GROUP BY. Use WHERE for ungrouped filtering.",
59 having_span,
60 "rules.RG04.msg",
61 vec![],
62 )];
63 }
64
65 vec![]
66 }
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72 use crate::test_utils::lint_sql;
73
74 #[test]
75 fn test_rg04_flags_having_without_group_by() {
76 let violations = lint_sql("SELECT COUNT(*) FROM t HAVING COUNT(*) > 1", RuleRG04);
77 assert_eq!(violations.len(), 1);
78 }
79
80 #[test]
81 fn test_rg04_accepts_having_with_group_by() {
82 let violations = lint_sql(
83 "SELECT a, COUNT(*) FROM t GROUP BY a HAVING COUNT(*) > 1",
84 RuleRG04,
85 );
86 assert_eq!(violations.len(), 0);
87 }
88}