sqrust_rules/lint/
create_schema_statement.rs1use sqrust_core::{Diagnostic, FileContext, Rule};
2use sqlparser::ast::Statement;
3
4pub struct CreateSchemaStatement;
5
6impl Rule for CreateSchemaStatement {
7 fn name(&self) -> &'static str {
8 "Lint/CreateSchemaStatement"
9 }
10
11 fn check(&self, ctx: &FileContext) -> Vec<Diagnostic> {
12 if !ctx.parse_errors.is_empty() {
14 return Vec::new();
15 }
16
17 let source = &ctx.source;
18 let source_upper = source.to_uppercase();
19 let mut diags = Vec::new();
20 let mut occurrence: usize = 0;
21
22 for stmt in &ctx.statements {
23 if let Statement::CreateSchema { .. } = stmt {
24 let (line, col) =
25 find_nth_keyword(source, &source_upper, "CREATE", occurrence);
26 occurrence += 1;
27 diags.push(Diagnostic {
28 rule: self.name(),
29 message: "CREATE SCHEMA is managed by dbt project configuration \
30 — avoid manual schema DDL in SQL files"
31 .to_string(),
32 line,
33 col,
34 });
35 }
36 }
37
38 diags
39 }
40}
41
42fn find_nth_keyword(
46 source: &str,
47 source_upper: &str,
48 keyword: &str,
49 nth: usize,
50) -> (usize, usize) {
51 let kw_len = keyword.len();
52 let bytes = source_upper.as_bytes();
53 let text_len = bytes.len();
54
55 let mut count = 0usize;
56 let mut search_from = 0usize;
57
58 while search_from < text_len {
59 let Some(rel) = source_upper[search_from..].find(keyword) else {
60 break;
61 };
62 let abs = search_from + rel;
63
64 let before_ok = abs == 0
65 || {
66 let b = bytes[abs - 1];
67 !b.is_ascii_alphanumeric() && b != b'_'
68 };
69 let after = abs + kw_len;
70 let after_ok = after >= text_len
71 || {
72 let b = bytes[after];
73 !b.is_ascii_alphanumeric() && b != b'_'
74 };
75
76 if before_ok && after_ok {
77 if count == nth {
78 return offset_to_line_col(source, abs);
79 }
80 count += 1;
81 }
82 search_from = abs + 1;
83 }
84
85 (1, 1)
86}
87
88fn offset_to_line_col(source: &str, offset: usize) -> (usize, usize) {
90 let before = &source[..offset];
91 let line = before.chars().filter(|&c| c == '\n').count() + 1;
92 let col = before.rfind('\n').map(|p| offset - p - 1).unwrap_or(offset) + 1;
93 (line, col)
94}