Refactor handlers (#3)

* fix workflows

* remove a unused function

* initialize record table state every time specifying table

* add a crates.io badge

* close pool if exists

* remove unneeded lines

* change gobang.gif

* fix layout

* update gobang.gif

* refactor `utils::get_records`

* refactor handlers

* change gobang.gif

* fix clippy warnings
pull/8/head
Takayuki Maeda 3 years ago committed by GitHub
parent 63e96b6aed
commit 4b26b71a38
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -61,7 +61,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
needs: check
steps:

@ -8,7 +8,10 @@ on:
jobs:
check:
name: Check
runs-on: ubuntu-latest
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- name: Cargo check

1
Cargo.lock generated

@ -472,6 +472,7 @@ dependencies = [
"chrono",
"crossterm 0.19.0",
"futures",
"regex",
"serde",
"serde_json",
"sqlx",

@ -21,3 +21,4 @@ futures = "0.3.5"
serde_json = "1.0"
serde = "1.0"
toml = "0.4"
regex = "1"

@ -4,7 +4,7 @@
A cross-platform terminal database tool written in Rust
[![github workflow status](https://img.shields.io/github/workflow/status/TaKO8Ki/gobang/CI/main)](https://github.com/TaKO8Ki/gobang/actions)
[![github workflow status](https://img.shields.io/github/workflow/status/TaKO8Ki/gobang/CI/main)](https://github.com/TaKO8Ki/gobang/actions) [![crates](https://img.shields.io/crates/v/gobang.svg?logo=rust)](https://crates.io/crates/gobang)
![gobang](./resources/gobang.gif)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 MiB

After

Width:  |  Height:  |  Size: 5.4 MiB

@ -10,23 +10,29 @@ pub enum InputMode {
Editing,
}
pub enum FocusType {
Dabatases(bool),
Tables(bool),
Records(bool),
Connections,
pub enum FocusBlock {
DabataseList(bool),
TableList(bool),
RecordTable(bool),
ConnectionList,
}
#[derive(Clone)]
pub struct Database {
pub selected_table: ListState,
pub name: String,
pub tables: Vec<Table>,
}
#[derive(Clone)]
#[derive(sqlx::FromRow, Debug, Clone)]
pub struct Table {
#[sqlx(rename = "Name")]
pub name: String,
#[sqlx(rename = "Create_time")]
pub create_time: chrono::DateTime<chrono::Utc>,
#[sqlx(rename = "Update_time")]
pub update_time: Option<chrono::DateTime<chrono::Utc>>,
#[sqlx(rename = "Engine")]
pub engine: String,
}
pub struct RecordTable {
@ -92,51 +98,23 @@ impl RecordTable {
impl Database {
pub async fn new(name: String, pool: &MySqlPool) -> anyhow::Result<Self> {
Ok(Self {
selected_table: ListState::default(),
name: name.clone(),
tables: get_tables(name, pool).await?,
})
}
pub fn next(&mut self) {
let i = match self.selected_table.selected() {
Some(i) => {
if i >= self.tables.len() - 1 {
0
} else {
i + 1
}
}
None => 0,
};
self.selected_table.select(Some(i));
}
pub fn previous(&mut self) {
let i = match self.selected_table.selected() {
Some(i) => {
if i == 0 {
self.tables.len() - 1
} else {
i - 1
}
}
None => 0,
};
self.selected_table.select(Some(i));
}
}
pub struct App {
pub input: String,
pub input_mode: InputMode,
pub messages: Vec<Vec<String>>,
pub selected_database: ListState,
pub query: String,
pub databases: Vec<Database>,
pub record_table: RecordTable,
pub focus_type: FocusType,
pub focus_type: FocusBlock,
pub user_config: Option<UserConfig>,
pub selected_connection: ListState,
pub selected_database: ListState,
pub selected_table: ListState,
pub pool: Option<MySqlPool>,
}
@ -145,21 +123,46 @@ impl Default for App {
App {
input: String::new(),
input_mode: InputMode::Normal,
messages: Vec::new(),
selected_database: ListState::default(),
query: String::new(),
databases: Vec::new(),
record_table: RecordTable::default(),
focus_type: FocusType::Dabatases(false),
focus_type: FocusBlock::DabataseList(false),
user_config: None,
selected_connection: ListState::default(),
selected_database: ListState::default(),
selected_table: ListState::default(),
pool: None,
}
}
}
impl App {
pub fn new(title: &str, enhanced_graphics: bool) -> App {
Self::default()
pub fn next_table(&mut self) {
let i = match self.selected_table.selected() {
Some(i) => {
if i >= self.selected_database().unwrap().tables.len() - 1 {
0
} else {
i + 1
}
}
None => 0,
};
self.selected_table.select(Some(i));
}
pub fn previous_table(&mut self) {
let i = match self.selected_table.selected() {
Some(i) => {
if i == 0 {
self.selected_database().unwrap().tables.len() - 1
} else {
i - 1
}
}
None => 0,
};
self.selected_table.select(Some(i));
}
pub fn next_database(&mut self) {
@ -173,6 +176,7 @@ impl App {
}
None => 0,
};
self.selected_table.select(Some(0));
self.selected_database.select(Some(i));
}
@ -187,6 +191,7 @@ impl App {
}
None => 0,
};
self.selected_table.select(Some(0));
self.selected_database.select(Some(i));
}
@ -224,18 +229,15 @@ impl App {
pub fn selected_database(&self) -> Option<&Database> {
match self.selected_database.selected() {
Some(i) => match self.databases.get(i) {
Some(db) => Some(db),
None => None,
},
Some(i) => self.databases.get(i),
None => None,
}
}
pub fn selected_table(&self) -> Option<&Table> {
match self.selected_database() {
Some(db) => match db.selected_table.selected() {
Some(i) => db.tables.get(i),
match self.selected_table.selected() {
Some(i) => match self.selected_database() {
Some(db) => db.tables.get(i),
None => None,
},
None => None,

@ -0,0 +1,34 @@
use crate::app::{App, Database, FocusBlock};
use crate::event::Key;
use crate::utils::get_databases;
use sqlx::mysql::MySqlPool;
pub async fn handler(key: Key, app: &mut App) -> anyhow::Result<()> {
match key {
Key::Char('j') => app.next_connection(),
Key::Char('k') => app.previous_connection(),
Key::Enter => {
app.selected_database.select(Some(0));
app.selected_table.select(Some(0));
if let Some(conn) = app.selected_connection() {
if let Some(pool) = app.pool.as_ref() {
pool.close().await;
}
let pool = MySqlPool::connect(conn.database_url().as_str()).await?;
app.pool = Some(pool);
app.focus_type = FocusBlock::DabataseList(false);
}
app.databases = match app.selected_connection() {
Some(conn) => match &conn.database {
Some(database) => {
vec![Database::new(database.clone(), app.pool.as_ref().unwrap()).await?]
}
None => get_databases(app.pool.as_ref().unwrap()).await?,
},
None => vec![],
};
}
_ => (),
}
Ok(())
}

@ -1,13 +0,0 @@
use crate::app::{App, FocusType};
use crate::event::Key;
use sqlx::mysql::MySqlPool;
pub async fn handler(_key: Key, app: &mut App) -> anyhow::Result<()> {
if let Some(conn) = app.selected_connection() {
app.pool.as_ref().unwrap().close().await;
let pool = MySqlPool::connect(conn.database_url().as_str()).await?;
app.pool = Some(pool);
app.focus_type = FocusType::Dabatases(true);
}
Ok(())
}

@ -1,16 +1,22 @@
use crate::app::{App, Database};
use crate::app::{App, FocusBlock};
use crate::event::Key;
use crate::utils::get_databases;
pub async fn handler(_key: Key, app: &mut App) -> anyhow::Result<()> {
app.databases = match app.selected_connection() {
Some(conn) => match &conn.database {
Some(database) => {
vec![Database::new(database.clone(), app.pool.as_ref().unwrap()).await?]
}
None => get_databases(app.pool.as_ref().unwrap()).await?,
},
None => vec![],
};
pub async fn handler(key: Key, app: &mut App, focused: bool) -> anyhow::Result<()> {
if focused {
match key {
Key::Char('j') => app.next_database(),
Key::Char('k') => app.previous_database(),
Key::Esc => app.focus_type = FocusBlock::DabataseList(false),
_ => (),
}
} else {
match key {
Key::Char('j') => app.focus_type = FocusBlock::TableList(false),
Key::Char('l') => app.focus_type = FocusBlock::RecordTable(false),
Key::Char('c') => app.focus_type = FocusBlock::ConnectionList,
Key::Enter => app.focus_type = FocusBlock::DabataseList(true),
_ => (),
}
}
Ok(())
}

@ -1,93 +1,36 @@
pub mod create_connection;
pub mod connection_list;
pub mod database_list;
pub mod query;
pub mod record_table;
pub mod table_list;
use crate::app::{App, FocusType, InputMode};
use crate::app::{App, FocusBlock, InputMode};
use crate::event::Key;
pub async fn handle_app(key: Key, app: &mut App) -> anyhow::Result<()> {
match app.input_mode {
InputMode::Normal => match key {
Key::Char('e') => {
app.input_mode = InputMode::Editing;
}
Key::Char('c') => {
app.focus_type = FocusType::Connections;
}
Key::Char('l') => app.focus_type = FocusType::Records(false),
Key::Char('h') => app.focus_type = FocusType::Tables(false),
Key::Char('j') => {
if let FocusType::Dabatases(_) = app.focus_type {
app.focus_type = FocusType::Tables(false)
InputMode::Normal => {
match app.focus_type {
FocusBlock::ConnectionList => connection_list::handler(key, app).await?,
FocusBlock::DabataseList(focused) => {
database_list::handler(key, app, focused).await?
}
}
Key::Char('k') => {
if let FocusType::Tables(_) = app.focus_type {
app.focus_type = FocusType::Dabatases(false)
}
}
Key::Right => match app.focus_type {
FocusType::Records(true) => app.record_table.next_column(),
_ => (),
},
Key::Left => match app.focus_type {
FocusType::Records(true) => app.record_table.previous_column(),
_ => (),
},
Key::Up => match app.focus_type {
FocusType::Connections => app.previous_connection(),
FocusType::Records(true) => app.record_table.previous(),
FocusType::Dabatases(true) => app.previous_database(),
FocusType::Tables(true) => match app.selected_database.selected() {
Some(index) => {
app.record_table.column_index = 0;
app.databases[index].previous();
record_table::handler(key, app).await?;
}
None => (),
},
_ => (),
},
Key::Down => match app.focus_type {
FocusType::Connections => app.next_connection(),
FocusType::Records(true) => app.record_table.next(),
FocusType::Dabatases(true) => app.next_database(),
FocusType::Tables(true) => match app.selected_database.selected() {
Some(index) => {
app.record_table.column_index = 0;
app.databases[index].next();
record_table::handler(key, app).await?
}
None => (),
},
_ => (),
},
Key::Enter => match app.focus_type {
FocusType::Connections => {
app.selected_database.select(Some(0));
create_connection::handler(key, app).await?;
database_list::handler(key, app).await?;
FocusBlock::TableList(focused) => table_list::handler(key, app, focused).await?,
FocusBlock::RecordTable(focused) => {
record_table::handler(key, app, focused).await?
}
FocusType::Records(false) => app.focus_type = FocusType::Records(true),
FocusType::Dabatases(false) => app.focus_type = FocusType::Dabatases(true),
FocusType::Tables(false) => app.focus_type = FocusType::Tables(true),
_ => (),
},
_ => {}
},
InputMode::Editing => match key {
Key::Enter => {
app.messages.push(vec![app.input.drain(..).collect()]);
}
Key::Char(c) => {
app.input.push(c);
if let Key::Char('e') = key {
app.input_mode = InputMode::Editing
}
}
InputMode::Editing => match key {
Key::Enter => query::handler(key, app).await?,
Key::Char(c) => app.input.push(c),
Key::Backspace => {
app.input.pop();
}
Key::Esc => {
app.input_mode = InputMode::Normal;
}
Key::Esc => app.input_mode = InputMode::Normal,
_ => {}
},
}

@ -0,0 +1,32 @@
use crate::app::App;
use crate::event::Key;
use crate::utils::convert_column_value_to_string;
use futures::TryStreamExt;
use regex::Regex;
use sqlx::Row;
pub async fn handler(_key: Key, app: &mut App) -> anyhow::Result<()> {
app.query = app.input.drain(..).collect();
let re = Regex::new(r"select .+ from (.+)").unwrap();
if let Some(caps) = re.captures(app.query.as_str()) {
let mut rows = sqlx::query(app.query.as_str()).fetch(app.pool.as_ref().unwrap());
let headers = sqlx::query(format!("desc `{}`", caps.get(1).unwrap().as_str()).as_str())
.fetch_all(app.pool.as_ref().unwrap())
.await?
.iter()
.map(|table| table.get(0))
.collect::<Vec<String>>();
let mut records = vec![];
while let Some(row) = rows.try_next().await? {
records.push(
row.columns()
.iter()
.map(|col| convert_column_value_to_string(&row, col))
.collect::<Vec<String>>(),
)
}
app.record_table.headers = headers;
app.record_table.rows = records;
}
Ok(())
}

@ -1,14 +1,22 @@
use crate::app::App;
use crate::app::{App, FocusBlock};
use crate::event::Key;
use crate::utils::get_records;
pub async fn handler(_key: Key, app: &mut App) -> anyhow::Result<()> {
if let Some(database) = app.selected_database() {
if let Some(table) = app.selected_table() {
let (headers, records) =
get_records(database, table, app.pool.as_ref().unwrap()).await?;
app.record_table.headers = headers;
app.record_table.rows = records;
pub async fn handler(key: Key, app: &mut App, focused: bool) -> anyhow::Result<()> {
if focused {
match key {
Key::Char('h') => app.record_table.previous_column(),
Key::Char('j') => app.record_table.next(),
Key::Char('k') => app.record_table.previous(),
Key::Char('l') => app.record_table.next_column(),
Key::Esc => app.focus_type = FocusBlock::RecordTable(false),
_ => (),
}
} else {
match key {
Key::Char('h') => app.focus_type = FocusBlock::TableList(false),
Key::Char('c') => app.focus_type = FocusBlock::ConnectionList,
Key::Enter => app.focus_type = FocusBlock::RecordTable(true),
_ => (),
}
}
Ok(())

@ -0,0 +1,51 @@
use crate::app::{App, FocusBlock};
use crate::event::Key;
use crate::utils::get_records;
pub async fn handler(key: Key, app: &mut App, focused: bool) -> anyhow::Result<()> {
if focused {
match key {
Key::Char('j') => {
if app.selected_database.selected().is_some() {
app.record_table.column_index = 0;
app.next_table();
if let Some(database) = app.selected_database() {
if let Some(table) = app.selected_table() {
let (headers, records) =
get_records(database, table, app.pool.as_ref().unwrap()).await?;
app.record_table.state.select(Some(0));
app.record_table.headers = headers;
app.record_table.rows = records;
}
}
}
}
Key::Char('k') => {
if app.selected_database.selected().is_some() {
app.record_table.column_index = 0;
app.previous_table();
if let Some(database) = app.selected_database() {
if let Some(table) = app.selected_table() {
let (headers, records) =
get_records(database, table, app.pool.as_ref().unwrap()).await?;
app.record_table.state.select(Some(0));
app.record_table.headers = headers;
app.record_table.rows = records;
}
}
}
}
Key::Esc => app.focus_type = FocusBlock::TableList(false),
_ => (),
}
} else {
match key {
Key::Char('k') => app.focus_type = FocusBlock::DabataseList(false),
Key::Char('l') => app.focus_type = FocusBlock::RecordTable(false),
Key::Char('c') => app.focus_type = FocusBlock::ConnectionList,
Key::Enter => app.focus_type = FocusBlock::TableList(true),
_ => (),
}
}
Ok(())
}

@ -5,7 +5,7 @@ mod ui;
mod user_config;
mod utils;
use crate::app::FocusType;
use crate::app::{App, FocusBlock};
use crate::event::{Event, Key};
use crate::handlers::handle_app;
use crossterm::{
@ -13,7 +13,6 @@ use crossterm::{
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use sqlx::mysql::MySqlPool;
use std::io::stdout;
use tui::{backend::CrosstermBackend, Terminal};
@ -21,7 +20,7 @@ use tui::{backend::CrosstermBackend, Terminal};
async fn main() -> anyhow::Result<()> {
enable_raw_mode()?;
let config = user_config::UserConfig::new("sample.toml").unwrap();
let user_config = user_config::UserConfig::new("sample.toml").ok();
let mut stdout = stdout();
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
@ -30,32 +29,11 @@ async fn main() -> anyhow::Result<()> {
let mut terminal = Terminal::new(backend)?;
let events = event::Events::new(250);
let mut app = &mut app::App::default();
app.user_config = Some(config);
let conn = &app.user_config.as_ref().unwrap().conn.get(0).unwrap();
let pool = MySqlPool::connect(
format!(
"mysql://{user}:@{host}:{port}",
user = conn.user,
host = conn.host,
port = conn.port
)
.as_str(),
)
.await?;
app.pool = Some(pool);
app.databases = utils::get_databases(app.pool.as_ref().unwrap()).await?;
let (headers, records) = utils::get_records(
app.databases.first().unwrap(),
app.databases.first().unwrap().tables.first().unwrap(),
app.pool.as_ref().unwrap(),
)
.await?;
app.record_table.rows = records;
app.record_table.headers = headers;
app.selected_database.select(Some(0));
app.focus_type = FocusType::Connections;
let mut app = App {
user_config,
focus_type: FocusBlock::ConnectionList,
..App::default()
};
terminal.clear()?;
@ -66,7 +44,7 @@ async fn main() -> anyhow::Result<()> {
if key == Key::Char('q') {
break;
};
handle_app(key, app).await?
handle_app(key, &mut app).await?
}
Event::Tick => (),
}

@ -1,5 +1,5 @@
use crate::app::InputMode;
use crate::app::{App, FocusType};
use crate::app::{App, FocusBlock};
use tui::{
backend::Backend,
layout::{Constraint, Direction, Layout},
@ -11,7 +11,7 @@ use tui::{
use unicode_width::UnicodeWidthStr;
pub fn draw<B: Backend>(f: &mut Frame<'_, B>, app: &mut App) -> anyhow::Result<()> {
if let FocusType::Connections = app.focus_type {
if let FocusBlock::ConnectionList = app.focus_type {
let percent_x = 60;
let percent_y = 50;
let conns = &app.user_config.as_ref().unwrap().conn;
@ -26,7 +26,7 @@ pub fn draw<B: Backend>(f: &mut Frame<'_, B>, app: &mut App) -> anyhow::Result<(
.block(Block::default().borders(Borders::ALL).title("Connections"))
.highlight_style(Style::default().fg(Color::Green))
.style(match app.focus_type {
FocusType::Connections => Style::default().fg(Color::Green),
FocusBlock::ConnectionList => Style::default().fg(Color::Green),
_ => Style::default(),
});
let popup_layout = Layout::default()
@ -58,15 +58,20 @@ pub fn draw<B: Backend>(f: &mut Frame<'_, B>, app: &mut App) -> anyhow::Result<(
}
let main_chunks = Layout::default()
.direction(Direction::Vertical)
.margin(2)
.constraints([Constraint::Percentage(15), Constraint::Percentage(85)])
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(15), Constraint::Percentage(85)])
.split(f.size());
let left_chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Percentage(30), Constraint::Percentage(70)].as_ref())
.constraints(
[
Constraint::Length(9),
Constraint::Min(8),
Constraint::Length(7),
]
.as_ref(),
)
.split(main_chunks[0]);
let databases: Vec<ListItem> = app
.databases
@ -80,8 +85,8 @@ pub fn draw<B: Backend>(f: &mut Frame<'_, B>, app: &mut App) -> anyhow::Result<(
.block(Block::default().borders(Borders::ALL).title("Databases"))
.highlight_style(Style::default().fg(Color::Green))
.style(match app.focus_type {
FocusType::Dabatases(false) => Style::default().fg(Color::Magenta),
FocusType::Dabatases(true) => Style::default().fg(Color::Green),
FocusBlock::DabataseList(false) => Style::default().fg(Color::Magenta),
FocusBlock::DabataseList(true) => Style::default().fg(Color::Green),
_ => Style::default(),
});
f.render_stateful_widget(tasks, left_chunks[0], &mut app.selected_database);
@ -99,28 +104,46 @@ pub fn draw<B: Backend>(f: &mut Frame<'_, B>, app: &mut App) -> anyhow::Result<(
.block(Block::default().borders(Borders::ALL).title("Tables"))
.highlight_style(Style::default().fg(Color::Green))
.style(match app.focus_type {
FocusType::Tables(false) => Style::default().fg(Color::Magenta),
FocusType::Tables(true) => Style::default().fg(Color::Green),
FocusBlock::TableList(false) => Style::default().fg(Color::Magenta),
FocusBlock::TableList(true) => Style::default().fg(Color::Green),
_ => Style::default(),
});
f.render_stateful_widget(
tasks,
left_chunks[1],
&mut app.databases[app.selected_database.selected().unwrap_or(0)].selected_table,
);
f.render_stateful_widget(tasks, left_chunks[1], &mut app.selected_table);
let info: Vec<ListItem> = vec![
format!(
"created: {}",
app.selected_table().unwrap().create_time.to_string()
),
// format!(
// "updated: {}",
// app.selected_table().unwrap().update_time.to_string()
// ),
format!("rows: {}", app.record_table.rows.len()),
]
.iter()
.map(|i| {
ListItem::new(vec![Spans::from(Span::raw(i.to_string()))])
.style(Style::default().fg(Color::White))
})
.collect();
let tasks = List::new(info)
.block(Block::default().borders(Borders::ALL))
.highlight_style(Style::default().fg(Color::Green));
f.render_widget(tasks, left_chunks[2]);
let right_chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Length(3), Constraint::Min(1)].as_ref())
.constraints([Constraint::Length(3), Constraint::Length(5)].as_ref())
.split(main_chunks[1]);
let input = Paragraph::new(app.input.as_ref())
let query = Paragraph::new(app.input.as_ref())
.style(match app.input_mode {
InputMode::Normal => Style::default(),
InputMode::Editing => Style::default().fg(Color::Yellow),
})
.block(Block::default().borders(Borders::ALL).title("Query"));
f.render_widget(input, right_chunks[0]);
f.render_widget(query, right_chunks[0]);
match app.input_mode {
InputMode::Normal => (),
InputMode::Editing => f.set_cursor(
@ -153,8 +176,8 @@ pub fn draw<B: Backend>(f: &mut Frame<'_, B>, app: &mut App) -> anyhow::Result<(
.block(Block::default().borders(Borders::ALL).title("Records"))
.highlight_style(Style::default().fg(Color::Green))
.style(match app.focus_type {
FocusType::Records(false) => Style::default().fg(Color::Magenta),
FocusType::Records(true) => Style::default().fg(Color::Green),
FocusBlock::RecordTable(false) => Style::default().fg(Color::Magenta),
FocusBlock::RecordTable(true) => Style::default().fg(Color::Green),
_ => Style::default(),
})
.widths(&widths);

@ -1,11 +1,11 @@
use crate::app::{Database, Table};
use chrono::NaiveDate;
use futures::TryStreamExt;
use sqlx::mysql::MySqlPool;
use sqlx::{Column, Executor, Row, TypeInfo};
use sqlx::mysql::{MySqlColumn, MySqlPool, MySqlRow};
use sqlx::{Column, Row, TypeInfo};
pub async fn get_databases(pool: &MySqlPool) -> anyhow::Result<Vec<Database>> {
let databases = sqlx::query("show databases")
let databases = sqlx::query("SHOW DATABASES")
.fetch_all(pool)
.await?
.iter()
@ -19,12 +19,10 @@ pub async fn get_databases(pool: &MySqlPool) -> anyhow::Result<Vec<Database>> {
}
pub async fn get_tables(database: String, pool: &MySqlPool) -> anyhow::Result<Vec<Table>> {
let tables = sqlx::query(format!("show tables from `{}`", database).as_str())
.fetch_all(pool)
.await?
.iter()
.map(|table| Table { name: table.get(0) })
.collect::<Vec<Table>>();
let tables =
sqlx::query_as::<_, Table>(format!("SHOW TABLE STATUS FROM `{}`", database).as_str())
.fetch_all(pool)
.await?;
Ok(tables)
}
@ -33,10 +31,8 @@ pub async fn get_records(
table: &Table,
pool: &MySqlPool,
) -> anyhow::Result<(Vec<String>, Vec<Vec<String>>)> {
pool.execute(format!("use `{}`", database.name).as_str())
.await?;
let table_name = format!("SELECT * FROM `{}`", table.name);
let mut rows = sqlx::query(table_name.as_str()).fetch(pool);
let query = format!("SELECT * FROM `{}`.`{}`", database.name, table.name);
let mut rows = sqlx::query(query.as_str()).fetch(pool);
let headers = sqlx::query(format!("desc `{}`", table.name).as_str())
.fetch_all(pool)
.await?
@ -45,57 +41,55 @@ pub async fn get_records(
.collect::<Vec<String>>();
let mut records = vec![];
while let Some(row) = rows.try_next().await? {
let mut row_vec = vec![];
for col in row.columns() {
let col_name = col.name();
match col.type_info().clone().name() {
"INT" | "DECIMAL" | "SMALLINT" => match row.try_get(col_name) {
Ok(value) => {
let value: i64 = value;
row_vec.push(value.to_string())
}
Err(_) => row_vec.push("".to_string()),
},
"INT UNSIGNED" => match row.try_get(col_name) {
Ok(value) => {
let value: u64 = value;
row_vec.push(value.to_string())
}
Err(_) => row_vec.push("".to_string()),
},
"VARCHAR" | "CHAR" => {
let value: String = row.try_get(col_name).unwrap_or("".to_string());
row_vec.push(value);
}
"DATE" => match row.try_get(col_name) {
Ok(value) => {
let value: NaiveDate = value;
row_vec.push(value.to_string())
}
Err(_) => row_vec.push("".to_string()),
},
"TIMESTAMP" => match row.try_get(col_name) {
Ok(value) => {
let value: chrono::DateTime<chrono::Utc> = value;
row_vec.push(value.to_string())
}
Err(_) => row_vec.push("".to_string()),
},
"BOOLEAN" => match row.try_get(col_name) {
Ok(value) => {
let value: bool = value;
row_vec.push(value.to_string())
}
Err(_) => row_vec.push("".to_string()),
},
"ENUM" => {
let value: String = row.try_get(col_name).unwrap_or("".to_string());
row_vec.push(value);
}
_ => (),
}
}
records.push(row_vec)
records.push(
row.columns()
.iter()
.map(|col| convert_column_value_to_string(&row, col))
.collect::<Vec<String>>(),
)
}
Ok((headers, records))
}
pub fn convert_column_value_to_string(row: &MySqlRow, column: &MySqlColumn) -> String {
let column_name = column.name();
match column.type_info().clone().name() {
"INT" | "DECIMAL" | "SMALLINT" => match row.try_get(column_name) {
Ok(value) => {
let value: i64 = value;
value.to_string()
}
Err(_) => "".to_string(),
},
"INT UNSIGNED" => match row.try_get(column_name) {
Ok(value) => {
let value: u64 = value;
value.to_string()
}
Err(_) => "".to_string(),
},
"VARCHAR" | "CHAR" | "ENUM" => row.try_get(column_name).unwrap_or_else(|_| "".to_string()),
"DATE" => match row.try_get(column_name) {
Ok(value) => {
let value: NaiveDate = value;
value.to_string()
}
Err(_) => "".to_string(),
},
"TIMESTAMP" => match row.try_get(column_name) {
Ok(value) => {
let value: chrono::DateTime<chrono::Utc> = value;
value.to_string()
}
Err(_) => "".to_string(),
},
"BOOLEAN" => match row.try_get(column_name) {
Ok(value) => {
let value: bool = value;
value.to_string()
}
Err(_) => "".to_string(),
},
_ => "".to_string(),
}
}

Loading…
Cancel
Save