sqruff_lib/rules/convention/
cv02.rs1use ahash::AHashMap;
2use smol_str::StrExt;
3use sqruff_lib_core::dialects::syntax::{SyntaxKind, SyntaxSet};
4use sqruff_lib_core::lint_fix::LintFix;
5use sqruff_lib_core::parser::segments::base::SegmentBuilder;
6
7use crate::core::config::Value;
8use crate::core::rules::base::{Erased, ErasedRule, LintResult, Rule, RuleGroups};
9use crate::core::rules::context::RuleContext;
10use crate::core::rules::crawlers::{Crawler, SegmentSeekerCrawler};
11
12#[derive(Debug, Default, Clone)]
40pub struct RuleCV02;
41
42impl Rule for RuleCV02 {
43 fn load_from_config(&self, _config: &AHashMap<String, Value>) -> Result<ErasedRule, String> {
44 Ok(RuleCV02.erased())
45 }
46
47 fn name(&self) -> &'static str {
48 "convention.coalesce"
49 }
50
51 fn description(&self) -> &'static str {
52 "Use 'COALESCE' instead of 'IFNULL' or 'NVL'."
53 }
54
55 fn long_description(&self) -> &'static str {
56 r#"
57**Anti-pattern**
58
59`IFNULL` or `NVL` are used to fill `NULL` values.
60
61```sql
62SELECT ifnull(foo, 0) AS bar,
63FROM baz;
64
65SELECT nvl(foo, 0) AS bar,
66FROM baz;
67```
68
69**Best practice**
70
71Use COALESCE instead. COALESCE is universally supported, whereas Redshift doesn’t support IFNULL and BigQuery doesn’t support NVL. Additionally, COALESCE is more flexible and accepts an arbitrary number of arguments.
72
73```sql
74SELECT coalesce(foo, 0) AS bar,
75FROM baz;
76```
77"#
78 }
79
80 fn groups(&self) -> &'static [RuleGroups] {
81 &[RuleGroups::All, RuleGroups::Convention]
82 }
83
84 fn eval(&self, context: &RuleContext) -> Vec<LintResult> {
85 if !(context.segment.raw().eq_ignore_ascii_case("IFNULL")
93 || context.segment.raw().eq_ignore_ascii_case("NVL"))
94 {
95 return Vec::new();
96 }
97
98 let fix = LintFix::replace(
100 context.segment.clone(),
101 vec![
102 SegmentBuilder::token(
103 context.tables.next_id(),
104 "COALESCE",
105 SyntaxKind::FunctionNameIdentifier,
106 )
107 .finish(),
108 ],
109 None,
110 );
111
112 vec![LintResult::new(
113 context.segment.clone().into(),
114 vec![fix],
115 Some(format!(
116 "Use 'COALESCE' instead of '{}'.",
117 context.segment.raw().to_uppercase_smolstr()
118 )),
119 None,
120 )]
121 }
122
123 fn crawl_behaviour(&self) -> Crawler {
124 SegmentSeekerCrawler::new(const { SyntaxSet::new(&[SyntaxKind::FunctionNameIdentifier]) })
125 .into()
126 }
127}