sqrust_rules/structure/
update_with_join.rs1use sqrust_core::{Diagnostic, FileContext, Rule};
2use sqlparser::ast::Statement;
3
4pub struct UpdateWithJoin;
5
6impl Rule for UpdateWithJoin {
7 fn name(&self) -> &'static str {
8 "Structure/UpdateWithJoin"
9 }
10
11 fn check(&self, ctx: &FileContext) -> Vec<Diagnostic> {
12 if !ctx.parse_errors.is_empty() {
13 return Vec::new();
14 }
15
16 let mut diags = Vec::new();
17 let source = &ctx.source;
18 let source_upper = source.to_uppercase();
19
20 for stmt in &ctx.statements {
21 if let Statement::Update { table, from, .. } = stmt {
22 let table_has_join = !table.joins.is_empty();
26 let has_from = from.is_some();
27
28 if table_has_join || has_from {
29 let (line, col) =
30 find_keyword_position(source, &source_upper, "UPDATE");
31 diags.push(Diagnostic {
32 rule: self.name(),
33 message:
34 "UPDATE with JOIN/FROM syntax is SQL Server/PostgreSQL-specific \
35 — use a correlated subquery for portability"
36 .to_string(),
37 line,
38 col,
39 });
40 }
41 }
42 }
43
44 diags
45 }
46}
47
48fn find_keyword_position(source: &str, source_upper: &str, keyword: &str) -> (usize, usize) {
54 let kw_len = keyword.len();
55 let bytes = source_upper.as_bytes();
56 let text_len = bytes.len();
57
58 let mut search_from = 0usize;
59 while search_from < text_len {
60 let Some(rel) = source_upper[search_from..].find(keyword) else {
61 break;
62 };
63 let abs = search_from + rel;
64
65 let before_ok = abs == 0
66 || {
67 let b = bytes[abs - 1];
68 !b.is_ascii_alphanumeric() && b != b'_'
69 };
70 let after = abs + kw_len;
71 let after_ok = after >= text_len
72 || {
73 let b = bytes[after];
74 !b.is_ascii_alphanumeric() && b != b'_'
75 };
76
77 if before_ok && after_ok {
78 return offset_to_line_col(source, abs);
79 }
80 search_from = abs + 1;
81 }
82
83 (1, 1)
84}
85
86fn offset_to_line_col(source: &str, offset: usize) -> (usize, usize) {
88 let mut line = 1usize;
89 let mut col = 1usize;
90 for (i, ch) in source.char_indices() {
91 if i == offset {
92 break;
93 }
94 if ch == '\n' {
95 line += 1;
96 col = 1;
97 } else {
98 col += 1;
99 }
100 }
101 (line, col)
102}