dm_database_parser_sqllog/
sqllog.rs1use atoi::atoi;
2use memchr::{memchr, memrchr};
3use simdutf8::basic::from_utf8 as simd_from_utf8;
4use std::borrow::Cow;
5
6#[derive(Debug, Clone, PartialEq, Default)]
12pub struct Sqllog<'a> {
13 pub ts: Cow<'a, str>,
15
16 pub meta_raw: Cow<'a, str>,
18
19 pub content_raw: Cow<'a, [u8]>,
21}
22
23impl<'a> Sqllog<'a> {
24 pub fn body(&self) -> Cow<'a, str> {
26 let split = self.find_indicators_split();
27 let body_bytes = &self.content_raw[..split];
28 match simd_from_utf8(body_bytes) {
29 Ok(s) => match &self.content_raw {
30 Cow::Borrowed(_) => unsafe {
31 let ptr = body_bytes.as_ptr();
32 let len = body_bytes.len();
33 let slice = std::slice::from_raw_parts(ptr, len);
34 Cow::Borrowed(std::str::from_utf8_unchecked(slice))
35 },
36 Cow::Owned(_) => Cow::Owned(s.to_string()),
37 },
38 Err(_) => Cow::Owned(String::from_utf8_lossy(body_bytes).into_owned()),
39 }
40 }
41
42 pub fn indicators_raw(&self) -> Option<Cow<'a, str>> {
44 let split = self.find_indicators_split();
45 let indicators_bytes = &self.content_raw[split..];
46 if indicators_bytes.is_empty() {
47 return None;
48 }
49 match &self.content_raw {
50 Cow::Borrowed(_) => unsafe {
51 let ptr = indicators_bytes.as_ptr();
52 let len = indicators_bytes.len();
53 let slice = std::slice::from_raw_parts(ptr, len);
54 Some(Cow::Borrowed(std::str::from_utf8_unchecked(slice)))
55 },
56 Cow::Owned(_) => unsafe {
57 Some(Cow::Owned(
58 std::str::from_utf8_unchecked(indicators_bytes).to_string(),
59 ))
60 },
61 }
62 }
63
64 fn find_indicators_split(&self) -> usize {
65 let body = &self.content_raw;
66 let current_len = body.len();
67 let search_limit = 256;
68 let start_search = current_len.saturating_sub(search_limit);
69 let search_slice = &body[start_search..current_len];
70
71 let mut tail_len = search_slice.len();
72
73 let mut search_end = tail_len;
75 while let Some(idx) = memrchr(b':', &search_slice[..search_end]) {
76 if idx >= 7
77 && &search_slice[idx - 7..idx] == b"EXEC_ID"
78 && idx + 1 < search_slice.len()
79 && search_slice[idx + 1] == b' '
80 {
81 tail_len = idx - 7;
82 break;
83 }
84 if idx == 0 {
85 break;
86 }
87 search_end = idx;
88 }
89
90 let slice_view = &search_slice[..tail_len];
92 search_end = slice_view.len();
93 while let Some(idx) = memrchr(b':', &slice_view[..search_end]) {
94 if idx >= 8
95 && &search_slice[idx - 8..idx] == b"ROWCOUNT"
96 && idx + 1 < search_slice.len()
97 && search_slice[idx + 1] == b' '
98 {
99 tail_len = idx - 8;
100 break;
101 }
102 if idx == 0 {
103 break;
104 }
105 search_end = idx;
106 }
107
108 let slice_view = &search_slice[..tail_len];
110 search_end = slice_view.len();
111 while let Some(idx) = memrchr(b':', &slice_view[..search_end]) {
112 if idx >= 8
113 && &search_slice[idx - 8..idx] == b"EXECTIME"
114 && idx + 1 < search_slice.len()
115 && search_slice[idx + 1] == b' '
116 {
117 tail_len = idx - 8;
118 break;
119 }
120 if idx == 0 {
121 break;
122 }
123 search_end = idx;
124 }
125
126 start_search + tail_len
127 }
128
129 pub fn parse_indicators(&self) -> Option<IndicatorsParts> {
131 let raw_cow = self.indicators_raw()?;
132 let raw = raw_cow.as_ref();
133 let bytes = raw.as_bytes();
134
135 let mut indicators = IndicatorsParts::default();
143 let mut has_indicators = false;
144
145 fn trim(b: &[u8]) -> &[u8] {
147 let start = b
148 .iter()
149 .position(|&x| !x.is_ascii_whitespace())
150 .unwrap_or(0);
151 let end = b
152 .iter()
153 .rposition(|&x| !x.is_ascii_whitespace())
154 .map(|i| i + 1)
155 .unwrap_or(start);
156 &b[start..end]
157 }
158
159 if let Some(idx) = memchr::memmem::find(bytes, b"EXECTIME:")
164 && let Some(end) = memchr(b'(', &bytes[idx..])
165 {
166 let val_bytes = &bytes[idx + 9..idx + end]; let val_trimmed = trim(val_bytes);
168 let s = unsafe { std::str::from_utf8_unchecked(val_trimmed) };
170 if let Ok(time) = s.parse::<f32>() {
171 indicators.execute_time = time;
172 has_indicators = true;
173 }
174 }
175
176 if let Some(idx) = memchr::memmem::find(bytes, b"ROWCOUNT:")
178 && let Some(end) = memchr(b'(', &bytes[idx..])
179 {
180 let val_bytes = &bytes[idx + 9..idx + end];
181 let val_trimmed = trim(val_bytes);
182 if let Some(count) = atoi::<u32>(val_trimmed) {
183 indicators.row_count = count;
184 has_indicators = true;
185 }
186 }
187
188 if let Some(idx) = memchr::memmem::find(bytes, b"EXEC_ID:") {
190 let suffix = &bytes[idx + 8..];
192 let end = memchr(b'.', suffix).unwrap_or(suffix.len());
193 let val_bytes = &suffix[..end];
194 let val_trimmed = trim(val_bytes);
195 if let Some(id) = atoi::<i64>(val_trimmed) {
196 indicators.execute_id = id;
197 has_indicators = true;
198 }
199 }
200
201 if has_indicators {
202 Some(indicators)
203 } else {
204 None
205 }
206 }
207
208 pub fn parse_meta(&self) -> MetaParts<'a> {
210 let meta_bytes = self.meta_raw.as_bytes();
211 let mut meta = MetaParts::default();
212 let mut idx = 0;
213 let len = meta_bytes.len();
214
215 while idx < len {
216 while idx < len && meta_bytes[idx].is_ascii_whitespace() {
218 idx += 1;
219 }
220 if idx >= len {
221 break;
222 }
223
224 let start = idx;
225 let end = match memchr(b' ', &meta_bytes[idx..]) {
227 Some(i) => idx + i,
228 None => len,
229 };
230
231 let part = &meta_bytes[start..end];
232 idx = end;
233
234 if part.starts_with(b"EP[") && part.ends_with(b"]") {
235 let num_bytes = &part[3..part.len() - 1];
237 if let Some(ep) = atoi::<u8>(num_bytes) {
238 meta.ep = ep;
239 }
240 continue;
241 }
242
243 if let Some(sep_idx) = memchr(b':', part) {
244 let key = &part[0..sep_idx];
245 let val = &part[sep_idx + 1..];
246
247 let to_cow_trusted = |bytes: &[u8]| -> Cow<'a, str> {
249 match &self.meta_raw {
264 Cow::Borrowed(_) => unsafe {
265 let ptr = bytes.as_ptr();
268 let len = bytes.len();
269 let slice = std::slice::from_raw_parts(ptr, len);
270 Cow::Borrowed(std::str::from_utf8_unchecked(slice))
271 },
272 Cow::Owned(_) => {
273 unsafe { Cow::Owned(std::str::from_utf8_unchecked(bytes).to_string()) }
275 }
276 }
277 };
278
279 let to_cow = |bytes: &[u8]| -> Cow<'a, str> {
280 match &self.meta_raw {
281 Cow::Borrowed(_) => match simd_from_utf8(bytes) {
282 Ok(_) => unsafe {
283 let ptr = bytes.as_ptr();
284 let len = bytes.len();
285 let slice = std::slice::from_raw_parts(ptr, len);
286 Cow::Borrowed(std::str::from_utf8_unchecked(slice))
287 },
288 Err(_) => Cow::Owned(String::from_utf8_lossy(bytes).into_owned()),
289 },
290 Cow::Owned(_) => match simd_from_utf8(bytes) {
291 Ok(s) => Cow::Owned(s.to_string()),
292 Err(_) => Cow::Owned(String::from_utf8_lossy(bytes).into_owned()),
293 },
294 }
295 };
296
297 match key {
298 b"sess" => meta.sess_id = to_cow_trusted(val),
299 b"thrd" => meta.thrd_id = to_cow_trusted(val),
300 b"user" => meta.username = to_cow(val),
301 b"trxid" => meta.trxid = to_cow_trusted(val),
302 b"stmt" => meta.statement = to_cow_trusted(val),
303 b"appname" => {
304 if val.is_empty() {
305 let mut next_idx = idx;
306 while next_idx < len && meta_bytes[next_idx].is_ascii_whitespace() {
307 next_idx += 1;
308 }
309 if next_idx < len {
310 let next_start = next_idx;
311 let next_end = match memchr(b' ', &meta_bytes[next_idx..]) {
312 Some(i) => next_idx + i,
313 None => len,
314 };
315 let next_part = &meta_bytes[next_start..next_end];
316
317 if next_part.starts_with(b"ip:") && !next_part.starts_with(b"ip::")
318 {
319 } else {
321 meta.appname = to_cow(next_part);
322 idx = next_end;
323 }
324 }
325 } else {
326 meta.appname = to_cow(val);
327 }
328 }
329 b"ip" => {
330 meta.client_ip = to_cow_trusted(val);
331 }
332 _ => {}
333 }
334 }
335 }
336 meta
337 }
338}
339
340#[derive(Debug, Clone, PartialEq, Default)]
344pub struct MetaParts<'a> {
345 pub ep: u8,
347
348 pub sess_id: Cow<'a, str>,
350
351 pub thrd_id: Cow<'a, str>,
353
354 pub username: Cow<'a, str>,
356
357 pub trxid: Cow<'a, str>,
359
360 pub statement: Cow<'a, str>,
362
363 pub appname: Cow<'a, str>,
365
366 pub client_ip: Cow<'a, str>,
368}
369
370#[derive(Debug, Clone, Copy, PartialEq, Default)]
376pub struct IndicatorsParts {
377 pub execute_time: f32,
379
380 pub row_count: u32,
382
383 pub execute_id: i64,
385}