sqrust_rules/lint/
update_without_where.rs1use sqrust_core::{Diagnostic, FileContext, Rule};
2use sqlparser::ast::Statement;
3
4pub struct UpdateWithoutWhere;
5
6impl Rule for UpdateWithoutWhere {
7 fn name(&self) -> &'static str {
8 "Lint/UpdateWithoutWhere"
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 mut diags = Vec::new();
18 let source = &ctx.source;
19 let source_upper = source.to_uppercase();
20
21 for stmt in &ctx.statements {
22 if let Statement::Update { selection, .. } = stmt {
23 if selection.is_none() {
24 let (line, col) = find_keyword_position(source, &source_upper, "UPDATE");
27 diags.push(Diagnostic {
28 rule: self.name(),
29 message: "UPDATE without WHERE clause will update all rows".to_string(),
30 line,
31 col,
32 });
33 }
34 }
35 }
36
37 diags
38 }
39}
40
41fn find_keyword_position(source: &str, source_upper: &str, keyword: &str) -> (usize, usize) {
45 let kw_len = keyword.len();
46 let bytes = source_upper.as_bytes();
47 let text_len = bytes.len();
48
49 let mut search_from = 0usize;
50 while search_from < text_len {
51 let Some(rel) = source_upper[search_from..].find(keyword) else {
52 break;
53 };
54 let abs = search_from + rel;
55
56 let before_ok = abs == 0
57 || {
58 let b = bytes[abs - 1];
59 !b.is_ascii_alphanumeric() && b != b'_'
60 };
61 let after = abs + kw_len;
62 let after_ok = after >= text_len
63 || {
64 let b = bytes[after];
65 !b.is_ascii_alphanumeric() && b != b'_'
66 };
67
68 if before_ok && after_ok {
69 return offset_to_line_col(source, abs);
70 }
71 search_from = abs + 1;
72 }
73
74 (1, 1)
75}
76
77fn offset_to_line_col(source: &str, offset: usize) -> (usize, usize) {
79 let mut line = 1usize;
80 let mut col = 1usize;
81 for (i, ch) in source.char_indices() {
82 if i == offset {
83 break;
84 }
85 if ch == '\n' {
86 line += 1;
87 col = 1;
88 } else {
89 col += 1;
90 }
91 }
92 (line, col)
93}