mysql_slowlog_parser/
types.rs

1use crate::{EntryAdminCommand, SessionLine, SqlStatementContext, StatsLine};
2use bytes::{BufMut, Bytes, BytesMut};
3use sqlparser::ast::{Statement, visit_relations};
4use std::borrow::Cow;
5use std::collections::BTreeSet;
6use std::fmt::{Display, Formatter};
7use std::ops::ControlFlow;
8use winnow_datetime::DateTime;
9
10/// a struct representing a single log entry
11#[derive(Clone, Debug, PartialEq)]
12pub struct Entry {
13    /// holds information about the call made to mysqld
14    pub call: EntryCall,
15    /// holds information about the connection that made the call
16    pub session: EntrySession,
17    /// stats about how long it took for the query to run
18    pub stats: EntryStats,
19    /// information obtained while parsing the SQL query
20    pub sql_attributes: EntrySqlAttributes,
21}
22
23impl Entry {
24    /// returns the time the entry was recorded
25    pub fn log_time(&self) -> DateTime {
26        self.call.log_time.clone()
27    }
28
29    /// returns the mysql user name that requested the command
30    pub fn user_name(&self) -> Cow<str> {
31        String::from_utf8_lossy(&self.session.user_name)
32    }
33
34    /// returns the mysql user name that requested the command
35    pub fn user_name_bytes(&self) -> Bytes {
36        self.session.user_name.clone()
37    }
38
39    /// returns the system user name that requested the command
40    pub fn sys_user_name(&self) -> Cow<str> {
41        String::from_utf8_lossy(&self.session.sys_user_name)
42    }
43
44    /// returns the system user name that requested the command
45    pub fn sys_user_name_bytes(&self) -> Bytes {
46        self.session.sys_user_name.clone()
47    }
48
49    /// returns the host name which requested the command
50    pub fn host_name(&self) -> Option<Cow<str>> {
51        if let Some(v) = &self.session.host_name {
52            Some(String::from_utf8_lossy(v.as_ref()))
53        } else {
54            None
55        }
56    }
57
58    /// returns the host name which requested the command
59    pub fn host_name_bytes(&self) -> Option<Bytes> {
60        self.session.host_name_bytes()
61    }
62
63    /// returns the ip address which requested the command
64    pub fn ip_address(&self) -> Option<Cow<'_, str>> {
65        self.session.ip_address()
66    }
67
68    /// returns the ip address which requested the command
69    pub fn ip_address_bytes(&self) -> Option<Bytes> {
70        self.session.ip_address_bytes()
71    }
72
73    /// returns the the thread id of the session which requested the command
74    pub fn thread_id(&self) -> u32 {
75        self.session.thread_id()
76    }
77
78    /// returns a ref to the entry's EntryStats struct
79    pub fn stats(&self) -> &EntryStats {
80        &self.stats
81    }
82
83    /// returns how long the query took to run
84    pub fn query_time(&self) -> f64 {
85        self.stats.query_time()
86    }
87
88    /// returns how long it took to lock
89    pub fn lock_time(&self) -> f64 {
90        self.stats.lock_time()
91    }
92
93    /// returns number of rows returned when query was executed
94    pub fn rows_sent(&self) -> u32 {
95        self.stats.rows_sent()
96    }
97
98    /// returns how many rows where examined to execute the query
99    pub fn rows_examined(&self) -> u32 {
100        self.stats.rows_examined()
101    }
102}
103
104#[derive(Clone, Debug, PartialEq)]
105///
106pub struct EntrySqlStatement {
107    /// Holds the Statement
108    pub statement: Statement,
109    pub context: Option<SqlStatementContext>,
110}
111
112impl EntrySqlStatement {
113    pub fn sql_context(&self) -> Option<SqlStatementContext> {
114        self.context.clone()
115    }
116
117    pub fn objects(&self) -> Vec<EntrySqlStatementObject> {
118        let mut visited = BTreeSet::new();
119
120        let _ = visit_relations(&self.statement, |relation| {
121            let ident = &relation.0;
122
123            let _ = visited.insert(if ident.len() == 2 {
124                EntrySqlStatementObject {
125                    schema_name: Some(Bytes::from(ident[0].to_string())),
126                    object_name: Bytes::from(ident[1].to_string()),
127                }
128            } else {
129                EntrySqlStatementObject {
130                    schema_name: None,
131                    object_name: ident.last().unwrap().to_string().to_owned().into(),
132                }
133            });
134
135            ControlFlow::<()>::Continue(())
136        });
137        visited.into_iter().collect()
138    }
139
140    pub fn sql_type(&self) -> EntrySqlType {
141        match self.statement {
142            Statement::Query(_) => EntrySqlType::Query,
143            Statement::Insert { .. } => EntrySqlType::Insert,
144            Statement::Update { .. } => EntrySqlType::Update,
145            Statement::Delete { .. } => EntrySqlType::Delete,
146            Statement::CreateTable { .. } => EntrySqlType::CreateTable,
147            Statement::CreateIndex { .. } => EntrySqlType::CreateIndex,
148            Statement::CreateView { .. } => EntrySqlType::CreateView,
149            Statement::AlterTable { .. } => EntrySqlType::AlterTable,
150            Statement::AlterIndex { .. } => EntrySqlType::AlterIndex,
151            Statement::Drop { .. } => EntrySqlType::Drop,
152            Statement::DropFunction { .. } => EntrySqlType::DropFunction,
153            Statement::Set { .. } => EntrySqlType::Set,
154            Statement::ShowVariable { .. } => EntrySqlType::ShowVariable,
155            Statement::ShowVariables { .. } => EntrySqlType::ShowVariables,
156            Statement::ShowCreate { .. } => EntrySqlType::ShowCreate,
157            Statement::ShowColumns { .. } => EntrySqlType::ShowColumns,
158            Statement::ShowTables { .. } => EntrySqlType::ShowTables,
159            Statement::ShowCollation { .. } => EntrySqlType::ShowCollation,
160            Statement::Use { .. } => EntrySqlType::Use,
161            Statement::StartTransaction { .. } => EntrySqlType::StartTransaction,
162            Statement::Commit { .. } => EntrySqlType::Commit,
163            Statement::Rollback { .. } => EntrySqlType::Rollback,
164            Statement::CreateSchema { .. } => EntrySqlType::CreateSchema,
165            Statement::CreateDatabase { .. } => EntrySqlType::CreateDatabase,
166            Statement::Grant { .. } => EntrySqlType::Grant,
167            Statement::Revoke { .. } => EntrySqlType::Revoke,
168            Statement::Kill { .. } => EntrySqlType::Kill,
169            Statement::ExplainTable { .. } => EntrySqlType::ExplainTable,
170            Statement::Explain { .. } => EntrySqlType::Explain,
171            Statement::Savepoint { .. } => EntrySqlType::Savepoint,
172            Statement::LockTables { .. } => EntrySqlType::LockTables,
173            Statement::UnlockTables { .. } => EntrySqlType::LockTables,
174            Statement::Flush { .. } => EntrySqlType::Flush,
175            _ => EntrySqlType::Unknown,
176        }
177    }
178}
179
180impl From<Statement> for EntrySqlStatement {
181    fn from(statement: Statement) -> Self {
182        EntrySqlStatement {
183            statement,
184            context: None,
185        }
186    }
187}
188
189/// Database objects called from within a query
190#[derive(Clone, Debug, Ord, PartialOrd, PartialEq, Eq)]
191pub struct EntrySqlStatementObject {
192    /// optional schema name
193    pub schema_name: Option<Bytes>,
194    /// object name (i.e. table name)
195    pub object_name: Bytes,
196}
197
198impl EntrySqlStatementObject {
199    /// returns the optional schema name of object
200    pub fn schema_name(&self) -> Option<Cow<str>> {
201        if let Some(v) = &self.schema_name {
202            Some(String::from_utf8_lossy(v.as_ref()))
203        } else {
204            None
205        }
206    }
207
208    /// returns the optional schema name of object as bytes
209    pub fn schema_name_bytes(&self) -> Option<Bytes> {
210        self.schema_name.clone()
211    }
212
213    /// returns the object name of object
214    pub fn object_name(&self) -> Cow<str> {
215        String::from_utf8_lossy(self.object_name.as_ref())
216    }
217
218    /// returns the object name of object as bytes
219    pub fn object_name_bytes(&self) -> Bytes {
220        self.object_name.clone()
221    }
222
223    /// full object name \[schema.\]object in Bytes
224    pub fn full_object_name_bytes(&self) -> Bytes {
225        let mut s = if let Some(n) = self.schema_name.clone() {
226            let mut s = BytesMut::from(n.as_ref());
227            s.put_slice(b".");
228            s
229        } else {
230            BytesMut::new()
231        };
232
233        s.put_slice(self.object_name.as_ref());
234        s.freeze()
235    }
236
237    /// full object name \[schema.\]object as a CoW
238    pub fn full_object_name(&self) -> Cow<'_, str> {
239        String::from_utf8_lossy(self.full_object_name_bytes().as_ref())
240            .to_string()
241            .into()
242    }
243}
244
245/// Types of possible statements parsed from the log:
246/// * SqlStatement: parseable statement with a proper SQL AST
247/// * AdminCommand: commands passed from the mysql cli/admin tools
248/// * InvalidStatement: statement which isn't currently parseable as plain-text
249#[derive(Clone, Debug, PartialEq)]
250pub enum EntryStatement {
251    /// AdminCommand: commands passed from the mysql cli/admin tools
252    AdminCommand(EntryAdminCommand),
253    /// SqlStatement: parseable statement with a proper SQL AST
254    SqlStatement(EntrySqlStatement),
255    /// InvalidStatement: statement which isn't currently parseable by `sql-parser` crate
256    InvalidStatement(String),
257}
258
259impl EntryStatement {
260    /// returns the `EntrySqlStatement` objects associated with this statement, if known
261    pub fn objects(&self) -> Option<Vec<EntrySqlStatementObject>> {
262        match self {
263            Self::SqlStatement(s) => Some(s.objects().clone()),
264            _ => None,
265        }
266    }
267
268    /// returns the `EntrySqlType` associated with this statement if known
269    pub fn sql_type(&self) -> Option<EntrySqlType> {
270        match self {
271            Self::SqlStatement(s) => Some(s.sql_type().clone()),
272            _ => None,
273        }
274    }
275
276    /// returns the `SqlStatementContext` associated with this statement
277    pub fn sql_context(&self) -> Option<SqlStatementContext> {
278        match self {
279            Self::SqlStatement(s) => s.sql_context().clone(),
280            _ => None,
281        }
282    }
283}
284
285/// The SQL statement type of the EntrySqlStatement.
286///
287/// NOTE: this is a MySQL specific sub-set of the entries in `sql_parser::ast::Statement`. This is
288/// a simpler enum to match against and displays as the start of the SQL command.
289#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
290pub enum EntrySqlType {
291    /// SELECT
292    Query,
293    /// INSERT
294    Insert,
295    /// UPDATE
296    Update,
297    /// DELETE
298    Delete,
299    /// CREATE TABLE
300    CreateTable,
301    /// CREATE INDEX
302    CreateIndex,
303    /// CREATE VIEW
304    CreateView,
305    /// ALTER TABLE
306    AlterTable,
307    /// ALTER INDEX
308    AlterIndex,
309    /// DROP TABLE
310    Drop,
311    /// DROP FUNCTION
312    DropFunction,
313    /// SET
314    Set,
315    /// SHOW VARIABLE
316    ShowVariable,
317    /// SHOW VARIABLES
318    ShowVariables,
319    /// SHOW CREATE TABLE
320    ShowCreate,
321    /// SHOW COLUMNS
322    ShowColumns,
323    /// SHOW TABLES
324    ShowTables,
325    /// SHOW COLLATION
326    ShowCollation,
327    /// USE
328    Use,
329    /// BEGIN TRANSACTION
330    StartTransaction,
331    /// SET TRANSACTION
332    SetTransaction,
333    /// COMMIT TRANSACTION
334    Commit,
335    /// ROLLBACK TRANSACTION
336    Rollback,
337    /// CREATE SCHEMA
338    CreateSchema,
339    /// CREATE DATABASE
340    CreateDatabase,
341    /// GRANT
342    Grant,
343    /// REVOKE
344    Revoke,
345    /// KILL
346    Kill,
347    /// EXPLAIN TABLE
348    ExplainTable,
349    /// EXPLAIN
350    Explain,
351    /// SAVEPOINT
352    Savepoint,
353    /// LOCK TABLES
354    LockTables,
355    /// UNLOCK TABLES
356    UnlockTables,
357    /// FLUSH
358    Flush,
359    /// Unable to identy if the type of statement
360    Unknown,
361}
362
363impl Display for EntrySqlType {
364    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
365        let out = match self {
366            Self::Query => "SELECT",
367            Self::Insert => "INSERT",
368            Self::Update => "UPDATE",
369            Self::Delete => "DELETE",
370            Self::CreateTable => "CREATE TABLE",
371            Self::CreateIndex => "CREATE INDEX",
372            Self::CreateView => "CREATE VIEW",
373            Self::AlterTable => "ALTER TABLE",
374            Self::AlterIndex => "ALTER INDEX",
375            Self::Drop => "DROP TABLE",
376            Self::DropFunction => "DROP FUNCTION",
377            Self::Set => "SET",
378            Self::ShowVariable => "SHOW VARIABLE",
379            Self::ShowVariables => "SHOW VARIABLES",
380            Self::ShowCreate => "SHOW CREATE TABLE",
381            Self::ShowColumns => "SHOW COLUMNS",
382            Self::ShowTables => "SHOW TABLES",
383            Self::ShowCollation => "SHOW COLLATION",
384            Self::Use => "USE",
385            Self::StartTransaction => "BEGIN TRANSACTION",
386            Self::SetTransaction => "SET TRANSACTION",
387            Self::Commit => "COMMIT TRANSACTION",
388            Self::Rollback => "ROLLBACK TRANSACTION",
389            Self::CreateSchema => "CREATE SCHEMA",
390            Self::CreateDatabase => "CREATE DATABASE",
391            Self::Grant => "GRANT",
392            Self::Revoke => "REVOKE",
393            Self::Kill => "KILL",
394            Self::ExplainTable => "EXPLAIN TABLE",
395            Self::Explain => "EXPLAIN",
396            Self::Savepoint => "SAVEPOINT",
397            Self::LockTables => "LOCK TABLES",
398            Self::UnlockTables => "UNLOCK TABLES",
399            Self::Flush => "FLUSH",
400            Self::Unknown => "NULL",
401        };
402
403        write!(f, "{}", out)
404    }
405}
406
407/// struct containing information about the connection where the query originated
408#[derive(Clone, Debug, PartialEq)]
409pub struct EntrySession {
410    /// user name of the connected user who ran the query
411    pub user_name: Bytes,
412    /// system user name of the connected user who ran the query
413    pub sys_user_name: Bytes,
414    /// hostname of the connected user who ran the query
415    pub host_name: Option<Bytes>,
416    /// ip address of the connected user who ran the query
417    pub ip_address: Option<Bytes>,
418    /// the thread id that the session was conntected on
419    pub thread_id: u32,
420}
421
422impl From<SessionLine> for EntrySession {
423    fn from(line: SessionLine) -> Self {
424        Self {
425            user_name: line.user,
426            sys_user_name: line.sys_user,
427            host_name: line.host,
428            ip_address: line.ip_address,
429            thread_id: line.thread_id,
430        }
431    }
432}
433
434impl EntrySession {
435    /// returns the mysql user name that requested the command
436    pub fn user_name(&self) -> Cow<str> {
437        String::from_utf8_lossy(&self.user_name)
438    }
439
440    /// returns the mysql user name that requested the command
441    pub fn user_name_bytes(&self) -> Bytes {
442        self.user_name.clone()
443    }
444
445    /// returns the system user name that requested the command
446    pub fn sys_user_name(&self) -> Cow<str> {
447        String::from_utf8_lossy(&self.sys_user_name)
448    }
449
450    /// returns the system user name that requested the command
451    pub fn sys_user_name_bytes(&self) -> Bytes {
452        self.sys_user_name.clone()
453    }
454
455    /// returns the host name which requested the command
456    pub fn host_name(&self) -> Option<Cow<str>> {
457        if let Some(v) = &self.host_name {
458            Some(String::from_utf8_lossy(v.as_ref()))
459        } else {
460            None
461        }
462    }
463
464    /// returns the host name which requested the command
465    pub fn host_name_bytes(&self) -> Option<Bytes> {
466        self.host_name.clone()
467    }
468
469    /// returns the ip address which requested the command
470    pub fn ip_address(&self) -> Option<Cow<'_, str>> {
471        if let Some(v) = &self.ip_address {
472            Some(String::from_utf8_lossy(v.as_ref()))
473        } else {
474            None
475        }
476    }
477
478    /// returns the ip address which requested the command
479    pub fn ip_address_bytes(&self) -> Option<Bytes> {
480        self.ip_address.clone()
481    }
482
483    /// returns the the thread id of the which requested the command
484    pub fn thread_id(&self) -> u32 {
485        self.thread_id
486    }
487}
488
489/// struct with information about the Entry's SQL query
490#[derive(Clone, Debug, PartialEq)]
491pub struct EntrySqlAttributes {
492    /// the sql for this entry, possibly with values replaced by parameters
493    pub sql: Bytes,
494    /// the `EntryStatement for this entry
495    pub statement: EntryStatement,
496}
497
498impl EntrySqlAttributes {
499    /// returns the sql statement as bytes
500    pub fn sql_bytes(&self) -> Bytes {
501        self.sql.clone()
502    }
503
504    /// returns the ip address which requested the command
505    pub fn sql(&self) -> Cow<'_, str> {
506        String::from_utf8_lossy(self.sql.as_ref())
507    }
508
509    /// returns the ip address which requested the command
510    pub fn sql_type(&self) -> Option<EntrySqlType> {
511        self.statement.sql_type()
512    }
513
514    /// returns entry sql statment objects
515    pub fn objects(&self) -> Option<Vec<EntrySqlStatementObject>> {
516        self.statement.objects()
517    }
518
519    /// returns the entry's `EntryStatement`
520    pub fn statement(&self) -> &EntryStatement {
521        &self.statement
522    }
523}
524
525/// struct containing details of how long the query took
526#[derive(Clone, Debug, PartialEq)]
527pub struct EntryCall {
528    /// time recorded for the log entry
529    pub log_time: DateTime,
530    /// effective time of NOW() during the query run
531    pub set_timestamp: u32,
532}
533
534impl EntryCall {
535    /// create a new instance of EntryCall
536    pub fn new(log_time: DateTime, set_timestamp: u32) -> Self {
537        Self {
538            log_time,
539            set_timestamp,
540        }
541    }
542
543    /// returns the entry time as an `DateTime`
544    pub fn log_time(&self) -> DateTime {
545        self.log_time.clone()
546    }
547
548    /// returns the time stamp set at the beginning of each entry
549    pub fn set_timestamp(&self) -> u32 {
550        self.set_timestamp
551    }
552}
553
554/// struct with stats on how long a query took and number of rows examined
555#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
556pub struct EntryStats {
557    /// how long the query took
558    pub query_time: f64,
559    /// how long the query held locks
560    pub lock_time: f64,
561    /// how many rows were returned to the client
562    pub rows_sent: u32,
563    /// how many rows were scanned to find result
564    pub rows_examined: u32,
565}
566
567impl EntryStats {
568    /// returns how long the query took to run
569    pub fn query_time(&self) -> f64 {
570        self.query_time
571    }
572
573    /// returns how long it took to lock
574    pub fn lock_time(&self) -> f64 {
575        self.lock_time
576    }
577
578    /// returns number of rows returned when query was executed
579    pub fn rows_sent(&self) -> u32 {
580        self.rows_sent
581    }
582
583    /// returns how many rows where examined to execute the query
584    pub fn rows_examined(&self) -> u32 {
585        self.rows_examined
586    }
587}
588
589impl From<StatsLine> for EntryStats {
590    fn from(line: StatsLine) -> Self {
591        Self {
592            query_time: line.query_time,
593            lock_time: line.lock_time,
594            rows_sent: line.rows_sent,
595            rows_examined: line.rows_examined,
596        }
597    }
598}
599
600/// Values parsed from a query comment, these values are currently overly-specific
601#[derive(Clone, Debug, PartialEq)]
602pub struct EntryContext {
603    /// optional request id, such as an SSRID
604    pub request_id: Option<Bytes>,
605    /// optional caller
606    pub caller: Option<Bytes>,
607    /// optional function/method
608    pub function: Option<Bytes>,
609    /// optional line number
610    pub line: Option<u32>,
611}
612
613impl EntryContext {
614    /// returns the request_id from query comment
615    pub fn request_id(&self) -> Option<Cow<str>> {
616        if let Some(v) = &self.request_id {
617            Some(String::from_utf8_lossy(v.as_ref()))
618        } else {
619            None
620        }
621    }
622
623    /// returns the request_id from query comment
624    pub fn request_id_bytes(&self) -> Option<Bytes> {
625        self.request_id.clone()
626    }
627
628    /// returns the caller from query comment
629    pub fn caller(&self) -> Option<Cow<str>> {
630        if let Some(v) = &self.caller {
631            Some(String::from_utf8_lossy(v.as_ref()))
632        } else {
633            None
634        }
635    }
636
637    /// returns the caller from query comment
638    pub fn caller_bytes(&self) -> Option<Bytes> {
639        self.caller.clone()
640    }
641
642    /// returns the function from query comment
643    pub fn function(&self) -> Option<Cow<str>> {
644        if let Some(v) = &self.function {
645            Some(String::from_utf8_lossy(v.as_ref()))
646        } else {
647            None
648        }
649    }
650
651    /// returns the function from query comment
652    pub fn function_bytes(&self) -> Option<Bytes> {
653        self.function.clone()
654    }
655
656    /// returns the line from query comment
657    pub fn line(&self) -> Option<u32> {
658        self.line
659    }
660}