From ae639a3da27092b6e4be820362c359b2ae7f359c Mon Sep 17 00:00:00 2001 From: Takayuki Maeda <41065217+TaKO8Ki@users.noreply.github.com> Date: Sat, 28 Aug 2021 17:16:38 +0900 Subject: [PATCH] get indexes (#46) --- src/app.rs | 37 +++++++++++++++++++- src/components/command.rs | 4 +++ src/components/tab.rs | 5 +++ src/config.rs | 2 ++ src/database/mod.rs | 5 +++ src/database/mysql.rs | 63 +++++++++++++++++++++++++++++++++ src/database/postgres.rs | 74 +++++++++++++++++++++++++++++++++++++++ 7 files changed, 189 insertions(+), 1 deletion(-) diff --git a/src/app.rs b/src/app.rs index 74c8bd2..ea00d1b 100644 --- a/src/app.rs +++ b/src/app.rs @@ -27,6 +27,7 @@ pub struct App { column_table: TableComponent, constraint_table: TableComponent, foreign_key_table: TableComponent, + index_table: TableComponent, focus: Focus, tab: TabComponent, help: HelpComponent, @@ -47,6 +48,7 @@ impl App { column_table: TableComponent::new(config.key_config.clone()), constraint_table: TableComponent::new(config.key_config.clone()), foreign_key_table: TableComponent::new(config.key_config.clone()), + index_table: TableComponent::new(config.key_config.clone()), tab: TabComponent::new(config.key_config.clone()), help: HelpComponent::new(config.key_config.clone()), databases: DatabasesComponent::new(config.key_config.clone()), @@ -111,6 +113,10 @@ impl App { right_chunks[1], matches!(self.focus, Focus::Table), )?, + Tab::Indexes => { + self.index_table + .draw(f, right_chunks[1], matches!(self.focus, Focus::Table))? + } } self.error.draw(f, Rect::default(), false)?; self.help.draw(f, Rect::default(), false)?; @@ -229,7 +235,7 @@ impl App { .unwrap() .get_foreign_keys(&database, &table) .await?; - if !constraints.is_empty() { + if !foreign_keys.is_empty() { self.foreign_key_table.update( foreign_keys .iter() @@ -240,6 +246,24 @@ impl App { table.clone(), ); } + self.index_table.reset(); + let indexes = self + .pool + .as_ref() + .unwrap() + .get_indexes(&database, &table) + .await?; + if !indexes.is_empty() { + self.index_table.update( + indexes + .iter() + .map(|c| c.columns()) + .collect::>>(), + indexes.get(0).unwrap().fields(), + database.clone(), + table.clone(), + ); + } self.table_status .update(self.record_table.len() as u64, table); } @@ -397,6 +421,17 @@ impl App { } }; } + Tab::Indexes => { + if self.index_table.event(key)?.is_consumed() { + return Ok(EventState::Consumed); + }; + + if key == self.config.key_config.copy { + if let Some(text) = self.index_table.selected_cells() { + copy_to_clipboard(text.as_str())? + } + }; + } }; } } diff --git a/src/components/command.rs b/src/components/command.rs index bb54d1c..cc13d7a 100644 --- a/src/components/command.rs +++ b/src/components/command.rs @@ -117,6 +117,10 @@ pub fn tab_foreign_keys(key: &KeyConfig) -> CommandText { ) } +pub fn tab_indexes(key: &KeyConfig) -> CommandText { + CommandText::new(format!("Indexes [{}]", key.tab_indexes), CMD_GROUP_TABLE) +} + pub fn toggle_tabs(key_config: &KeyConfig) -> CommandText { CommandText::new( format!( diff --git a/src/components/tab.rs b/src/components/tab.rs index 47cda93..f825499 100644 --- a/src/components/tab.rs +++ b/src/components/tab.rs @@ -19,6 +19,7 @@ pub enum Tab { Columns, Constraints, ForeignKeys, + Indexes, } impl std::fmt::Display for Tab { @@ -50,6 +51,7 @@ impl TabComponent { command::tab_columns(&self.key_config).name, command::tab_constraints(&self.key_config).name, command::tab_foreign_keys(&self.key_config).name, + command::tab_indexes(&self.key_config).name, ] } } @@ -87,6 +89,9 @@ impl Component for TabComponent { } else if key == self.key_config.tab_foreign_keys { self.selected_tab = Tab::ForeignKeys; return Ok(EventState::Consumed); + } else if key == self.key_config.tab_indexes { + self.selected_tab = Tab::Indexes; + return Ok(EventState::Consumed); } Ok(EventState::NotConsumed) } diff --git a/src/config.rs b/src/config.rs index 631cf0a..4b522e8 100644 --- a/src/config.rs +++ b/src/config.rs @@ -90,6 +90,7 @@ pub struct KeyConfig { pub tab_columns: Key, pub tab_constraints: Key, pub tab_foreign_keys: Key, + pub tab_indexes: Key, } impl Default for KeyConfig { @@ -121,6 +122,7 @@ impl Default for KeyConfig { tab_columns: Key::Char('2'), tab_constraints: Key::Char('3'), tab_foreign_keys: Key::Char('4'), + tab_indexes: Key::Char('5'), } } } diff --git a/src/database/mod.rs b/src/database/mod.rs index 2b0cc5c..5bdd866 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -35,6 +35,11 @@ pub trait Pool { database: &Database, table: &Table, ) -> anyhow::Result>>; + async fn get_indexes( + &self, + database: &Database, + table: &Table, + ) -> anyhow::Result>>; async fn close(&self); } diff --git a/src/database/mysql.rs b/src/database/mysql.rs index 664a661..c9f91f3 100644 --- a/src/database/mysql.rs +++ b/src/database/mysql.rs @@ -108,6 +108,36 @@ impl TableRow for ForeignKey { } } +pub struct Index { + name: Option, + column_name: Option, + r#type: Option, +} + +impl TableRow for Index { + fn fields(&self) -> Vec { + vec![ + "name".to_string(), + "column_name".to_string(), + "type".to_string(), + ] + } + + fn columns(&self) -> Vec { + vec![ + self.name + .as_ref() + .map_or(String::new(), |name| name.to_string()), + self.column_name + .as_ref() + .map_or(String::new(), |column_name| column_name.to_string()), + self.r#type + .as_ref() + .map_or(String::new(), |r#type| r#type.to_string()), + ] + } +} + #[async_trait] impl Pool for MySqlPool { async fn get_databases(&self) -> anyhow::Result> { @@ -271,6 +301,39 @@ impl Pool for MySqlPool { Ok(foreign_keys) } + async fn get_indexes( + &self, + database: &Database, + table: &Table, + ) -> anyhow::Result>> { + let mut rows = sqlx::query( + " + SELECT + DISTINCT TABLE_NAME, + INDEX_NAME, + INDEX_TYPE, + COLUMN_NAME + FROM + INFORMATION_SCHEMA.STATISTICS + WHERE + TABLE_SCHEMA = ? + AND TABLE_NAME = ? + ", + ) + .bind(&database.name) + .bind(&table.name) + .fetch(&self.pool); + let mut foreign_keys: Vec> = vec![]; + while let Some(row) = rows.try_next().await? { + foreign_keys.push(Box::new(Index { + name: row.try_get("INDEX_NAME")?, + column_name: row.try_get("COLUMN_NAME")?, + r#type: row.try_get("INDEX_TYPE")?, + })) + } + Ok(foreign_keys) + } + async fn close(&self) { self.pool.close().await; } diff --git a/src/database/postgres.rs b/src/database/postgres.rs index 1a0b281..dbf4767 100644 --- a/src/database/postgres.rs +++ b/src/database/postgres.rs @@ -109,6 +109,36 @@ impl TableRow for ForeignKey { } } +pub struct Index { + name: Option, + column_name: Option, + r#type: Option, +} + +impl TableRow for Index { + fn fields(&self) -> Vec { + vec![ + "name".to_string(), + "column_name".to_string(), + "type".to_string(), + ] + } + + fn columns(&self) -> Vec { + vec![ + self.name + .as_ref() + .map_or(String::new(), |name| name.to_string()), + self.column_name + .as_ref() + .map_or(String::new(), |column_name| column_name.to_string()), + self.r#type + .as_ref() + .map_or(String::new(), |r#type| r#type.to_string()), + ] + } +} + #[async_trait] impl Pool for PostgresPool { async fn get_databases(&self) -> anyhow::Result> { @@ -338,6 +368,50 @@ impl Pool for PostgresPool { Ok(constraints) } + async fn get_indexes( + &self, + _database: &Database, + table: &Table, + ) -> anyhow::Result>> { + let mut rows = sqlx::query( + " + SELECT + t.relname AS table_name, + i.relname AS index_name, + a.attname AS column_name, + am.amname AS type + FROM + pg_class t, + pg_class i, + pg_index ix, + pg_attribute a, + pg_am am + WHERE + t.oid = ix.indrelid + and i.oid = ix.indexrelid + and a.attrelid = t.oid + and a.attnum = ANY(ix.indkey) + and t.relkind = 'r' + and am.oid = i.relam + and t.relname = $1 + ORDER BY + t.relname, + i.relname + ", + ) + .bind(&table.name) + .fetch(&self.pool); + let mut foreign_keys: Vec> = vec![]; + while let Some(row) = rows.try_next().await? { + foreign_keys.push(Box::new(Index { + name: row.try_get("index_name")?, + column_name: row.try_get("column_name")?, + r#type: row.try_get("type")?, + })) + } + Ok(foreign_keys) + } + async fn close(&self) { self.pool.close().await; }