sqruff_lib/rules/structure/
st03.rs

1use std::cell::RefCell;
2
3use ahash::AHashMap;
4use smol_str::StrExt;
5use sqruff_lib_core::dialects::syntax::{SyntaxKind, SyntaxSet};
6use sqruff_lib_core::helpers::IndexMap;
7use sqruff_lib_core::utils::analysis::query::Query;
8
9use crate::core::config::Value;
10use crate::core::rules::base::{Erased, ErasedRule, LintResult, Rule, RuleGroups};
11use crate::core::rules::context::RuleContext;
12use crate::core::rules::crawlers::{Crawler, SegmentSeekerCrawler};
13
14#[derive(Debug, Default, Clone)]
15pub struct RuleST03;
16
17impl Rule for RuleST03 {
18    fn load_from_config(&self, _config: &AHashMap<String, Value>) -> Result<ErasedRule, String> {
19        Ok(RuleST03.erased())
20    }
21
22    fn name(&self) -> &'static str {
23        "structure.unused_cte"
24    }
25
26    fn description(&self) -> &'static str {
27        "Query defines a CTE (common-table expression) but does not use it."
28    }
29
30    fn long_description(&self) -> &'static str {
31        r#"
32**Anti-pattern**
33
34Defining a CTE that is not used by the query is harmless, but it means the code is unnecessary and could be removed.
35
36```sql
37WITH cte1 AS (
38  SELECT a
39  FROM t
40),
41cte2 AS (
42  SELECT b
43  FROM u
44)
45
46SELECT *
47FROM cte1
48```
49
50**Best practice**
51
52Remove unused CTEs.
53
54```sql
55WITH cte1 AS (
56  SELECT a
57  FROM t
58)
59
60SELECT *
61FROM cte1
62```
63"#
64    }
65
66    fn groups(&self) -> &'static [RuleGroups] {
67        &[RuleGroups::All, RuleGroups::Core, RuleGroups::Structure]
68    }
69
70    fn eval(&self, context: &RuleContext) -> Vec<LintResult> {
71        let mut result = Vec::new();
72        let query: Query<'_, ()> = Query::from_root(&context.segment, context.dialect).unwrap();
73
74        let mut remaining_ctes: IndexMap<_, _> = RefCell::borrow(&query.inner)
75            .ctes
76            .keys()
77            .map(|it| (it.to_uppercase_smolstr(), it.clone()))
78            .collect();
79
80        for reference in context.segment.recursive_crawl(
81            const { &SyntaxSet::new(&[SyntaxKind::TableReference]) },
82            true,
83            const { &SyntaxSet::single(SyntaxKind::WithCompoundStatement) },
84            true,
85        ) {
86            remaining_ctes.shift_remove(&reference.raw().to_uppercase_smolstr());
87        }
88
89        for name in remaining_ctes.values() {
90            let tmp = RefCell::borrow(&query.inner);
91            let cte = RefCell::borrow(&tmp.ctes[name].inner);
92            result.push(LintResult::new(
93                cte.cte_name_segment.clone(),
94                Vec::new(),
95                Some(format!(
96                    "Query defines CTE \"{}\" but does not use it.",
97                    cte.cte_name_segment.as_ref().unwrap().raw()
98                )),
99                None,
100            ));
101        }
102
103        result
104    }
105
106    fn crawl_behaviour(&self) -> Crawler {
107        SegmentSeekerCrawler::new(const { SyntaxSet::new(&[SyntaxKind::WithCompoundStatement]) })
108            .into()
109    }
110}