From eaf58af5d5028f3deed46e6f5b34803b0689aac1 Mon Sep 17 00:00:00 2001 From: Kremilly Date: Tue, 25 Feb 2025 14:09:46 -0300 Subject: [PATCH] feat: add diagram handlers to generate ASCII diagrams for table definitions --- src/constants/regexp.rs | 1 + src/handlers/diagram_handlers.rs | 173 +++++++++++++++++++++++++++++++ src/handlers/mod.rs | 3 +- src/plugins/diagram.rs | 160 ++-------------------------- 4 files changed, 183 insertions(+), 154 deletions(-) create mode 100644 src/handlers/diagram_handlers.rs diff --git a/src/constants/regexp.rs b/src/constants/regexp.rs index 64e1b2d..7c16de2 100644 --- a/src/constants/regexp.rs +++ b/src/constants/regexp.rs @@ -4,6 +4,7 @@ impl RegExp { pub const USE_CASE: &'static str = r"(?i)(USE\s+`?)(\w+)(`?)"; pub const CREATE_TABLE: &'static str = r"(?i)CREATE TABLE\s+`?(\w+)`?"; + pub const CREATE_TABLE_ERD: &'static str = r"(?i)CREATE TABLE\s+`?(\w+)`?\s*\("; pub const CREATE_TABLE_INSERTS: &'static str = r"(?i)\b(?:CREATE\s+TABLE|INSERT\s+INTO)\s+`?(\w+)`?"; pub const CREATE_DATABASE_CASES: &'static str = r"(?i)CREATE DATABASE\s+(`?)(\w+)(`?)\s*(IF NOT EXISTS)?;"; diff --git a/src/handlers/diagram_handlers.rs b/src/handlers/diagram_handlers.rs new file mode 100644 index 0000000..074ed1d --- /dev/null +++ b/src/handlers/diagram_handlers.rs @@ -0,0 +1,173 @@ +use regex::Regex; +use std::error::Error; + +use crate::constants::regexp::RegExp; + +#[derive(Debug)] +pub struct ColumnDefinition { + pub name: String, + pub col_type: String, + pub key: Option, +} + +#[derive(Debug)] +pub struct TableDefinition { + pub name: String, + pub columns: Vec, +} + +pub struct DiagramHandlers; + +impl DiagramHandlers { + + pub fn generate_ascii_diagram_with_key(&self, table: &TableDefinition) -> String { + let header_col = "Column"; + let header_type = "Type"; + let header_key = "Key"; + + let col1_width = table.columns + .iter() + .map(|col| col.name.len()) + .max() + .unwrap_or(0) + .max(header_col.len()); + + let col2_width = table.columns + .iter() + .map(|col| col.col_type.len()) + .max() + .unwrap_or(0) + .max(header_type.len()); + + let col3_width = table.columns + .iter() + .map(|col| col.key.as_ref().map(|s| s.len()).unwrap_or(0)) + .max() + .unwrap_or(0) + .max(header_key.len()); + + let border_line = format!( + "+-{:- Result> { + let table_name_re = Regex::new(RegExp::CREATE_TABLE_ERD)?; + let table_name_caps = table_name_re + .captures(sql) + .ok_or("Table name not found")?; + + let table_name = table_name_caps.get(1).unwrap().as_str().to_string(); + + let start = sql.find('(').ok_or("Opening parenthesis not found")?; + let end = sql.rfind(')').ok_or("Closing parenthesis not found")?; + let columns_str = &sql[start + 1..end]; + + let column_lines: Vec<&str> = columns_str + .lines() + .map(|s| s.trim().trim_end_matches(',')) + .filter(|s| !s.is_empty()) + .collect(); + + let mut columns = Vec::new(); + let mut constraints = Vec::new(); + let column_re = Regex::new(r"(?i)^`?(\w+)`?\s+([^\s]+)(.*)$")?; + + for line in &column_lines { + let line_upper = line.to_uppercase(); + + if line_upper.starts_with("PRIMARY KEY") || line_upper.starts_with("FOREIGN KEY") || line_upper.starts_with("KEY") || line_upper.starts_with("CONSTRAINT") { + constraints.push(*line); + continue; + } + + if let Some(caps) = column_re.captures(line) { + let col_name = caps.get(1).unwrap().as_str().to_string(); + let col_type = caps.get(2).unwrap().as_str().to_string(); + + columns.push(ColumnDefinition { + name: col_name, + col_type, + key: None, + }); + } + } + + let cols_in_constraint_re = Regex::new(r"\(([^)]+)\)")?; + for cons_line in constraints { + let cons_line_upper = cons_line.to_uppercase(); + + if let Some(caps) = cols_in_constraint_re.captures(cons_line) { + let cols_str = caps.get(1).unwrap().as_str(); + + let col_names: Vec<&str> = cols_str + .split(',') + .map(|s| s.trim().trim_matches('`')) + .collect(); + + for col in col_names { + for column in columns.iter_mut() { + if column.name == col { + if cons_line_upper.starts_with("PRIMARY KEY") { + column.key = Some("PK".to_string()); + } else if cons_line_upper.contains("FOREIGN KEY") || cons_line_upper.contains("REFERENCES") { + column.key = Some("FK".to_string()); + } else if cons_line_upper.starts_with("KEY") && column.key.is_none() { + column.key = Some("KEY".to_string()); + } + } + } + } + } + } + + Ok(TableDefinition { + name: table_name, + columns, + }) + } + +} diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index 0feadb0..860ca55 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -4,4 +4,5 @@ pub mod html_handlers; pub mod export_handlers; pub mod import_handlers; pub mod queries_builders; -pub mod reports_handlers; \ No newline at end of file +pub mod reports_handlers; +pub mod diagram_handlers; \ No newline at end of file diff --git a/src/plugins/diagram.rs b/src/plugins/diagram.rs index f7a1127..038d981 100644 --- a/src/plugins/diagram.rs +++ b/src/plugins/diagram.rs @@ -8,7 +8,11 @@ use mysql::{ use crate::{ core::connection::Connection, - handlers::queries_builders::MySqlQueriesBuilders, + + handlers::{ + diagram_handlers::DiagramHandlers, + queries_builders::MySqlQueriesBuilders, + }, }; #[derive(Debug)] @@ -53,156 +57,6 @@ impl Diagram { } } - fn parse_show_create_table(&self, sql: &str) -> Result> { - let table_name_re = Regex::new(r"(?i)CREATE\s+TABLE\s+`?(\w+)`?\s*\(")?; - let table_name_caps = table_name_re - .captures(sql) - .ok_or("Table name not found")?; - - let table_name = table_name_caps.get(1).unwrap().as_str().to_string(); - - let start = sql.find('(').ok_or("Opening parenthesis not found")?; - let end = sql.rfind(')').ok_or("Closing parenthesis not found")?; - let columns_str = &sql[start + 1..end]; - - let column_lines: Vec<&str> = columns_str - .lines() - .map(|s| s.trim().trim_end_matches(',')) - .filter(|s| !s.is_empty()) - .collect(); - - let mut columns = Vec::new(); - let mut constraints = Vec::new(); - let column_re = Regex::new(r"(?i)^`?(\w+)`?\s+([^\s]+)(.*)$")?; - - for line in &column_lines { - let line_upper = line.to_uppercase(); - - if line_upper.starts_with("PRIMARY KEY") || line_upper.starts_with("FOREIGN KEY") || line_upper.starts_with("KEY") || line_upper.starts_with("CONSTRAINT") { - constraints.push(*line); - continue; - } - - if let Some(caps) = column_re.captures(line) { - let col_name = caps.get(1).unwrap().as_str().to_string(); - let col_type = caps.get(2).unwrap().as_str().to_string(); - - columns.push(ColumnDefinition { - name: col_name, - col_type, - key: None, - }); - } - } - - let cols_in_constraint_re = Regex::new(r"\(([^)]+)\)")?; - for cons_line in constraints { - let cons_line_upper = cons_line.to_uppercase(); - - if let Some(caps) = cols_in_constraint_re.captures(cons_line) { - let cols_str = caps.get(1).unwrap().as_str(); - - let col_names: Vec<&str> = cols_str - .split(',') - .map(|s| s.trim().trim_matches('`')) - .collect(); - - for col in col_names { - for column in columns.iter_mut() { - if column.name == col { - if cons_line_upper.starts_with("PRIMARY KEY") { - column.key = Some("PK".to_string()); - } else if cons_line_upper.contains("FOREIGN KEY") || cons_line_upper.contains("REFERENCES") { - column.key = Some("FK".to_string()); - } else if cons_line_upper.starts_with("KEY") && column.key.is_none() { - column.key = Some("KEY".to_string()); - } - } - } - } - } - } - - Ok(TableDefinition { - name: table_name, - columns, - }) - } - - fn generate_ascii_diagram_with_key(&self, table: &TableDefinition) -> String { - let header_col = "Column"; - let header_type = "Type"; - let header_key = "Key"; - - let col1_width = table.columns - .iter() - .map(|col| col.name.len()) - .max() - .unwrap_or(0) - .max(header_col.len()); - - let col2_width = table.columns - .iter() - .map(|col| col.col_type.len()) - .max() - .unwrap_or(0) - .max(header_type.len()); - - let col3_width = table.columns - .iter() - .map(|col| col.key.as_ref().map(|s| s.len()).unwrap_or(0)) - .max() - .unwrap_or(0) - .max(header_key.len()); - - let border_line = format!( - "+-{:- Result<(), Box> { let pool = Connection { host: self.host.clone(), @@ -223,8 +77,8 @@ impl Diagram { return Err("No result found for the given table".into()); }; - let table = self.parse_show_create_table(&table_sql)?; - let diagram = self.generate_ascii_diagram_with_key(&table); + let table = DiagramHandlers.parse_show_create_table(&table_sql)?; + let diagram = DiagramHandlers.generate_ascii_diagram_with_key(&table); println!("{}", diagram); Ok(())