sqrust_rules/ambiguous/
interval_expression.rs1use sqrust_core::{Diagnostic, FileContext, Rule};
2
3pub struct IntervalExpression;
4
5const INTERVAL_KEYWORD: &[u8] = b"INTERVAL";
6const INTERVAL_KEYWORD_LEN: usize = INTERVAL_KEYWORD.len();
7
8impl Rule for IntervalExpression {
9 fn name(&self) -> &'static str {
10 "Ambiguous/IntervalExpression"
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 mut diags = Vec::new();
28 let mut i = 0;
29
30 while i + INTERVAL_KEYWORD_LEN <= len {
31 if skip[i] {
32 i += 1;
33 continue;
34 }
35
36 let before_ok = i == 0 || !is_word_char(bytes[i - 1]);
38 if before_ok && bytes[i..i + INTERVAL_KEYWORD_LEN].eq_ignore_ascii_case(INTERVAL_KEYWORD) {
39 let after = i + INTERVAL_KEYWORD_LEN;
40 let after_ok = after >= len || !is_word_char(bytes[after]);
43 if after_ok {
44 let (line, col) = line_col(source, i);
45 diags.push(Diagnostic {
46 rule: rule_name,
47 message: "INTERVAL expression syntax varies across databases — \
48 SQL Server uses DATEADD(), MySQL/BigQuery use INTERVAL N UNIT, \
49 PostgreSQL uses INTERVAL 'N unit'; consider using a date \
50 arithmetic function abstraction"
51 .to_string(),
52 line,
53 col,
54 });
55 i += INTERVAL_KEYWORD_LEN;
56 continue;
57 }
58 }
59
60 i += 1;
61 }
62
63 diags
64}
65
66#[inline]
67fn is_word_char(ch: u8) -> bool {
68 ch.is_ascii_alphanumeric() || ch == b'_'
69}
70
71fn line_col(source: &str, offset: usize) -> (usize, usize) {
72 let before = &source[..offset.min(source.len())];
73 let line = before.chars().filter(|&c| c == '\n').count() + 1;
74 let col = before.rfind('\n').map(|p| offset - p - 1).unwrap_or(offset) + 1;
75 (line, col)
76}
77
78fn build_skip_set(bytes: &[u8], len: usize) -> Vec<bool> {
81 let mut skip = vec![false; len];
82 let mut i = 0;
83
84 while i < len {
85 if bytes[i] == b'\'' {
87 skip[i] = true;
88 i += 1;
89 while i < len {
90 skip[i] = true;
91 if bytes[i] == b'\'' {
92 if i + 1 < len && bytes[i + 1] == b'\'' {
93 i += 1;
94 skip[i] = true;
95 i += 1;
96 continue;
97 }
98 i += 1;
99 break;
100 }
101 i += 1;
102 }
103 continue;
104 }
105
106 if bytes[i] == b'"' {
108 skip[i] = true;
109 i += 1;
110 while i < len {
111 skip[i] = true;
112 if bytes[i] == b'"' {
113 if i + 1 < len && bytes[i + 1] == b'"' {
114 i += 1;
115 skip[i] = true;
116 i += 1;
117 continue;
118 }
119 i += 1;
120 break;
121 }
122 i += 1;
123 }
124 continue;
125 }
126
127 if i + 1 < len && bytes[i] == b'/' && bytes[i + 1] == b'*' {
129 skip[i] = true;
130 skip[i + 1] = true;
131 i += 2;
132 while i < len {
133 skip[i] = true;
134 if i + 1 < len && bytes[i] == b'*' && bytes[i + 1] == b'/' {
135 skip[i + 1] = true;
136 i += 2;
137 break;
138 }
139 i += 1;
140 }
141 continue;
142 }
143
144 if i + 1 < len && bytes[i] == b'-' && bytes[i + 1] == b'-' {
146 skip[i] = true;
147 skip[i + 1] = true;
148 i += 2;
149 while i < len && bytes[i] != b'\n' {
150 skip[i] = true;
151 i += 1;
152 }
153 continue;
154 }
155
156 i += 1;
157 }
158
159 skip
160}