sqruff_lib/rules/structure/
st03.rs1use 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}