Skip to main content

rigsql_rules/convention/
cv02.rs

1use rigsql_core::{Segment, SegmentType};
2
3use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
4use crate::violation::LintViolation;
5
6/// CV02: Use COALESCE instead of IFNULL or NVL.
7///
8/// COALESCE is ANSI standard and portable across databases.
9#[derive(Debug, Default)]
10pub struct RuleCV02;
11
12impl Rule for RuleCV02 {
13    fn code(&self) -> &'static str {
14        "CV02"
15    }
16    fn name(&self) -> &'static str {
17        "convention.coalesce"
18    }
19    fn description(&self) -> &'static str {
20        "Use COALESCE instead of IFNULL or NVL."
21    }
22    fn explanation(&self) -> &'static str {
23        "COALESCE is the ANSI SQL standard function for handling NULL values. \
24         IFNULL (MySQL) and NVL (Oracle) are database-specific alternatives. \
25         Using COALESCE improves portability and consistency."
26    }
27    fn groups(&self) -> &[RuleGroup] {
28        &[RuleGroup::Convention]
29    }
30    fn is_fixable(&self) -> bool {
31        true
32    }
33
34    fn crawl_type(&self) -> CrawlType {
35        CrawlType::Segment(vec![SegmentType::FunctionCall])
36    }
37
38    fn eval(&self, ctx: &RuleContext) -> Vec<LintViolation> {
39        let children = ctx.segment.children();
40
41        // First non-trivia child should be the function name
42        let func_name = children.iter().find(|c| !c.segment_type().is_trivia());
43
44        if let Some(Segment::Token(t)) = func_name {
45            let name = t.token.text.as_str();
46            if name.eq_ignore_ascii_case("IFNULL") || name.eq_ignore_ascii_case("NVL") {
47                return vec![LintViolation::new(
48                    self.code(),
49                    format!("Use COALESCE instead of '{}'.", name),
50                    t.token.span,
51                )];
52            }
53        }
54
55        vec![]
56    }
57}