Implement Help component (#32)

* implement help compoment

* fix database filter

* add column types

* fix clippy warnings

* help scrolling

* panic if type is not implemented yet

* not panic but return error

* add decimal

* add a test for get_text

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

23
Cargo.lock generated

@ -532,7 +532,9 @@ dependencies = [
"database-tree",
"easy-cast",
"futures",
"itertools",
"regex",
"rust_decimal",
"serde",
"serde_json",
"sqlx",
@ -615,6 +617,15 @@ dependencies = [
"libc",
]
[[package]]
name = "itertools"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "0.4.7"
@ -1171,6 +1182,17 @@ dependencies = [
"zeroize",
]
[[package]]
name = "rust_decimal"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5446d1cf2dfe2d6367c8b27f2082bdf011e60e76fa1fcd140047f535156d6e7"
dependencies = [
"arrayvec",
"num-traits",
"serde",
]
[[package]]
name = "rustls"
version = "0.18.1"
@ -1389,6 +1411,7 @@ dependencies = [
"percent-encoding",
"rand",
"rsa",
"rust_decimal",
"rustls",
"sha-1",
"sha2",

@ -19,7 +19,7 @@ tui = { version = "0.14.0", features = ["crossterm"], default-features = false }
crossterm = "0.19"
anyhow = "1.0.38"
unicode-width = "0.1"
sqlx = { version = "0.4.1", features = ["mysql", "chrono", "runtime-tokio-rustls"] }
sqlx = { version = "0.4.1", features = ["mysql", "chrono", "runtime-tokio-rustls", "decimal"] }
chrono = "0.4"
tokio = { version = "0.2.22", features = ["full"] }
futures = "0.3.5"
@ -33,6 +33,8 @@ database-tree = { path = "./database-tree", version = "0.1" }
easy-cast = "0.4"
copypasta = { version = "0.7.0", default-features = false }
async-trait = "0.1.50"
itertools = "0.10.0"
rust_decimal = "1.15"
[target.'cfg(any(target_os = "macos", windows))'.dependencies]
copypasta = { version = "0.7.0", default-features = false }

@ -1,14 +1,12 @@
use crate::clipboard::Clipboard;
use crate::components::Component as _;
use crate::components::DrawableComponent as _;
use crate::components::EventState;
use crate::components::{CommandInfo, Component as _, DrawableComponent as _, EventState};
use crate::event::Key;
use crate::utils::{MySqlPool, Pool};
use crate::{
components::tab::Tab,
components::{
ConnectionsComponent, DatabasesComponent, ErrorComponent, RecordTableComponent,
TabComponent, TableComponent, TableStatusComponent,
ConnectionsComponent, DatabasesComponent, ErrorComponent, HelpComponent,
RecordTableComponent, TabComponent, TableComponent, TableStatusComponent,
},
user_config::UserConfig,
};
@ -29,6 +27,7 @@ pub struct App {
structure_table: TableComponent,
focus: Focus,
tab: TabComponent,
help: HelpComponent,
databases: DatabasesComponent,
connections: ConnectionsComponent,
table_status: TableStatusComponent,
@ -45,6 +44,7 @@ impl Default for App {
structure_table: TableComponent::default(),
focus: Focus::DabataseList,
tab: TabComponent::default(),
help: HelpComponent::new(),
user_config: None,
databases: DatabasesComponent::new(),
connections: ConnectionsComponent::default(),
@ -58,7 +58,7 @@ impl Default for App {
impl App {
pub fn new(user_config: UserConfig) -> App {
App {
Self {
user_config: Some(user_config.clone()),
connections: ConnectionsComponent::new(user_config.conn),
focus: Focus::ConnectionList,
@ -110,10 +110,36 @@ impl App {
}
}
self.error.draw(f, Rect::default(), false)?;
self.help.draw(f, Rect::default(), false)?;
Ok(())
}
fn update_commands(&mut self) {
self.help.set_cmds(self.commands());
}
fn commands(&self) -> Vec<CommandInfo> {
let res = vec![
CommandInfo::new(crate::components::command::move_left("h"), true, true),
CommandInfo::new(crate::components::command::move_down("j"), true, true),
CommandInfo::new(crate::components::command::move_up("k"), true, true),
CommandInfo::new(crate::components::command::move_right("l"), true, true),
CommandInfo::new(crate::components::command::filter("/"), true, true),
CommandInfo::new(
crate::components::command::move_focus_to_right_widget(
Key::Right.to_string().as_str(),
),
true,
true,
),
];
res
}
pub async fn event(&mut self, key: Key) -> anyhow::Result<EventState> {
self.update_commands();
if let Key::Esc = key {
if self.error.error.is_some() {
self.error.error = None;
@ -132,6 +158,10 @@ impl App {
}
pub async fn components_event(&mut self, key: Key) -> anyhow::Result<EventState> {
if self.help.event(key)?.is_consumed() {
return Ok(EventState::Consumed);
}
match self.focus {
Focus::ConnectionList => {
if self.connections.event(key)?.is_consumed() {

@ -1,3 +1,5 @@
static CMD_GROUP_GENERAL: &str = "-- General --";
#[derive(Clone, PartialEq, PartialOrd, Ord, Eq)]
pub struct CommandText {
pub name: String,
@ -5,6 +7,18 @@ pub struct CommandText {
pub group: &'static str,
pub hide_help: bool,
}
impl CommandText {
pub const fn new(name: String, desc: &'static str, group: &'static str) -> Self {
Self {
name,
desc,
group,
hide_help: false,
}
}
}
pub struct CommandInfo {
pub text: CommandText,
pub enabled: bool,
@ -12,3 +26,65 @@ pub struct CommandInfo {
pub available: bool,
pub order: i8,
}
impl CommandInfo {
pub const fn new(text: CommandText, enabled: bool, available: bool) -> Self {
Self {
text,
enabled,
quick_bar: true,
available,
order: 0,
}
}
pub const fn order(self, order: i8) -> Self {
let mut res = self;
res.order = order;
res
}
}
pub fn move_down(key: &str) -> CommandText {
CommandText::new(
format!("Move down [{}]", key),
"move down",
CMD_GROUP_GENERAL,
)
}
pub fn move_up(key: &str) -> CommandText {
CommandText::new(format!("Move up [{}]", key), "move up", CMD_GROUP_GENERAL)
}
pub fn move_right(key: &str) -> CommandText {
CommandText::new(
format!("Move right [{}]", key),
"move right",
CMD_GROUP_GENERAL,
)
}
pub fn move_left(key: &str) -> CommandText {
CommandText::new(
format!("Move left [{}]", key),
"move left",
CMD_GROUP_GENERAL,
)
}
pub fn filter(key: &str) -> CommandText {
CommandText::new(
format!("Filter [{}]", key),
"enter input for filter",
CMD_GROUP_GENERAL,
)
}
pub fn move_focus_to_right_widget(key: &str) -> CommandText {
CommandText::new(
format!("Move focus to right [{}]", key),
"move focus to right",
CMD_GROUP_GENERAL,
)
}

@ -1,4 +1,5 @@
use super::{Component, DrawableComponent, EventState};
use crate::components::command::CommandInfo;
use crate::event::Key;
use crate::user_config::Connection;
use anyhow::Result;
@ -99,6 +100,8 @@ impl DrawableComponent for ConnectionsComponent {
}
impl Component for ConnectionsComponent {
fn commands(&self, out: &mut Vec<CommandInfo>) {}
fn event(&mut self, key: Key) -> Result<EventState> {
match key {
Key::Char('j') => {

@ -2,6 +2,7 @@ use super::{
compute_character_width, utils::scroll_vertical::VerticalScroll, Component, DrawableComponent,
EventState,
};
use crate::components::command::CommandInfo;
use crate::event::Key;
use crate::ui::common_nav;
use crate::ui::scrolllist::draw_list_block;
@ -13,9 +14,8 @@ use tui::{
backend::Backend,
layout::{Constraint, Direction, Layout, Rect},
style::{Color, Style},
symbols::line::HORIZONTAL,
text::Span,
widgets::{Block, Borders},
widgets::{Block, Borders, Paragraph},
Frame,
};
use unicode_width::UnicodeWidthStr;
@ -113,7 +113,45 @@ impl DatabasesComponent {
}
fn draw_tree<B: Backend>(&self, f: &mut Frame<B>, area: Rect, focused: bool) {
let tree_height = usize::from(area.height.saturating_sub(4));
f.render_widget(
Block::default()
.title("Databases")
.borders(Borders::ALL)
.style(if focused {
Style::default()
} else {
Style::default().fg(Color::DarkGray)
}),
area,
);
let chunks = Layout::default()
.vertical_margin(1)
.horizontal_margin(1)
.direction(Direction::Vertical)
.constraints([Constraint::Length(2), Constraint::Min(1)].as_ref())
.split(area);
let filter = Paragraph::new(Span::styled(
format!(
"{}{:w$}",
if self.input.is_empty() && matches!(self.focus_block, FocusBlock::Tree) {
"Filter tables".to_string()
} else {
self.input_str()
},
w = area.width as usize
),
if let FocusBlock::Filter = self.focus_block {
Style::default()
} else {
Style::default().fg(Color::DarkGray)
},
))
.block(Block::default().borders(Borders::BOTTOM));
f.render_widget(filter, chunks[0]);
let tree_height = chunks[1].height as usize;
let tree = if let Some(tree) = self.filterd_tree.as_ref() {
tree
} else {
@ -124,64 +162,16 @@ impl DatabasesComponent {
self.scroll.reset();
},
|selection| {
self.scroll.update(
selection.index,
selection.count.saturating_sub(2),
tree_height,
);
self.scroll
.update(selection.index, selection.count, tree_height);
},
);
let mut items = tree
let items = tree
.iterate(self.scroll.get_top(), tree_height)
.map(|(item, selected)| Self::tree_item_to_span(item.clone(), selected, area.width))
.collect::<Vec<Span>>();
.map(|(item, selected)| Self::tree_item_to_span(item.clone(), selected, area.width));
items.insert(
0,
Span::styled(
(0..area.width as usize)
.map(|_| HORIZONTAL)
.collect::<Vec<&str>>()
.join(""),
Style::default(),
),
);
items.insert(
0,
Span::styled(
format!(
"{}{:w$}",
if self.input.is_empty() && matches!(self.focus_block, FocusBlock::Tree) {
"Filter tables".to_string()
} else {
self.input_str()
},
w = area.width as usize
),
if let FocusBlock::Filter = self.focus_block {
Style::default()
} else {
Style::default().fg(Color::DarkGray)
},
),
);
let title = "Databases";
draw_list_block(
f,
area,
Block::default()
.title(Span::styled(title, Style::default()))
.style(if focused {
Style::default()
} else {
Style::default().fg(Color::DarkGray)
})
.borders(Borders::ALL)
.border_style(Style::default()),
items.into_iter(),
);
draw_list_block(f, chunks[1], Block::default().borders(Borders::NONE), items);
self.scroll.draw(f, area);
if let FocusBlock::Filter = self.focus_block {
f.set_cursor(area.x + self.input_cursor_position + 1, area.y + 1)
@ -202,6 +192,8 @@ impl DrawableComponent for DatabasesComponent {
}
impl Component for DatabasesComponent {
fn commands(&self, out: &mut Vec<CommandInfo>) {}
fn event(&mut self, key: Key) -> Result<EventState> {
let input_str: String = self.input.iter().collect();
if tree_nav(

@ -1,4 +1,5 @@
use super::{Component, DrawableComponent, EventState};
use crate::components::command::CommandInfo;
use crate::event::Key;
use anyhow::Result;
use tui::{
@ -49,6 +50,8 @@ impl DrawableComponent for ErrorComponent {
}
impl Component for ErrorComponent {
fn commands(&self, out: &mut Vec<CommandInfo>) {}
fn event(&mut self, _key: Key) -> Result<EventState> {
Ok(EventState::NotConsumed)
}

@ -0,0 +1,196 @@
use super::{Component, DrawableComponent, EventState};
use crate::components::command::CommandInfo;
use crate::event::Key;
use anyhow::Result;
use itertools::Itertools;
use std::convert::From;
use tui::{
backend::Backend,
layout::{Alignment, Constraint, Direction, Layout, Rect},
style::{Color, Modifier, Style},
text::{Span, Spans},
widgets::{Block, BorderType, Borders, Clear, Paragraph},
Frame,
};
pub struct HelpComponent {
cmds: Vec<CommandInfo>,
visible: bool,
selection: u16,
}
impl DrawableComponent for HelpComponent {
fn draw<B: Backend>(&mut self, f: &mut Frame<B>, _area: Rect, _focused: bool) -> Result<()> {
if self.visible {
const SIZE: (u16, u16) = (65, 24);
let scroll_threshold = SIZE.1 / 3;
let scroll = self.selection.saturating_sub(scroll_threshold);
let area = Rect::new(
(f.size().width.saturating_sub(SIZE.0)) / 2,
(f.size().height.saturating_sub(SIZE.1)) / 2,
SIZE.0.min(f.size().width),
SIZE.1.min(f.size().height),
);
f.render_widget(Clear, area);
f.render_widget(
Block::default()
.title("Help")
.borders(Borders::ALL)
.border_type(BorderType::Thick),
area,
);
let chunks = Layout::default()
.vertical_margin(1)
.horizontal_margin(1)
.direction(Direction::Vertical)
.constraints([Constraint::Min(1), Constraint::Length(1)].as_ref())
.split(area);
f.render_widget(
Paragraph::new(self.get_text(chunks[0].width as usize)).scroll((scroll, 0)),
chunks[0],
);
f.render_widget(
Paragraph::new(Spans::from(vec![Span::styled(
format!("gobang {}", "0.1.0"),
Style::default(),
)]))
.alignment(Alignment::Right),
chunks[1],
);
}
Ok(())
}
}
impl Component for HelpComponent {
fn commands(&self, out: &mut Vec<CommandInfo>) {}
fn event(&mut self, key: Key) -> Result<EventState> {
if self.visible {
match key {
Key::Esc => {
self.hide();
return Ok(EventState::Consumed);
}
Key::Char('j') => {
self.move_selection(true);
return Ok(EventState::Consumed);
}
Key::Char('k') => {
self.move_selection(false);
return Ok(EventState::Consumed);
}
_ => (),
}
return Ok(EventState::NotConsumed);
} else if let Key::Char('?') = key {
self.show()?;
return Ok(EventState::Consumed);
}
Ok(EventState::NotConsumed)
}
fn hide(&mut self) {
self.visible = false;
}
fn show(&mut self) -> Result<()> {
self.visible = true;
Ok(())
}
}
impl HelpComponent {
pub const fn new() -> Self {
Self {
cmds: vec![],
visible: false,
selection: 0,
}
}
pub fn set_cmds(&mut self, cmds: Vec<CommandInfo>) {
self.cmds = cmds
.into_iter()
.filter(|e| !e.text.hide_help)
.collect::<Vec<_>>();
}
fn move_selection(&mut self, inc: bool) {
let mut new_selection = self.selection;
new_selection = if inc {
new_selection.saturating_add(1)
} else {
new_selection.saturating_sub(1)
};
new_selection = new_selection.max(0);
self.selection = new_selection.min(self.cmds.len().saturating_sub(1) as u16);
}
fn get_text(&self, width: usize) -> Vec<Spans> {
let mut txt: Vec<Spans> = Vec::new();
let mut processed = 0;
for (key, group) in &self.cmds.iter().group_by(|e| e.text.group) {
txt.push(Spans::from(Span::styled(
key.to_string(),
Style::default().add_modifier(Modifier::REVERSED),
)));
for command_info in group {
let is_selected = self.selection == processed;
processed += 1;
txt.push(Spans::from(Span::styled(
format!("{}{:w$}", command_info.text.name, w = width),
if is_selected {
Style::default().bg(Color::Blue)
} else {
Style::default()
},
)));
}
}
txt
}
}
#[cfg(test)]
mod test {
use super::{Color, CommandInfo, HelpComponent, Modifier, Span, Spans, Style};
#[test]
fn test_get_text() {
let width = 3;
let mut component = HelpComponent::new();
component.set_cmds(vec![
CommandInfo::new(crate::components::command::move_left("h"), true, true),
CommandInfo::new(crate::components::command::move_right("l"), true, true),
]);
assert_eq!(
component.get_text(width),
vec![
Spans::from(Span::styled(
"-- General --",
Style::default().add_modifier(Modifier::REVERSED)
)),
Spans::from(Span::styled(
"Move left [h] 3",
Style::default().bg(Color::Blue)
)),
Spans::from(Span::styled("Move right [l] 3", Style::default()))
]
);
}
}

@ -2,6 +2,7 @@ pub mod command;
pub mod connections;
pub mod databases;
pub mod error;
pub mod help;
pub mod record_table;
pub mod tab;
pub mod table;
@ -14,6 +15,7 @@ pub use command::{CommandInfo, CommandText};
pub use connections::ConnectionsComponent;
pub use databases::DatabasesComponent;
pub use error::ErrorComponent;
pub use help::HelpComponent;
pub use record_table::RecordTableComponent;
pub use tab::TabComponent;
pub use table::TableComponent;
@ -27,22 +29,6 @@ use std::convert::TryInto;
use tui::{backend::Backend, layout::Rect, Frame};
use unicode_width::UnicodeWidthChar;
#[derive(Copy, Clone)]
pub enum ScrollType {
Up,
Down,
Home,
End,
PageUp,
PageDown,
}
#[derive(Copy, Clone)]
pub enum Direction {
Up,
Down,
}
#[derive(PartialEq)]
pub enum EventState {
Consumed,
@ -72,6 +58,8 @@ pub trait DrawableComponent {
/// base component trait
#[async_trait]
pub trait Component {
fn commands(&self, out: &mut Vec<CommandInfo>);
fn event(&mut self, key: crate::event::Key) -> Result<EventState>;
fn focused(&self) -> bool {

@ -1,4 +1,5 @@
use super::{Component, DrawableComponent, EventState};
use crate::components::command::CommandInfo;
use crate::components::{TableComponent, TableFilterComponent};
use crate::event::Key;
use anyhow::Result;
@ -85,6 +86,8 @@ impl DrawableComponent for RecordTableComponent {
}
impl Component for RecordTableComponent {
fn commands(&self, out: &mut Vec<CommandInfo>) {}
fn event(&mut self, key: Key) -> Result<EventState> {
match key {
Key::Char('/') => {

@ -1,4 +1,5 @@
use super::{Component, DrawableComponent, EventState};
use crate::components::command::CommandInfo;
use crate::event::Key;
use anyhow::Result;
use strum::IntoEnumIterator;
@ -62,6 +63,8 @@ impl DrawableComponent for TabComponent {
}
impl Component for TabComponent {
fn commands(&self, out: &mut Vec<CommandInfo>) {}
fn event(&mut self, key: Key) -> Result<EventState> {
match key {
Key::Char('1') => {

@ -2,6 +2,7 @@ use super::{
utils::scroll_vertical::VerticalScroll, Component, DrawableComponent, EventState,
TableValueComponent,
};
use crate::components::command::CommandInfo;
use crate::event::Key;
use anyhow::Result;
use std::convert::From;
@ -447,6 +448,8 @@ impl DrawableComponent for TableComponent {
}
impl Component for TableComponent {
fn commands(&self, out: &mut Vec<CommandInfo>) {}
fn event(&mut self, key: Key) -> Result<EventState> {
match key {
Key::Char('h') => {

@ -1,4 +1,5 @@
use super::{compute_character_width, Component, DrawableComponent, EventState};
use crate::components::command::CommandInfo;
use crate::event::Key;
use anyhow::Result;
use tui::{
@ -78,6 +79,8 @@ impl DrawableComponent for TableFilterComponent {
}
impl Component for TableFilterComponent {
fn commands(&self, out: &mut Vec<CommandInfo>) {}
fn event(&mut self, key: Key) -> Result<EventState> {
let input_str: String = self.input.iter().collect();
match key {

@ -1,4 +1,5 @@
use super::{Component, DrawableComponent, EventState};
use crate::components::command::CommandInfo;
use crate::event::Key;
use anyhow::Result;
use database_tree::Table;
@ -85,6 +86,8 @@ impl DrawableComponent for TableStatusComponent {
}
impl Component for TableStatusComponent {
fn commands(&self, out: &mut Vec<CommandInfo>) {}
fn event(&mut self, _key: Key) -> Result<EventState> {
Ok(EventState::NotConsumed)
}

@ -1,4 +1,5 @@
use super::{Component, DrawableComponent, EventState};
use crate::components::command::CommandInfo;
use crate::event::Key;
use anyhow::Result;
use tui::{
@ -45,6 +46,8 @@ impl DrawableComponent for TableValueComponent {
}
impl Component for TableValueComponent {
fn commands(&self, out: &mut Vec<CommandInfo>) {}
fn event(&mut self, _key: Key) -> Result<EventState> {
todo!("scroll");
}

@ -1,4 +1,4 @@
use crate::{components::ScrollType, ui::scrollbar::draw_scrollbar};
use crate::ui::scrollbar::draw_scrollbar;
use std::cell::Cell;
use tui::{backend::Backend, layout::Rect, Frame};
@ -23,29 +23,6 @@ impl VerticalScroll {
self.top.set(0);
}
pub fn _move_top(&self, move_type: ScrollType) -> bool {
let old = self.top.get();
let max = self.max_top.get();
let new_scroll_top = match move_type {
ScrollType::Down => old.saturating_add(1),
ScrollType::Up => old.saturating_sub(1),
ScrollType::Home => 0,
ScrollType::End => max,
_ => old,
};
let new_scroll_top = new_scroll_top.clamp(0, max);
if new_scroll_top == old {
return false;
}
self.top.set(new_scroll_top);
true
}
pub fn update(&self, selection: usize, selection_max: usize, visual_height: usize) -> usize {
let new_top = calc_scroll_top(self.get_top(), visual_height, selection, selection_max);
self.top.set(new_top);
@ -60,10 +37,6 @@ impl VerticalScroll {
new_top
}
pub fn _update_no_selection(&self, line_count: usize, visual_height: usize) -> usize {
self.update(self.get_top(), line_count, visual_height)
}
pub fn draw<B: Backend>(&self, f: &mut Frame<B>, r: Rect) {
draw_scrollbar(f, r, self.max_top.get(), self.top.get());
}

@ -99,12 +99,11 @@ impl Pool for MySqlPool {
.iter()
.map(|column| column.name().to_string())
.collect();
records.push(
row.columns()
.iter()
.map(|col| convert_column_value_to_string(&row, col))
.collect::<Vec<String>>(),
)
let mut new_row = vec![];
for column in row.columns() {
new_row.push(convert_column_value_to_string(&row, column)?)
}
records.push(new_row)
}
Ok((headers, records))
}
@ -124,12 +123,11 @@ impl Pool for MySqlPool {
.iter()
.map(|column| column.name().to_string())
.collect();
records.push(
row.columns()
.iter()
.map(|col| convert_column_value_to_string(&row, col))
.collect::<Vec<String>>(),
)
let mut new_row = vec![];
for column in row.columns() {
new_row.push(convert_column_value_to_string(&row, column)?)
}
records.push(new_row)
}
Ok((headers, records))
}
@ -147,47 +145,58 @@ pub async fn get_tables(database: String, pool: &MPool) -> anyhow::Result<Vec<Ta
Ok(tables)
}
pub fn convert_column_value_to_string(row: &MySqlRow, column: &MySqlColumn) -> String {
pub fn convert_column_value_to_string(
row: &MySqlRow,
column: &MySqlColumn,
) -> anyhow::Result<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()
"INT" | "SMALLINT" | "BIGINT" => {
if let Ok(value) = row.try_get(column_name) {
let value: Option<i64> = value;
return Ok(value.map_or("NULL".to_string(), |v| v.to_string()));
}
Err(_) => "".to_string(),
},
"INT UNSIGNED" => match row.try_get(column_name) {
Ok(value) => {
let value: u64 = value;
value.to_string()
}
"DECIMAL" => {
if let Ok(value) = row.try_get(column_name) {
let value: Option<rust_decimal::Decimal> = value;
return Ok(value.map_or("NULL".to_string(), |v| v.to_string()));
}
Err(_) => "".to_string(),
},
"VARCHAR" | "CHAR" | "ENUM" | "TEXT" => {
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()
"INT UNSIGNED" => {
if let Ok(value) = row.try_get(column_name) {
let value: Option<u64> = value;
return Ok(value.map_or("NULL".to_string(), |v| v.to_string()));
}
Err(_) => "".to_string(),
},
"TIMESTAMP" => match row.try_get(column_name) {
Ok(value) => {
let value: chrono::DateTime<chrono::Utc> = value;
value.to_string()
}
"VARCHAR" | "CHAR" | "ENUM" | "TEXT" | "LONGTEXT" => {
return Ok(row
.try_get(column_name)
.unwrap_or_else(|_| "NULL".to_string()))
}
"DATE" => {
if let Ok(value) = row.try_get(column_name) {
let value: Option<NaiveDate> = value;
return Ok(value.map_or("NULL".to_string(), |v| v.to_string()));
}
Err(_) => "".to_string(),
},
"BOOLEAN" => match row.try_get(column_name) {
Ok(value) => {
let value: bool = value;
value.to_string()
}
"TIMESTAMP" => {
if let Ok(value) = row.try_get(column_name) {
let value: Option<chrono::DateTime<chrono::Utc>> = value;
return Ok(value.map_or("NULL".to_string(), |v| v.to_string()));
}
Err(_) => "".to_string(),
},
column_type => unimplemented!("not implemented column type: {}", column_type),
}
"BOOLEAN" => {
if let Ok(value) = row.try_get(column_name) {
let value: Option<bool> = value;
return Ok(value.map_or("NULL".to_string(), |v| v.to_string()));
}
}
_ => (),
}
Err(anyhow::anyhow!(
"column type not implemented: `{}` {}",
column_name,
column.type_info().clone().name()
))
}

Loading…
Cancel
Save