sqrust_rules/structure/
lateral_join.rs1use sqrust_core::{Diagnostic, FileContext, Rule};
2
3pub struct LateralJoin;
7
8impl Rule for LateralJoin {
9 fn name(&self) -> &'static str {
10 "Structure/LateralJoin"
11 }
12
13 fn check(&self, ctx: &FileContext) -> Vec<Diagnostic> {
14 find_violations(&ctx.source, self.name())
15 }
16}
17
18fn find_violations(source: &str, rule_name: &'static str) -> Vec<Diagnostic> {
19 let bytes = source.as_bytes();
20 let len = bytes.len();
21
22 if len == 0 {
23 return Vec::new();
24 }
25
26 let skip = build_skip_set(bytes, len);
27 let keyword = b"LATERAL";
28 let kw_len = keyword.len();
29 let mut diags = Vec::new();
30 let mut i = 0;
31
32 while i + kw_len <= len {
33 if skip[i] {
34 i += 1;
35 continue;
36 }
37
38 let before_ok = i == 0 || !is_word_char(bytes[i - 1]);
40 if !before_ok {
41 i += 1;
42 continue;
43 }
44
45 let matches = bytes[i..i + kw_len]
47 .iter()
48 .zip(keyword.iter())
49 .all(|(a, b)| a.eq_ignore_ascii_case(b));
50
51 if !matches {
52 i += 1;
53 continue;
54 }
55
56 if (i..i + kw_len).any(|k| skip[k]) {
58 i += 1;
59 continue;
60 }
61
62 let after = i + kw_len;
64 let after_ok = after >= len || !is_word_char(bytes[after]);
65
66 if after_ok {
67 let (line, col) = offset_to_line_col(source, i);
68 diags.push(Diagnostic {
69 rule: rule_name,
70 message: "LATERAL join is not supported in all databases (unsupported in SQL Server); verify dialect compatibility".to_string(),
71 line,
72 col,
73 });
74 i = after;
75 } else {
76 i += 1;
77 }
78 }
79
80 diags
81}
82
83#[inline]
84fn is_word_char(ch: u8) -> bool {
85 ch.is_ascii_alphanumeric() || ch == b'_'
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}
95
96fn build_skip_set(bytes: &[u8], len: usize) -> Vec<bool> {
100 let mut skip = vec![false; len];
101 let mut i = 0;
102
103 while i < len {
104 if bytes[i] == b'\'' {
106 skip[i] = true;
107 i += 1;
108 while i < len {
109 skip[i] = true;
110 if bytes[i] == b'\'' {
111 if i + 1 < len && bytes[i + 1] == b'\'' {
112 i += 1;
113 skip[i] = true;
114 i += 1;
115 continue;
116 }
117 i += 1;
118 break;
119 }
120 i += 1;
121 }
122 continue;
123 }
124
125 if bytes[i] == b'"' {
127 skip[i] = true;
128 i += 1;
129 while i < len {
130 skip[i] = true;
131 if bytes[i] == b'"' {
132 if i + 1 < len && bytes[i + 1] == b'"' {
133 i += 1;
134 skip[i] = true;
135 i += 1;
136 continue;
137 }
138 i += 1;
139 break;
140 }
141 i += 1;
142 }
143 continue;
144 }
145
146 if i + 1 < len && bytes[i] == b'/' && bytes[i + 1] == b'*' {
148 skip[i] = true;
149 skip[i + 1] = true;
150 i += 2;
151 while i < len {
152 skip[i] = true;
153 if i + 1 < len && bytes[i] == b'*' && bytes[i + 1] == b'/' {
154 skip[i + 1] = true;
155 i += 2;
156 break;
157 }
158 i += 1;
159 }
160 continue;
161 }
162
163 if i + 1 < len && bytes[i] == b'-' && bytes[i + 1] == b'-' {
165 skip[i] = true;
166 skip[i + 1] = true;
167 i += 2;
168 while i < len && bytes[i] != b'\n' {
169 skip[i] = true;
170 i += 1;
171 }
172 continue;
173 }
174
175 i += 1;
176 }
177
178 skip
179}