dm_database_parser_sqllog/
tools.rs1use once_cell::sync::Lazy;
2use super::matcher::Matcher;
3
4#[allow(dead_code)]
6static DEFAULT_PATTERNS: &[&str] = &[
7 "EP[", "sess:", "thrd:", "user:", "trxid:", "stmt:", "appname:",
8];
9
10#[allow(dead_code)]
12static DEFAULT_MATCHER: Lazy<Matcher> = Lazy::new(|| Matcher::from_patterns(DEFAULT_PATTERNS));
13
14#[inline(always)]
15pub fn is_ts_millis(s: &str) -> bool {
16 let bytes = s.as_bytes();
17 if bytes.len() != 23 {
18 return false;
19 }
20 if bytes[4] != b'-'
22 || bytes[7] != b'-'
23 || bytes[10] != b' '
24 || bytes[13] != b':'
25 || bytes[16] != b':'
26 || bytes[19] != b'.'
27 {
28 return false;
29 }
30 for &i in &[
32 0usize, 1, 2, 3, 5, 6, 8, 9, 11, 12, 14, 15, 17, 18, 20, 21, 22,
33 ] {
34 if !bytes[i].is_ascii_digit() {
35 return false;
36 }
37 }
38 true
39}
40
41#[inline(always)]
44pub fn is_ts_millis_bytes(bytes: &[u8]) -> bool {
45 if bytes.len() != 23 {
46 return false;
47 }
48 if bytes[4] != b'-'
49 || bytes[7] != b'-'
50 || bytes[10] != b' '
51 || bytes[13] != b':'
52 || bytes[16] != b':'
53 || bytes[19] != b'.'
54 {
55 return false;
56 }
57 for &i in &[
59 0usize, 1, 2, 3, 5, 6, 8, 9, 11, 12, 14, 15, 17, 18, 20, 21, 22,
60 ] {
61 if !bytes[i].is_ascii_digit() {
62 return false;
63 }
64 }
65 true
66}
67
68pub fn is_record_start(line: &str) -> bool {
92 if line.len() < 23 {
95 return false;
96 }
97
98 if !is_ts_millis(&line[0..23]) {
100 return false;
101 }
102
103 let rest = &line[23..];
106 let rest_bytes = rest.as_bytes();
108 if rest_bytes.len() < 2 || rest_bytes[0] != b' ' || rest_bytes[1] != b'(' {
109 return false;
110 }
111 let open = 1usize; let close = match rest[open..].find(')') {
114 Some(p) => open + p,
115 None => return false,
117 };
118 let meta = &rest[open + 1..close];
120
121 let matcher = &*DEFAULT_MATCHER;
124 let fp = matcher.find_first_positions(meta.as_bytes());
125 let mut first_pos: [Option<usize>; 7] = [None, None, None, None, None, None, None];
126 for (i, p) in fp.into_iter().enumerate().take(first_pos.len()) {
127 first_pos[i] = p;
128 }
129
130 if first_pos.iter().any(|p| p.is_none()) {
132 return false;
133 }
134
135 let mut prev: Option<usize> = None;
137 for p in &first_pos {
138 let cur = p.unwrap();
139 if let Some(prev_pos) = prev {
140 if cur <= prev_pos {
142 return false;
143 }
144 }
145 prev = Some(cur);
146 }
147
148 true
149}
150
151#[allow(dead_code)]
153pub fn prewarm() {
154 let _ = &*DEFAULT_MATCHER;
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161
162 #[test]
163 fn test_is_ts_millis() {
164 let valid_ts = "2023-10-05 14:23:45.123";
165 let invalid_ts_1 = "2023/10/05 14:23:45.123"; let invalid_ts_2 = "2023-10-05 14:23:45"; let invalid_ts_3 = "2023-10-05T14:23:45.123"; let invalid_ts_4 = "2023-10-05 14:23:4a.123"; assert!(is_ts_millis(valid_ts));
171 assert!(!is_ts_millis(invalid_ts_1));
172 assert!(!is_ts_millis(invalid_ts_2));
173 assert!(!is_ts_millis(invalid_ts_3));
174 assert!(!is_ts_millis(invalid_ts_4));
175 }
176
177 #[test]
178 fn test_is_record_start_basic() {
179 let line = "2025-08-12 10:57:09.561 (EP[0] sess:abc thrd:1 user:joe trxid:123 stmt:0x1 appname:my)";
180 assert!(is_record_start(line));
181 }
182
183 #[test]
184 fn test_is_record_start_different_order() {
185 let line = "2025-08-12 10:57:09.561 (user:joe appname:my trxid:123 thrd:1 sess:abc stmt:0x1 EP[0])";
187 assert!(!is_record_start(line));
188 }
189
190 #[test]
191 fn test_is_record_start_correct_order_complex() {
192 let line = "2025-08-12 10:57:09.561 (EP[0] foobar sess:abc baz thrd:1 qux user:joe trxid:123 stmt:0x1 zz appname:my)";
194 assert!(is_record_start(line));
195 }
196
197 #[test]
198 fn test_is_record_start_leading_whitespace() {
199 let line = " 2025-08-12 10:57:09.561 (EP[0] sess:abc thrd:1 user:joe trxid:123 stmt:0x1 appname:my)";
201 assert!(!is_record_start(line));
202 }
203
204 #[test]
205 fn test_is_record_start_missing_keyword() {
206 let line = "2025-08-12 10:57:09.561 (EP[0] sess:abc thrd:1 trxid:123 stmt:0x1 appname:my)"; assert!(!is_record_start(line));
208 }
209
210 #[test]
211 fn test_is_record_start_keyword_outside_parentheses() {
212 let line =
213 "2025-08-12 10:57:09.561 EP[0] sess:abc thrd:1 user:joe trxid:123 stmt:0x1 appname:my";
214 assert!(!is_record_start(line));
216 }
217
218 #[test]
219 fn test_is_record_start_no_parentheses() {
220 let line = "2025-08-12 10:57:09.561 some random text";
221 assert!(!is_record_start(line));
222 }
223
224 #[test]
225 fn test_is_record_start_invalid_timestamp() {
226 let line =
227 "2025-08-12T10:57:09 (EP[0] sess:abc thrd:1 user:joe trxid:123 stmt:0x1 appname:my)";
228 assert!(!is_record_start(line));
229 }
230}