rigsql_rules/ambiguous/
am04.rs1use rigsql_core::{Segment, SegmentType};
2
3use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
4use crate::violation::LintViolation;
5
6#[derive(Debug, Default)]
11pub struct RuleAM04;
12
13impl Rule for RuleAM04 {
14 fn code(&self) -> &'static str {
15 "AM04"
16 }
17 fn name(&self) -> &'static str {
18 "ambiguous.column_count"
19 }
20 fn description(&self) -> &'static str {
21 "SELECT * should list columns explicitly."
22 }
23 fn explanation(&self) -> &'static str {
24 "Using SELECT * makes the query's column set depend on the table schema, which \
25 can change over time. Listing columns explicitly makes the query self-documenting \
26 and prevents unexpected changes when columns are added or removed."
27 }
28 fn groups(&self) -> &[RuleGroup] {
29 &[RuleGroup::Ambiguous]
30 }
31 fn is_fixable(&self) -> bool {
32 false
33 }
34
35 fn crawl_type(&self) -> CrawlType {
36 CrawlType::Segment(vec![SegmentType::SelectClause])
37 }
38
39 fn eval(&self, ctx: &RuleContext) -> Vec<LintViolation> {
40 let mut violations = Vec::new();
41 find_bare_stars(ctx.segment, false, &mut violations);
42 violations
43 }
44}
45
46fn find_bare_stars(segment: &Segment, in_function: bool, violations: &mut Vec<LintViolation>) {
48 if segment.segment_type() == SegmentType::Star && !in_function {
49 violations.push(LintViolation::with_msg_key(
50 "AM04",
51 "SELECT * used. List columns explicitly.",
52 segment.span(),
53 "rules.AM04.msg",
54 vec![],
55 ));
56 return;
57 }
58
59 let entering_function = segment.segment_type() == SegmentType::FunctionCall;
60
61 for child in segment.children() {
62 find_bare_stars(child, in_function || entering_function, violations);
63 }
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69 use crate::test_utils::lint_sql;
70
71 #[test]
72 fn test_am04_flags_select_star() {
73 let violations = lint_sql("SELECT * FROM t", RuleAM04);
74 assert_eq!(violations.len(), 1);
75 }
76
77 #[test]
78 fn test_am04_accepts_explicit_columns() {
79 let violations = lint_sql("SELECT a, b FROM t", RuleAM04);
80 assert_eq!(violations.len(), 0);
81 }
82
83 #[test]
84 fn test_am04_accepts_count_star() {
85 let violations = lint_sql("SELECT COUNT(*) FROM t", RuleAM04);
86 assert_eq!(violations.len(), 0);
87 }
88}